v0.27.0 packaging update
This commit is contained in:
1 parent
b5ac9084c7
commit
60abc97eb0
17 files changed
+1194
-43
No files matched your search
@@ -0,0 +1,321 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/lixenwraith/logwisp/internal/config"
|
||||
)
|
||||
|
||||
// compFlag is a flag as a shell completes it: value is its row in a shell's
|
||||
// values, "" for a switch; repeat lets a shell offer it again.
|
||||
type compFlag struct {
|
||||
long, short, value, help string
|
||||
repeat bool
|
||||
}
|
||||
|
||||
type compCommand struct {
|
||||
name, summary string
|
||||
subs []compSub
|
||||
}
|
||||
|
||||
type compSub struct {
|
||||
name, summary string
|
||||
flags []compFlag
|
||||
}
|
||||
|
||||
// shell is a completion script make completion writes under
|
||||
// deploy/package/completion: values completes a flag's value by its kind,
|
||||
// "text" as the shell would without lw's script, PRESETS the preset names.
|
||||
type shell struct {
|
||||
file string
|
||||
values map[string]string
|
||||
write func(w io.Writer, top []compFlag, cmds []compCommand, values map[string]string)
|
||||
}
|
||||
|
||||
var shells = []shell{
|
||||
{"lw.bash", map[string]string{
|
||||
"text": "COMPREPLY=()",
|
||||
"dir": "_lw_reply -d",
|
||||
"preset": "mapfile -t COMPREPLY < <(compgen -W 'PRESETS' -- \"$cur\")",
|
||||
}, writeBash},
|
||||
{"_lw", map[string]string{
|
||||
"text": ":value:_default",
|
||||
"dir": ":directory:_files -/",
|
||||
"preset": ":preset:(PRESETS)",
|
||||
}, writeZsh},
|
||||
{"lw.fish", map[string]string{
|
||||
"text": "-r -F",
|
||||
"dir": "-x -a '(__fish_complete_directories)'",
|
||||
"preset": "-x -a 'PRESETS'",
|
||||
}, writeFish},
|
||||
}
|
||||
|
||||
// valueOf is the kind of value an option of lw's own takes when it is not
|
||||
// text: "" for none, as a bare --color means always.
|
||||
var valueOf = map[string]string{"preset": "preset", "logging.file.directory": "dir", "color": "", "help": ""}
|
||||
|
||||
const generated = "Generated by make completion from lw's own tables: edit cmd/lw, not this file."
|
||||
|
||||
// script is the shell's completion for lw as its tables stand
|
||||
func (s shell) script() []byte {
|
||||
top, cmds, presets := completionModel()
|
||||
values := map[string]string{}
|
||||
for kind, action := range s.values {
|
||||
values[kind] = strings.ReplaceAll(action, "PRESETS", presets)
|
||||
}
|
||||
var b bytes.Buffer
|
||||
s.write(&b, top, cmds, values)
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
// options maps each long option lw itself takes to its short letter, if any
|
||||
func options() map[string]string {
|
||||
options := map[string]string{}
|
||||
for _, long := range config.SpecFlags() {
|
||||
options[long] = ""
|
||||
}
|
||||
for long := range config.Settings() {
|
||||
options[long] = ""
|
||||
}
|
||||
for letter, short := range shorts {
|
||||
if short.top {
|
||||
options[short.long] = letter
|
||||
}
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
// completionModel reads the parser's tables: lw's options, then each
|
||||
// command's subcommands with the flags their define registers.
|
||||
func completionModel() (top []compFlag, cmds []compCommand, presets string) {
|
||||
settings, specFlags, shortOf := config.Settings(), config.SpecFlags(), options()
|
||||
for _, long := range slices.Sorted(maps.Keys(shortOf)) {
|
||||
f := compFlag{long: long, short: shortOf[long], value: "text", repeat: slices.Contains(specFlags, long)}
|
||||
if v, ok := valueOf[long]; ok {
|
||||
f.value = v
|
||||
} else if settings[long] {
|
||||
f.value = ""
|
||||
}
|
||||
top = append(top, f)
|
||||
}
|
||||
for _, c := range commands {
|
||||
cmd := compCommand{name: c.name, summary: c.summary}
|
||||
for _, s := range c.subcommands {
|
||||
fs := flag.NewFlagSet(s.name, flag.ContinueOnError)
|
||||
s.define(fs)
|
||||
sub := compSub{name: s.name, summary: s.summary}
|
||||
fs.VisitAll(func(f *flag.Flag) {
|
||||
if f.Usage == "" {
|
||||
return // an alias, unlisted like a short
|
||||
}
|
||||
arg, help := flag.UnquoteUsage(f)
|
||||
value := arg // "" for a switch; a backquoted `dir` names its row
|
||||
if _, known := shells[0].values[arg]; arg != "" && !known {
|
||||
value = "text"
|
||||
}
|
||||
sub.flags = append(sub.flags, compFlag{long: f.Name, short: shortFor(f.Name), value: value, help: help})
|
||||
})
|
||||
// flag answers -h and --help itself
|
||||
sub.flags = append(sub.flags, compFlag{long: "help", short: shortFor("help"), help: "print the flags"})
|
||||
cmd.subs = append(cmd.subs, sub)
|
||||
}
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
var names []string
|
||||
for _, p := range config.Presets() {
|
||||
names = append(names, p.Name)
|
||||
}
|
||||
return top, cmds, strings.Join(names, " ")
|
||||
}
|
||||
|
||||
func shortFor(long string) string {
|
||||
for letter, short := range shorts {
|
||||
if short.long == long {
|
||||
return letter
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// words lists the flags as typed: --long, then -s for a short
|
||||
func words(flags []compFlag, dashes ...string) string {
|
||||
var w []string
|
||||
for _, f := range flags {
|
||||
for _, d := range dashes {
|
||||
w = append(w, d+f.long)
|
||||
}
|
||||
if f.short != "" {
|
||||
w = append(w, "-"+f.short)
|
||||
}
|
||||
}
|
||||
return strings.Join(w, " ")
|
||||
}
|
||||
|
||||
// bashValues is a case statement completing each valued flag's argument
|
||||
func bashValues(w io.Writer, indent string, flags []compFlag, values map[string]string, dashes ...string) {
|
||||
byValue := map[string][]string{}
|
||||
for _, f := range flags {
|
||||
if f.value != "" {
|
||||
byValue[f.value] = append(byValue[f.value], strings.Fields(words([]compFlag{f}, dashes...))...)
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(w, "%scase $prev in\n", indent)
|
||||
for _, v := range slices.Sorted(maps.Keys(byValue)) {
|
||||
fmt.Fprintf(w, "%s%s) %s; return ;;\n", indent, strings.Join(byValue[v], "|"), values[v])
|
||||
}
|
||||
fmt.Fprintf(w, "%sesac\n", indent)
|
||||
}
|
||||
|
||||
func writeBash(w io.Writer, top []compFlag, cmds []compCommand, values map[string]string) {
|
||||
var names []string
|
||||
for _, c := range cmds {
|
||||
names = append(names, c.name)
|
||||
}
|
||||
fmt.Fprintf(w, "# bash completion for lw. %s\n\n", generated)
|
||||
fmt.Fprintf(w, "_lw_reply() { compopt -o filenames 2>/dev/null; mapfile -t COMPREPLY < <(compgen \"$@\" -- \"$cur\"); }\n\n")
|
||||
fmt.Fprintf(w, "_lw() {\n")
|
||||
fmt.Fprintf(w, "\tlocal cur=${COMP_WORDS[COMP_CWORD]} prev='' i=$COMP_CWORD words\n")
|
||||
fmt.Fprintf(w, "\t((i > 0)) && prev=${COMP_WORDS[i-1]}\n")
|
||||
fmt.Fprintf(w, "\t# bash splits --key=value at '=': complete the value of the key before it\n")
|
||||
fmt.Fprintf(w, "\tif [[ $cur == = ]]; then cur=''\n\telif [[ $prev == = ]]; then prev=${COMP_WORDS[i-2]}\n\tfi\n")
|
||||
fmt.Fprintf(w, "\tcase ${COMP_WORDS[1]}:$i in\n")
|
||||
fmt.Fprintf(w, "\t*:1) words='%s %s help' ;;\n", words(top, "--"), strings.Join(names, " "))
|
||||
for _, c := range cmds {
|
||||
var subs []string
|
||||
for _, s := range c.subs {
|
||||
subs = append(subs, s.name)
|
||||
}
|
||||
fmt.Fprintf(w, "\t%s:2) words='%s' ;;\n", c.name, strings.Join(subs, " "))
|
||||
fmt.Fprintf(w, "\t%s:*)\n\t\tcase ${COMP_WORDS[2]} in\n", c.name)
|
||||
for _, s := range c.subs {
|
||||
fmt.Fprintf(w, "\t\t%s)\n", s.name)
|
||||
bashValues(w, "\t\t\t", s.flags, values, "--", "-")
|
||||
fmt.Fprintf(w, "\t\t\twords='%s' ;;\n", words(s.flags, "--"))
|
||||
}
|
||||
fmt.Fprintf(w, "\t\tesac ;;\n")
|
||||
}
|
||||
fmt.Fprintf(w, "\t*)\n")
|
||||
bashValues(w, "\t\t", top, values, "--")
|
||||
fmt.Fprintf(w, "\t\twords='%s' ;;\n", words(top, "--"))
|
||||
fmt.Fprintf(w, "\tesac\n")
|
||||
fmt.Fprintf(w, "\tmapfile -t COMPREPLY < <(compgen -W \"$words\" -- \"$cur\")\n}\n\n")
|
||||
fmt.Fprintf(w, "# An empty reply falls back to file names, as without this script\n")
|
||||
fmt.Fprintf(w, "complete -o bashdefault -o default -F _lw lw\n")
|
||||
}
|
||||
|
||||
// zshQuote makes help text safe inside a single-quoted _arguments spec
|
||||
func zshQuote(s string) string {
|
||||
return strings.NewReplacer("'", `'\''`, "]", `\]`).Replace(s)
|
||||
}
|
||||
|
||||
// zshSpecs are _arguments specs for flags; a value follows '=' or as the next
|
||||
// word, never attached to a short, the forms lw parses.
|
||||
func zshSpecs(flags []compFlag, values map[string]string) []string {
|
||||
var specs []string
|
||||
for _, f := range flags {
|
||||
help, action, eq := "", "", ""
|
||||
if f.help != "" {
|
||||
help = "[" + zshQuote(f.help) + "]"
|
||||
}
|
||||
if f.value != "" {
|
||||
action, eq = values[f.value], "="
|
||||
}
|
||||
names, repeat := fmt.Sprintf("'(-%s --%s)'{-%s%s,--%s%s}", f.short, f.long, f.short, eq, f.long, eq), ""
|
||||
if f.repeat {
|
||||
names, repeat = fmt.Sprintf("'*'{-%s%s,--%s%s}", f.short, eq, f.long, eq), "*"
|
||||
}
|
||||
switch tail := help + action; {
|
||||
case f.short == "":
|
||||
specs = append(specs, fmt.Sprintf("'%s--%s%s%s'", repeat, f.long, eq, tail))
|
||||
case tail == "":
|
||||
specs = append(specs, names)
|
||||
default:
|
||||
specs = append(specs, names+"'"+tail+"'")
|
||||
}
|
||||
}
|
||||
return specs
|
||||
}
|
||||
|
||||
func writeZsh(w io.Writer, top []compFlag, cmds []compCommand, values map[string]string) {
|
||||
fmt.Fprintf(w, "#compdef lw\n# zsh completion for lw. %s\n\n", generated)
|
||||
fmt.Fprintf(w, "local -a described\n")
|
||||
fmt.Fprintf(w, "case ${words[2]}:$CURRENT in\n")
|
||||
for _, c := range cmds {
|
||||
fmt.Fprintf(w, "%s:2) ;;\n%s:3)\n\tdescribed=(\n", c.name, c.name)
|
||||
for _, s := range c.subs {
|
||||
fmt.Fprintf(w, "\t\t'%s:%s'\n", s.name, zshQuote(s.summary))
|
||||
}
|
||||
fmt.Fprintf(w, "\t)\n\t_describe -t commands 'lw %s command' described\n\treturn ;;\n", c.name)
|
||||
// _arguments reads words from the second: make that the subcommand's first flag
|
||||
fmt.Fprintf(w, "%s:*)\n\tshift 2 words\n\t(( CURRENT -= 2 ))\n\tcase ${words[1]} in\n", c.name)
|
||||
for _, s := range c.subs {
|
||||
fmt.Fprintf(w, "\t%s) _arguments -S \\\n\t\t%s ;;\n", s.name, strings.Join(zshSpecs(s.flags, values), " \\\n\t\t"))
|
||||
}
|
||||
fmt.Fprintf(w, "\tesac\n\treturn ;;\n")
|
||||
}
|
||||
fmt.Fprintf(w, "esac\n")
|
||||
fmt.Fprintf(w, "if (( CURRENT == 2 )) && [[ $PREFIX != -* ]]; then\n\tdescribed=(\n")
|
||||
for _, c := range cmds {
|
||||
fmt.Fprintf(w, "\t\t'%s:%s'\n", c.name, zshQuote(c.summary))
|
||||
}
|
||||
fmt.Fprintf(w, "\t\t'help:Print the help and exit'\n\t)\n")
|
||||
fmt.Fprintf(w, "\t_describe -t commands 'lw command' described\n\treturn\nfi\n")
|
||||
fmt.Fprintf(w, "_arguments -S \\\n\t%s\n", strings.Join(zshSpecs(top, values), " \\\n\t"))
|
||||
}
|
||||
|
||||
// fishQuote makes text safe inside a single-quoted fish string
|
||||
func fishQuote(s string) string {
|
||||
return strings.NewReplacer(`\`, `\\`, "'", `\'`).Replace(s)
|
||||
}
|
||||
|
||||
// fishFlags completes a short as an old-style option (-o): lw takes no
|
||||
// bundled shorts and no value attached to one.
|
||||
func fishFlags(w io.Writer, cond string, flags []compFlag, values map[string]string) {
|
||||
for _, f := range flags {
|
||||
line := fmt.Sprintf("complete -c lw -n '%s' -l %s", cond, f.long)
|
||||
if f.short != "" {
|
||||
line += " -o " + f.short
|
||||
}
|
||||
if f.value != "" {
|
||||
line += " " + values[f.value]
|
||||
}
|
||||
if f.help != "" {
|
||||
line += " -d '" + fishQuote(f.help) + "'"
|
||||
}
|
||||
fmt.Fprintln(w, line)
|
||||
}
|
||||
}
|
||||
|
||||
func writeFish(w io.Writer, top []compFlag, cmds []compCommand, values map[string]string) {
|
||||
fmt.Fprintf(w, "# fish completion for lw. %s\n\n", generated)
|
||||
fmt.Fprintf(w, "# __lw_in WORD...: the command line so far is lw WORD...\n")
|
||||
fmt.Fprintf(w, "function __lw_in\n\tset -l w (commandline -opc)\n\tset -l n (math (count $argv) + 1)\n")
|
||||
fmt.Fprintf(w, "\ttest (count $w) -ge $n; and test \"$w[2..$n]\" = \"$argv\"\nend\n\n")
|
||||
fmt.Fprintf(w, "complete -c lw -f\n")
|
||||
fmt.Fprintf(w, "set -l __lw_first 'test (count (commandline -opc)) -eq 1'\n")
|
||||
for _, c := range cmds {
|
||||
fmt.Fprintf(w, "complete -c lw -n $__lw_first -a %s -d '%s'\n", c.name, fishQuote(c.summary))
|
||||
}
|
||||
fmt.Fprintf(w, "complete -c lw -n $__lw_first -a help -d 'Print the help and exit'\n")
|
||||
var cond []string
|
||||
for _, c := range cmds {
|
||||
cond = append(cond, "__lw_in "+c.name)
|
||||
}
|
||||
fishFlags(w, "not "+strings.Join(cond, "; and not "), top, values)
|
||||
for _, c := range cmds {
|
||||
fmt.Fprintf(w, "\n")
|
||||
for _, s := range c.subs {
|
||||
fmt.Fprintf(w, "complete -c lw -n '__lw_in %s; and test (count (commandline -opc)) -eq 2' -a %s -d '%s'\n",
|
||||
c.name, s.name, fishQuote(s.summary))
|
||||
}
|
||||
for _, s := range c.subs {
|
||||
fishFlags(w, "__lw_in "+c.name+" "+s.name, s.flags, values)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user