Parse global options

This commit is contained in:
Kovid Goyal 2022-08-16 18:02:31 +05:30
parent 10b74d0703
commit 77f7ce82c0
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
6 changed files with 114 additions and 28 deletions

1
go.mod
View File

@ -10,6 +10,7 @@ require (
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
) )
require ( require (

3
go.sum
View File

@ -21,7 +21,10 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJ
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

View File

@ -201,7 +201,7 @@ global_options_spec = partial('''\
An address for the kitty instance to control. Corresponds to the address given 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 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 :opt:`listen_on` setting in :file:`kitty.conf`. If not specified, the
environment variable :env:`KITTY_LISTEN_ON` is checked. If that is also not 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. 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. they will only work if this process is run within a kitty window.

View File

@ -30,17 +30,17 @@ func GetTTYSize() (*unix.Winsize, error) {
return nil, fmt.Errorf("STDOUT is not a TTY") return nil, fmt.Errorf("STDOUT is not a TTY")
} }
func add_choices(cmd *cobra.Command, flags *pflag.FlagSet, choices []string, name string, usage string) { func add_choices(cmd *cobra.Command, flags *pflag.FlagSet, choices []string, name string, usage string) *string {
flags.String(name, choices[0], usage)
cmd.Annotations["choices-"+name] = strings.Join(choices, "\000") cmd.Annotations["choices-"+name] = strings.Join(choices, "\000")
return flags.String(name, choices[0], usage)
} }
func Choices(cmd *cobra.Command, name string, usage string, choices ...string) { func Choices(cmd *cobra.Command, name string, usage string, choices ...string) *string {
add_choices(cmd, cmd.Flags(), choices, name, usage) return add_choices(cmd, cmd.Flags(), choices, name, usage)
} }
func PersistentChoices(cmd *cobra.Command, name string, usage string, choices ...string) { func PersistentChoices(cmd *cobra.Command, name string, usage string, choices ...string) *string {
add_choices(cmd, cmd.PersistentFlags(), choices, name, usage) return add_choices(cmd, cmd.PersistentFlags(), choices, name, usage)
} }
func key_in_slice(vals []string, key string) bool { func key_in_slice(vals []string, key string) bool {
@ -333,43 +333,53 @@ func show_usage(cmd *cobra.Command) error {
} }
output_text := output.String() output_text := output.String()
// fmt.Printf("%#v\n", output_text) // fmt.Printf("%#v\n", output_text)
if stdout_is_terminal && cmd.Annotations["allow-pager"] != "no" { if cmd.Annotations["use-pager-for-usage"] == "true" && stdout_is_terminal && cmd.Annotations["allow-pager"] != "no" {
pager := exec.Command(kitty.DefaultPager[0], kitty.DefaultPager[1:]...) pager := exec.Command(kitty.DefaultPager[0], kitty.DefaultPager[1:]...)
pager.Stdin = strings.NewReader(output_text) pager.Stdin = strings.NewReader(output_text)
pager.Stdout = os.Stdout pager.Stdout = os.Stdout
pager.Stderr = os.Stderr pager.Stderr = os.Stderr
pager.Run() pager.Run()
} else { } else {
print(output_text) cmd.OutOrStdout().Write([]byte(output_text))
} }
return nil return nil
} }
func CreateCommand(cmd *cobra.Command) *cobra.Command { func CreateCommand(cmd *cobra.Command) *cobra.Command {
cmd.Annotations = make(map[string]string) cmd.Annotations = make(map[string]string)
if cmd.Run == nil { if cmd.Run == nil && cmd.RunE == nil {
cmd.Run = SubCommandRequired cmd.RunE = func(cmd *cobra.Command, args []string) error {
if len(cmd.Commands()) > 0 {
return fmt.Errorf("%s. Use %s -h to get a list of available sub-commands", err_fmt("No sub-command specified"), full_command_name(cmd))
}
return nil
}
} }
cmd.SilenceErrors = true
cmd.SilenceUsage = true
orig_pre_run := cmd.PersistentPreRunE
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
err := ValidateChoices(cmd, args)
if err != nil || orig_pre_run == nil {
return err
}
return orig_pre_run(cmd, args)
}
cmd.PersistentFlags().SortFlags = false cmd.PersistentFlags().SortFlags = false
cmd.Flags().SortFlags = false cmd.Flags().SortFlags = false
return cmd return cmd
} }
func UsageAndError(cmd *cobra.Command, err string) { func show_help(cmd *cobra.Command, args []string) {
cmd.Annotations["usage-suffix"] = err cmd.Annotations["use-pager-for-usage"] = "true"
cmd.Usage() show_usage(cmd)
}
func SubCommandRequired(cmd *cobra.Command, args []string) {
UsageAndError(cmd, "No command specified: "+exe_fmt(full_command_name(cmd))+err_fmt(" add-a-command-here"))
os.Exit(1)
} }
func Init(root *cobra.Command) { func Init(root *cobra.Command) {
stdout_is_terminal = isatty.IsTerminal(os.Stdout.Fd()) stdout_is_terminal = isatty.IsTerminal(os.Stdout.Fd())
RootCmd = root RootCmd = root
root.Version = kitty.VersionString root.Version = kitty.VersionString
root.PersistentPreRunE = ValidateChoices
root.SetUsageFunc(show_usage) root.SetUsageFunc(show_usage)
root.SetHelpTemplate("{{.UsageString}}") root.SetHelpFunc(show_help)
} }

View File

@ -1,7 +1,15 @@
package at package at
import ( import (
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/mattn/go-isatty"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/sys/unix"
"golang.org/x/term"
"kitty/tools/cli" "kitty/tools/cli"
"kitty/tools/crypto" "kitty/tools/crypto"
@ -9,36 +17,100 @@ import (
var encrypt_cmd = crypto.Encrypt_cmd var encrypt_cmd = crypto.Encrypt_cmd
type GlobalOptions struct {
to, password, use_password string
to_from_env bool
}
var global_options GlobalOptions
func get_password(password string, password_file string, password_env string, use_password string) (string, error) {
if use_password == "never" {
return "", nil
}
ans := ""
if password != "" {
ans = password
}
if ans == "" && password_file != "" {
if password_file == "-" {
if isatty.IsTerminal(os.Stdin.Fd()) {
q, err := term.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
ans = string(q)
}
} else {
q, err := ioutil.ReadAll(os.Stdin)
if err != nil {
ans = strings.TrimRight(string(q), " \n\t")
}
ttyf, err := os.Open("/dev/tty")
if err != nil {
err = unix.Dup2(int(ttyf.Fd()), int(os.Stdin.Fd()))
ttyf.Close()
}
}
} else {
q, err := ioutil.ReadFile(password_file)
if err != nil {
ans = strings.TrimRight(string(q), " \n\t")
}
}
}
if ans == "" && password_env != "" {
ans = os.Getenv(password_env)
}
if ans == "" && use_password == "always" {
return ans, fmt.Errorf("No password was found")
}
if len(ans) > 1024 {
return ans, fmt.Errorf("Specified password is too long")
}
return ans, nil
}
func EntryPoint(tool_root *cobra.Command) *cobra.Command { func EntryPoint(tool_root *cobra.Command) *cobra.Command {
var to, password, password_file, password_env, use_password *string
var root = cli.CreateCommand(&cobra.Command{ var root = cli.CreateCommand(&cobra.Command{
Use: "@ [global options] command [command options] [command args]", Use: "@ [global options] command [command options] [command args]",
Short: "Control kitty remotely", 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.", 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.",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
if *to == "" {
*to = os.Getenv("KITTY_LISTEN_ON")
global_options.to_from_env = true
}
global_options.to = *to
global_options.use_password = *use_password
q, err := get_password(*password, *password_file, *password_env, *use_password)
global_options.password = q
return err
},
}) })
root.Annotations["options_title"] = "Global options" root.Annotations["options_title"] = "Global options"
root.PersistentFlags().String("to", "", to = root.PersistentFlags().String("to", "",
"An address for the kitty instance to control. Corresponds to the address given"+ "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"+ " 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 :env:`KITTY_LISTEN_ON` is checked. If that"+ " 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"+ " 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.") " process, i.e. they will only work if this process is run within a kitty window.")
root.PersistentFlags().String("password", "", password = root.PersistentFlags().String("password", "",
"A password to use when contacting kitty. This will cause kitty to ask the user"+ "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"+ " for permission to perform the specified action, unless the password has been"+
" accepted before or is pre-configured in :file:`kitty.conf`.") " accepted before or is pre-configured in :file:`kitty.conf`.")
root.PersistentFlags().String("password-file", "rc-pass", password_file = root.PersistentFlags().String("password-file", "rc-pass",
"A file from which to read the password. Trailing whitespace is ignored. Relative"+ "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."+ " 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"+ " Used if no :option:`--password` is supplied. Defaults to checking for the"+
" :file:`rc-pass` file in the kitty configuration directory.") " :file:`rc-pass` file in the kitty configuration directory.")
root.PersistentFlags().String("password-env", "KITTY_RC_PASSWORD", password_env = root.PersistentFlags().String("password-env", "KITTY_RC_PASSWORD",
"The name of an environment variable to read the password from."+ "The name of an environment variable to read the password from."+
" Used if no :option:`--password-file` or :option:`--password` is supplied.") " Used if no :option:`--password-file` or :option:`--password` is supplied.")
cli.PersistentChoices(root, "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") use_password = cli.PersistentChoices(root, "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")
return root return root
} }

View File

@ -19,7 +19,7 @@ func main() {
cli.Init(root) cli.Init(root)
if err := root.Execute(); err != nil { if err := root.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, "Error:", err)
os.Exit(1) os.Exit(1)
} }
} }