diff --git a/internal/app/wwctl/completions/completions.go b/internal/app/wwctl/completions/completions.go index 367a3750..67d48e34 100644 --- a/internal/app/wwctl/completions/completions.go +++ b/internal/app/wwctl/completions/completions.go @@ -7,6 +7,7 @@ import ( "github.com/warewulf/warewulf/internal/pkg/image" "github.com/warewulf/warewulf/internal/pkg/kernel" "github.com/warewulf/warewulf/internal/pkg/node" + "github.com/warewulf/warewulf/internal/pkg/overlay" ) func NodeKernelVersion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { @@ -51,18 +52,37 @@ func ProfileKernelVersion(cmd *cobra.Command, args []string, toComplete string) func Images(num int) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if num <= 0 || len(args) < num { - sources, _ := image.ListSources() - return sources, cobra.ShellCompDirectiveNoFileComp - } else { - return nil, cobra.ShellCompDirectiveNoFileComp + if sources, err := image.ListSources(); err != nil { + return sources, cobra.ShellCompDirectiveNoFileComp + } } + return nil, cobra.ShellCompDirectiveNoFileComp } } -func Profiles(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - if len(args) != 0 { +func Nodes(num int) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if num <= 0 || len(args) < num { + if registry, err := node.New(); err == nil { + return registry.ListAllNodes(), cobra.ShellCompDirectiveNoFileComp + } + } return nil, cobra.ShellCompDirectiveNoFileComp } - registry, _ := node.New() - return registry.ListAllProfiles(), cobra.ShellCompDirectiveNoFileComp +} + +func Profiles(num int) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if num <= 0 || len(args) < num { + if registry, err := node.New(); err == nil { + return registry.ListAllProfiles(), cobra.ShellCompDirectiveNoFileComp + } + } + return nil, cobra.ShellCompDirectiveNoFileComp + } +} + +func Overlays(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + list := overlay.FindOverlays() + return list, cobra.ShellCompDirectiveNoFileComp } diff --git a/internal/app/wwctl/node/add/root.go b/internal/app/wwctl/node/add/root.go index 6e7d63b5..7058834b 100644 --- a/internal/app/wwctl/node/add/root.go +++ b/internal/app/wwctl/node/add/root.go @@ -1,14 +1,10 @@ package add import ( - "log" - "github.com/spf13/cobra" "github.com/warewulf/warewulf/internal/app/wwctl/completions" "github.com/warewulf/warewulf/internal/app/wwctl/flags" - "github.com/warewulf/warewulf/internal/pkg/image" "github.com/warewulf/warewulf/internal/pkg/node" - "github.com/warewulf/warewulf/internal/pkg/overlay" ) // Holds the variables which are needed in CobraRunE @@ -29,42 +25,26 @@ func GetCommand() *cobra.Command { Aliases: []string{"new", "create"}, RunE: CobraRunE(&vars), Args: cobra.MinimumNArgs(1), + ValidArgsFunction: cobra.FixedCompletions(nil, cobra.ShellCompDirectiveNoFileComp), } vars.nodeConf.CreateFlags(baseCmd) vars.nodeAdd.CreateAddFlags(baseCmd) flags.AddContainer(baseCmd, &(vars.nodeConf.Profile.ImageName)) // register the command line completions - if err := baseCmd.RegisterFlagCompletionFunc("image", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - list, _ := image.ListSources() - return list, cobra.ShellCompDirectiveNoFileComp - }); err != nil { - log.Println(err) + if err := baseCmd.RegisterFlagCompletionFunc("image", completions.Images(0)); err != nil { // no limit + panic(err) } if err := baseCmd.RegisterFlagCompletionFunc("kernelversion", completions.NodeKernelVersion); err != nil { - log.Println(err) + panic(err) } - if err := baseCmd.RegisterFlagCompletionFunc("runtime", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - list := overlay.FindOverlays() - return list, cobra.ShellCompDirectiveNoFileComp - }); err != nil { - log.Println(err) + if err := baseCmd.RegisterFlagCompletionFunc("runtime", completions.Overlays); err != nil { + panic(err) } - if err := baseCmd.RegisterFlagCompletionFunc("wwinit", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - list := overlay.FindOverlays() - return list, cobra.ShellCompDirectiveNoFileComp - }); err != nil { - log.Println(err) + if err := baseCmd.RegisterFlagCompletionFunc("wwinit", completions.Overlays); err != nil { + panic(err) } - if err := baseCmd.RegisterFlagCompletionFunc("profile", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - var list []string - nodeDB, _ := node.New() - profiles, _ := nodeDB.FindAllProfiles() - for _, profile := range profiles { - list = append(list, profile.Id()) - } - return list, cobra.ShellCompDirectiveNoFileComp - }); err != nil { - log.Println(err) + if err := baseCmd.RegisterFlagCompletionFunc("profile", completions.Profiles(0)); err != nil { // no limit + panic(err) } // GetRootCommand returns the root cobra.Command for the application. diff --git a/internal/app/wwctl/node/console/root.go b/internal/app/wwctl/node/console/root.go index 3256d1bf..b107864c 100644 --- a/internal/app/wwctl/node/console/root.go +++ b/internal/app/wwctl/node/console/root.go @@ -2,6 +2,7 @@ package console import ( "github.com/spf13/cobra" + "github.com/warewulf/warewulf/internal/app/wwctl/completions" ) var ( @@ -12,6 +13,7 @@ var ( Long: "Start a new IPMI console for NODENAME.", Args: cobra.MinimumNArgs(1), RunE: CobraRunE, + ValidArgsFunction: completions.Nodes(0), // no limit } ) diff --git a/internal/app/wwctl/node/delete/root.go b/internal/app/wwctl/node/delete/root.go index 2f8d7472..0890e626 100644 --- a/internal/app/wwctl/node/delete/root.go +++ b/internal/app/wwctl/node/delete/root.go @@ -2,7 +2,7 @@ package delete import ( "github.com/spf13/cobra" - "github.com/warewulf/warewulf/internal/pkg/node" + "github.com/warewulf/warewulf/internal/app/wwctl/completions" ) var ( @@ -14,15 +14,7 @@ var ( Args: cobra.MinimumNArgs(1), RunE: CobraRunE, Aliases: []string{"rm", "del", "remove"}, - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - if len(args) != 0 { - return nil, cobra.ShellCompDirectiveNoFileComp - } - - nodeDB, _ := node.New() - nodes := nodeDB.ListAllNodes() - return nodes, cobra.ShellCompDirectiveNoFileComp - }, + ValidArgsFunction: completions.Nodes(0), // no limit } SetYes bool SetForce bool // no hash checking, so always using force diff --git a/internal/app/wwctl/node/edit/main.go b/internal/app/wwctl/node/edit/main.go index a1d6b599..113bba00 100644 --- a/internal/app/wwctl/node/edit/main.go +++ b/internal/app/wwctl/node/edit/main.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/spf13/cobra" + "github.com/warewulf/warewulf/internal/pkg/hostlist" "github.com/warewulf/warewulf/internal/pkg/node" "github.com/warewulf/warewulf/internal/pkg/util" "github.com/warewulf/warewulf/internal/pkg/warewulfd" @@ -34,6 +35,8 @@ func CobraRunE(cmd *cobra.Command, args []string) error { for nodeID := range registry.Nodes { args = append(args, nodeID) } + } else { + args = hostlist.Expand(args) } wwlog.Debug("node list: %v", args) diff --git a/internal/app/wwctl/node/edit/root.go b/internal/app/wwctl/node/edit/root.go index 66848c8e..6147a274 100644 --- a/internal/app/wwctl/node/edit/root.go +++ b/internal/app/wwctl/node/edit/root.go @@ -2,7 +2,7 @@ package edit import ( "github.com/spf13/cobra" - "github.com/warewulf/warewulf/internal/pkg/node" + "github.com/warewulf/warewulf/internal/app/wwctl/completions" ) var ( @@ -12,20 +12,7 @@ var ( Short: "Edit node(s) with editor", Long: "This command opens an editor for the given nodes.", RunE: CobraRunE, - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - if len(args) != 0 { - return nil, cobra.ShellCompDirectiveNoFileComp - } - - nodeDB, _ := node.New() - nodes, _ := nodeDB.FindAllNodes() - var node_names []string - for _, node := range nodes { - node_names = append(node_names, node.Id()) - } - - return node_names, cobra.ShellCompDirectiveNoFileComp - }, + ValidArgsFunction: completions.Nodes(0), // no limit } NoHeader bool ) diff --git a/internal/app/wwctl/node/export/main.go b/internal/app/wwctl/node/export/main.go index 0e397cc7..e68c93a8 100644 --- a/internal/app/wwctl/node/export/main.go +++ b/internal/app/wwctl/node/export/main.go @@ -8,18 +8,10 @@ import ( ) func CobraRunE(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - args = append(args, ".*") - } filterList := wwapiv1.NodeList{ Output: args, } nodeListMsg := apinode.FilteredNodes(&filterList) - /* - nodeMap := make(map[string]*node.NodeConf) - // got proper yaml back - _ = yaml.Unmarshal([]byte(nodeListMsg.NodeConfMapYaml), nodeMap) - */ wwlog.Info(nodeListMsg.NodeConfMapYaml) return nil } diff --git a/internal/app/wwctl/node/export/root.go b/internal/app/wwctl/node/export/root.go index 90b136cf..97865eb0 100644 --- a/internal/app/wwctl/node/export/root.go +++ b/internal/app/wwctl/node/export/root.go @@ -2,7 +2,7 @@ package export import ( "github.com/spf13/cobra" - "github.com/warewulf/warewulf/internal/pkg/node" + "github.com/warewulf/warewulf/internal/app/wwctl/completions" ) var ( @@ -12,19 +12,7 @@ var ( Short: "Export nodes as yaml to stdout", Long: "This command exports the given nodes as yaml to stdout.", RunE: CobraRunE, - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - if len(args) != 0 { - return nil, cobra.ShellCompDirectiveNoFileComp - } - - nodeDB, _ := node.New() - nodes, _ := nodeDB.FindAllNodes() - var node_names []string - for _, node := range nodes { - node_names = append(node_names, node.Id()) - } - return node_names, cobra.ShellCompDirectiveNoFileComp - }, + ValidArgsFunction: completions.Nodes(0), // no limit } NoHeader bool ) diff --git a/internal/app/wwctl/node/imprt/root.go b/internal/app/wwctl/node/imprt/root.go index 2332e8fd..3aa9bbcc 100644 --- a/internal/app/wwctl/node/imprt/root.go +++ b/internal/app/wwctl/node/imprt/root.go @@ -13,7 +13,7 @@ var ( Short: "Import node(s) from yaml file", Long: "This command imports all the nodes defined in a file. It will overwrite nodes with same name.", RunE: CobraRunE, - Args: cobra.MinimumNArgs(1), + Args: cobra.ExactArgs(1), Aliases: []string{"import"}, } ImportCVS bool diff --git a/internal/app/wwctl/node/list/main.go b/internal/app/wwctl/node/list/main.go index 80b0a3d5..06f9cea2 100644 --- a/internal/app/wwctl/node/list/main.go +++ b/internal/app/wwctl/node/list/main.go @@ -13,9 +13,6 @@ import ( func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err error) { return func(cmd *cobra.Command, args []string) (err error) { - if len(args) > 0 && strings.Contains(args[0], ",") { - args = strings.FieldsFunc(args[0], func(r rune) bool { return r == ',' }) - } req := wwapiv1.GetNodeList{ Nodes: args, Type: wwapiv1.GetNodeList_Simple, diff --git a/internal/app/wwctl/node/list/root.go b/internal/app/wwctl/node/list/root.go index 5d7bb8bc..9015ea1c 100644 --- a/internal/app/wwctl/node/list/root.go +++ b/internal/app/wwctl/node/list/root.go @@ -2,7 +2,7 @@ package list import ( "github.com/spf13/cobra" - "github.com/warewulf/warewulf/internal/pkg/node" + "github.com/warewulf/warewulf/internal/app/wwctl/completions" ) type variables struct { @@ -22,21 +22,9 @@ func GetCommand() *cobra.Command { Short: "List nodes", Long: "This command lists all configured nodes. Optionally, it will list only\n" + "nodes matching a PATTERN.", - RunE: CobraRunE(&vars), - Aliases: []string{"ls"}, - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - if len(args) != 0 { - return nil, cobra.ShellCompDirectiveNoFileComp - } - - nodeDB, _ := node.New() - nodes, _ := nodeDB.FindAllNodes() - var node_names []string - for _, node := range nodes { - node_names = append(node_names, node.Id()) - } - return node_names, cobra.ShellCompDirectiveNoFileComp - }, + RunE: CobraRunE(&vars), + Aliases: []string{"ls"}, + ValidArgsFunction: completions.Nodes(0), // no limit } baseCmd.PersistentFlags().BoolVarP(&vars.showNet, "net", "n", false, "Show node network configurations") baseCmd.PersistentFlags().BoolVarP(&vars.showIpmi, "ipmi", "i", false, "Show node IPMI configurations") diff --git a/internal/app/wwctl/node/sensors/root.go b/internal/app/wwctl/node/sensors/root.go index f367caeb..9da16769 100644 --- a/internal/app/wwctl/node/sensors/root.go +++ b/internal/app/wwctl/node/sensors/root.go @@ -2,7 +2,7 @@ package sensors import ( "github.com/spf13/cobra" - "github.com/warewulf/warewulf/internal/pkg/node" + "github.com/warewulf/warewulf/internal/app/wwctl/completions" ) type variables struct { @@ -21,19 +21,7 @@ func GetCommand() *cobra.Command { Long: "Show IPMI sensor information for nodes matching PATTERN.", Args: cobra.MinimumNArgs(1), RunE: CobraRunE(&vars), - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - if len(args) != 0 { - return nil, cobra.ShellCompDirectiveNoFileComp - } - - nodeDB, _ := node.New() - nodes, _ := nodeDB.FindAllNodes() - var node_names []string - for _, node := range nodes { - node_names = append(node_names, node.Id()) - } - return node_names, cobra.ShellCompDirectiveNoFileComp - }, + ValidArgsFunction: completions.Nodes(0), // no limit } powerCmd.PersistentFlags().BoolVarP(&vars.Full, "full", "F", false, "show detailed output.") powerCmd.PersistentFlags().BoolVarP(&vars.Showcmd, "show", "s", false, "only show command which will be executed") diff --git a/internal/app/wwctl/node/set/root.go b/internal/app/wwctl/node/set/root.go index f5c180e6..d563279b 100644 --- a/internal/app/wwctl/node/set/root.go +++ b/internal/app/wwctl/node/set/root.go @@ -1,14 +1,10 @@ package set import ( - "log" - "github.com/spf13/cobra" "github.com/warewulf/warewulf/internal/app/wwctl/completions" "github.com/warewulf/warewulf/internal/app/wwctl/flags" - "github.com/warewulf/warewulf/internal/pkg/image" "github.com/warewulf/warewulf/internal/pkg/node" - "github.com/warewulf/warewulf/internal/pkg/overlay" ) type variables struct { @@ -31,15 +27,7 @@ func GetCommand() *cobra.Command { Aliases: []string{"modify"}, Args: cobra.MinimumNArgs(1), // require pattern as a mandatory arg RunE: CobraRunE(&vars), - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - if len(args) != 0 { - return nil, cobra.ShellCompDirectiveNoFileComp - } - - nodeDB, _ := node.New() - nodes := nodeDB.ListAllNodes() - return nodes, cobra.ShellCompDirectiveNoFileComp - }, + ValidArgsFunction: completions.Nodes(0), // no limit } vars.nodeConf.CreateFlags(baseCmd) @@ -50,37 +38,20 @@ func GetCommand() *cobra.Command { baseCmd.PersistentFlags().BoolVarP(&vars.setYes, "yes", "y", false, "Set 'yes' to all questions asked") baseCmd.PersistentFlags().BoolVarP(&vars.setForce, "force", "f", false, "Force configuration (even on error)") // register the command line completions - if err := baseCmd.RegisterFlagCompletionFunc("image", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - list, _ := image.ListSources() - return list, cobra.ShellCompDirectiveNoFileComp - }); err != nil { - log.Println(err) + if err := baseCmd.RegisterFlagCompletionFunc("image", completions.Images(0)); err != nil { // no limit + panic(err) } if err := baseCmd.RegisterFlagCompletionFunc("kernelversion", completions.NodeKernelVersion); err != nil { - log.Println(err) + panic(err) } - if err := baseCmd.RegisterFlagCompletionFunc("runtime", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - list := overlay.FindOverlays() - return list, cobra.ShellCompDirectiveNoFileComp - }); err != nil { - log.Println(err) + if err := baseCmd.RegisterFlagCompletionFunc("runtime", completions.Overlays); err != nil { + panic(err) } - if err := baseCmd.RegisterFlagCompletionFunc("wwinit", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - list := overlay.FindOverlays() - return list, cobra.ShellCompDirectiveNoFileComp - }); err != nil { - log.Println(err) + if err := baseCmd.RegisterFlagCompletionFunc("wwinit", completions.Overlays); err != nil { + panic(err) } - if err := baseCmd.RegisterFlagCompletionFunc("profile", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - var list []string - nodeDB, _ := node.New() - profiles, _ := nodeDB.FindAllProfiles() - for _, profile := range profiles { - list = append(list, profile.Id()) - } - return list, cobra.ShellCompDirectiveNoFileComp - }); err != nil { - log.Println(err) + if err := baseCmd.RegisterFlagCompletionFunc("profile", completions.Profiles(0)); err != nil { // no limit + panic(err) } return baseCmd diff --git a/internal/app/wwctl/node/status/root.go b/internal/app/wwctl/node/status/root.go index 4871c13b..36ffeb54 100644 --- a/internal/app/wwctl/node/status/root.go +++ b/internal/app/wwctl/node/status/root.go @@ -1,6 +1,9 @@ package nodestatus -import "github.com/spf13/cobra" +import ( + "github.com/spf13/cobra" + "github.com/warewulf/warewulf/internal/app/wwctl/completions" +) var ( baseCmd = &cobra.Command{ @@ -9,7 +12,7 @@ var ( Short: "View the provisioning status of nodes", Long: "View and monitor the status of nodes as they are provisioned and check in.", RunE: CobraRunE, - Args: cobra.MinimumNArgs(0), + ValidArgsFunction: completions.Nodes(0), // no limit } SetWatch bool SetUpdate int diff --git a/internal/pkg/hostlist/hostlist.go b/internal/pkg/hostlist/hostlist.go index 3c821f87..11ce93c1 100644 --- a/internal/pkg/hostlist/hostlist.go +++ b/internal/pkg/hostlist/hostlist.go @@ -4,77 +4,113 @@ import ( "fmt" "strconv" "strings" - "unicode" ) -func toInt(i string) int { - ret, _ := strconv.Atoi(i) - return ret -} - -func isDigit(s string) bool { - r := []rune(s) - - for i := 0; i < len(r); i++ { - if !unicode.IsDigit(r[i]) { - return false - } - } - - return true -} - -func expand_iterate(list []string) ([]string, int) { - var ret []string - var count int - - for _, i := range list { - - bracketIndex1 := strings.Index(i, "[") - bracketIndex2 := strings.Index(i, "]") - - if bracketIndex1 >= 0 && bracketIndex1 < bracketIndex2 { - prefix := i[:bracketIndex1] - suffix := i[bracketIndex2+1:] - ranges := strings.Split(i[bracketIndex1+1:bracketIndex2], ",") - count++ - - for _, r := range ranges { - iterate := strings.Split(r, "-") - - if len(iterate) == 1 { - if !isDigit(iterate[0]) { - return ret, 0 - } else { - ret = append(ret, prefix+iterate[0]+suffix) - } - } else if len(iterate) == 2 { - if !isDigit(iterate[0]) || !isDigit(iterate[1]) { - return ret, 0 - } else { - sigfigures := len(iterate[0]) - for i := toInt(iterate[0]); i <= toInt(iterate[1]); i++ { - ret = append(ret, fmt.Sprintf(`%s%.`+fmt.Sprintf("%d", sigfigures)+`d%s`, prefix, i, suffix)) - } - } - } - } - } - } - return ret, count -} - +// Expand takes a slice of host strings, possibly containing comma-separated +// values and bracketed ranges (e.g. "node[01-03]") and returns a fully expanded +// slice of host names. func Expand(list []string) []string { - ret := list + // First, split each input string on commas that occur outside brackets. + var preList []string + for _, s := range list { + parts := splitTopLevel(s) + preList = append(preList, parts...) + } + expanded := preList for { - loop, count := expand_iterate(ret) - + onceExpanded, count := expandOnce(expanded) if count == 0 { break } - ret = loop + expanded = onceExpanded } - return ret + return expanded +} + +// expandOnce performs a single round of bracket expansion. +func expandOnce(hosts []string) ([]string, int) { + var result []string + var expansionCount int + + for _, host := range hosts { + bracketStart := strings.Index(host, "[") + bracketEnd := strings.Index(host, "]") + + if bracketStart >= 0 && bracketStart < bracketEnd { + prefix := host[:bracketStart] + suffix := host[bracketEnd+1:] + // Extract the content between brackets and split on commas. + ranges := strings.Split(host[bracketStart+1:bracketEnd], ",") + expansionCount++ + + for _, rng := range ranges { + parts := strings.Split(rng, "-") + + if len(parts) == 1 { + if !isDigit(parts[0]) { + // Abort expansion on invalid input. + return result, 0 + } + result = append(result, prefix+parts[0]+suffix) + } else if len(parts) == 2 { + if !isDigit(parts[0]) || !isDigit(parts[1]) { + return result, 0 + } + sigFigures := len(parts[0]) + startNum := toInt(parts[0]) + endNum := toInt(parts[1]) + for num := startNum; num <= endNum; num++ { + result = append(result, fmt.Sprintf("%s%0*d%s", prefix, sigFigures, num, suffix)) + } + } + } + } else { + // No brackets; keep the string as is. + result = append(result, host) + } + } + + return result, expansionCount +} + +// splitTopLevel splits s on commas that are not inside square brackets. +func splitTopLevel(s string) []string { + var parts []string + depth := 0 + start := 0 + for i, ch := range s { + switch ch { + case '[': + depth++ + case ']': + if depth > 0 { + depth-- + } + case ',': + if depth == 0 { + parts = append(parts, s[start:i]) + start = i + 1 + } + } + } + parts = append(parts, s[start:]) + return parts +} + +// toInt converts a numeric string to an integer. Assumes valid input. +func toInt(s string) int { + num, _ := strconv.Atoi(s) + return num +} + +// isDigit returns true if s consists solely of ASCII digits. +func isDigit(s string) bool { + for i := 0; i < len(s); i++ { + if s[i] < '0' || s[i] > '9' { + return false + } + } + return true } diff --git a/internal/pkg/hostlist/hostlist_test.go b/internal/pkg/hostlist/hostlist_test.go index 39c73971..62c81e1f 100644 --- a/internal/pkg/hostlist/hostlist_test.go +++ b/internal/pkg/hostlist/hostlist_test.go @@ -6,28 +6,48 @@ import ( "github.com/stretchr/testify/assert" ) -func Test_Single(t *testing.T) { - assert.Equal(t, []string{"node1"}, Expand([]string{"node1"})) -} +func TestHostList(t *testing.T) { + tests := map[string]struct { + input []string + output []string + }{ + "single": { + input: []string{"node1"}, + output: []string{"node1"}, + }, + "multiple": { + input: []string{"node1", "node2"}, + output: []string{"node1", "node2"}, + }, + "range": { + input: []string{"node[1-2]"}, + output: []string{"node1", "node2"}, + }, + "internal comma": { + input: []string{"node[1,2]"}, + output: []string{"node1", "node2"}, + }, + "mixed range comma": { + input: []string{"node[1,2-3]"}, + output: []string{"node1", "node2", "node3"}, + }, + "external comma": { + input: []string{"node1,node2"}, + output: []string{"node1", "node2"}, + }, + "mixed external comma with range": { + input: []string{"n[1-3],n5,n[7-8,10]"}, + output: []string{"n1", "n2", "n3", "n5", "n7", "n8", "n10"}, + }, + "leading zeroes": { + input: []string{"n[01-03]"}, + output: []string{"n01", "n02", "n03"}, + }, + } -func Test_Multiple(t *testing.T) { - assert.Equal(t, []string{"node1", "node2"}, Expand([]string{"node1", "node2"})) + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + assert.Equal(t, tt.output, Expand(tt.input)) + }) + } } - -func Test_Range(t *testing.T) { - assert.Equal(t, []string{"node1", "node2"}, Expand([]string{"node[1-2]"})) -} - -func Test_Internal_Comma(t *testing.T) { - assert.Equal(t, []string{"node1", "node2"}, Expand([]string{"node[1,2]"})) -} - -func Test_Mixed_Range_Comma(t *testing.T) { - assert.Equal(t, []string{"node1", "node2", "node3"}, Expand([]string{"node[1,2-3]"})) -} - -// not currently supported -// -// func Test_External_Comma(t *testing.T) { -// assert.Equal(t, []string{"node1", "node2"}, Expand([]string{"node1,node2"})) -// }