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/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..dec7597 100644 --- a/internal/app/options.go +++ b/internal/app/options.go @@ -45,22 +45,75 @@ 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, + } + err := survey.AskOne(prompt, &selected, nil) + if err != nil { + return err + } + + 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 +122,13 @@ 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 + + err := survey.AskOne(prompt, &selected, nil) + if err != nil { + return err } + + val.Active = strings.Join(selected, ";") return nil }, }) @@ -115,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 }, }) @@ -138,7 +190,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 +207,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 0cd3222..87e81fd 100644 --- a/internal/manifest/manifest.go +++ b/internal/manifest/manifest.go @@ -20,8 +20,9 @@ package manifest import ( "fmt" - "io/ioutil" + "os" "path/filepath" + "sort" "strings" "github.com/desertbit/grml/internal/options" @@ -118,6 +119,21 @@ func (m *Manifest) ParseOptions() (o *options.Options, err error) { Options: list, } + case string: + entries, err := filepath.Glob(v) + if err != nil || len(entries) == 0 { + 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, + } + default: err = fmt.Errorf("invalid option: %v: %v", name, i) return @@ -128,7 +144,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 +164,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 +181,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 } 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 +}