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
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
)
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/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-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/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/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
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 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.
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")
}
func add_choices(cmd *cobra.Command, flags *pflag.FlagSet, choices []string, name string, usage string) {
flags.String(name, choices[0], usage)
func add_choices(cmd *cobra.Command, flags *pflag.FlagSet, choices []string, name string, usage string) *string {
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) {
add_choices(cmd, cmd.Flags(), choices, name, usage)
func Choices(cmd *cobra.Command, name string, usage string, choices ...string) *string {
return add_choices(cmd, cmd.Flags(), choices, name, usage)
}
func PersistentChoices(cmd *cobra.Command, name string, usage string, choices ...string) {
add_choices(cmd, cmd.PersistentFlags(), choices, name, usage)
func PersistentChoices(cmd *cobra.Command, name string, usage string, choices ...string) *string {
return add_choices(cmd, cmd.PersistentFlags(), choices, name, usage)
}
func key_in_slice(vals []string, key string) bool {
@ -333,43 +333,53 @@ func show_usage(cmd *cobra.Command) error {
}
output_text := output.String()
// 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.Stdin = strings.NewReader(output_text)
pager.Stdout = os.Stdout
pager.Stderr = os.Stderr
pager.Run()
} else {
print(output_text)
cmd.OutOrStdout().Write([]byte(output_text))
}
return nil
}
func CreateCommand(cmd *cobra.Command) *cobra.Command {
cmd.Annotations = make(map[string]string)
if cmd.Run == nil {
cmd.Run = SubCommandRequired
if cmd.Run == nil && cmd.RunE == nil {
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.Flags().SortFlags = false
return cmd
}
func UsageAndError(cmd *cobra.Command, err string) {
cmd.Annotations["usage-suffix"] = err
cmd.Usage()
}
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 show_help(cmd *cobra.Command, args []string) {
cmd.Annotations["use-pager-for-usage"] = "true"
show_usage(cmd)
}
func Init(root *cobra.Command) {
stdout_is_terminal = isatty.IsTerminal(os.Stdout.Fd())
RootCmd = root
root.Version = kitty.VersionString
root.PersistentPreRunE = ValidateChoices
root.SetUsageFunc(show_usage)
root.SetHelpTemplate("{{.UsageString}}")
root.SetHelpFunc(show_help)
}

View File

@ -1,7 +1,15 @@
package at
import (
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/mattn/go-isatty"
"github.com/spf13/cobra"
"golang.org/x/sys/unix"
"golang.org/x/term"
"kitty/tools/cli"
"kitty/tools/crypto"
@ -9,36 +17,100 @@ import (
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 {
var to, password, password_file, password_env, use_password *string
var root = 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.",
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.PersistentFlags().String("to", "",
to = root.PersistentFlags().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 :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"+
" 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"+
" for permission to perform the specified action, unless the password has been"+
" 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"+
" 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.")
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."+
" 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
}

View File

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