Compare commits
25 Commits
Author | SHA1 | Date |
---|---|---|
|
160b3321ef | |
|
73d7db2078 | |
|
b4759d5f08 | |
|
1fbc94aa49 | |
|
26bee58adc | |
|
ba90879ff0 | |
|
a573f722af | |
|
c5bd7662f2 | |
|
b4d610e5fd | |
|
384863fcdf | |
|
1ee91301f4 | |
|
81a1f6d97c | |
|
5979ea58a2 | |
|
71cddc5b20 | |
|
2c26eda5e2 | |
|
31c4264e4f | |
|
349cdf01d6 | |
|
018a72256b | |
|
c50491da9f | |
|
1692790506 | |
|
7123217e58 | |
|
3f7f04e4f1 | |
|
9e5d7e2519 | |
|
c6e41de5c8 | |
|
9e6ca2ce76 |
|
@ -0,0 +1,39 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"go.fifitido.net/cmd"
|
||||
"go.fifitido.net/cmd/opt"
|
||||
)
|
||||
|
||||
var (
|
||||
so = opt.String("string", "s", "", "example string option")
|
||||
io = opt.Int("int", "i", 0, "example int option")
|
||||
fo = opt.Float("float", "f", 0, "example float option")
|
||||
bo = opt.Bool("bool", "b", false, "example bool option")
|
||||
)
|
||||
|
||||
var root = cmd.NewRoot(
|
||||
"example-cmd",
|
||||
cmd.WithShortDescription("Example command"),
|
||||
cmd.WithLongDescription(`An example command to show how to use go.fifitido.net/cmd
|
||||
|
||||
this example is just a simple hello world program to show
|
||||
the basics of the library.`),
|
||||
cmd.WithArgument("name", false),
|
||||
cmd.WithSubcommand(cmd.CompletionsSubcommand()),
|
||||
cmd.WithOptions(so, io, fo, bo),
|
||||
cmd.WithRunFunc(func(args []string) {
|
||||
if len(args) == 0 {
|
||||
fmt.Println("Hello World!")
|
||||
} else {
|
||||
fmt.Printf("Hello %s!\n", args[0])
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
func main() {
|
||||
root.Execute(os.Args)
|
||||
}
|
69
command.go
69
command.go
|
@ -3,32 +3,32 @@ package cmd
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"go.fifitido.net/cmd/opts"
|
||||
"go.fifitido.net/cmd/opt"
|
||||
)
|
||||
|
||||
type Command struct {
|
||||
name string
|
||||
shortDescription string
|
||||
longDescription string
|
||||
aliases []string
|
||||
arguments []*Argument
|
||||
opts opts.Set
|
||||
subcommands Set
|
||||
parent *Command
|
||||
Name string
|
||||
version string
|
||||
ShortDescription string
|
||||
LongDescription string
|
||||
Aliases []string
|
||||
Args []*Argument
|
||||
Opts opt.Set
|
||||
Subcommands Set
|
||||
Parent *Command
|
||||
run func(args []string)
|
||||
isRoot bool
|
||||
}
|
||||
|
||||
func NewRoot(options ...Option) *Command {
|
||||
cmd := &Command{isRoot: true}
|
||||
func NewRoot(name string, version string, options ...Option) *Command {
|
||||
cmd := &Command{Name: name, version: version, isRoot: true}
|
||||
cmd.ApplyOptions(options...)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func New(name string, options ...Option) *Command {
|
||||
cmd := &Command{name: name}
|
||||
cmd := &Command{Name: name}
|
||||
cmd.ApplyOptions(options...)
|
||||
return cmd
|
||||
}
|
||||
|
@ -44,15 +44,24 @@ func (c *Command) Root() *Command {
|
|||
return c
|
||||
}
|
||||
|
||||
return c.parent.Root()
|
||||
return c.Parent.Root()
|
||||
}
|
||||
|
||||
func (c *Command) IsRoot() bool {
|
||||
return c.isRoot
|
||||
}
|
||||
|
||||
func (c *Command) CommandPath() string {
|
||||
if c.parent == nil {
|
||||
return filepath.Base(os.Args[0])
|
||||
if c.Parent == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return c.parent.CommandPath() + " " + c.name
|
||||
parentPath := c.Parent.CommandPath()
|
||||
if parentPath == "" {
|
||||
return c.Name
|
||||
}
|
||||
|
||||
return c.Parent.CommandPath() + " " + c.Name
|
||||
}
|
||||
|
||||
func (c *Command) CanRun() bool {
|
||||
|
@ -67,8 +76,15 @@ func (c *Command) Execute(args []string) {
|
|||
if c.isRoot {
|
||||
args = args[1:]
|
||||
}
|
||||
if len(args) > 0 {
|
||||
sc, ok := c.Subcommands.Get(args[0])
|
||||
if ok {
|
||||
sc.Execute(args[1:])
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
parser := opts.NewParser(args, c.opts, false)
|
||||
parser := opt.NewParser(args, c.Opts, false)
|
||||
restArgs, err := parser.Parse()
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
|
@ -76,15 +92,13 @@ func (c *Command) Execute(args []string) {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(restArgs) > 0 {
|
||||
sc, ok := c.subcommands.Get(restArgs[0])
|
||||
if ok {
|
||||
sc.Execute(restArgs[1:])
|
||||
return
|
||||
}
|
||||
versionOpt, ok := opt.Globals().GetBool("version")
|
||||
if ok && versionOpt.Value() {
|
||||
c.ShowVersion()
|
||||
return
|
||||
}
|
||||
|
||||
helpOpt, ok := opts.Globals().GetBool("help")
|
||||
helpOpt, ok := opt.Globals().GetBool("help")
|
||||
if ok && helpOpt.Value() {
|
||||
c.ShowHelp()
|
||||
return
|
||||
|
@ -97,3 +111,8 @@ func (c *Command) Execute(args []string) {
|
|||
|
||||
c.ShowHelp()
|
||||
}
|
||||
|
||||
func (c *Command) ShowVersion() {
|
||||
rootName := c.Root().Name
|
||||
fmt.Printf("%s %s\n", rootName, c.version)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"go.fifitido.net/cmd/opt"
|
||||
)
|
||||
|
||||
var outputFileOption = opt.String("output", "o", "", "The file to output the completions to")
|
||||
|
||||
func CompletionsSubcommand() *Command {
|
||||
cmd := New(
|
||||
"completions",
|
||||
WithShortDescription("Generate shell completions"),
|
||||
WithLongDescription("Generate shell completions"),
|
||||
)
|
||||
|
||||
registerBashCompletions(cmd)
|
||||
registerFishCompletions(cmd)
|
||||
registerPowerShellCompletions(cmd)
|
||||
registerZsgCompletions(cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func getCompletionsOut() io.Writer {
|
||||
outputFile := outputFileOption.Value()
|
||||
|
||||
if outputFile != "" {
|
||||
var err error
|
||||
out, err := os.OpenFile(outputFile, os.O_WRONLY|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
return os.Stdout
|
||||
}
|
||||
|
||||
func registerBashCompletions(parent *Command) *Command {
|
||||
return New(
|
||||
"bash",
|
||||
WithShortDescription("Generate bash completions"),
|
||||
WithLongDescription("Generate bash completions"),
|
||||
WithOptions(outputFileOption),
|
||||
WithParent(parent),
|
||||
WithRunFunc(func(args []string) {
|
||||
out := getCompletionsOut()
|
||||
|
||||
if fc, ok := out.(io.Closer); ok {
|
||||
defer fc.Close()
|
||||
}
|
||||
|
||||
if err := WriteBashCompletions(out, parent.Root()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func registerFishCompletions(parent *Command) *Command {
|
||||
return New(
|
||||
"fish",
|
||||
WithShortDescription("Generate fish completions"),
|
||||
WithLongDescription("Generate fish completions"),
|
||||
WithOptions(outputFileOption),
|
||||
WithParent(parent),
|
||||
WithRunFunc(func(args []string) {
|
||||
out := getCompletionsOut()
|
||||
|
||||
if fc, ok := out.(io.Closer); ok {
|
||||
defer fc.Close()
|
||||
}
|
||||
|
||||
if err := WriteFishCompletions(out, parent.Root()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func registerPowerShellCompletions(parent *Command) *Command {
|
||||
return New(
|
||||
"powershell",
|
||||
WithShortDescription("Generate powershell completions"),
|
||||
WithLongDescription("Generate powershell completions"),
|
||||
WithOptions(outputFileOption),
|
||||
WithParent(parent),
|
||||
WithRunFunc(func(args []string) {
|
||||
out := getCompletionsOut()
|
||||
|
||||
if fc, ok := out.(io.Closer); ok {
|
||||
defer fc.Close()
|
||||
}
|
||||
|
||||
if err := WritePowerShellCompletions(out, parent.Root()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func registerZsgCompletions(parent *Command) *Command {
|
||||
return New(
|
||||
"zsh",
|
||||
WithShortDescription("Generate zsh completions"),
|
||||
WithLongDescription("Generate zsh completions"),
|
||||
WithOptions(outputFileOption),
|
||||
WithParent(parent),
|
||||
WithRunFunc(func(args []string) {
|
||||
out := getCompletionsOut()
|
||||
|
||||
if fc, ok := out.(io.Closer); ok {
|
||||
defer fc.Close()
|
||||
}
|
||||
|
||||
if err := WriteZshCompletions(out, parent.Root()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"io"
|
||||
"text/template"
|
||||
|
||||
"go.fifitido.net/cmd/opt"
|
||||
)
|
||||
|
||||
func WriteBashCompletions(out io.Writer, rootCmd *Command) error {
|
||||
return bashTpl.Execute(out, map[string]any{
|
||||
"RootCmd": rootCmd,
|
||||
"GlobalOpts": opt.Globals(),
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Add --option|--other-option...) to return nothing for options that require a value
|
||||
// TODO: Add descriptions to completions using https://stackoverflow.com/a/10130007
|
||||
var bashTpl = template.Must(template.New("bash").Funcs(tplFuncs).Parse(`
|
||||
{{- $rootCmd := .RootCmd -}}
|
||||
{{- $progName := $rootCmd.Name -}}
|
||||
{{- $varName := under $rootCmd.Name -}}
|
||||
|
||||
{{- define "cmd" }}
|
||||
{{ $currIdx := .Index -}}
|
||||
{{- $nextIdx := inc .Index -}}
|
||||
{{- $indentSize := mult $currIdx 2 -}}
|
||||
{{ indent $indentSize }}"{{ .Cmd.Name }}")
|
||||
{{ indent $indentSize }} case ${COMP_WORDS[{{ $currIdx }}]} in
|
||||
{{ indent $indentSize }} {{ range .Cmd.Subcommands -}}
|
||||
{{ indent $indentSize }} {{ template "cmd" (map "Cmd" . "Index" $nextIdx) -}}
|
||||
{{ indent $indentSize }} {{ end }}
|
||||
{{ indent $indentSize }} *)
|
||||
{{ indent $indentSize }} COMPREPLY=($(compgen -W "{{ join .Cmd.Subcommands.Names " " }} {{ join .Cmd.Opts.Names " " }}" -- $curr))
|
||||
{{ indent $indentSize }} ;;
|
||||
{{ indent $indentSize }} esac
|
||||
{{ indent $indentSize }};; # {{ .Cmd.Name }}
|
||||
{{- end -}}
|
||||
|
||||
_{{$varName}}_completions()
|
||||
{
|
||||
COMPREPLY=()
|
||||
|
||||
case ${COMP_WORDS[1]} in
|
||||
{{- range .RootCmd.Subcommands -}}
|
||||
{{ template "cmd" (map "Cmd" . "Index" 2) -}}
|
||||
{{ end }}
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "{{ join .RootCmd.Subcommands.Names " " }} {{ join .RootCmd.Opts.Names " " }}" -- $curr))
|
||||
;;
|
||||
esac
|
||||
|
||||
# Global Options
|
||||
COMPREPLY+=($(compgen -W "{{ join .GlobalOpts.Names " " }}" -- $curr))
|
||||
}
|
||||
|
||||
complete -F _{{$varName}}_completions {{$progName}}
|
||||
`))
|
|
@ -0,0 +1,100 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"io"
|
||||
"text/template"
|
||||
|
||||
"go.fifitido.net/cmd/opt"
|
||||
)
|
||||
|
||||
func WriteFishCompletions(out io.Writer, rootCmd *Command) error {
|
||||
return fishTpl.Execute(out, map[string]any{
|
||||
"RootCmd": rootCmd,
|
||||
"GlobalOpts": opt.Globals(),
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Fix fish infinitely completing last command at the end of the command chain
|
||||
var fishTpl = template.Must(template.New("fish").Funcs(tplFuncs).Parse(`
|
||||
{{- $rootCmd := .RootCmd -}}
|
||||
{{- $progName := $rootCmd.Name -}}
|
||||
{{- $varName := under $rootCmd.Name -}}
|
||||
set -l commands {{ join $rootCmd.Subcommands.Names " " }}
|
||||
|
||||
function __fish_{{ $varName }}_needs_command
|
||||
set -l cmd (commandline -opc)
|
||||
if test (count $cmd) -eq 1
|
||||
return 0
|
||||
end
|
||||
return 1
|
||||
end
|
||||
|
||||
function __fish_{{ $varName }}_using_command
|
||||
set -l cmd (commandline -opc)
|
||||
set -l cnt (count $argv)
|
||||
if test (count $cmd) -gt $cnt
|
||||
for i in (seq 1 $cnt);
|
||||
if test $argv[$i] != $cmd[(math $i + 1)]
|
||||
return 1
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
return 1
|
||||
end
|
||||
|
||||
{{/* Option template */ -}}
|
||||
{{ define "opt" }}
|
||||
{{ $progName := .ProgName -}}
|
||||
{{- $varName := .VarName -}}
|
||||
complete -c {{ $progName }}
|
||||
{{- if ne .Opt.ShortName "" }} -s {{ .Opt.ShortName }} {{ end -}}
|
||||
{{- if ne .Opt.Name "" }} -l {{ .Opt.Name }} {{ end -}}
|
||||
{{- if .Cmd }} {{- if ne .Cmd.CommandPath "" -}}
|
||||
-n "__fish_{{ $varName }}_using_command {{.Cmd.CommandPath}}" {{ else -}}
|
||||
-n "__fish_{{ $varName }}_needs_command" {{ end -}}{{- end -}}
|
||||
-d '{{ .Opt.Description }}'
|
||||
{{- end }}
|
||||
|
||||
{{- /* Command template */ -}}
|
||||
{{ define "cmd" }}
|
||||
{{ $progName := .ProgName -}}
|
||||
{{- $varName := .VarName -}}
|
||||
{{- $cmd := .Cmd -}}
|
||||
{{- $parentVarPrefix := varPrefix .Cmd.Parent.CommandPath -}}
|
||||
{{- $varPrefix := varPrefix .Cmd.CommandPath -}}
|
||||
|
||||
{{- if eq .Cmd.Parent.CommandPath "" -}}
|
||||
complete -f -c {{ $progName }} -n "__fish_{{ $varName }}_needs_command" -a {{ .Cmd.Name }} -d "{{ .Cmd.ShortDescription }}"
|
||||
{{- else -}}
|
||||
complete -f -c {{ $progName }} -n "__fish_{{ $varName }}_using_command {{.Cmd.Parent.CommandPath}}" -a {{ .Cmd.Name }} -d "{{ .Cmd.ShortDescription }}"
|
||||
{{- end -}}
|
||||
|
||||
{{- range .Cmd.Opts }}
|
||||
{{- template "opt" (map "Opt" . "ProgName" $progName "VarName" $varName "Cmd" $cmd) -}}
|
||||
{{ end -}}
|
||||
|
||||
{{ if gt (len .Cmd.Subcommands) 0 }}
|
||||
set -l {{ $varPrefix }}commands {{ join .Cmd.Subcommands.Names " " }}
|
||||
{{ $cmdName := .Cmd.Name }}
|
||||
{{- range .Cmd.Subcommands }}
|
||||
{{- template "cmd" (map "Cmd" . "ProgName" $progName "VarName" $varName) -}}
|
||||
{{ end -}}
|
||||
{{- end -}}
|
||||
{{ end }}
|
||||
|
||||
{{- /* Top-level commands */ -}}
|
||||
{{ range $rootCmd.Subcommands }}
|
||||
{{- template "cmd" (map "Cmd" . "ProgName" $progName "VarName" $varName) -}}
|
||||
{{ end }}
|
||||
|
||||
{{- /* Root command options */ -}}
|
||||
{{ range $rootCmd.Opts }}
|
||||
{{- template "opt" (map "Opt" . "ProgName" $progName "VarName" $varName "Cmd" $rootCmd) -}}
|
||||
{{ end }}
|
||||
|
||||
{{- /* Global options */ -}}
|
||||
{{ range .GlobalOpts }}
|
||||
{{- template "opt" (map "Opt" . "ProgName" $progName "VarName" $varName) -}}
|
||||
{{ end }}
|
||||
`))
|
|
@ -0,0 +1,90 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"io"
|
||||
"text/template"
|
||||
|
||||
"go.fifitido.net/cmd/opt"
|
||||
)
|
||||
|
||||
func WritePowerShellCompletions(out io.Writer, rootCmd *Command) error {
|
||||
return powerShellTpl.Execute(out, map[string]any{
|
||||
"RootCmd": rootCmd,
|
||||
"GlobalOpts": opt.Globals(),
|
||||
})
|
||||
}
|
||||
|
||||
var powerShellTpl = template.Must(template.New("PowerShell").Funcs(tplFuncs).Parse(`
|
||||
{{- $rootCmd := .RootCmd -}}
|
||||
{{- $progName := $rootCmd.Name -}}
|
||||
{{- $varName := $rootCmd.Name | camel -}}
|
||||
|
||||
{{- define "cmd" -}}
|
||||
{{- $progName := .ProgName -}}
|
||||
{{- if .Cmd.IsRoot }}
|
||||
{{- /**/}} '{{ $progName }}' {
|
||||
{{- else }}
|
||||
{{- /**/}} '{{ $progName }};{{ join (split .Cmd.CommandPath " ") ";" }}' {
|
||||
{{- end -}}
|
||||
{{- range .Cmd.Opts -}}
|
||||
{{ if ne .Name "" }}
|
||||
[CompletionResult]::new('--{{ .Name }}', '{{ .Name }}', [CompletionResultType]::ParameterName, '{{ .Description }}')
|
||||
{{- end -}}
|
||||
{{- if ne .ShortName "" }}
|
||||
[CompletionResult]::new('-{{ .ShortName }}', '{{ .ShortName }}', [CompletionResultType]::ParameterName, '{{ .Description }}')
|
||||
{{- end -}}
|
||||
{{- end }}
|
||||
{{- range .Cmd.Subcommands }}
|
||||
[CompletionResult]::new('{{ .Name }}', '{{ .Name }}', [CompletionResultType]::ParameterValue, '{{ .ShortDescription }}')
|
||||
{{- end }}
|
||||
break;
|
||||
}
|
||||
{{- range .Cmd.Subcommands }}
|
||||
{{ template "cmd" (map "Cmd" . "ProgName" $progName) }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
using namespace System.Management.Automation
|
||||
using namespace System.Management.Automation.Language
|
||||
|
||||
[scriptblock]$__{{ $varName }}CompleterBlock = {
|
||||
param(
|
||||
$wordToComplete,
|
||||
$commandAst,
|
||||
$cursorPosition
|
||||
)
|
||||
|
||||
$commandElements
|
||||
$command = @(
|
||||
'{{ $progName }}'
|
||||
for ($i = 1; $i -lt $commandElements.Count; $i++) {
|
||||
$element = $commandElements[$i]
|
||||
if ($element -isnot [StringConstantExpressionAst] -or
|
||||
$element.StringConstantType -ne [StringConstantType]::BareWord -or
|
||||
$element.Value.StartsWith('-') -or
|
||||
$element.Value -eq $wordToComplete) {
|
||||
break
|
||||
}
|
||||
$element.Value
|
||||
}
|
||||
) -join ';'
|
||||
|
||||
$completions = @(
|
||||
switch ($command) {
|
||||
{{ template "cmd" (map "Cmd" $rootCmd "ProgName" $progName) }}
|
||||
}
|
||||
{{- range .GlobalOpts -}}
|
||||
{{- if ne .Name "" }}
|
||||
[CompletionResult]::new('--{{ .Name }}', '{{ .Name }}', [CompletionResultType]::ParameterName, '{{ .Description }}')
|
||||
{{- end -}}
|
||||
{{- if ne .ShortName "" }}
|
||||
[CompletionResult]::new('-{{ .ShortName }}', '{{ .ShortName }}', [CompletionResultType]::ParameterName, '{{ .Description }}')
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
)
|
||||
|
||||
$completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
|
||||
Sort-Object -Property ListItemText
|
||||
}
|
||||
|
||||
Register-ArgumentCompleter -CommandName {{ $progName }} -ScriptBlock $__{{ $varName }}CompleterBlock
|
||||
`))
|
|
@ -0,0 +1,85 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"io"
|
||||
"text/template"
|
||||
|
||||
"go.fifitido.net/cmd/opt"
|
||||
)
|
||||
|
||||
func WriteZshCompletions(out io.Writer, rootCmd *Command) error {
|
||||
return zshTpl.Execute(out, map[string]any{
|
||||
"RootCmd": rootCmd,
|
||||
"GlobalOpts": opt.Globals(),
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Fix indentation and other spacing
|
||||
var zshTpl = template.Must(template.New("zsh").Funcs(tplFuncs).Parse(`
|
||||
{{- $rootCmd := .RootCmd -}}
|
||||
{{- $progName := $rootCmd.Name -}}
|
||||
{{- $varName := under $rootCmd.Name -}}
|
||||
|
||||
{{- define "cmd" }}
|
||||
{{- $varPrefix := varPrefix .Cmd.CommandPath -}}
|
||||
{{- if not .Cmd.IsRoot}}
|
||||
{{ .Cmd.Name }})
|
||||
{{ end -}}
|
||||
{{ if gt (len .Cmd.Subcommands) 0 -}}
|
||||
local -a {{ $varPrefix }}commands
|
||||
{{ $varPrefix }}commands=(
|
||||
{{- range .Cmd.Subcommands}}
|
||||
'{{ .Name }}:{{ .ShortDescription }}'
|
||||
{{- end }}
|
||||
)
|
||||
{{ end }}
|
||||
|
||||
{{- if or (gt (len .Cmd.Opts) 0) (gt (len .Cmd.Subcommands) 0) -}}
|
||||
_arguments
|
||||
{{- end -}}
|
||||
{{- range .Cmd.Opts -}}
|
||||
{{- if ne .Name ""}} \
|
||||
'--{{ .Name }}[{{ .Description }}]'
|
||||
{{- end }}
|
||||
{{- if ne .ShortName ""}} \
|
||||
'-{{ .ShortName }}[{{ .Description }}]'
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if gt (len .Cmd.Subcommands) 0 -}}
|
||||
{{ " " }}\
|
||||
'1: :{_describe 'command' {{ $varPrefix }}commands}' \
|
||||
'*:: :->args'
|
||||
|
||||
case $state in
|
||||
args)
|
||||
case $words[1] in
|
||||
{{- range .Cmd.Subcommands }}
|
||||
{{ template "cmd" (map "Cmd" .) }}
|
||||
{{- end }}
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
{{- end }}
|
||||
{{- if not .Cmd.IsRoot}}
|
||||
;;
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
function _{{ $varName }} {
|
||||
{{ template "cmd" (map "Cmd" .RootCmd) -}}
|
||||
|
||||
{{- if gt (len .GlobalOpts) 0 }}
|
||||
_arguments
|
||||
{{- range .GlobalOpts -}}
|
||||
{{- if ne .Name ""}} \
|
||||
'--{{ .Name }}[{{ .Description }}]'
|
||||
{{- end }}
|
||||
{{- if ne .ShortName ""}} \
|
||||
'-{{ .ShortName }}[{{ .Description }}]'
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
}
|
||||
|
||||
compdef _{{ $varName }} {{ $progName }}
|
||||
`))
|
|
@ -8,6 +8,7 @@ import (
|
|||
)
|
||||
|
||||
var root = cmd.NewRoot(
|
||||
"hello-world",
|
||||
cmd.WithShortDescription("Example command"),
|
||||
cmd.WithLongDescription(`An example command to show how to use go.fifitido.net/cmd
|
||||
|
||||
|
|
|
@ -2,4 +2,4 @@ module go.fifitido.net/cmd/examples/hello-world
|
|||
|
||||
go 1.21.3
|
||||
|
||||
require go.fifitido.net/cmd v0.0.0-20231110055906-31e40ecc826a
|
||||
require go.fifitido.net/cmd v0.2.0
|
||||
|
|
|
@ -1,6 +1,2 @@
|
|||
go.fifitido.net/cmd v0.0.0-20231110043437-c32ce2efcd5f h1:v+sjO+t6cGEtDhStOnuypYBrkOMO4suiDVxL93rOUCs=
|
||||
go.fifitido.net/cmd v0.0.0-20231110043437-c32ce2efcd5f/go.mod h1:8SaDxCG1m6WwShUlZApSbNleCvV7oTfqZIyYu4aAqO0=
|
||||
go.fifitido.net/cmd v0.0.0-20231110054944-def39983fdfa h1:Z62sZG2rnKMqa0jI+WD1t8vO9ESQsQa4j6ad9OFeREM=
|
||||
go.fifitido.net/cmd v0.0.0-20231110054944-def39983fdfa/go.mod h1:8SaDxCG1m6WwShUlZApSbNleCvV7oTfqZIyYu4aAqO0=
|
||||
go.fifitido.net/cmd v0.0.0-20231110055906-31e40ecc826a h1:qT/xBA3vcVJYgaPoei70yRlLoB1CdaL3FPzi0kZ+JF0=
|
||||
go.fifitido.net/cmd v0.0.0-20231110055906-31e40ecc826a/go.mod h1:8SaDxCG1m6WwShUlZApSbNleCvV7oTfqZIyYu4aAqO0=
|
||||
go.fifitido.net/cmd v0.2.0 h1:QEq4Gbts4DjnzXLwxV4xaSI3kleVXmtvSFcuOGjBGqc=
|
||||
go.fifitido.net/cmd v0.2.0/go.mod h1:8SaDxCG1m6WwShUlZApSbNleCvV7oTfqZIyYu4aAqO0=
|
||||
|
|
2
go.mod
2
go.mod
|
@ -1,3 +1,5 @@
|
|||
module go.fifitido.net/cmd
|
||||
|
||||
go 1.21.3
|
||||
|
||||
require github.com/iancoleman/strcase v0.3.0
|
||||
|
|
2
go.sum
2
go.sum
|
@ -0,0 +1,2 @@
|
|||
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
|
||||
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
54
help.go
54
help.go
|
@ -2,29 +2,40 @@ package cmd
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"go.fifitido.net/cmd/opts"
|
||||
"go.fifitido.net/cmd/opt"
|
||||
)
|
||||
|
||||
func (c *Command) ShowHelp() {
|
||||
cmdPath := c.CommandPath()
|
||||
binaryName := c.Root().Name
|
||||
|
||||
fmt.Println(c.longDescription)
|
||||
fmt.Println(c.LongDescription)
|
||||
fmt.Println()
|
||||
fmt.Println("Usage: ")
|
||||
fmt.Printf(" %s ", cmdPath)
|
||||
fmt.Printf(" %s %s ", binaryName, cmdPath)
|
||||
|
||||
if len(c.subcommands) > 0 {
|
||||
if len(c.Subcommands) > 0 {
|
||||
if c.CanRun() {
|
||||
fmt.Print("[command] ")
|
||||
} else {
|
||||
fmt.Print("<command> ")
|
||||
}
|
||||
}
|
||||
fmt.Print("[options]")
|
||||
if len(c.Args) > 0 {
|
||||
for _, a := range c.Args {
|
||||
if a.Required {
|
||||
fmt.Print(" <" + a.Name + ">")
|
||||
} else {
|
||||
fmt.Print(" [" + a.Name + "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
fmt.Println("[options]")
|
||||
|
||||
if len(c.subcommands) > 0 {
|
||||
if len(c.Subcommands) > 0 {
|
||||
fmt.Println()
|
||||
|
||||
if c.isRoot {
|
||||
|
@ -33,31 +44,34 @@ func (c *Command) ShowHelp() {
|
|||
fmt.Println("Available subcommands:")
|
||||
}
|
||||
|
||||
for _, s := range c.subcommands {
|
||||
fmt.Println(" " + s.name + " " + s.shortDescription)
|
||||
paddedWidth := c.Subcommands.MaxWidth()
|
||||
for _, s := range c.Subcommands {
|
||||
spaceSize := 2 + paddedWidth - len(s.Name)
|
||||
fmt.Println(" " + s.Name + strings.Repeat(" ", spaceSize) + s.ShortDescription)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("Available options:")
|
||||
|
||||
paddedWidth := c.opts.MaxWidth()
|
||||
for _, f := range c.opts {
|
||||
fmt.Println(" " + opts.HelpLine(f, paddedWidth))
|
||||
if len(c.Opts) > 0 {
|
||||
fmt.Println()
|
||||
fmt.Println("Available options:")
|
||||
paddedWidth := c.Opts.MaxWidth()
|
||||
for _, f := range c.Opts {
|
||||
fmt.Println(" " + opt.HelpLine(f, paddedWidth))
|
||||
}
|
||||
}
|
||||
|
||||
globalOpts := opts.Globals()
|
||||
globalOpts := opt.Globals()
|
||||
if len(globalOpts) > 0 {
|
||||
paddedWidth = globalOpts.MaxWidth()
|
||||
fmt.Println()
|
||||
fmt.Println("Global options:")
|
||||
paddedWidth := globalOpts.MaxWidth()
|
||||
for _, f := range globalOpts {
|
||||
fmt.Println(" " + opts.HelpLine(f, paddedWidth))
|
||||
fmt.Println(" " + opt.HelpLine(f, paddedWidth))
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.subcommands) > 0 {
|
||||
if len(c.Subcommands) > 0 {
|
||||
fmt.Println()
|
||||
fmt.Printf("Run '%s <command> --help' for more information about a command.\n", c.Root().CommandPath())
|
||||
fmt.Printf("Run '%s <command> --help' for more information about a command.\n", binaryName)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package opts
|
||||
package opt
|
||||
|
||||
import (
|
||||
"strconv"
|
|
@ -1,4 +1,4 @@
|
|||
package opts
|
||||
package opt
|
||||
|
||||
import "strconv"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package opts
|
||||
package opt
|
||||
|
||||
var globalOpts = Set{
|
||||
Bool("help", "h", false, "Show the help menu"),
|
|
@ -1,4 +1,4 @@
|
|||
package opts
|
||||
package opt
|
||||
|
||||
import "strconv"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package opts
|
||||
package opt
|
||||
|
||||
import "fmt"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package opts
|
||||
package opt
|
||||
|
||||
import (
|
||||
"errors"
|
|
@ -1,15 +1,15 @@
|
|||
package opts_test
|
||||
package opt_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"go.fifitido.net/cmd/opts"
|
||||
"go.fifitido.net/cmd/opt"
|
||||
)
|
||||
|
||||
func TestParseUnknownLongOption(t *testing.T) {
|
||||
set := opts.Set{}
|
||||
set := opt.Set{}
|
||||
args := []string{"--unknown"}
|
||||
parser := opts.NewParser(args, set, false)
|
||||
parser := opt.NewParser(args, set, false)
|
||||
_, err := parser.Parse()
|
||||
if err == nil {
|
||||
t.Error("Expected error")
|
||||
|
@ -19,9 +19,9 @@ func TestParseUnknownLongOption(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestParseUnknownShortOption(t *testing.T) {
|
||||
set := opts.Set{}
|
||||
set := opt.Set{}
|
||||
args := []string{"-u"}
|
||||
parser := opts.NewParser(args, set, false)
|
||||
parser := opt.NewParser(args, set, false)
|
||||
_, err := parser.Parse()
|
||||
if err == nil {
|
||||
t.Error("Expected error")
|
||||
|
@ -31,12 +31,12 @@ func TestParseUnknownShortOption(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestParseUnknownShortChainedOption1(t *testing.T) {
|
||||
set := opts.Set{
|
||||
opts.Bool("banana", "b", false, ""),
|
||||
opts.Bool("cucumber", "c", false, ""),
|
||||
set := opt.Set{
|
||||
opt.Bool("banana", "b", false, ""),
|
||||
opt.Bool("cucumber", "c", false, ""),
|
||||
}
|
||||
args := []string{"-abc"}
|
||||
parser := opts.NewParser(args, set, false)
|
||||
parser := opt.NewParser(args, set, false)
|
||||
_, err := parser.Parse()
|
||||
if err == nil {
|
||||
t.Error("Expected error")
|
||||
|
@ -46,12 +46,12 @@ func TestParseUnknownShortChainedOption1(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestParseUnknownShortChainedOption2(t *testing.T) {
|
||||
set := opts.Set{
|
||||
opts.Bool("apple", "a", false, ""),
|
||||
opts.Bool("cucumber", "c", false, ""),
|
||||
set := opt.Set{
|
||||
opt.Bool("apple", "a", false, ""),
|
||||
opt.Bool("cucumber", "c", false, ""),
|
||||
}
|
||||
args := []string{"-abc"}
|
||||
parser := opts.NewParser(args, set, false)
|
||||
parser := opt.NewParser(args, set, false)
|
||||
_, err := parser.Parse()
|
||||
if err == nil {
|
||||
t.Error("Expected error")
|
||||
|
@ -61,12 +61,12 @@ func TestParseUnknownShortChainedOption2(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestParseUnknownShortChainedOption3(t *testing.T) {
|
||||
set := opts.Set{
|
||||
opts.Bool("apple", "a", false, ""),
|
||||
opts.Bool("banana", "b", false, ""),
|
||||
set := opt.Set{
|
||||
opt.Bool("apple", "a", false, ""),
|
||||
opt.Bool("banana", "b", false, ""),
|
||||
}
|
||||
args := []string{"-abc"}
|
||||
parser := opts.NewParser(args, set, false)
|
||||
parser := opt.NewParser(args, set, false)
|
||||
_, err := parser.Parse()
|
||||
if err == nil {
|
||||
t.Error("Expected error")
|
||||
|
@ -76,12 +76,12 @@ func TestParseUnknownShortChainedOption3(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestParseUnchaninableOption(t *testing.T) {
|
||||
set := opts.Set{
|
||||
opts.Bool("apple", "a", false, ""),
|
||||
opts.String("banana", "b", "", ""),
|
||||
set := opt.Set{
|
||||
opt.Bool("apple", "a", false, ""),
|
||||
opt.String("banana", "b", "", ""),
|
||||
}
|
||||
args := []string{"-ab"}
|
||||
parser := opts.NewParser(args, set, false)
|
||||
parser := opt.NewParser(args, set, false)
|
||||
_, err := parser.Parse()
|
||||
if err == nil {
|
||||
t.Error("Expected error")
|
||||
|
@ -91,92 +91,92 @@ func TestParseUnchaninableOption(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestParseShortOptionWithValueAndNoSpace(t *testing.T) {
|
||||
opt := opts.String("fruit", "f", "", "")
|
||||
set := opts.Set{opt}
|
||||
o := opt.String("fruit", "f", "", "")
|
||||
set := opt.Set{o}
|
||||
args := []string{"-fapple"}
|
||||
parser := opts.NewParser(args, set, false)
|
||||
parser := opt.NewParser(args, set, false)
|
||||
_, err := parser.Parse()
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got: %s", err.Error())
|
||||
}
|
||||
|
||||
if opt.Value() != "apple" {
|
||||
t.Errorf("Expected fruit to be 'apple', got: %s", opt.Value())
|
||||
if o.Value() != "apple" {
|
||||
t.Errorf("Expected fruit to be 'apple', got: %s", o.Value())
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseShortOptionWithValueAndSpace(t *testing.T) {
|
||||
opt := opts.String("fruit", "f", "", "")
|
||||
set := opts.Set{opt}
|
||||
o := opt.String("fruit", "f", "", "")
|
||||
set := opt.Set{o}
|
||||
args := []string{"-f", "apple"}
|
||||
parser := opts.NewParser(args, set, false)
|
||||
parser := opt.NewParser(args, set, false)
|
||||
_, err := parser.Parse()
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got: %s", err.Error())
|
||||
}
|
||||
|
||||
if opt.Value() != "apple" {
|
||||
t.Errorf("Expected fruit to be 'apple', got: %s", opt.Value())
|
||||
if o.Value() != "apple" {
|
||||
t.Errorf("Expected fruit to be 'apple', got: %s", o.Value())
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseShortOptionWithValueAndEqual(t *testing.T) {
|
||||
opt := opts.String("fruit", "f", "", "")
|
||||
set := opts.Set{opt}
|
||||
o := opt.String("fruit", "f", "", "")
|
||||
set := opt.Set{o}
|
||||
args := []string{"-f=apple"}
|
||||
parser := opts.NewParser(args, set, false)
|
||||
parser := opt.NewParser(args, set, false)
|
||||
_, err := parser.Parse()
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got: %s", err.Error())
|
||||
}
|
||||
|
||||
if opt.Value() != "apple" {
|
||||
t.Errorf("Expected fruit to be 'apple', got: %s", opt.Value())
|
||||
if o.Value() != "apple" {
|
||||
t.Errorf("Expected fruit to be 'apple', got: %s", o.Value())
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseLongOptionWithValueAndEqual(t *testing.T) {
|
||||
opt := opts.String("fruit", "f", "", "")
|
||||
set := opts.Set{opt}
|
||||
o := opt.String("fruit", "f", "", "")
|
||||
set := opt.Set{o}
|
||||
args := []string{"--fruit=apple"}
|
||||
parser := opts.NewParser(args, set, false)
|
||||
parser := opt.NewParser(args, set, false)
|
||||
_, err := parser.Parse()
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got: %s", err.Error())
|
||||
}
|
||||
|
||||
if opt.Value() != "apple" {
|
||||
t.Errorf("Expected fruit to be 'apple', got: %s", opt.Value())
|
||||
if o.Value() != "apple" {
|
||||
t.Errorf("Expected fruit to be 'apple', got: %s", o.Value())
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseLongOptionWithValueAndSpace(t *testing.T) {
|
||||
opt := opts.String("fruit", "f", "", "")
|
||||
set := opts.Set{opt}
|
||||
o := opt.String("fruit", "f", "", "")
|
||||
set := opt.Set{o}
|
||||
args := []string{"--fruit", "apple"}
|
||||
parser := opts.NewParser(args, set, false)
|
||||
parser := opt.NewParser(args, set, false)
|
||||
_, err := parser.Parse()
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got: %s", err.Error())
|
||||
}
|
||||
|
||||
if opt.Value() != "apple" {
|
||||
t.Errorf("Expected fruit to be 'apple', got: %s", opt.Value())
|
||||
if o.Value() != "apple" {
|
||||
t.Errorf("Expected fruit to be 'apple', got: %s", o.Value())
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseOptionTerminator(t *testing.T) {
|
||||
opt := opts.String("fruit", "f", "banana", "")
|
||||
set := opts.Set{opt}
|
||||
o := opt.String("fruit", "f", "banana", "")
|
||||
set := opt.Set{o}
|
||||
args := []string{"--fruit", "apple", "--", "hello", "world"}
|
||||
parser := opts.NewParser(args, set, false)
|
||||
parser := opt.NewParser(args, set, false)
|
||||
restArgs, err := parser.Parse()
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got: %s", err.Error())
|
||||
}
|
||||
|
||||
if opt.Value() != "apple" {
|
||||
t.Errorf("Expected fruit to be 'apple', got: %s", opt.Value())
|
||||
if o.Value() != "apple" {
|
||||
t.Errorf("Expected fruit to be 'apple', got: %s", o.Value())
|
||||
}
|
||||
|
||||
if len(restArgs) != 2 {
|
||||
|
@ -193,9 +193,9 @@ func TestParseOptionTerminator(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestParseLongOptionIgnoreUnknown(t *testing.T) {
|
||||
set := opts.Set{}
|
||||
set := opt.Set{}
|
||||
args := []string{"--unknown", "hello", "world"}
|
||||
parser := opts.NewParser(args, set, true)
|
||||
parser := opt.NewParser(args, set, true)
|
||||
restArgs, err := parser.Parse()
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got: %s", err.Error())
|
||||
|
@ -215,9 +215,9 @@ func TestParseLongOptionIgnoreUnknown(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestParseShortOptionIgnoreUnknown(t *testing.T) {
|
||||
set := opts.Set{}
|
||||
set := opt.Set{}
|
||||
args := []string{"-q"}
|
||||
parser := opts.NewParser(args, set, true)
|
||||
parser := opt.NewParser(args, set, true)
|
||||
|
||||
_, err := parser.Parse()
|
||||
if err != nil {
|
||||
|
@ -226,12 +226,12 @@ func TestParseShortOptionIgnoreUnknown(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestParseChainedShortOptionIgnoreUnknown(t *testing.T) {
|
||||
apple := opts.Bool("apple", "a", false, "")
|
||||
banana := opts.Bool("banana", "b", false, "")
|
||||
cucumber := opts.Bool("cucumber", "c", false, "")
|
||||
set := opts.Set{apple, banana, cucumber}
|
||||
apple := opt.Bool("apple", "a", false, "")
|
||||
banana := opt.Bool("banana", "b", false, "")
|
||||
cucumber := opt.Bool("cucumber", "c", false, "")
|
||||
set := opt.Set{apple, banana, cucumber}
|
||||
args := []string{"-adc"}
|
||||
parser := opts.NewParser(args, set, true)
|
||||
parser := opt.NewParser(args, set, true)
|
||||
|
||||
_, err := parser.Parse()
|
||||
if err != nil {
|
||||
|
@ -252,25 +252,25 @@ func TestParseChainedShortOptionIgnoreUnknown(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestParseSingleDashValue(t *testing.T) {
|
||||
opt := opts.String("fruit", "f", "", "")
|
||||
set := opts.Set{opt}
|
||||
o := opt.String("fruit", "f", "", "")
|
||||
set := opt.Set{o}
|
||||
args := []string{"-f-"}
|
||||
parser := opts.NewParser(args, set, false)
|
||||
parser := opt.NewParser(args, set, false)
|
||||
_, err := parser.Parse()
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got: %s", err.Error())
|
||||
}
|
||||
|
||||
if opt.Value() != "-" {
|
||||
t.Errorf("Expected fruit to be '-', got: %s", opt.Value())
|
||||
if o.Value() != "-" {
|
||||
t.Errorf("Expected fruit to be '-', got: %s", o.Value())
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseLongOptionBadValue(t *testing.T) {
|
||||
opt := opts.Int("fruit", "f", 0, "")
|
||||
set := opts.Set{opt}
|
||||
o := opt.Int("fruit", "f", 0, "")
|
||||
set := opt.Set{o}
|
||||
args := []string{"--fruit=five"}
|
||||
parser := opts.NewParser(args, set, false)
|
||||
parser := opt.NewParser(args, set, false)
|
||||
_, err := parser.Parse()
|
||||
if err == nil {
|
||||
t.Error("Expected error")
|
||||
|
@ -278,10 +278,10 @@ func TestParseLongOptionBadValue(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestParseShortOptionWithSpaceBadValue(t *testing.T) {
|
||||
opt := opts.Int("fruit", "f", 0, "")
|
||||
set := opts.Set{opt}
|
||||
o := opt.Int("fruit", "f", 0, "")
|
||||
set := opt.Set{o}
|
||||
args := []string{"-f", "five"}
|
||||
parser := opts.NewParser(args, set, false)
|
||||
parser := opt.NewParser(args, set, false)
|
||||
_, err := parser.Parse()
|
||||
if err == nil {
|
||||
t.Error("Expected error")
|
||||
|
@ -289,11 +289,11 @@ func TestParseShortOptionWithSpaceBadValue(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestParseShortOptionWithoutEqualBadValue(t *testing.T) {
|
||||
optG := opts.Bool("green", "g", false, "")
|
||||
optF := opts.Int("fruit", "f", 0, "")
|
||||
set := opts.Set{optG, optF}
|
||||
og := opt.Bool("green", "g", false, "")
|
||||
of := opt.Int("fruit", "f", 0, "")
|
||||
set := opt.Set{og, of}
|
||||
args := []string{"-ffive"}
|
||||
parser := opts.NewParser(args, set, false)
|
||||
parser := opt.NewParser(args, set, false)
|
||||
_, err := parser.Parse()
|
||||
if err == nil {
|
||||
t.Error("Expected error")
|
||||
|
@ -301,11 +301,11 @@ func TestParseShortOptionWithoutEqualBadValue(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestParseShortOptionWithEqualBadValue(t *testing.T) {
|
||||
optG := opts.Bool("green", "g", false, "")
|
||||
optF := opts.Int("fruit", "f", 0, "")
|
||||
set := opts.Set{optG, optF}
|
||||
og := opt.Bool("green", "g", false, "")
|
||||
of := opt.Int("fruit", "f", 0, "")
|
||||
set := opt.Set{og, of}
|
||||
args := []string{"-f=five"}
|
||||
parser := opts.NewParser(args, set, false)
|
||||
parser := opt.NewParser(args, set, false)
|
||||
_, err := parser.Parse()
|
||||
if err == nil {
|
||||
t.Error("Expected error")
|
||||
|
@ -313,10 +313,10 @@ func TestParseShortOptionWithEqualBadValue(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestParseLongOptionMissingValue(t *testing.T) {
|
||||
opt := opts.Float("fruit-price", "f", 0, "")
|
||||
set := opts.Set{opt}
|
||||
o := opt.Float("fruit-price", "f", 0, "")
|
||||
set := opt.Set{o}
|
||||
args := []string{"--fruit-price"}
|
||||
parser := opts.NewParser(args, set, false)
|
||||
parser := opt.NewParser(args, set, false)
|
||||
_, err := parser.Parse()
|
||||
if err == nil {
|
||||
t.Error("Expected error")
|
||||
|
@ -324,10 +324,10 @@ func TestParseLongOptionMissingValue(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestParseShortOptionMissingValue(t *testing.T) {
|
||||
opt := opts.Float("fruit-price", "f", 0, "")
|
||||
set := opts.Set{opt}
|
||||
o := opt.Float("fruit-price", "f", 0, "")
|
||||
set := opt.Set{o}
|
||||
args := []string{"-f"}
|
||||
parser := opts.NewParser(args, set, false)
|
||||
parser := opt.NewParser(args, set, false)
|
||||
_, err := parser.Parse()
|
||||
if err == nil {
|
||||
t.Error("Expected error")
|
|
@ -1,4 +1,4 @@
|
|||
package opts
|
||||
package opt
|
||||
|
||||
type Set []Option
|
||||
|
||||
|
@ -33,6 +33,16 @@ func (s Set) Get(name string) (Option, bool) {
|
|||
return nil, false
|
||||
}
|
||||
|
||||
func (s Set) Names() []string {
|
||||
names := []string{}
|
||||
for _, o := range s {
|
||||
names = append(names, "--"+o.Name())
|
||||
names = append(names, "-"+o.ShortName())
|
||||
}
|
||||
return names
|
||||
|
||||
}
|
||||
|
||||
func (s Set) GetByLongName(longName string) (Option, bool) {
|
||||
for _, o := range s {
|
||||
if o.Name() == longName {
|
|
@ -1,4 +1,4 @@
|
|||
package opts
|
||||
package opt
|
||||
|
||||
type StringOption struct {
|
||||
name string
|
30
option.go
30
option.go
|
@ -1,67 +1,67 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"go.fifitido.net/cmd/opts"
|
||||
"go.fifitido.net/cmd/opt"
|
||||
)
|
||||
|
||||
type Option func(*Command)
|
||||
|
||||
func WithShortDescription(s string) Option {
|
||||
return func(c *Command) {
|
||||
c.shortDescription = s
|
||||
c.ShortDescription = s
|
||||
}
|
||||
}
|
||||
|
||||
func WithLongDescription(s string) Option {
|
||||
return func(c *Command) {
|
||||
c.longDescription = s
|
||||
c.LongDescription = s
|
||||
}
|
||||
}
|
||||
|
||||
func WithArgument(name string, required bool) Option {
|
||||
return func(c *Command) {
|
||||
c.arguments = append(c.arguments, &Argument{name, required})
|
||||
c.Args = append(c.Args, &Argument{name, required})
|
||||
}
|
||||
}
|
||||
|
||||
func WithArguments(args ...*Argument) Option {
|
||||
return func(c *Command) {
|
||||
c.arguments = append(c.arguments, args...)
|
||||
c.Args = append(c.Args, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func WithOption(f opts.Option) Option {
|
||||
func WithOption(f opt.Option) Option {
|
||||
return func(c *Command) {
|
||||
c.opts = append(c.opts, f)
|
||||
c.Opts = append(c.Opts, f)
|
||||
}
|
||||
}
|
||||
|
||||
func WithOptions(os ...opts.Option) Option {
|
||||
func WithOptions(os ...opt.Option) Option {
|
||||
return func(c *Command) {
|
||||
c.opts = append(c.opts, os...)
|
||||
c.Opts = append(c.Opts, os...)
|
||||
}
|
||||
}
|
||||
|
||||
func WithSubcommand(s *Command) Option {
|
||||
return func(c *Command) {
|
||||
c.subcommands.Add(c)
|
||||
s.parent = c
|
||||
c.Subcommands.Add(s)
|
||||
s.Parent = c
|
||||
}
|
||||
}
|
||||
|
||||
func WithSubcommands(ss ...*Command) Option {
|
||||
return func(c *Command) {
|
||||
c.subcommands.Add(c)
|
||||
for _, s := range ss {
|
||||
s.parent = c
|
||||
c.Subcommands.Add(s)
|
||||
s.Parent = c
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithParent(p *Command) Option {
|
||||
return func(c *Command) {
|
||||
c.parent = p
|
||||
p.subcommands.Add(c)
|
||||
c.Parent = p
|
||||
p.Subcommands.Add(c)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
17
set.go
17
set.go
|
@ -12,11 +12,11 @@ func (s *Set) Add(c *Command) {
|
|||
|
||||
func (s Set) Get(name string) (*Command, bool) {
|
||||
for _, c := range s {
|
||||
if c.name == name {
|
||||
if c.Name == name {
|
||||
return c, true
|
||||
}
|
||||
|
||||
for _, alias := range c.aliases {
|
||||
for _, alias := range c.Aliases {
|
||||
if alias == name {
|
||||
return c, true
|
||||
}
|
||||
|
@ -25,16 +25,25 @@ func (s Set) Get(name string) (*Command, bool) {
|
|||
return nil, false
|
||||
}
|
||||
|
||||
func (s Set) MaxNameWidth() int {
|
||||
func (s Set) MaxWidth() int {
|
||||
max := 0
|
||||
for _, f := range s {
|
||||
if w := len(f.name); w > max {
|
||||
if w := len(f.Name); w > max {
|
||||
max = w
|
||||
}
|
||||
}
|
||||
return max
|
||||
}
|
||||
|
||||
func (s Set) Names() []string {
|
||||
names := make([]string, 0, len(s))
|
||||
for _, c := range s {
|
||||
names = append(names, c.Name)
|
||||
names = append(names, c.Aliases...)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func (s Set) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/iancoleman/strcase"
|
||||
)
|
||||
|
||||
var tplFuncs = template.FuncMap{
|
||||
"map": tplMap,
|
||||
"cat": tplCat,
|
||||
"split": tplSplit,
|
||||
"join": tplJoin,
|
||||
"under": tplUnder,
|
||||
"varPrefix": tplVarPrefix,
|
||||
"repeat": tplRepeat,
|
||||
"indent": tplIndent,
|
||||
"add": tplAdd,
|
||||
"inc": tplInc,
|
||||
"sub": tplSub,
|
||||
"dec": tplDec,
|
||||
"mult": tplMult,
|
||||
"pascal": tplPascal,
|
||||
"camel": tplCamel,
|
||||
}
|
||||
|
||||
func tplMap(vals ...any) (map[string]any, error) {
|
||||
if len(vals)%2 != 0 {
|
||||
return nil, fmt.Errorf("missing value, need one key and one value per kv pair")
|
||||
}
|
||||
|
||||
m := make(map[string]any)
|
||||
for i := 0; i < len(vals); i += 2 {
|
||||
m[vals[i].(string)] = vals[i+1]
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func tplCat(strs ...string) string {
|
||||
return strings.Join(strs, "")
|
||||
}
|
||||
|
||||
func tplSplit(s string, sep string) []string {
|
||||
return strings.Split(s, sep)
|
||||
}
|
||||
|
||||
func tplJoin(strs []string, sep string) string {
|
||||
return strings.Join(strs, sep)
|
||||
}
|
||||
|
||||
func tplUnder(s string) string {
|
||||
return strcase.ToSnake(s)
|
||||
}
|
||||
|
||||
func tplVarPrefix(s string) string {
|
||||
if s == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
return tplUnder(s) + "_"
|
||||
}
|
||||
|
||||
func tplRepeat(s string, n int) string {
|
||||
return strings.Repeat(s, n)
|
||||
}
|
||||
|
||||
func tplIndent(n int) string {
|
||||
return tplRepeat(" ", n)
|
||||
}
|
||||
|
||||
func tplAdd(a, b int) int {
|
||||
return a + b
|
||||
}
|
||||
|
||||
func tplInc(i int) int {
|
||||
return tplAdd(i, 1)
|
||||
}
|
||||
|
||||
func tplSub(a, b int) int {
|
||||
return a - b
|
||||
}
|
||||
|
||||
func tplDec(i int) int {
|
||||
return tplSub(i, 1)
|
||||
}
|
||||
|
||||
func tplMult(a, b int) int {
|
||||
return a * b
|
||||
}
|
||||
|
||||
func tplPascal(s string) string {
|
||||
return strcase.ToCamel(s)
|
||||
}
|
||||
|
||||
func tplCamel(s string) string {
|
||||
return strcase.ToLowerCamel(s)
|
||||
}
|
Loading…
Reference in New Issue