Skip to content

Commit

Permalink
Merge pull request #13 from rsteube/positional-modifier
Browse files Browse the repository at this point in the history
positional modifier
  • Loading branch information
rsteube authored May 2, 2022
2 parents 7ae0011 + 3b8fb75 commit 4acc4fe
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 115 deletions.
6 changes: 3 additions & 3 deletions example/example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ commands:
-s, --styled=: styled values
-r, --repeatable*: repeatable flag
-o, --optarg?: optarg flag
-l, --list=,: list flag
-l, --list=: list flag
--dynamic=: dynamic value
--env=: env
--novalue: no value
-n, --nospace=!: nospace
-n, --nospace=: nospace
completion:
flag:
styled:
Expand All @@ -27,7 +27,7 @@ commands:
- "unstyled\tunstyled with description"
dynamic: ["$(git branch --all | cut -c 3- | sed 's/$/\t\tblue/')", "static value"]
env: ["$(env)"]
list: ["a", "b", "c", "d"]
list: ["$_list(,)", "a", "b", "c", "d"]
nospace: ["1", "2", "3"]
optarg: ["first", "second", "third"]
positional:
Expand Down
7 changes: 4 additions & 3 deletions example/pkill.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ flags:
-x, --exact: match exactly with the command name
-F, --pidfile=: read PIDs from file
-L, --logpidfile: fail if PID file is not locked
-r, --runstates=,: match runstates [D,S,Z,...]
-r, --runstates=: match runstates [D,S,Z,...]
--ns=: match the processes that belong to the same namespace as <pid>
--nslist=,: list which namespaces will be considered for the --ns option.
--nslist=: list which namespaces will be considered for the --ns option.
-h, --help: display this help and exit
-V, --version: output version information and exit
completion:
Expand Down Expand Up @@ -59,8 +59,9 @@ completion:
- "WINCH\tWindow size change\tblue"
- "XCPU\tCPU time limit exceeded\tmagenta"
- "XFSZ\tFile size limit exceeded\tmagenta"
nslist: ["ipc", "mnt", "net", "pid", "user", "uts"]
nslist: ["$_list(,)", "ipc", "mnt", "net", "pid", "user", "uts"]
runstates:
- "$_list(,)"
- "D\tuninterruptible sleep (usually IO)"
- "I\tIdle kernel thread"
- "R\trunning or runnable (on run queue)"
Expand Down
168 changes: 59 additions & 109 deletions spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,50 +34,17 @@ func (c *Command) ToCobra() *cobra.Command {
}
carapace.Gen(cmd).Standalone()

flagCompletions := make(carapace.ActionMap)

for id, description := range c.PersistentFlags {
fs := parseFlagSpec(id, description)
fs.addTo(cmd.PersistentFlags())
if a, ok := c.Completion.Flag[fs.name()]; ok {
var action carapace.Action
if fs.delimiter != 0 {
action = carapace.ActionMultiParts(string(fs.delimiter), func(c carapace.Context) carapace.Action {
return parseAction(cmd, a).Invoke(c).Filter(c.Parts).ToA()
})
} else {
action = carapace.ActionCallback(func(c carapace.Context) carapace.Action {
return parseAction(cmd, a)
})
}

if fs.nospace {
action = action.NoSpace()
}
flagCompletions[fs.name()] = action
}
parseFlag(cmd.PersistentFlags(), id, description)
}

for id, description := range c.Flags {
fs := parseFlagSpec(id, description)
fs.addTo(cmd.Flags())
if a, ok := c.Completion.Flag[fs.name()]; ok {
var action carapace.Action
if fs.delimiter != 0 {
action = carapace.ActionMultiParts(string(fs.delimiter), func(c carapace.Context) carapace.Action {
return parseAction(cmd, a).Invoke(c).Filter(c.Parts).ToA()
})
} else {
action = carapace.ActionCallback(func(c carapace.Context) carapace.Action {
return parseAction(cmd, a)
})
}
parseFlag(cmd.Flags(), id, description)
}

if fs.nospace {
action = action.NoSpace()
}
flagCompletions[fs.name()] = action
}
flagCompletions := make(carapace.ActionMap)
for key, value := range c.Completion.Flag {
flagCompletions[key] = parseAction(cmd, value)
}
carapace.Gen(cmd).FlagCompletion(flagCompletions)

Expand All @@ -104,99 +71,67 @@ func (c *Command) ToCobra() *cobra.Command {
return cmd
}

type flagSpec struct {
longhand string
shorthand string
description string
slice bool
optarg bool
value bool
nospace bool
delimiter rune
}

func (fs flagSpec) name() string {
if fs.longhand != "" {
return fs.longhand
}
return fs.shorthand
}

func parseFlagSpec(spec, description string) flagSpec {
r := regexp.MustCompile(`^(?P<shorthand>-[^-])?(, *)?(?P<longhand>--[^ =*?!\/:.,]*)?(?P<modifier>[=*?!\/:.,]*)(?P<delimiter>.*)$`)
matches := findNamedMatches(r, spec)
func parseFlag(flagSet *pflag.FlagSet, id, description string) error {
r := regexp.MustCompile(`^(?P<shorthand>-[^-])?(, )?(?P<longhand>--[^ =*?]*)?(?P<modifier>[=*?]*)$`)
matches := findNamedMatches(r, id)

fs := flagSpec{
longhand: strings.TrimPrefix(matches["longhand"], "--"),
shorthand: strings.TrimPrefix(matches["shorthand"], "-"),
description: description,
slice: strings.Contains(matches["modifier"], "*"),
optarg: strings.Contains(matches["modifier"], "?"),
nospace: strings.Contains(matches["modifier"], "!"),
}
fs.value = fs.optarg || strings.Contains(matches["modifier"], "=")
longhand := strings.TrimPrefix(matches["longhand"], "--")
shorthand := strings.TrimPrefix(matches["shorthand"], "-")
slice := strings.Contains(matches["modifier"], "*")
optarg := strings.Contains(matches["modifier"], "?")
value := optarg || strings.Contains(matches["modifier"], "=")

for _, d := range "/:.," {
if strings.ContainsRune(matches["modifier"], d) {
fs.delimiter = d
break
}
}
return fs
}

func (fs flagSpec) addTo(flagSet *pflag.FlagSet) error {
if fs.longhand != "" && fs.shorthand != "" {
if fs.value {
if fs.slice {
flagSet.StringSliceP(fs.longhand, fs.shorthand, []string{}, fs.description)
if longhand != "" && shorthand != "" {
if value {
if slice {
flagSet.StringSliceP(longhand, shorthand, []string{}, description)
} else {
flagSet.StringP(fs.longhand, fs.shorthand, "", fs.description)
flagSet.StringP(longhand, shorthand, "", description)
}
} else {
if fs.slice {
flagSet.CountP(fs.longhand, fs.shorthand, fs.description)
if slice {
flagSet.CountP(longhand, shorthand, description)
} else {
flagSet.BoolP(fs.longhand, fs.shorthand, false, fs.description)
flagSet.BoolP(longhand, shorthand, false, description)
}
}
} else if fs.longhand != "" {
if fs.value {
if fs.slice {
flagSet.StringSlice(fs.longhand, []string{}, fs.description)
} else if longhand != "" {
if value {
if slice {
flagSet.StringSlice(longhand, []string{}, description)
} else {
flagSet.String(fs.longhand, "", fs.description)
flagSet.String(longhand, "", description)
}
} else {
if fs.slice {
flagSet.Count(fs.longhand, fs.description)
if slice {
flagSet.Count(longhand, description)
} else {
flagSet.Bool(fs.longhand, false, fs.description)
flagSet.Bool(longhand, false, description)
}
}
} else if fs.shorthand != "" {
if fs.value {
if fs.slice {
flagSet.StringSliceS(fs.shorthand, fs.shorthand, []string{}, fs.description)
} else if shorthand != "" {
if value {
if slice {
flagSet.StringSliceS(shorthand, shorthand, []string{}, description)
} else {
flagSet.StringS(fs.shorthand, fs.shorthand, "", fs.description)
flagSet.StringS(shorthand, shorthand, "", description)
}
} else {
if fs.slice {
flagSet.CountS(fs.shorthand, fs.shorthand, fs.description)
if slice {
flagSet.CountS(shorthand, shorthand, description)
} else {
flagSet.BoolS(fs.shorthand, fs.shorthand, false, fs.description)
flagSet.BoolS(shorthand, shorthand, false, description)
}
}
} else {
return fmt.Errorf("malformed flag") // TODO context info
return fmt.Errorf("malformed flag: %v", id)
}

if fs.optarg {
if fs.longhand != "" {
flagSet.Lookup(fs.longhand).NoOptDefVal = " "
if optarg {
if longhand != "" {
flagSet.Lookup(longhand).NoOptDefVal = " "
} else {
flagSet.Lookup(fs.shorthand).NoOptDefVal = " "
flagSet.Lookup(shorthand).NoOptDefVal = " "
}
}

Expand All @@ -205,15 +140,22 @@ func (fs flagSpec) addTo(flagSet *pflag.FlagSet) error {

func parseAction(cmd *cobra.Command, arr []string) carapace.Action {
r := regexp.MustCompile(`^\$\((?P<cmd>.*)\)$`)
rList := regexp.MustCompile(`^\$_list\((?P<delimiter>.*)\)$`)
listDelimiter := ""
nospace := false

batch := carapace.Batch()

vals := make([]string, 0)
for _, elem := range arr {
if strings.HasPrefix(elem, "$_files") {
if elem == "$_nospace" {
nospace = true
} else if strings.HasPrefix(elem, "$_files") {
return carapace.ActionFiles() // TODO params
} else if strings.HasPrefix(elem, "$_directories") {
return carapace.ActionDirectories()
} else if rList.MatchString(elem) {
listDelimiter = rList.FindStringSubmatch(elem)[1]
} else if r.MatchString(elem) {
elemCopy := elem
batch = append(batch, carapace.ActionCallback(func(c carapace.Context) carapace.Action {
Expand Down Expand Up @@ -247,6 +189,14 @@ func parseAction(cmd *cobra.Command, arr []string) carapace.Action {
}
// TODO $(execute) actions
batch = append(batch, carapace.ActionStyledValuesDescribed(vals...))

if listDelimiter != "" {
return carapace.ActionMultiParts(listDelimiter, func(c carapace.Context) carapace.Action {
return batch.ToA()
})
} else if nospace {
return batch.ToA().NoSpace()
}
return batch.ToA()
}

Expand Down

0 comments on commit 4acc4fe

Please sign in to comment.