From f3eaec92a59cda32164a733ddce24923f99c2df2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mantas=20Kandrtavi=C4=8Dius?= Date: Wed, 4 Sep 2024 14:13:18 +0200 Subject: [PATCH 1/6] implemented folder support --- grml.yaml | 2 +- internal/manifest/manifest.go | 43 +++++++++++++++++++++++++++++++---- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/grml.yaml b/grml.yaml index 29ea9ce..327aa54 100644 --- a/grml.yaml +++ b/grml.yaml @@ -1,4 +1,4 @@ -version: 1 +version: 2 project: grml env: diff --git a/internal/manifest/manifest.go b/internal/manifest/manifest.go index 0cd3222..3107380 100644 --- a/internal/manifest/manifest.go +++ b/internal/manifest/manifest.go @@ -20,8 +20,9 @@ package manifest import ( "fmt" - "io/ioutil" + "os" "path/filepath" + "reflect" "strings" "github.com/desertbit/grml/internal/options" @@ -63,6 +64,14 @@ func (cs Commands) Count() (n int) { return } +func isValidDirectory(path string) bool { + info, err := os.Stat(path) + if err != nil { + return false + } + return info.IsDir() +} + func (m *Manifest) EvalEnv(parentEnv map[string]string) (env map[string]string) { // Prepare and evaluate the environment variables. env = make(map[string]string) @@ -91,6 +100,10 @@ func (m *Manifest) EvalEnv(parentEnv map[string]string) (env map[string]string) func (m *Manifest) ParseOptions() (o *options.Options, err error) { o = options.New() for name, i := range m.Options { + fmt.Println(name, i) + + // Print type of i + fmt.Println(reflect.TypeOf(i)) switch v := i.(type) { case bool: if _, ok := o.Bools[name]; ok { @@ -118,6 +131,28 @@ func (m *Manifest) ParseOptions() (o *options.Options, err error) { Options: list, } + case string: + if !isValidDirectory(v) { + err = fmt.Errorf("invalid path: %v", v) + return + } + + // Read the directory + entries, err := os.ReadDir(v) + if err != nil { + return nil, err + } + + list := make([]string, len(entries)) + for i, iv := range entries { + list[i] = fmt.Sprintf("%v", iv) + } + + o.Choices[name] = &options.Choice{ + Active: list[0], + Options: list, + } + default: err = fmt.Errorf("invalid option: %v: %v", name, i) return @@ -128,7 +163,7 @@ func (m *Manifest) ParseOptions() (o *options.Options, err error) { // Parse a grml build file. func Parse(path string) (m *Manifest, err error) { - data, err := ioutil.ReadFile(path) + data, err := os.ReadFile(path) if err != nil { return } @@ -148,7 +183,7 @@ func Parse(path string) (m *Manifest, err error) { return } - // Parse inlcudes. + // Parse includes. rootPath := filepath.Dir(path) err = parseIncludes(rootPath, m.Commands) if err != nil { @@ -165,7 +200,7 @@ func parseIncludes(rootPath string, cmds Commands) (err error) { } var data []byte - data, err = ioutil.ReadFile(filepath.Join(rootPath, cmd.Include)) + data, err = os.ReadFile(filepath.Join(rootPath, cmd.Include)) if err != nil { return } From 2448e578d92c0d4a8dc49ca20a6e0a51e10cc62d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mantas=20Kandratavi=C4=8Dius?= Date: Wed, 4 Sep 2024 15:07:07 +0200 Subject: [PATCH 2/6] removed debugging --- internal/manifest/manifest.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/internal/manifest/manifest.go b/internal/manifest/manifest.go index 3107380..07a13ae 100644 --- a/internal/manifest/manifest.go +++ b/internal/manifest/manifest.go @@ -22,7 +22,6 @@ import ( "fmt" "os" "path/filepath" - "reflect" "strings" "github.com/desertbit/grml/internal/options" @@ -100,10 +99,6 @@ func (m *Manifest) EvalEnv(parentEnv map[string]string) (env map[string]string) func (m *Manifest) ParseOptions() (o *options.Options, err error) { o = options.New() for name, i := range m.Options { - fmt.Println(name, i) - - // Print type of i - fmt.Println(reflect.TypeOf(i)) switch v := i.(type) { case bool: if _, ok := o.Bools[name]; ok { From 93e08e652794a4dccbb94f9686813e441b0e4618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mantas=20Kandratavi=C4=8Dius?= Date: Thu, 5 Sep 2024 13:24:53 +0200 Subject: [PATCH 3/6] option lists can now be both single-choice and multiple-choice --- internal/app/app.go | 7 ++- internal/app/options.go | 87 ++++++++++++++++++++++++++--------- internal/manifest/manifest.go | 4 +- 3 files changed, 72 insertions(+), 26 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index 26efac3..92390ef 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -133,7 +133,12 @@ func (a *app) load() (err error) { if err != nil { return } - a.Println("parsed grml file and reloaded successfully") + + _, err = a.Println("parsed grml file and reloaded successfully") + if err != nil { + return err + } + a.printOptions() return }, diff --git a/internal/app/options.go b/internal/app/options.go index 2008e9e..6297396 100644 --- a/internal/app/options.go +++ b/internal/app/options.go @@ -45,22 +45,72 @@ func (a *app) initOptions() { cmd.AddCommand(&grumble.Command{ Name: "check", Help: "select options", + Args: func(a *grumble.Args) { + a.String("option", "name of option", grumble.Default("")) + }, Run: func(c *grumble.Context) error { - l := len(a.options.Bools) + + // Multiple args are provided. + if len(c.Args) > 1 { + return fmt.Errorf("invalid args: arg should be a path") + } + path := c.Args.String("option") + + // No arg is provided. + if path == "" { + l := len(a.options.Bools) + if l == 0 { + return fmt.Errorf("no check options available") + } + + options := make([]string, l) + var defaults []string + + i := 0 + for name, o := range a.options.Bools { + options[i] = name + if o { + defaults = append(defaults, name) + } + i++ + } + + var selected []string + prompt := &survey.MultiSelect{ + Message: "Select Options:", + Options: options, + Default: defaults, + } + survey.AskOne(prompt, &selected, nil) + + Loop: + for _, o := range options { + for _, s := range selected { + if s == o { + a.options.Bools[o] = true + continue Loop + } + } + a.options.Bools[o] = false + } + return nil + } + + // Arg is provided but not a valid option. + val, ok := a.options.Choices[path] + if !ok { + return fmt.Errorf("invalid option: %v", path) + } + + l := len(val.Options) if l == 0 { - return fmt.Errorf("no check options available") + return fmt.Errorf("folder is empty") } options := make([]string, l) - var defaults []string - - i := 0 - for name, o := range a.options.Bools { + defaults := strings.Split(val.Active, ";") + for i, name := range a.options.Choices[path].Options { options[i] = name - if o { - defaults = append(defaults, name) - } - i++ } var selected []string @@ -69,18 +119,9 @@ func (a *app) initOptions() { Options: options, Default: defaults, } - survey.AskOne(prompt, &selected, nil) - Loop: - for _, o := range options { - for _, s := range selected { - if s == o { - a.options.Bools[o] = true - continue Loop - } - } - a.options.Bools[o] = false - } + survey.AskOne(prompt, &selected, nil) + val.Active = strings.Join(selected, ";") return nil }, }) @@ -138,7 +179,7 @@ func (a *app) printOptions() { config.Prefix = " " // Print all check options sorted. - for k, _ := range a.options.Bools { + for k := range a.options.Bools { keys = append(keys, k) } sort.Strings(keys) @@ -155,7 +196,7 @@ func (a *app) printOptions() { // Print all choice options sorted. output = nil keys = nil - for k, _ := range a.options.Choices { + for k := range a.options.Choices { keys = append(keys, k) } sort.Strings(keys) diff --git a/internal/manifest/manifest.go b/internal/manifest/manifest.go index 07a13ae..94d4549 100644 --- a/internal/manifest/manifest.go +++ b/internal/manifest/manifest.go @@ -139,8 +139,8 @@ func (m *Manifest) ParseOptions() (o *options.Options, err error) { } list := make([]string, len(entries)) - for i, iv := range entries { - list[i] = fmt.Sprintf("%v", iv) + for i, dirEntry := range entries { + list[i] = dirEntry.Name() } o.Choices[name] = &options.Choice{ From c77cb1ba119a1902a595ec8b942886fec66d5d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mantas=20Kandratavi=C4=8Dius?= Date: Thu, 5 Sep 2024 13:40:36 +0200 Subject: [PATCH 4/6] readDir replace with glob --- internal/manifest/manifest.go | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/internal/manifest/manifest.go b/internal/manifest/manifest.go index 94d4549..506785c 100644 --- a/internal/manifest/manifest.go +++ b/internal/manifest/manifest.go @@ -63,14 +63,6 @@ func (cs Commands) Count() (n int) { return } -func isValidDirectory(path string) bool { - info, err := os.Stat(path) - if err != nil { - return false - } - return info.IsDir() -} - func (m *Manifest) EvalEnv(parentEnv map[string]string) (env map[string]string) { // Prepare and evaluate the environment variables. env = make(map[string]string) @@ -127,25 +119,15 @@ func (m *Manifest) ParseOptions() (o *options.Options, err error) { } case string: - if !isValidDirectory(v) { - err = fmt.Errorf("invalid path: %v", v) - return - } - - // Read the directory - entries, err := os.ReadDir(v) + entries, err := filepath.Glob(v) if err != nil { + err = fmt.Errorf("failed to read directory: %v: %v", v, err) return nil, err } - list := make([]string, len(entries)) - for i, dirEntry := range entries { - list[i] = dirEntry.Name() - } - o.Choices[name] = &options.Choice{ - Active: list[0], - Options: list, + Active: entries[0], + Options: entries, } default: From 33a6f8e72674ea90b865b26de03889ca0377f453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mantas=20Kandratavi=C4=8Dius?= Date: Thu, 5 Sep 2024 14:13:17 +0200 Subject: [PATCH 5/6] fixed uncaught error when glob doesn't match anything --- internal/manifest/manifest.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/manifest/manifest.go b/internal/manifest/manifest.go index 506785c..565d714 100644 --- a/internal/manifest/manifest.go +++ b/internal/manifest/manifest.go @@ -120,9 +120,8 @@ func (m *Manifest) ParseOptions() (o *options.Options, err error) { case string: entries, err := filepath.Glob(v) - if err != nil { - err = fmt.Errorf("failed to read directory: %v: %v", v, err) - return nil, err + if err != nil || len(entries) == 0 { + return nil, fmt.Errorf("failed reading path: %v: %v", name, v) } o.Choices[name] = &options.Choice{ From 0b225add90549aef5136d0aa20b7d429f2be76a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mantas=20Kandratavi=C4=8Dius?= Date: Wed, 11 Dec 2024 17:10:14 +0100 Subject: [PATCH 6/6] fixed uncaught errors, the files are now sorted naturally and not lexicographically --- internal/app/options.go | 17 +++++++-- internal/manifest/manifest.go | 5 +++ internal/manifest/utils.go | 66 +++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 internal/manifest/utils.go diff --git a/internal/app/options.go b/internal/app/options.go index 6297396..dec7597 100644 --- a/internal/app/options.go +++ b/internal/app/options.go @@ -81,7 +81,10 @@ func (a *app) initOptions() { Options: options, Default: defaults, } - survey.AskOne(prompt, &selected, nil) + err := survey.AskOne(prompt, &selected, nil) + if err != nil { + return err + } Loop: for _, o := range options { @@ -120,7 +123,11 @@ func (a *app) initOptions() { Default: defaults, } - survey.AskOne(prompt, &selected, nil) + err := survey.AskOne(prompt, &selected, nil) + if err != nil { + return err + } + val.Active = strings.Join(selected, ";") return nil }, @@ -156,7 +163,11 @@ func (a *app) initOptions() { Message: "Select Option:", Options: o.Options, } - survey.AskOne(prompt, &o.Active, nil) + err := survey.AskOne(prompt, &o.Active, nil) + if err != nil { + return err + } + return nil }, }) diff --git a/internal/manifest/manifest.go b/internal/manifest/manifest.go index 565d714..87e81fd 100644 --- a/internal/manifest/manifest.go +++ b/internal/manifest/manifest.go @@ -22,6 +22,7 @@ import ( "fmt" "os" "path/filepath" + "sort" "strings" "github.com/desertbit/grml/internal/options" @@ -124,6 +125,10 @@ func (m *Manifest) ParseOptions() (o *options.Options, err error) { return nil, fmt.Errorf("failed reading path: %v: %v", name, v) } + sort.Slice(entries, func(i, j int) bool { + return naturalLess(entries[i], entries[j]) + }) + o.Choices[name] = &options.Choice{ Active: entries[0], Options: entries, diff --git a/internal/manifest/utils.go b/internal/manifest/utils.go new file mode 100644 index 0000000..26cb27f --- /dev/null +++ b/internal/manifest/utils.go @@ -0,0 +1,66 @@ +/* + * grml - A simple build automation tool written in Go + * Copyright (C) 2017 Roland Singer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package manifest + +import ( + "path/filepath" + "regexp" + "strconv" +) + +var numberRegex = regexp.MustCompile(`(\d+)`) + +// naturalLess compares two strings in a natural way. +func naturalLess(a, b string) bool { + aBase := filepath.Base(a) + bBase := filepath.Base(b) + + // Split into text and numeric segments + aParts := numberRegex.Split(aBase, -1) + bParts := numberRegex.Split(bBase, -1) + aNums := numberRegex.FindAllString(aBase, -1) + bNums := numberRegex.FindAllString(bBase, -1) + + minLen := len(aParts) + if len(bParts) < minLen { + minLen = len(bParts) + } + + for i := 0; i < minLen; i++ { + // Compare text segments first + if aParts[i] != bParts[i] { + return aParts[i] < bParts[i] + } + + // If we have numeric segments at this position, compare them + if i < len(aNums) && i < len(bNums) { + aNum, _ := strconv.Atoi(aNums[i]) + bNum, _ := strconv.Atoi(bNums[i]) + if aNum != bNum { + return aNum < bNum + } + } else if i < len(aNums) { + return false + } else if i < len(bNums) { + return true + } + } + + return aBase < bBase +}