Skip to content

Commit

Permalink
feat(list): support output formats (simple, command, yaml)
Browse files Browse the repository at this point in the history
  • Loading branch information
moyiz committed Jan 6, 2024
1 parent 8f2765a commit 0b98a71
Show file tree
Hide file tree
Showing 4 changed files with 240 additions and 8 deletions.
31 changes: 24 additions & 7 deletions cmd/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@ import (
)

var listCmd = &cobra.Command{
Use: "list [prefix ...]",
Use: "list [-o (simple|cmd|yaml)] [prefix ...]",
DisableFlagsInUseLine: true,
Short: "Lists aliases and target commands",
Long: `Lists all aliases under optional given partial prefix.
The output format is a list of full alias names and their target commands,
separated by double-dash (--).
Long: `Lists all aliases under optional partial alias name.
To match partial path name, use '-p|--prefix'.
Supports 3 output formats, selectable with '-o|--output':
- Simple (default): <alias> -- <command>
- Command: na add <alias> -- <command>
- YAML: Same as configuration format.
The short forms of 'list' are 'l' and 'ls'.
By default, all configuration files are merged for this command.`,
Expand All @@ -25,6 +29,8 @@ By default, all configuration files are merged for this command.`,
Run: listRun,
}

var flagOutputFormat = cli.OutputFormatSimple

func validListArgs(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if slices.Contains(os.Args, "--") {
return []string{}, cobra.ShellCompDirectiveDefault
Expand All @@ -38,13 +44,24 @@ func listRun(cmd *cobra.Command, args []string) {
if byPrefix, _ := cmd.Flags().GetBool("prefix"); byPrefix {
aliases = config.ListAliasesByPrefix(args...)
} else {
aliases = config.ListAliasesByPrefix(args...)
aliases = config.ListAliases(args...)
}
for _, alias := range aliases {
fmt.Println(alias.Name, "--", alias.Command)
if out, err := cli.OutputFormatToFunc[flagOutputFormat](aliases); err != nil {
fmt.Println("na: list:", err)
os.Exit(1)
} else {
fmt.Print(out)
}
}

func init() {
listCmd.Flags().BoolP("prefix", "p", false, "List aliases by prefix instead of exact match")
listCmd.Flags().VarP(&flagOutputFormat, "output", "o", "Output format (simple|cmd|yaml)")
listCmd.RegisterFlagCompletionFunc("output", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
formats := make([]string, 0)
for _, f := range cli.AllOutputFormats() {
formats = append(formats, string(f))
}
return formats, cobra.ShellCompDirectiveDefault
})
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/spf13/afero v1.11.0
github.com/spf13/cobra v1.8.0
github.com/spf13/viper v1.17.0
gopkg.in/yaml.v3 v3.0.1
)

require (
Expand All @@ -30,5 +31,4 @@ require (
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
93 changes: 93 additions & 0 deletions internal/cli/output.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package cli

import (
"errors"
"fmt"
"slices"
"strings"

"github.com/moyiz/na/internal/config"
"gopkg.in/yaml.v3"
)

type OutputFormat string

const (
OutputFormatSimple = OutputFormat("simple")
OutputFormatCmd = OutputFormat("cmd")
OutputFormatYaml = OutputFormat("yaml")
)

func AllOutputFormats() []OutputFormat {
return []OutputFormat{
OutputFormatSimple,
OutputFormatCmd,
OutputFormatYaml,
}
}

func (o *OutputFormat) String() string {
return string(*o)
}

func (o *OutputFormat) Set(v string) error {
switch {
case slices.Contains(AllOutputFormats(), OutputFormat(v)):
*o = OutputFormat(v)
return nil
default:
return errors.New("invalid format: " + v)
}
}

func (o *OutputFormat) Type() string {
return "format"
}

var OutputFormatToFunc = map[OutputFormat](func([]config.Alias) (string, error)){
OutputFormatSimple: simpleOutput,
OutputFormatCmd: cmdOutput,
OutputFormatYaml: yamlOutput,
}

func simpleOutput(aliases []config.Alias) (string, error) {
builder := strings.Builder{}
for _, alias := range aliases {
builder.WriteString(fmt.Sprintln(alias.Name, "--", alias.Command))
}
return builder.String(), nil
}

func cmdOutput(aliases []config.Alias) (string, error) {
builder := strings.Builder{}
for _, alias := range aliases {
builder.WriteString(fmt.Sprintln("na add", alias.Name, "--", alias.Command))
}
return builder.String(), nil
}

func yamlOutput(aliases []config.Alias) (string, error) {
aliasesMap := make(map[string]interface{})
for _, alias := range aliases {
aliasWalker := aliasesMap
aliasParts := strings.Fields(alias.Name)
n := len(aliasParts)
for i := 0; i < n-1; i++ {
if _, ok := aliasWalker[aliasParts[i]]; !ok {
aliasWalker[aliasParts[i]] = make(map[string]interface{})
}
aliasWalker = aliasWalker[aliasParts[i]].(map[string]interface{})
}
aliasWalker[aliasParts[n-1]] = alias.Command
}
builder := strings.Builder{}
if yamlData, err := yaml.Marshal(aliasesMap); err == nil {
if yamlStr := string(yamlData); yamlStr != "{}\n" {
builder.WriteString(fmt.Sprintln("---"))
builder.WriteString(yamlStr)
}
} else {
return string(yamlData), err
}
return builder.String(), nil
}
122 changes: 122 additions & 0 deletions internal/cli/output_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package cli

import (
"testing"

"github.com/moyiz/na/internal/config"
)

func TestSimpleOutput(t *testing.T) {
for _, testCase := range []struct {
Aliases []config.Alias
ExpectedOut string
ExpectedErr error
}{
{
Aliases: []config.Alias{
{
Name: "alias1",
Command: "cmd1",
},
{
Name: "alias2",
Command: "cmd2",
},
{
Name: "my alias1",
Command: "my cmd1",
},
{
Name: "my alias2",
Command: "my cmd2",
},
},
ExpectedOut: "alias1 -- cmd1\nalias2 -- cmd2\nmy alias1 -- my cmd1\nmy alias2 -- my cmd2\n",
},
{
Aliases: []config.Alias{},
ExpectedOut: "",
},
} {
if out, err := simpleOutput(testCase.Aliases); out != testCase.ExpectedOut || err != testCase.ExpectedErr {
t.Errorf("Simple output differ. Expected (%#v, %#v) but got (%#v, %#v)", testCase.ExpectedOut, testCase.ExpectedErr, out, err)
}
}
}

func TestCmdOutput(t *testing.T) {
for _, testCase := range []struct {
Aliases []config.Alias
ExpectedOut string
ExpectedErr error
}{
{
Aliases: []config.Alias{
{
Name: "alias1",
Command: "cmd1",
},
{
Name: "alias2",
Command: "cmd2",
},
{
Name: "my alias1",
Command: "my cmd1",
},
{
Name: "my alias2",
Command: "my cmd2",
},
},
ExpectedOut: "na add alias1 -- cmd1\nna add alias2 -- cmd2\nna add my alias1 -- my cmd1\nna add my alias2 -- my cmd2\n",
},
{
Aliases: []config.Alias{},
ExpectedOut: "",
},
} {
if out, err := cmdOutput(testCase.Aliases); out != testCase.ExpectedOut || err != testCase.ExpectedErr {
t.Errorf("Command output differ. Expected (%#v, %#v) but got (%#v, %#v)", testCase.ExpectedOut, testCase.ExpectedErr, out, err)
}
}
}

func TestYamlOutput(t *testing.T) {
for _, testCase := range []struct {
Aliases []config.Alias
ExpectedOut string
ExpectedErr error
}{
{
Aliases: []config.Alias{},
ExpectedOut: "",
},
{
Aliases: []config.Alias{
{
Name: "alias1",
Command: "cmd1",
},
},
ExpectedOut: "---\nalias1: cmd1\n",
},
{
Aliases: []config.Alias{
{
Name: "my alias1",
Command: "my cmd1",
},
{
Name: "my alias2",
Command: "my cmd2",
},
},
ExpectedOut: "---\nmy:\n alias1: my cmd1\n alias2: my cmd2\n",
},
} {
if out, err := yamlOutput(testCase.Aliases); out != testCase.ExpectedOut || err != testCase.ExpectedErr {
t.Errorf("Command output differ. Expected (%#v, %#v) but got (%#v, %#v)", testCase.ExpectedOut, testCase.ExpectedErr, out, err)
}
}
}

0 comments on commit 0b98a71

Please sign in to comment.