Compare commits

..

No commits in common. "main" and "v0.2.1" have entirely different histories.
main ... v0.2.1

11 changed files with 26 additions and 301 deletions

View File

@ -9,7 +9,6 @@ import (
type Command struct {
Name string
version string
ShortDescription string
LongDescription string
Aliases []string
@ -21,8 +20,8 @@ type Command struct {
isRoot bool
}
func NewRoot(name string, version string, options ...Option) *Command {
cmd := &Command{Name: name, version: version, isRoot: true}
func NewRoot(name string, options ...Option) *Command {
cmd := &Command{Name: name, isRoot: true}
cmd.ApplyOptions(options...)
return cmd
}
@ -47,10 +46,6 @@ func (c *Command) Root() *Command {
return c.Parent.Root()
}
func (c *Command) IsRoot() bool {
return c.isRoot
}
func (c *Command) CommandPath() string {
if c.Parent == nil {
return ""
@ -76,13 +71,6 @@ 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 := opt.NewParser(args, c.Opts, false)
restArgs, err := parser.Parse()
@ -92,10 +80,12 @@ func (c *Command) Execute(args []string) {
os.Exit(1)
}
versionOpt, ok := opt.Globals().GetBool("version")
if ok && versionOpt.Value() {
c.ShowVersion()
return
if len(restArgs) > 0 {
sc, ok := c.Subcommands.Get(restArgs[0])
if ok {
sc.Execute(restArgs[1:])
return
}
}
helpOpt, ok := opt.Globals().GetBool("help")
@ -111,8 +101,3 @@ func (c *Command) Execute(args []string) {
c.ShowHelp()
}
func (c *Command) ShowVersion() {
rootName := c.Root().Name
fmt.Printf("%s %s\n", rootName, c.version)
}

View File

@ -9,50 +9,11 @@ import (
func WriteBashCompletions(out io.Writer, rootCmd *Command) error {
return bashTpl.Execute(out, map[string]any{
"RootCmd": rootCmd,
"GlobalOpts": opt.Globals(),
"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}}
var bashTpl = template.Must(template.New("bash").Parse(`
`))

View File

@ -14,12 +14,11 @@ func WriteFishCompletions(out io.Writer, rootCmd *Command) error {
})
}
// 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 " " }}
set -l commands {{- range $rootCmd.Subcommands }} {{ .Name }}{{ end }}
function __fish_{{ $varName }}_needs_command
set -l cmd (commandline -opc)
@ -75,7 +74,7 @@ complete -f -c {{ $progName }} -n "__fish_{{ $varName }}_using_command {{.Cmd.Pa
{{ end -}}
{{ if gt (len .Cmd.Subcommands) 0 }}
set -l {{ $varPrefix }}commands {{ join .Cmd.Subcommands.Names " " }}
set -l {{ $varPrefix }}commands {{- range .Cmd.Subcommands }} {{ .Name }}{{ end -}}
{{ $cmdName := .Cmd.Name }}
{{- range .Cmd.Subcommands }}
{{- template "cmd" (map "Cmd" . "ProgName" $progName "VarName" $varName) -}}

View File

@ -9,82 +9,11 @@ import (
func WritePowerShellCompletions(out io.Writer, rootCmd *Command) error {
return powerShellTpl.Execute(out, map[string]any{
"RootCmd": rootCmd,
"GlobalOpts": opt.Globals(),
"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
var powerShellTpl = template.Must(template.New("PowerShell").Parse(`
`))

View File

@ -9,77 +9,11 @@ import (
func WriteZshCompletions(out io.Writer, rootCmd *Command) error {
return zshTpl.Execute(out, map[string]any{
"RootCmd": rootCmd,
"GlobalOpts": opt.Globals(),
"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 }}
var zshTpl = template.Must(template.New("zsh").Parse(`
`))

2
go.mod
View File

@ -1,5 +1,3 @@
module go.fifitido.net/cmd
go 1.21.3
require github.com/iancoleman/strcase v0.3.0

2
go.sum
View File

@ -1,2 +0,0 @@
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=

View File

@ -2,7 +2,6 @@ package cmd
import (
"fmt"
"strings"
"go.fifitido.net/cmd/opt"
)
@ -44,10 +43,8 @@ func (c *Command) ShowHelp() {
fmt.Println("Available subcommands:")
}
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(" " + s.Name + " " + s.ShortDescription)
}
}

View File

@ -33,16 +33,6 @@ 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 {

11
set.go
View File

@ -25,7 +25,7 @@ func (s Set) Get(name string) (*Command, bool) {
return nil, false
}
func (s Set) MaxWidth() int {
func (s Set) MaxNameWidth() int {
max := 0
for _, f := range s {
if w := len(f.Name); w > max {
@ -35,15 +35,6 @@ func (s Set) MaxWidth() int {
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)
}

View File

@ -4,26 +4,13 @@ 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) {
@ -38,20 +25,12 @@ func tplMap(vals ...any) (map[string]any, error) {
return m, nil
}
func tplCat(strs ...string) string {
func tplJoin(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)
return strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(s, " ", "_"), "-", "_"))
}
func tplVarPrefix(s string) string {
@ -61,39 +40,3 @@ func tplVarPrefix(s string) string {
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)
}