split up types.go

This commit is contained in:
Kovid Goyal
2022-09-25 19:05:25 +05:30
parent 97acb60981
commit 79ca0408e7
3 changed files with 284 additions and 271 deletions

View File

@@ -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
View 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
View 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
}