Skip to content

Commit 4808be9

Browse files
authored
Merge pull request #285 from Arfey/master
added subgroup name
2 parents 16bd131 + 3390a5d commit 4808be9

8 files changed

Lines changed: 282 additions & 3 deletions

File tree

cmd/root.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package cmd
33
import (
44
"fmt"
55
"strings"
6+
"sort"
67

78
"github.com/spf13/cobra"
9+
"github.com/lets-cli/lets/set"
810
)
911

1012
// newRootCmd represents the base command when called without any subcommands.
@@ -58,6 +60,94 @@ func PrintHelpMessage(cmd *cobra.Command) error {
5860
return err
5961
}
6062

63+
func buildGroupCommandHelp(cmd *cobra.Command, group *cobra.Group) string {
64+
help := ""
65+
cmds := []*cobra.Command{}
66+
67+
// select commands that belong to the specified group
68+
for _, c := range cmd.Commands() {
69+
if c.GroupID == group.ID && (c.IsAvailableCommand() || c.Name() == "help") {
70+
cmds = append(cmds, c)
71+
}
72+
}
73+
74+
sort.Slice(cmds, func(i, j int) bool {
75+
return cmds[i].Name() < cmds[j].Name()
76+
})
77+
78+
// Create a list of subgroups
79+
subGroupNameSet := set.NewSet[string]()
80+
81+
for _, c := range cmds {
82+
if subgroup, ok := c.Annotations["SubGroupName"]; ok && subgroup != "" {
83+
subGroupNameSet.Add(subgroup)
84+
}
85+
}
86+
87+
subGroupNameList := subGroupNameSet.ToList()
88+
sort.Strings(subGroupNameList)
89+
90+
// generate output
91+
help += fmt.Sprintf("%s\n", group.Title)
92+
93+
for _, subgroupName := range subGroupNameList {
94+
intend := ""
95+
if len(subGroupNameList) > 1 {
96+
help += fmt.Sprintf("\n %s\n", subgroupName)
97+
intend = " "
98+
}
99+
for _, c := range cmds {
100+
if subgroup, ok := c.Annotations["SubGroupName"]; ok && subgroup == subgroupName {
101+
help += fmt.Sprintf("%s %-*s %s\n", intend, cmd.NamePadding(), c.Name(), c.Short)
102+
}
103+
}
104+
}
105+
106+
for _, c := range cmds {
107+
if _, ok := c.Annotations["SubGroupName"]; !ok {
108+
help += fmt.Sprintf(" %-*s %s\n", cmd.NamePadding(), c.Name(), c.Short)
109+
}
110+
}
111+
112+
help += "\n"
113+
114+
return help
115+
}
116+
117+
118+
func PrintRootHelpMessage(cmd *cobra.Command) error {
119+
help := ""
120+
help = fmt.Sprintf("%s\n\n%s", cmd.Short, help)
121+
122+
// General
123+
help += "Usage:\n"
124+
if cmd.Runnable() {
125+
help += fmt.Sprintf(" %s\n", cmd.UseLine())
126+
}
127+
if cmd.HasAvailableSubCommands() {
128+
help += fmt.Sprintf(" %s [command]\n", cmd.CommandPath())
129+
}
130+
help += "\n"
131+
132+
// Commands
133+
for _, group := range cmd.Groups() {
134+
help += buildGroupCommandHelp(cmd, group)
135+
}
136+
137+
// Flags
138+
if cmd.HasAvailableLocalFlags() {
139+
help += "Flags:\n"
140+
help += cmd.LocalFlags().FlagUsagesWrapped(120)
141+
help += "\n"
142+
}
143+
144+
// Usage
145+
help += fmt.Sprintf(`Use "%s help [command]" for more information about a command.`, cmd.CommandPath())
146+
147+
_, err := fmt.Fprint(cmd.OutOrStdout(), help)
148+
return err
149+
}
150+
61151
func PrintVersionMessage(cmd *cobra.Command) error {
62152
_, err := fmt.Fprintf(cmd.OutOrStdout(), "lets version %s\n", cmd.Version)
63153
return err

cmd/subcommand.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,9 @@ func newSubcommand(command *config.Command, conf *config.Config, showAll bool, o
202202
c.Println(err)
203203
}
204204
})
205+
subCmd.Annotations = map[string]string{
206+
"SubGroupName": command.GroupName,
207+
}
205208

206209
return subCmd
207210
}

config/config/command.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
type Command struct {
1616
Name string
17+
GroupName string
1718
// Represents a list of commands (scripts)
1819
Cmds Cmds
1920
// script to run after cmd finished (cleanup, etc)
@@ -57,6 +58,7 @@ func (c *Command) UnmarshalYAML(unmarshal func(interface{}) error) error {
5758
}
5859

5960
var cmd struct {
61+
GroupName string `yaml:"group"`
6062
Cmd Cmds
6163
Description string
6264
Shell string
@@ -75,6 +77,12 @@ func (c *Command) UnmarshalYAML(unmarshal func(interface{}) error) error {
7577
return err
7678
}
7779

80+
if cmd.GroupName != "" {
81+
c.GroupName = cmd.GroupName
82+
} else {
83+
c.GroupName = "Common"
84+
}
85+
7886
c.Cmds = cmd.Cmd
7987
c.Description = cmd.Description
8088
c.Env = cmd.Env
@@ -141,6 +149,7 @@ func (c *Command) GetEnv(cfg Config) (map[string]string, error) {
141149
func (c *Command) Clone() *Command {
142150
cmd := &Command{
143151
Name: c.Name,
152+
GroupName: c.GroupName,
144153
Cmds: c.Cmds.Clone(),
145154
After: c.After,
146155
Shell: c.Shell,

config/config/command_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package config
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
7+
"github.com/lithammer/dedent"
8+
"gopkg.in/yaml.v3"
9+
)
10+
11+
func CommandFixture(t *testing.T, text string) *Command {
12+
buf := bytes.NewBufferString(text)
13+
c := &Command{}
14+
if err := yaml.NewDecoder(buf).Decode(&c); err != nil {
15+
t.Fatalf("command fixture decode error: %s", err)
16+
}
17+
18+
return c
19+
}
20+
21+
func TestParseCommand(t *testing.T) {
22+
t.Run("default group", func(t *testing.T) {
23+
text := dedent.Dedent(`
24+
cmd: [echo, Hello]
25+
`)
26+
command := CommandFixture(t, text)
27+
exp := "Common"
28+
29+
if command.GroupName != exp {
30+
t.Errorf("wrong output. \nexpect %s \ngot: %s", exp, command.GroupName)
31+
}
32+
})
33+
34+
t.Run("provided custom group", func(t *testing.T) {
35+
text := dedent.Dedent(`
36+
group: Group Name
37+
cmd: [echo, Hello]
38+
`)
39+
command := CommandFixture(t, text)
40+
exp := "Group Name"
41+
42+
if command.GroupName != exp {
43+
t.Errorf("wrong output. \nexpect %s \ngot: %s", exp, command.GroupName)
44+
}
45+
})
46+
}

docs/docs/config.md

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ title: Config reference
3131
- [`persist_checksum`](#persist_checksum)
3232
- [`ref`](#ref)
3333
- [`args`](#args)
34+
- [`group`](#group)
3435
- [Aliasing:](#aliasing)
3536
- [Env aliasing](#env-aliasing)
3637

@@ -193,7 +194,7 @@ Bar
193194
#### Conditional init
194195

195196
If you need to make sure that code in `init` is called once with some condition,
196-
you can for example create a file at the end of `init` script and check if this
197+
you can for example create a file at the end of `init` script and check if this
197198
file exists at the beginning of `init` script.
198199

199200
Example:
@@ -871,6 +872,46 @@ commands:
871872
`args` is used only with [ref](#ref) and allows to set additional positional args to referenced command. See [ref](#ref) example.
872873

873874

875+
### `group`
876+
877+
`key: group`
878+
879+
`type: string`
880+
881+
Commands can be organized into groups for better readability in the help output. To assign a command to a group, use the `group` key:
882+
883+
```yaml
884+
commands:
885+
build:
886+
group: Build & Deploy
887+
description: Build the project
888+
cmd: npm run build
889+
890+
deploy:
891+
group: Build & Deploy
892+
description: Deploy the project
893+
cmd: npm run deploy
894+
895+
test:
896+
group: Testing
897+
description: Run tests
898+
cmd: npm test
899+
```
900+
901+
When you run `lets help`, commands will be listed under their respective groups, making it easier to find related commands.
902+
903+
```
904+
Commands:
905+
906+
Build & Deploy
907+
build Build the project
908+
deploy Deploy the project
909+
910+
Testing
911+
test Run tests
912+
```
913+
914+
874915
## Aliasing:
875916
876917
Lets supports YAML aliasing in various places in the config

main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,10 @@ func main() {
108108
os.Exit(0)
109109
}
110110

111-
showUsage := rootFlags.help || (command.Name() == "help" && len(args) == 0)
111+
showUsage := rootFlags.help || (command.Name() == "help" && len(args) == 0) || (len(os.Args) == 1)
112112

113113
if showUsage {
114-
if err := cmd.PrintHelpMessage(rootCmd); err != nil {
114+
if err := cmd.PrintRootHelpMessage(rootCmd); err != nil {
115115
log.Errorf("lets: print help error: %s", err)
116116
os.Exit(1)
117117
}

tests/command_group.bats

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
load test_helpers
2+
3+
setup() {
4+
load "${BATS_UTILS_PATH}/bats-support/load.bash"
5+
load "${BATS_UTILS_PATH}/bats-assert/load.bash"
6+
cd ./tests/command_group
7+
}
8+
9+
HELP_MESSAGE=$(cat <<EOF
10+
A CLI task runner
11+
12+
Usage:
13+
lets [flags]
14+
lets [command]
15+
16+
Commands:
17+
18+
A group
19+
c c command
20+
21+
B group
22+
a a command
23+
b b command
24+
25+
Common
26+
d d command
27+
28+
Internal commands:
29+
help Help about any command
30+
self Manage lets CLI itself
31+
32+
Flags:
33+
--all show all commands (including the ones with _)
34+
-c, --config string config file (default is lets.yaml)
35+
-d, --debug count show debug logs (or use LETS_DEBUG=1). If used multiple times, shows more verbose logs
36+
-E, --env stringToString set env variable for running command KEY=VALUE (default [])
37+
--exclude stringArray run all but excluded command(s) described in cmd as map
38+
-h, --help help for lets
39+
--init create a new lets.yaml in the current folder
40+
--no-depends skip 'depends' for running command
41+
--only stringArray run only specified command(s) described in cmd as map
42+
--upgrade upgrade lets to latest version
43+
-v, --version version for lets
44+
45+
Use "lets help [command]" for more information about a command.
46+
EOF
47+
)
48+
49+
50+
@test "help: running 'lets help' should group commands by their group names" {
51+
run lets help
52+
assert_success
53+
54+
assert_output "$HELP_MESSAGE"
55+
}
56+
57+
@test "help: running 'lets --help' should group commands by their group names" {
58+
run lets --help
59+
assert_success
60+
61+
assert_output "$HELP_MESSAGE"
62+
}
63+
64+
@test "help: running 'lets' should group commands by their group names" {
65+
run lets
66+
assert_success
67+
68+
assert_output "$HELP_MESSAGE"
69+
}

tests/command_group/lets.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
shell: bash
2+
3+
commands:
4+
b:
5+
group: B group
6+
description: b command
7+
cmd: echo
8+
9+
a:
10+
group: B group
11+
description: a command
12+
cmd: echo
13+
14+
c:
15+
group: A group
16+
description: c command
17+
cmd: echo
18+
19+
d:
20+
description: d command
21+
cmd: echo

0 commit comments

Comments
 (0)