mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-17 22:14:53 +02:00
split up types.go
This commit is contained in:
@@ -7,282 +7,14 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"kitty/tools/cli/completion"
|
||||
"kitty/tools/utils"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
// Option {{{
|
||||
type OptionType int
|
||||
|
||||
const (
|
||||
StringOption OptionType = iota
|
||||
IntegerOption
|
||||
FloatOption
|
||||
BoolOption
|
||||
CountOption
|
||||
)
|
||||
|
||||
type Alias struct {
|
||||
NameWithoutHyphens string
|
||||
IsShort bool
|
||||
IsUnset bool
|
||||
}
|
||||
|
||||
func (self *Alias) String() string {
|
||||
if self.IsShort {
|
||||
return "-" + self.NameWithoutHyphens
|
||||
}
|
||||
return "--" + self.NameWithoutHyphens
|
||||
}
|
||||
|
||||
type OptionSpec struct {
|
||||
Name string
|
||||
Type string
|
||||
Dest string
|
||||
Choices string
|
||||
Depth int
|
||||
Default string
|
||||
Help string
|
||||
CompletionFunc *completion.CompletionFunc
|
||||
}
|
||||
|
||||
type Option struct {
|
||||
Name string
|
||||
Aliases []Alias
|
||||
Choices []string
|
||||
Default string
|
||||
OptionType OptionType
|
||||
Hidden bool
|
||||
Depth int
|
||||
Help string
|
||||
IsList bool
|
||||
Parent *Command
|
||||
CompletionFunc *completion.CompletionFunc
|
||||
|
||||
values_from_cmdline []string
|
||||
parsed_values_from_cmdline []any
|
||||
parsed_default any
|
||||
seen_option string
|
||||
}
|
||||
|
||||
func (self *Option) reset() {
|
||||
self.values_from_cmdline = self.values_from_cmdline[:0]
|
||||
self.parsed_values_from_cmdline = self.parsed_values_from_cmdline[:0]
|
||||
self.seen_option = ""
|
||||
}
|
||||
|
||||
func (self *Option) needs_argument() bool {
|
||||
return self.OptionType != BoolOption && self.OptionType != CountOption
|
||||
}
|
||||
|
||||
func (self *Option) HasAlias(name_without_hyphens string, is_short bool) bool {
|
||||
for _, a := range self.Aliases {
|
||||
if a.IsShort == is_short && a.NameWithoutHyphens == name_without_hyphens {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ParseError struct {
|
||||
Option *Option
|
||||
Message string
|
||||
}
|
||||
|
||||
func (self *ParseError) Error() string { return self.Message }
|
||||
|
||||
func NormalizeOptionName(name string) string {
|
||||
return strings.ReplaceAll(strings.TrimLeft(name, "-"), "_", "-")
|
||||
}
|
||||
|
||||
func (self *Option) parsed_value() any {
|
||||
if len(self.values_from_cmdline) == 0 {
|
||||
return self.parsed_default
|
||||
}
|
||||
switch self.OptionType {
|
||||
case CountOption:
|
||||
return len(self.parsed_values_from_cmdline)
|
||||
case StringOption:
|
||||
if self.IsList {
|
||||
ans := make([]string, len(self.parsed_values_from_cmdline))
|
||||
for i, x := range self.parsed_values_from_cmdline {
|
||||
ans[i] = x.(string)
|
||||
}
|
||||
return ans
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
return self.parsed_values_from_cmdline[len(self.parsed_values_from_cmdline)-1]
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Option) parse_value(val string) (any, error) {
|
||||
switch self.OptionType {
|
||||
case BoolOption:
|
||||
switch val {
|
||||
case "true":
|
||||
return true, nil
|
||||
case "false":
|
||||
return false, nil
|
||||
default:
|
||||
return nil, &ParseError{Option: self, Message: fmt.Sprintf(":yellow:`%s` is not a valid value for :bold:`%s`.", val, self.seen_option)}
|
||||
}
|
||||
case StringOption:
|
||||
return val, nil
|
||||
case IntegerOption, CountOption:
|
||||
pval, err := strconv.ParseInt(val, 0, 0)
|
||||
if err != nil {
|
||||
return nil, &ParseError{Option: self, Message: fmt.Sprintf(
|
||||
":yellow:`%s` is not a valid number for :bold:`%s`. Only integers in decimal, hexadecimal, binary or octal notation are accepted.", val, self.seen_option)}
|
||||
}
|
||||
return int(pval), nil
|
||||
case FloatOption:
|
||||
pval, err := strconv.ParseFloat(val, 64)
|
||||
if err != nil {
|
||||
return nil, &ParseError{Option: self, Message: fmt.Sprintf(
|
||||
":yellow:`%s` is not a valid number for :bold:`%s`. Only floats in decimal and hexadecimal notation are accepted.", val, self.seen_option)}
|
||||
}
|
||||
return pval, nil
|
||||
default:
|
||||
return nil, &ParseError{Option: self, Message: fmt.Sprintf("Unknown option type for %s", self.Name)}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Option) add_value(val string) error {
|
||||
name_without_hyphens := NormalizeOptionName(self.seen_option)
|
||||
switch self.OptionType {
|
||||
case BoolOption:
|
||||
for _, x := range self.Aliases {
|
||||
if x.NameWithoutHyphens == name_without_hyphens {
|
||||
if x.IsUnset {
|
||||
self.values_from_cmdline = append(self.values_from_cmdline, "false")
|
||||
self.parsed_values_from_cmdline = append(self.parsed_values_from_cmdline, false)
|
||||
} else {
|
||||
self.values_from_cmdline = append(self.values_from_cmdline, "true")
|
||||
self.parsed_values_from_cmdline = append(self.parsed_values_from_cmdline, true)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
case StringOption:
|
||||
if self.Choices != nil && !slices.Contains(self.Choices, val) {
|
||||
return &ParseError{Option: self, Message: fmt.Sprintf(":yellow:`%s` is not a valid value for :bold:`%s`. Valid values: %s",
|
||||
val, self.seen_option, strings.Join(self.Choices, ", "),
|
||||
)}
|
||||
}
|
||||
self.values_from_cmdline = append(self.values_from_cmdline, val)
|
||||
self.parsed_values_from_cmdline = append(self.parsed_values_from_cmdline, val)
|
||||
case IntegerOption, FloatOption:
|
||||
pval, err := self.parse_value(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
self.values_from_cmdline = append(self.values_from_cmdline, val)
|
||||
self.parsed_values_from_cmdline = append(self.parsed_values_from_cmdline, pval)
|
||||
case CountOption:
|
||||
self.values_from_cmdline = append(self.values_from_cmdline, val)
|
||||
self.parsed_values_from_cmdline = append(self.parsed_values_from_cmdline, 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
// Groups {{{
|
||||
type CommandGroup struct {
|
||||
SubCommands []*Command
|
||||
Title string
|
||||
}
|
||||
|
||||
func (self *CommandGroup) HasVisibleSubCommands() bool {
|
||||
for _, c := range self.SubCommands {
|
||||
if !c.Hidden {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *CommandGroup) Clone(parent *Command) *CommandGroup {
|
||||
ans := CommandGroup{Title: self.Title, SubCommands: make([]*Command, 0, len(self.SubCommands))}
|
||||
for i, o := range self.SubCommands {
|
||||
self.SubCommands[i] = o.Clone(parent)
|
||||
}
|
||||
return &ans
|
||||
}
|
||||
|
||||
func (self *CommandGroup) AddSubCommand(parent *Command, name string) *Command {
|
||||
ans := NewRootCommand()
|
||||
ans.Parent = parent
|
||||
ans.Name = name
|
||||
self.SubCommands = append(self.SubCommands, ans)
|
||||
return ans
|
||||
}
|
||||
|
||||
func (self *CommandGroup) FindSubCommand(name string) *Command {
|
||||
for _, c := range self.SubCommands {
|
||||
if c.Name == name {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type OptionGroup struct {
|
||||
Options []*Option
|
||||
Title string
|
||||
}
|
||||
|
||||
func (self *OptionGroup) Clone(parent *Command) *OptionGroup {
|
||||
ans := OptionGroup{Title: self.Title, Options: make([]*Option, len(self.Options))}
|
||||
for i, o := range self.Options {
|
||||
c := *o
|
||||
c.init_option()
|
||||
c.Parent = parent
|
||||
ans.Options[i] = &c
|
||||
}
|
||||
return &ans
|
||||
}
|
||||
|
||||
func (self *OptionGroup) AddOption(parent *Command, spec OptionSpec) (*Option, error) {
|
||||
ans, err := option_from_spec(spec)
|
||||
if err == nil {
|
||||
ans.Parent = parent
|
||||
self.Options = append(self.Options, ans)
|
||||
}
|
||||
return ans, err
|
||||
}
|
||||
|
||||
func (self *OptionGroup) AddOptionFromString(parent *Command, items ...string) (*Option, error) {
|
||||
ans, err := OptionFromString(items...)
|
||||
if err == nil {
|
||||
ans.Parent = parent
|
||||
self.Options = append(self.Options, ans)
|
||||
}
|
||||
return ans, err
|
||||
}
|
||||
|
||||
func (self *OptionGroup) FindOption(name_with_hyphens string) *Option {
|
||||
is_short := !strings.HasPrefix(name_with_hyphens, "--")
|
||||
option_name := NormalizeOptionName(name_with_hyphens)
|
||||
for _, q := range self.Options {
|
||||
if q.HasAlias(option_name, is_short) {
|
||||
return q
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
type Command struct { // {{{
|
||||
type Command struct {
|
||||
Name, Group string
|
||||
Usage, ShortDescription, HelpText string
|
||||
Hidden bool
|
||||
@@ -721,5 +453,3 @@ func (self *Command) Exec(args ...string) {
|
||||
}
|
||||
os.Exit(exit_code)
|
||||
}
|
||||
|
||||
// }}}
|
||||
94
tools/cli/group.go
Normal file
94
tools/cli/group.go
Normal file
@@ -0,0 +1,94 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
type CommandGroup struct {
|
||||
SubCommands []*Command
|
||||
Title string
|
||||
}
|
||||
|
||||
func (self *CommandGroup) HasVisibleSubCommands() bool {
|
||||
for _, c := range self.SubCommands {
|
||||
if !c.Hidden {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *CommandGroup) Clone(parent *Command) *CommandGroup {
|
||||
ans := CommandGroup{Title: self.Title, SubCommands: make([]*Command, 0, len(self.SubCommands))}
|
||||
for i, o := range self.SubCommands {
|
||||
self.SubCommands[i] = o.Clone(parent)
|
||||
}
|
||||
return &ans
|
||||
}
|
||||
|
||||
func (self *CommandGroup) AddSubCommand(parent *Command, name string) *Command {
|
||||
ans := NewRootCommand()
|
||||
ans.Parent = parent
|
||||
ans.Name = name
|
||||
self.SubCommands = append(self.SubCommands, ans)
|
||||
return ans
|
||||
}
|
||||
|
||||
func (self *CommandGroup) FindSubCommand(name string) *Command {
|
||||
for _, c := range self.SubCommands {
|
||||
if c.Name == name {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type OptionGroup struct {
|
||||
Options []*Option
|
||||
Title string
|
||||
}
|
||||
|
||||
func (self *OptionGroup) Clone(parent *Command) *OptionGroup {
|
||||
ans := OptionGroup{Title: self.Title, Options: make([]*Option, len(self.Options))}
|
||||
for i, o := range self.Options {
|
||||
c := *o
|
||||
c.init_option()
|
||||
c.Parent = parent
|
||||
ans.Options[i] = &c
|
||||
}
|
||||
return &ans
|
||||
}
|
||||
|
||||
func (self *OptionGroup) AddOption(parent *Command, spec OptionSpec) (*Option, error) {
|
||||
ans, err := option_from_spec(spec)
|
||||
if err == nil {
|
||||
ans.Parent = parent
|
||||
self.Options = append(self.Options, ans)
|
||||
}
|
||||
return ans, err
|
||||
}
|
||||
|
||||
func (self *OptionGroup) AddOptionFromString(parent *Command, items ...string) (*Option, error) {
|
||||
ans, err := OptionFromString(items...)
|
||||
if err == nil {
|
||||
ans.Parent = parent
|
||||
self.Options = append(self.Options, ans)
|
||||
}
|
||||
return ans, err
|
||||
}
|
||||
|
||||
func (self *OptionGroup) FindOption(name_with_hyphens string) *Option {
|
||||
is_short := !strings.HasPrefix(name_with_hyphens, "--")
|
||||
option_name := NormalizeOptionName(name_with_hyphens)
|
||||
for _, q := range self.Options {
|
||||
if q.HasAlias(option_name, is_short) {
|
||||
return q
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
189
tools/cli/option.go
Normal file
189
tools/cli/option.go
Normal file
@@ -0,0 +1,189 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"kitty/tools/cli/completion"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
type OptionType int
|
||||
|
||||
const (
|
||||
StringOption OptionType = iota
|
||||
IntegerOption
|
||||
FloatOption
|
||||
BoolOption
|
||||
CountOption
|
||||
)
|
||||
|
||||
type Alias struct {
|
||||
NameWithoutHyphens string
|
||||
IsShort bool
|
||||
IsUnset bool
|
||||
}
|
||||
|
||||
func (self *Alias) String() string {
|
||||
if self.IsShort {
|
||||
return "-" + self.NameWithoutHyphens
|
||||
}
|
||||
return "--" + self.NameWithoutHyphens
|
||||
}
|
||||
|
||||
type OptionSpec struct {
|
||||
Name string
|
||||
Type string
|
||||
Dest string
|
||||
Choices string
|
||||
Depth int
|
||||
Default string
|
||||
Help string
|
||||
CompletionFunc *completion.CompletionFunc
|
||||
}
|
||||
|
||||
type Option struct {
|
||||
Name string
|
||||
Aliases []Alias
|
||||
Choices []string
|
||||
Default string
|
||||
OptionType OptionType
|
||||
Hidden bool
|
||||
Depth int
|
||||
Help string
|
||||
IsList bool
|
||||
Parent *Command
|
||||
CompletionFunc *completion.CompletionFunc
|
||||
|
||||
values_from_cmdline []string
|
||||
parsed_values_from_cmdline []any
|
||||
parsed_default any
|
||||
seen_option string
|
||||
}
|
||||
|
||||
func (self *Option) reset() {
|
||||
self.values_from_cmdline = self.values_from_cmdline[:0]
|
||||
self.parsed_values_from_cmdline = self.parsed_values_from_cmdline[:0]
|
||||
self.seen_option = ""
|
||||
}
|
||||
|
||||
func (self *Option) needs_argument() bool {
|
||||
return self.OptionType != BoolOption && self.OptionType != CountOption
|
||||
}
|
||||
|
||||
func (self *Option) HasAlias(name_without_hyphens string, is_short bool) bool {
|
||||
for _, a := range self.Aliases {
|
||||
if a.IsShort == is_short && a.NameWithoutHyphens == name_without_hyphens {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ParseError struct {
|
||||
Option *Option
|
||||
Message string
|
||||
}
|
||||
|
||||
func (self *ParseError) Error() string { return self.Message }
|
||||
|
||||
func NormalizeOptionName(name string) string {
|
||||
return strings.ReplaceAll(strings.TrimLeft(name, "-"), "_", "-")
|
||||
}
|
||||
|
||||
func (self *Option) parsed_value() any {
|
||||
if len(self.values_from_cmdline) == 0 {
|
||||
return self.parsed_default
|
||||
}
|
||||
switch self.OptionType {
|
||||
case CountOption:
|
||||
return len(self.parsed_values_from_cmdline)
|
||||
case StringOption:
|
||||
if self.IsList {
|
||||
ans := make([]string, len(self.parsed_values_from_cmdline))
|
||||
for i, x := range self.parsed_values_from_cmdline {
|
||||
ans[i] = x.(string)
|
||||
}
|
||||
return ans
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
return self.parsed_values_from_cmdline[len(self.parsed_values_from_cmdline)-1]
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Option) parse_value(val string) (any, error) {
|
||||
switch self.OptionType {
|
||||
case BoolOption:
|
||||
switch val {
|
||||
case "true":
|
||||
return true, nil
|
||||
case "false":
|
||||
return false, nil
|
||||
default:
|
||||
return nil, &ParseError{Option: self, Message: fmt.Sprintf(":yellow:`%s` is not a valid value for :bold:`%s`.", val, self.seen_option)}
|
||||
}
|
||||
case StringOption:
|
||||
return val, nil
|
||||
case IntegerOption, CountOption:
|
||||
pval, err := strconv.ParseInt(val, 0, 0)
|
||||
if err != nil {
|
||||
return nil, &ParseError{Option: self, Message: fmt.Sprintf(
|
||||
":yellow:`%s` is not a valid number for :bold:`%s`. Only integers in decimal, hexadecimal, binary or octal notation are accepted.", val, self.seen_option)}
|
||||
}
|
||||
return int(pval), nil
|
||||
case FloatOption:
|
||||
pval, err := strconv.ParseFloat(val, 64)
|
||||
if err != nil {
|
||||
return nil, &ParseError{Option: self, Message: fmt.Sprintf(
|
||||
":yellow:`%s` is not a valid number for :bold:`%s`. Only floats in decimal and hexadecimal notation are accepted.", val, self.seen_option)}
|
||||
}
|
||||
return pval, nil
|
||||
default:
|
||||
return nil, &ParseError{Option: self, Message: fmt.Sprintf("Unknown option type for %s", self.Name)}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Option) add_value(val string) error {
|
||||
name_without_hyphens := NormalizeOptionName(self.seen_option)
|
||||
switch self.OptionType {
|
||||
case BoolOption:
|
||||
for _, x := range self.Aliases {
|
||||
if x.NameWithoutHyphens == name_without_hyphens {
|
||||
if x.IsUnset {
|
||||
self.values_from_cmdline = append(self.values_from_cmdline, "false")
|
||||
self.parsed_values_from_cmdline = append(self.parsed_values_from_cmdline, false)
|
||||
} else {
|
||||
self.values_from_cmdline = append(self.values_from_cmdline, "true")
|
||||
self.parsed_values_from_cmdline = append(self.parsed_values_from_cmdline, true)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
case StringOption:
|
||||
if self.Choices != nil && !slices.Contains(self.Choices, val) {
|
||||
return &ParseError{Option: self, Message: fmt.Sprintf(":yellow:`%s` is not a valid value for :bold:`%s`. Valid values: %s",
|
||||
val, self.seen_option, strings.Join(self.Choices, ", "),
|
||||
)}
|
||||
}
|
||||
self.values_from_cmdline = append(self.values_from_cmdline, val)
|
||||
self.parsed_values_from_cmdline = append(self.parsed_values_from_cmdline, val)
|
||||
case IntegerOption, FloatOption:
|
||||
pval, err := self.parse_value(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
self.values_from_cmdline = append(self.values_from_cmdline, val)
|
||||
self.parsed_values_from_cmdline = append(self.parsed_values_from_cmdline, pval)
|
||||
case CountOption:
|
||||
self.values_from_cmdline = append(self.values_from_cmdline, val)
|
||||
self.parsed_values_from_cmdline = append(self.parsed_values_from_cmdline, 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user