Get rid of the cobra dependency

This commit is contained in:
Kovid Goyal
2022-09-25 12:23:52 +05:30
parent cbc569af64
commit 4396dede85
13 changed files with 154 additions and 493 deletions

View File

@@ -31,7 +31,6 @@ func (self *Command) ShowVersion() {
}
func format_with_indent(output io.Writer, text string, indent string, screen_width int) {
text = formatter.Prettify(text)
indented := style.WrapText(text, indent, screen_width, "#placeholder_for_formatting#")
io.WriteString(output, indented)
}
@@ -41,10 +40,12 @@ func (self *Command) FormatSubCommands(output io.Writer, formatter *markup.Conte
if !g.HasVisibleSubCommands() {
continue
}
if g.Title != "" {
fmt.Fprintln(output)
fmt.Fprintln(output, formatter.Title(g.Title))
title := g.Title
if title == "" {
title = "Commands"
}
fmt.Fprintln(output)
fmt.Fprintln(output, formatter.Title(title)+":")
for _, c := range g.SubCommands {
if c.Hidden {
continue
@@ -64,22 +65,23 @@ func (self *Option) FormatOption(output io.Writer, formatter *markup.Context, sc
fmt.Fprint(output, ", ")
}
}
defval := ""
defval := self.Default
switch self.OptionType {
case BoolOption:
default:
defval = self.Default
fallthrough
case StringOption:
if self.IsList {
defval = ""
}
case BoolOption, CountOption:
defval = ""
}
if defval != "" {
fmt.Fprintf(output, " [=%s]", formatter.Italic(defval))
}
fmt.Fprintln(output)
format_with_indent(output, formatter.Prettify(prepare_help_text_for_display(self.Help)), " ", screen_width)
if self.Choices != nil {
format_with_indent(output, "Choices: "+strings.Join(self.Choices, ", "), " ", screen_width)
}
}
func (self *Command) ShowHelp() {
@@ -111,7 +113,6 @@ func (self *Command) ShowHelp() {
if self.HasVisibleSubCommands() {
fmt.Fprintln(&output)
fmt.Fprintln(&output, formatter.Title("Commands")+":")
self.FormatSubCommands(&output, formatter, screen_width)
fmt.Fprintln(&output)
format_with_indent(&output, "Get help for an individual command by running:", "", screen_width)
@@ -121,12 +122,12 @@ func (self *Command) ShowHelp() {
group_titles, gmap := self.GetVisibleOptions()
if len(group_titles) > 0 {
fmt.Fprintln(&output)
fmt.Fprintln(&output, formatter.Title("Options")+":")
for _, title := range group_titles {
if title != "" {
fmt.Fprintln(&output)
fmt.Fprintln(&output, formatter.Title(title))
ptitle := title
if title == "" {
ptitle = "Options"
}
fmt.Fprintln(&output, formatter.Title(ptitle)+":")
for _, opt := range gmap[title] {
opt.FormatOption(&output, formatter, screen_width)
fmt.Fprintln(&output)

View File

@@ -1,265 +0,0 @@
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
package cli
import (
"fmt"
"os"
"os/exec"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"golang.org/x/sys/unix"
"kitty"
"kitty/tools/cli/markup"
"kitty/tools/tty"
)
var RootCmd *cobra.Command
func key_in_slice(vals []string, key string) bool {
for _, q := range vals {
if q == key {
return true
}
}
return false
}
type ChoicesVal struct {
name, Choice string
allowed []string
}
type choicesVal ChoicesVal
func (i *choicesVal) String() string { return ChoicesVal(*i).Choice }
func (i *choicesVal) Type() string { return "string" }
func (i *choicesVal) Set(s string) error {
(*i).Choice = s
return nil
}
func newChoicesVal(val ChoicesVal, p *ChoicesVal) *choicesVal {
*p = val
return (*choicesVal)(p)
}
func add_choices(flags *pflag.FlagSet, p *ChoicesVal, choices []string, name string, short string, usage string) {
usage = strings.TrimSpace(usage) + "\n" + "Choices: " + strings.Join(choices, ", ")
value := ChoicesVal{Choice: choices[0], allowed: choices}
flags.VarP(newChoicesVal(value, p), name, short, usage)
}
func Choices(flags *pflag.FlagSet, name string, usage string, choices ...string) *ChoicesVal {
p := new(ChoicesVal)
add_choices(flags, p, choices, name, "", usage)
return p
}
func ChoicesP(flags *pflag.FlagSet, name string, short string, usage string, choices ...string) *ChoicesVal {
p := new(ChoicesVal)
add_choices(flags, p, choices, name, short, usage)
return p
}
var formatter *markup.Context
func full_command_name(cmd *cobra.Command) string {
var parent_names []string
cmd.VisitParents(func(p *cobra.Command) {
parent_names = append([]string{p.Name()}, parent_names...)
})
parent_names = append(parent_names, cmd.Name())
return strings.Join(parent_names, " ")
}
func show_usage(cmd *cobra.Command, use_pager bool) error {
screen_width := 80
if formatter.EscapeCodesAllowed() {
var sz *unix.Winsize
var tty_size_err error
for {
sz, tty_size_err = unix.IoctlGetWinsize(int(os.Stdout.Fd()), unix.TIOCGWINSZ)
if tty_size_err != unix.EINTR {
break
}
}
if tty_size_err == nil && sz.Col < 80 {
screen_width = int(sz.Col)
}
}
var output strings.Builder
use := cmd.Use
idx := strings.Index(use, " ")
if idx > -1 {
use = use[idx+1:]
} else {
use = ""
}
fmt.Fprintln(&output, formatter.Title("Usage")+":", formatter.Exe(full_command_name(cmd)), use)
fmt.Fprintln(&output)
if len(cmd.Long) > 0 {
format_with_indent(&output, cmd.Long, "", screen_width)
} else if len(cmd.Short) > 0 {
format_with_indent(&output, cmd.Short, "", screen_width)
}
if cmd.HasAvailableSubCommands() {
fmt.Fprintln(&output)
fmt.Fprintln(&output, formatter.Title("Commands")+":")
for _, child := range cmd.Commands() {
if child.Hidden {
continue
}
fmt.Fprintln(&output, " ", formatter.Opt(child.Name()))
format_with_indent(&output, child.Short, " ", screen_width)
}
fmt.Fprintln(&output)
format_with_indent(&output, "Get help for an individual command by running:", "", screen_width)
fmt.Fprintln(&output, " ", full_command_name(cmd), formatter.Italic("command"), "-h")
}
if cmd.HasAvailableFlags() {
options_title := cmd.Annotations["options_title"]
if len(options_title) == 0 {
options_title = "Options"
}
fmt.Fprintln(&output)
fmt.Fprintln(&output, formatter.Title(options_title)+":")
flag_set := cmd.LocalFlags()
flag_set.VisitAll(func(flag *pflag.Flag) {
fmt.Fprint(&output, formatter.Opt(" --"+flag.Name))
if flag.Shorthand != "" {
fmt.Fprint(&output, ", ", formatter.Opt("-"+flag.Shorthand))
}
defval := ""
switch flag.Value.Type() {
default:
if flag.DefValue != "" {
defval = fmt.Sprintf("[=%s]", formatter.Italic(flag.DefValue))
}
case "stringArray":
if flag.DefValue != "[]" {
defval = fmt.Sprintf("[=%s]", formatter.Italic(flag.DefValue))
}
case "bool":
case "count":
}
if defval != "" {
fmt.Fprint(&output, " ", defval)
}
fmt.Fprintln(&output)
msg := flag.Usage
switch flag.Name {
case "help":
msg = "Print this help message"
case "version":
msg = "Print the version of " + RootCmd.Name() + ": " + formatter.Italic(RootCmd.Version)
}
format_with_indent(&output, msg, " ", screen_width)
fmt.Fprintln(&output)
})
}
if cmd.Annotations["usage-suffix"] != "" {
fmt.Fprintln(&output, cmd.Annotations["usage-suffix"])
} else {
fmt.Fprintln(&output, formatter.Italic(RootCmd.Name()), formatter.Opt(kitty.VersionString), "created by", formatter.Title("Kovid Goyal"))
}
output_text := output.String()
// fmt.Printf("%#v\n", output_text)
if use_pager && formatter.EscapeCodesAllowed() && cmd.Annotations["allow-pager"] != "no" {
pager := exec.Command(kitty.DefaultPager[0], kitty.DefaultPager[1:]...)
pager.Stdin = strings.NewReader(output_text)
pager.Stdout = os.Stdout
pager.Stderr = os.Stderr
pager.Run()
} else {
cmd.OutOrStdout().Write([]byte(output_text))
}
return nil
}
func FlagNormalizer(name string) string {
return strings.ReplaceAll(name, "_", "-")
}
func DisallowArgs(cmd *cobra.Command, args []string) error {
if cmd.HasSubCommands() {
if len(args) == 0 {
return fmt.Errorf("No sub-command specified. Use %s -h to get a list of available sub-commands", full_command_name(cmd))
}
cmd.SuggestionsMinimumDistance = 2
suggestions := cmd.SuggestionsFor(args[0])
es := "Not a valid subcommand: " + args[0]
trailer := fmt.Sprintf("Use %s to get a list of available sub-commands", formatter.Bold(full_command_name(cmd)+" -h"))
if len(suggestions) > 0 {
es += "\nDid you mean?\n"
for _, s := range suggestions {
es += fmt.Sprintf("\t%s\n", formatter.Italic(s))
}
es += trailer
} else {
es += ". " + trailer
}
return fmt.Errorf("%s", es)
}
return nil
}
func CreateCommand(cmd *cobra.Command) *cobra.Command {
cmd.Annotations = make(map[string]string)
cmd.SilenceErrors = true
cmd.SilenceUsage = true
cmd.PersistentFlags().SortFlags = false
cmd.Flags().SortFlags = false
cmd.Flags().SetNormalizeFunc(func(fs *pflag.FlagSet, name string) pflag.NormalizedName {
return pflag.NormalizedName(FlagNormalizer(name))
})
cmd.PersistentFlags().SetNormalizeFunc(cmd.Flags().GetNormalizeFunc())
if !cmd.Runnable() {
cmd.Args = DisallowArgs
cmd.RunE = func(cmd *cobra.Command, args []string) error {
return nil
}
}
return cmd
}
func show_help(cmd *cobra.Command, args []string) {
show_usage(cmd, true)
}
func PrintError(err error) {
fmt.Println(formatter.Err("Error")+":", err)
}
func Init(root *cobra.Command) {
vs := kitty.VersionString
if kitty.VCSRevision != "" {
vs = vs + " (" + kitty.VCSRevision + ")"
}
formatter = markup.New(tty.IsTerminal(os.Stdout.Fd()))
RootCmd = root
root.Version = vs
root.SetUsageFunc(func(cmd *cobra.Command) error { return show_usage(cmd, false) })
root.SetHelpFunc(show_help)
root.SetHelpCommand(&cobra.Command{Hidden: true})
root.CompletionOptions.DisableDefaultCmd = true
}
func Execute(root *cobra.Command) error {
return root.Execute()
}
type FlagValGetter struct {
Flags *pflag.FlagSet
Err error
}
func (self *FlagValGetter) String(name string) string {
if self.Err != nil {
return ""
}
ans, err := self.Flags.GetString(name)
self.Err = err
return ans
}

View File

@@ -238,11 +238,11 @@ type OptionGroup struct {
}
func (self *OptionGroup) Clone(parent *Command) *OptionGroup {
ans := OptionGroup{Title: self.Title, Options: make([]*Option, 0, len(self.Options))}
ans := OptionGroup{Title: self.Title, Options: make([]*Option, len(self.Options))}
for i, o := range self.Options {
c := *o
c.Parent = parent
self.Options[i] = &c
ans.Options[i] = &c
}
return &ans
}
@@ -302,8 +302,8 @@ func (self *Command) Clone(parent *Command) *Command {
ans := *self
ans.Args = make([]string, 0, 8)
ans.Parent = parent
ans.SubCommandGroups = make([]*CommandGroup, 0, len(self.SubCommandGroups))
ans.OptionGroups = make([]*OptionGroup, 0, len(self.OptionGroups))
ans.SubCommandGroups = make([]*CommandGroup, len(self.SubCommandGroups))
ans.OptionGroups = make([]*OptionGroup, len(self.OptionGroups))
for i, o := range self.OptionGroups {
ans.OptionGroups[i] = o.Clone(&ans)
@@ -314,14 +314,11 @@ func (self *Command) Clone(parent *Command) *Command {
return &ans
}
func (self *Command) AddClone(group string, src *Command) (*Command, error) {
func (self *Command) AddClone(group string, src *Command) *Command {
c := src.Clone(self)
g := self.AddSubCommandGroup(group)
if g.FindSubCommand(c.Name) != nil {
return nil, fmt.Errorf("A sub command with the name: %s already exists in %s", c.Name, self.Name)
}
g.SubCommands = append(g.SubCommands, c)
return c, nil
return c
}
func NewRootCommand() *Command {
@@ -605,6 +602,19 @@ type Context struct {
SeenCommands []*Command
}
func GetOptionValue[T any](self *Command, name string) (ans T, err error) {
opt := self.option_map[name]
if opt == nil {
err = fmt.Errorf("No option with the name: %s", name)
return
}
ans, ok := opt.parsed_value().(T)
if !ok {
err = fmt.Errorf("The option %s is not of the correct type", name)
}
return
}
func (self *Command) GetOptionValues(pointer_to_options_struct any) error {
val := reflect.ValueOf(pointer_to_options_struct).Elem()
if val.Kind() != reflect.Struct {
@@ -689,7 +699,7 @@ func (self *Command) Exec(args ...string) {
} else if cmd.Run != nil {
exit_code, err = cmd.Run(cmd, cmd.Args)
if err != nil {
PrintError(err)
ShowError(err)
if exit_code == 0 {
exit_code = 1
}

View File

@@ -13,8 +13,6 @@ import (
"strings"
"time"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"golang.org/x/sys/unix"
"kitty"
@@ -30,13 +28,6 @@ import (
var ProtocolVersion [3]int = [3]int{0, 26, 0}
func add_bool_set(cmd *cobra.Command, name string, short string, usage string) *bool {
if short == "" {
return cmd.Flags().Bool(name, false, usage)
}
return cmd.Flags().BoolP(name, short, false, usage)
}
type GlobalOptions struct {
to_network, to_address, password string
to_address_is_from_env_var bool
@@ -142,7 +133,7 @@ type Response struct {
}
type rc_io_data struct {
cmd *cobra.Command
cmd *cli.Command
rc *utils.RemoteControlCmd
serializer serializer_func
on_key_event func(lp *loop.Loop, ke *loop.KeyEvent) error
@@ -294,82 +285,46 @@ func get_password(password string, password_file string, password_env string, us
return ans, nil
}
var all_commands map[string]func(*cobra.Command) *cobra.Command = make(map[string]func(*cobra.Command) *cobra.Command)
var command_objects map[string]*cobra.Command = make(map[string]*cobra.Command)
var all_commands map[string]func(*cli.Command) *cli.Command = make(map[string]func(*cli.Command) *cli.Command)
func add_global_options(fs *pflag.FlagSet) {
fs.String("to", "",
"An address for the kitty instance to control. Corresponds to the address given"+
" to the kitty instance via the :option:`kitty --listen-on` option or the :opt:`listen_on` setting in :file:`kitty.conf`. If not"+
" specified, the environment variable :envvar:`KITTY_LISTEN_ON` is checked. If that"+
" is also not found, messages are sent to the controlling terminal for this"+
" process, i.e. they will only work if this process is run within a kitty window.")
fs.String("password", "",
"A password to use when contacting kitty. This will cause kitty to ask the user"+
" for permission to perform the specified action, unless the password has been"+
" accepted before or is pre-configured in :file:`kitty.conf`.")
fs.String("password-file", "rc-pass",
"A file from which to read the password. Trailing whitespace is ignored. Relative"+
" paths are resolved from the kitty configuration directory. Use - to read from STDIN."+
" Used if no :option:`--password` is supplied. Defaults to checking for the"+
" :file:`rc-pass` file in the kitty configuration directory.")
fs.String("password-env", "KITTY_RC_PASSWORD",
"The name of an environment variable to read the password from."+
" Used if no :option:`--password-file` or :option:`--password` is supplied.")
cli.Choices(fs, "use-password", "If no password is available, kitty will usually just send the remote control command without a password. This option can be used to force it to always or never use the supplied password.", "if-available", "always", "never")
}
func setup_global_options(cmd *cobra.Command) (err error) {
var v = cli.FlagValGetter{Flags: cmd.Flags()}
to := v.String("to")
password := v.String("password")
password_file := v.String("password-file")
password_env := v.String("password-env")
use_password := v.String("use-password")
if v.Err != nil {
return v.Err
func setup_global_options(cmd *cli.Command) (err error) {
err = cmd.GetOptionValues(&rc_global_opts)
if err != nil {
return err
}
if to == "" {
to = os.Getenv("KITTY_LISTEN_ON")
if rc_global_opts.To == "" {
rc_global_opts.To = os.Getenv("KITTY_LISTEN_ON")
global_options.to_address_is_from_env_var = true
}
if to != "" {
network, address, err := utils.ParseSocketAddress(to)
if rc_global_opts.To != "" {
network, address, err := utils.ParseSocketAddress(rc_global_opts.To)
if err != nil {
return err
}
global_options.to_network = network
global_options.to_address = address
}
q, err := get_password(password, password_file, password_env, use_password)
q, err := get_password(rc_global_opts.Password, rc_global_opts.PasswordFile, rc_global_opts.PasswordEnv, rc_global_opts.UsePassword)
global_options.password = q
return err
}
func EntryPoint(tool_root *cobra.Command) *cobra.Command {
at_root_command := cli.CreateCommand(&cobra.Command{
Use: "@ [global options] command [command options] [command args]",
Short: "Control kitty remotely",
Long: "Control kitty by sending it commands. Set the allow_remote_control option in :file:`kitty.conf` or use a password, for this to work.",
})
at_root_command.Annotations["options_title"] = "Global options"
add_global_options(at_root_command.PersistentFlags())
func EntryPoint(tool_root *cli.Command) *cli.Command {
at_root_command := tool_root.AddSubCommand("", "@")
at_root_command.Usage = "[global options] [sub-command] [sub-command options] [sub-command args]"
at_root_command.ShortDescription = "Control kitty remotely"
at_root_command.HelpText = "Control kitty by sending it commands. Set the allow_remote_control option in :file:`kitty.conf` for this to work. When run without any sub-commands this will start an interactive shell to control kitty."
add_rc_global_opts(at_root_command)
for cmd_name, reg_func := range all_commands {
global_options_group := at_root_command.OptionGroups[0]
for _, reg_func := range all_commands {
c := reg_func(at_root_command)
at_root_command.AddCommand(c)
command_objects[cmd_name] = c
alias := *c
alias.Use = "@" + alias.Use
alias.Hidden = true
add_global_options(alias.Flags())
tool_root.AddCommand(&alias)
clone := tool_root.AddClone("", c)
clone.Name = "@" + c.Name
clone.Hidden = true
clone.OptionGroups = append(clone.OptionGroups, global_options_group.Clone(clone))
}
return at_root_command
}

View File

@@ -32,8 +32,8 @@ func parse_send_text(io_data *rc_io_data, args []string) error {
generators = append(generators, text_gen)
}
if options_send_text.from_file != "" {
f, err := os.Open(options_send_text.from_file)
if options_send_text.FromFile != "" {
f, err := os.Open(options_send_text.FromFile)
if err != nil {
return err
}
@@ -49,7 +49,7 @@ func parse_send_text(io_data *rc_io_data, args []string) error {
generators = append(generators, file_gen)
}
if options_send_text.stdin {
if options_send_text.Stdin {
pending_key_events := make([]string, 0, 1)
io_data.on_key_event = func(lp *loop.Loop, ke *loop.KeyEvent) error {

View File

@@ -2,7 +2,7 @@
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
// this file is autogenerated by __FILE__ do not edit
// Code generated by gen-go-code.py; DO NOT EDIT.
package at
@@ -11,9 +11,6 @@ import (
"strings"
"time"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"kitty/tools/cli"
"kitty/tools/utils"
)
@@ -31,7 +28,7 @@ type CMD_NAME_json_type struct {
JSON_DECLARATION_CODE
}
func create_payload_CMD_NAME(io_data *rc_io_data, flags *pflag.FlagSet, args []string) (err error) {
func create_payload_CMD_NAME(io_data *rc_io_data, cmd *cli.Command, args []string) (err error) {
payload := CMD_NAME_json_type{}
JSON_INIT_CODE
io_data.rc.Payload = payload
@@ -62,19 +59,22 @@ func create_rc_CMD_NAME(args []string) (*utils.RemoteControlCmd, error) {
return &rc, nil
}
func run_CMD_NAME(cmd *cobra.Command, args []string) (err error) {
SET_OPTION_VALUES_CODE
func run_CMD_NAME(cmd *cli.Command, args []string) (return_code int, err error) {
err = cmd.GetOptionValues(&options_CMD_NAME)
if err != nil {
return
}
rc, err := create_rc_CMD_NAME(args)
if err != nil {
return err
return
}
nrv, err := cmd.Flags().GetBool("no-response")
nrv, err := cli.GetOptionValue[bool](cmd, "NoResponse")
if err == nil {
rc.NoResponse = nrv
}
var timeout float64 = WAIT_TIMEOUT
rt, err := cmd.Flags().GetFloat64("response-timeout")
rt, err := cli.GetOptionValue[float64](cmd, "ResponseTimeout")
if err == nil {
timeout = rt
}
@@ -84,31 +84,22 @@ func run_CMD_NAME(cmd *cobra.Command, args []string) (err error) {
timeout: time.Duration(timeout * float64(time.Second)),
string_response_is_err: STRING_RESPONSE_IS_ERROR,
}
err = create_payload_CMD_NAME(&io_data, cmd.Flags(), args)
err = create_payload_CMD_NAME(&io_data, cmd, args)
if err != nil {
return err
return
}
err = send_rc_command(&io_data)
return
}
func aliasNormalizeFunc_CMD_NAME(f *pflag.FlagSet, name string) pflag.NormalizedName {
name = cli.FlagNormalizer(name)
ALIAS_NORMALIZE_CODE
return pflag.NormalizedName(name)
}
func setup_CMD_NAME(root *cobra.Command) *cobra.Command {
ans := cli.CreateCommand(&cobra.Command{
Use: "CLI_NAME [options]" + "ARGSPEC",
Short: "SHORT_DESC",
Long: "LONG_DESC",
RunE: run_CMD_NAME,
})
func setup_CMD_NAME(parent *cli.Command) *cli.Command {
ans := parent.AddSubCommand("", "CMD_NAME")
ans.Usage = "ARGSPEC"
ans.ShortDescription = "SHORT_DESC"
ans.HelpText = "LONG_DESC"
ans.Run = run_CMD_NAME
ADD_FLAGS_CODE
ans.Flags().SetNormalizeFunc(aliasNormalizeFunc_CMD_NAME)
return ans
}

View File

@@ -4,7 +4,7 @@ package main
import (
"kitty/tools/cli"
_ "kitty/tools/cmd/at"
"kitty/tools/cmd/at"
"kitty/tools/completion"
)
@@ -12,8 +12,9 @@ func main() {
root := cli.NewRootCommand()
root.ShortDescription = "Fast, statically compiled implementations for various kitty command-line tools"
root.Usage = "command [command options] [command args]"
// root.AddCommand(at.EntryPoint(root))
at.EntryPoint(root)
completion.EntryPoint(root)
root.Exec()
}