mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-01 15:00:00 +00:00
316 lines
6.9 KiB
Go
316 lines
6.9 KiB
Go
|
package flags
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"reflect"
|
||
|
"sort"
|
||
|
"strings"
|
||
|
"unicode/utf8"
|
||
|
)
|
||
|
|
||
|
// Completion is a type containing information of a completion.
|
||
|
type Completion struct {
|
||
|
// The completed item
|
||
|
Item string
|
||
|
|
||
|
// A description of the completed item (optional)
|
||
|
Description string
|
||
|
}
|
||
|
|
||
|
type completions []Completion
|
||
|
|
||
|
func (c completions) Len() int {
|
||
|
return len(c)
|
||
|
}
|
||
|
|
||
|
func (c completions) Less(i, j int) bool {
|
||
|
return c[i].Item < c[j].Item
|
||
|
}
|
||
|
|
||
|
func (c completions) Swap(i, j int) {
|
||
|
c[i], c[j] = c[j], c[i]
|
||
|
}
|
||
|
|
||
|
// Completer is an interface which can be implemented by types
|
||
|
// to provide custom command line argument completion.
|
||
|
type Completer interface {
|
||
|
// Complete receives a prefix representing a (partial) value
|
||
|
// for its type and should provide a list of possible valid
|
||
|
// completions.
|
||
|
Complete(match string) []Completion
|
||
|
}
|
||
|
|
||
|
type completion struct {
|
||
|
parser *Parser
|
||
|
}
|
||
|
|
||
|
// Filename is a string alias which provides filename completion.
|
||
|
type Filename string
|
||
|
|
||
|
func completionsWithoutDescriptions(items []string) []Completion {
|
||
|
ret := make([]Completion, len(items))
|
||
|
|
||
|
for i, v := range items {
|
||
|
ret[i].Item = v
|
||
|
}
|
||
|
|
||
|
return ret
|
||
|
}
|
||
|
|
||
|
// Complete returns a list of existing files with the given
|
||
|
// prefix.
|
||
|
func (f *Filename) Complete(match string) []Completion {
|
||
|
ret, _ := filepath.Glob(match + "*")
|
||
|
if len(ret) == 1 {
|
||
|
if info, err := os.Stat(ret[0]); err == nil && info.IsDir() {
|
||
|
ret[0] = ret[0] + "/"
|
||
|
}
|
||
|
}
|
||
|
return completionsWithoutDescriptions(ret)
|
||
|
}
|
||
|
|
||
|
func (c *completion) skipPositional(s *parseState, n int) {
|
||
|
if n >= len(s.positional) {
|
||
|
s.positional = nil
|
||
|
} else {
|
||
|
s.positional = s.positional[n:]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (c *completion) completeOptionNames(s *parseState, prefix string, match string, short bool) []Completion {
|
||
|
if short && len(match) != 0 {
|
||
|
return []Completion{
|
||
|
{
|
||
|
Item: prefix + match,
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var results []Completion
|
||
|
repeats := map[string]bool{}
|
||
|
|
||
|
for name, opt := range s.lookup.longNames {
|
||
|
if strings.HasPrefix(name, match) && !opt.Hidden {
|
||
|
results = append(results, Completion{
|
||
|
Item: defaultLongOptDelimiter + name,
|
||
|
Description: opt.Description,
|
||
|
})
|
||
|
|
||
|
if short {
|
||
|
repeats[string(opt.ShortName)] = true
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if short {
|
||
|
for name, opt := range s.lookup.shortNames {
|
||
|
if _, exist := repeats[name]; !exist && strings.HasPrefix(name, match) && !opt.Hidden {
|
||
|
results = append(results, Completion{
|
||
|
Item: string(defaultShortOptDelimiter) + name,
|
||
|
Description: opt.Description,
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return results
|
||
|
}
|
||
|
|
||
|
func (c *completion) completeNamesForLongPrefix(s *parseState, prefix string, match string) []Completion {
|
||
|
return c.completeOptionNames(s, prefix, match, false)
|
||
|
}
|
||
|
|
||
|
func (c *completion) completeNamesForShortPrefix(s *parseState, prefix string, match string) []Completion {
|
||
|
return c.completeOptionNames(s, prefix, match, true)
|
||
|
}
|
||
|
|
||
|
func (c *completion) completeCommands(s *parseState, match string) []Completion {
|
||
|
n := make([]Completion, 0, len(s.command.commands))
|
||
|
|
||
|
for _, cmd := range s.command.commands {
|
||
|
if cmd.data != c && !cmd.Hidden && strings.HasPrefix(cmd.Name, match) {
|
||
|
n = append(n, Completion{
|
||
|
Item: cmd.Name,
|
||
|
Description: cmd.ShortDescription,
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return n
|
||
|
}
|
||
|
|
||
|
func (c *completion) completeValue(value reflect.Value, prefix string, match string) []Completion {
|
||
|
if value.Kind() == reflect.Slice {
|
||
|
value = reflect.New(value.Type().Elem())
|
||
|
}
|
||
|
i := value.Interface()
|
||
|
|
||
|
var ret []Completion
|
||
|
|
||
|
if cmp, ok := i.(Completer); ok {
|
||
|
ret = cmp.Complete(match)
|
||
|
} else if value.CanAddr() {
|
||
|
if cmp, ok = value.Addr().Interface().(Completer); ok {
|
||
|
ret = cmp.Complete(match)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for i, v := range ret {
|
||
|
ret[i].Item = prefix + v.Item
|
||
|
}
|
||
|
|
||
|
return ret
|
||
|
}
|
||
|
|
||
|
func (c *completion) complete(args []string) []Completion {
|
||
|
if len(args) == 0 {
|
||
|
args = []string{""}
|
||
|
}
|
||
|
|
||
|
s := &parseState{
|
||
|
args: args,
|
||
|
}
|
||
|
|
||
|
c.parser.fillParseState(s)
|
||
|
|
||
|
var opt *Option
|
||
|
|
||
|
for len(s.args) > 1 {
|
||
|
arg := s.pop()
|
||
|
|
||
|
if (c.parser.Options&PassDoubleDash) != None && arg == "--" {
|
||
|
opt = nil
|
||
|
c.skipPositional(s, len(s.args)-1)
|
||
|
|
||
|
break
|
||
|
}
|
||
|
|
||
|
if argumentIsOption(arg) {
|
||
|
prefix, optname, islong := stripOptionPrefix(arg)
|
||
|
optname, _, argument := splitOption(prefix, optname, islong)
|
||
|
|
||
|
if argument == nil {
|
||
|
var o *Option
|
||
|
canarg := true
|
||
|
|
||
|
if islong {
|
||
|
o = s.lookup.longNames[optname]
|
||
|
} else {
|
||
|
for i, r := range optname {
|
||
|
sname := string(r)
|
||
|
o = s.lookup.shortNames[sname]
|
||
|
|
||
|
if o == nil {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
if i == 0 && o.canArgument() && len(optname) != len(sname) {
|
||
|
canarg = false
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if o == nil && (c.parser.Options&PassAfterNonOption) != None {
|
||
|
opt = nil
|
||
|
c.skipPositional(s, len(s.args)-1)
|
||
|
|
||
|
break
|
||
|
} else if o != nil && o.canArgument() && !o.OptionalArgument && canarg {
|
||
|
if len(s.args) > 1 {
|
||
|
s.pop()
|
||
|
} else {
|
||
|
opt = o
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
if len(s.positional) > 0 {
|
||
|
if !s.positional[0].isRemaining() {
|
||
|
// Don't advance beyond a remaining positional arg (because
|
||
|
// it consumes all subsequent args).
|
||
|
s.positional = s.positional[1:]
|
||
|
}
|
||
|
} else if cmd, ok := s.lookup.commands[arg]; ok {
|
||
|
cmd.fillParseState(s)
|
||
|
}
|
||
|
|
||
|
opt = nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
lastarg := s.args[len(s.args)-1]
|
||
|
var ret []Completion
|
||
|
|
||
|
if opt != nil {
|
||
|
// Completion for the argument of 'opt'
|
||
|
ret = c.completeValue(opt.value, "", lastarg)
|
||
|
} else if argumentStartsOption(lastarg) {
|
||
|
// Complete the option
|
||
|
prefix, optname, islong := stripOptionPrefix(lastarg)
|
||
|
optname, split, argument := splitOption(prefix, optname, islong)
|
||
|
|
||
|
if argument == nil && !islong {
|
||
|
rname, n := utf8.DecodeRuneInString(optname)
|
||
|
sname := string(rname)
|
||
|
|
||
|
if opt := s.lookup.shortNames[sname]; opt != nil && opt.canArgument() {
|
||
|
ret = c.completeValue(opt.value, prefix+sname, optname[n:])
|
||
|
} else {
|
||
|
ret = c.completeNamesForShortPrefix(s, prefix, optname)
|
||
|
}
|
||
|
} else if argument != nil {
|
||
|
if islong {
|
||
|
opt = s.lookup.longNames[optname]
|
||
|
} else {
|
||
|
opt = s.lookup.shortNames[optname]
|
||
|
}
|
||
|
|
||
|
if opt != nil {
|
||
|
ret = c.completeValue(opt.value, prefix+optname+split, *argument)
|
||
|
}
|
||
|
} else if islong {
|
||
|
ret = c.completeNamesForLongPrefix(s, prefix, optname)
|
||
|
} else {
|
||
|
ret = c.completeNamesForShortPrefix(s, prefix, optname)
|
||
|
}
|
||
|
} else if len(s.positional) > 0 {
|
||
|
// Complete for positional argument
|
||
|
ret = c.completeValue(s.positional[0].value, "", lastarg)
|
||
|
} else if len(s.command.commands) > 0 {
|
||
|
// Complete for command
|
||
|
ret = c.completeCommands(s, lastarg)
|
||
|
}
|
||
|
|
||
|
sort.Sort(completions(ret))
|
||
|
return ret
|
||
|
}
|
||
|
|
||
|
func (c *completion) print(items []Completion, showDescriptions bool) {
|
||
|
if showDescriptions && len(items) > 1 {
|
||
|
maxl := 0
|
||
|
|
||
|
for _, v := range items {
|
||
|
if len(v.Item) > maxl {
|
||
|
maxl = len(v.Item)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for _, v := range items {
|
||
|
fmt.Printf("%s", v.Item)
|
||
|
|
||
|
if len(v.Description) > 0 {
|
||
|
fmt.Printf("%s # %s", strings.Repeat(" ", maxl-len(v.Item)), v.Description)
|
||
|
}
|
||
|
|
||
|
fmt.Printf("\n")
|
||
|
}
|
||
|
} else {
|
||
|
for _, v := range items {
|
||
|
fmt.Println(v.Item)
|
||
|
}
|
||
|
}
|
||
|
}
|