diff --git a/internal/app/api.go b/internal/app/api.go index e0c3e938..42fb1c9e 100644 --- a/internal/app/api.go +++ b/internal/app/api.go @@ -134,17 +134,10 @@ without quotes; jq does not need to be installed. On a terminal, JSON responses are indented and colored. A response outside 2xx prints its body, then fails with the HTTP status.` -// apiExamples are the examples of tea api. -const apiExamples = `# The login of the authenticated account -$ tea api user --jq .login -# Open issues of the current repository, as number and title -$ tea api 'repos/{owner}/{repo}/issues?state=open' --jq '.[] | "\(.number) \(.title)"' +const apiExamples = `$ tea api user --jq .login # Create an issue: -f sends strings, -F typed values $ tea api repos/{owner}/{repo}/issues -f title="Crash on start" -F labels='[3]' -# Every page of the account's repositories -$ tea api user/repos --paginate --jq '.[].full_name' -# Send a JSON body from a file -$ tea api -X PATCH repos/alice/project --input settings.json` +$ tea api user/repos --paginate --jq '.[].full_name'` func (a *application) apiCommand() *cobra.Command { var method, input, output string @@ -293,9 +286,8 @@ func (a *application) apiCommand() *cobra.Command { return cmd } -// apiResponse writes response bodies for tea api: filtered by the jq query, as indented and colored -// JSON on a terminal, otherwise as received, without terminal controls on a terminal. Output to a -// terminal, JSON and pages end with a newline. +// apiResponse writes response bodies for tea api: filtered by --jq, indented and colored JSON on a +// terminal, otherwise as received (minus terminal controls on a terminal), ending with a newline. type apiResponse struct { tty, color, paginate bool query string diff --git a/internal/app/app.go b/internal/app/app.go index 2bd3149c..92e3a630 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -210,12 +210,10 @@ var rootGroups = map[string]string{ "run": "actions", "secret": "actions", "variable": "actions", "workflow": "actions", } -// verbAliases are the gh-style short names that every command with a common verb accepts. -// Commands add other gh verbs, such as new, co, add and get, to their own Aliases. +// verbAliases are the gh-style short names of common verbs; commands add others to their own Aliases. var verbAliases = map[string]string{"list": "ls", "delete": "rm"} -// addVerbAliases gives every list and delete command its short name, and lets its flags be named -// in the plural. +// addVerbAliases gives every list and delete command its short name and accepts plural flag names. func addVerbAliases(cmd *cobra.Command) { if alias, ok := verbAliases[cmd.Name()]; ok && cmd.HasParent() && !slices.Contains(cmd.Aliases, alias) { cmd.Aliases = append(cmd.Aliases, alias) @@ -415,11 +413,9 @@ func Execute(ctx context.Context, deps Dependencies, args []string) error { var jsonFieldList = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9_]*(,[A-Za-z][A-Za-z0-9_]*)*$`) -// joinJSONFields accepts `--json number,title` like `--json=number,title`. A field-shaped word after a -// bare --json stays a positional argument only when the selected command does not output those fields -// and accepts the word as an argument, so a misspelled field reports the available fields rather than -// an argument count. A bare --json still selects every field. Unknown commands, such as aliases before -// expansion, are joined. +// joinJSONFields accepts `--json number,title` like `--json=number,title`. The word stays a positional +// argument only when the command neither outputs those fields nor rejects the argument, so a +// misspelled field lists the available fields. Unknown commands, such as unexpanded aliases, are joined. func joinJSONFields(root *cobra.Command, args []string) []string { for i := 0; i < len(args); i++ { if args[i] == "--" { diff --git a/internal/app/auth.go b/internal/app/auth.go index 7871de05..f15f26af 100644 --- a/internal/app/auth.go +++ b/internal/app/auth.go @@ -75,8 +75,7 @@ func (a *application) instanceTarget(ctx context.Context) (target, error) { return a.resolveTarget(ctx, false, false) } -// credentialLabel describes how an account authenticates: "token, keyring", "OAuth, file store" or -// "SSH key". +// credentialLabel describes how an account authenticates, such as "token, keyring". func credentialLabel(account settings.Account) string { switch account.AuthMethod { case settings.AuthSSH: @@ -130,14 +129,9 @@ how to log in, where to keep the token and whether git should use tea for the in Create a token on the instance under Settings > Applications. Logging in to an account that tea already has replaces its stored credential after a confirmation (or with --yes).`, - Example: `# Log in interactively -$ tea auth login -# Log in with a token from a file or a secret store + Example: `$ tea auth login $ tea auth login --host gitea.com --with-token < token.txt -# Log in in the browser, with read-only access to repositories -$ tea auth login --host gitea.com --oauth --scopes read:repository,read:user -# Sign requests with an SSH key instead of a token -$ tea auth login --host gitea.com --ssh-key ~/.ssh/id_ed25519`, +$ tea auth login --host gitea.com --oauth --scopes read:repository,read:user`, RunE: func(cmd *cobra.Command, _ []string) error { selected := 0 for _, chosen := range []bool{withToken, oauth, sshKey != "", sshAgentPrincipal != "", sshAgentKey != ""} { @@ -377,18 +371,14 @@ func defaultSSHKey() string { return "" } for _, name := range []string{"id_ed25519", "id_ecdsa", "id_rsa"} { - if path := filepath.Join(home, ".ssh", name); fileExists(path) { + path := filepath.Join(home, ".ssh", name) + if _, err := os.Stat(path); err == nil { return path } } return "" } -func fileExists(path string) bool { - _, err := os.Stat(path) - return err == nil -} - // authStatusRow is one account, or the environment token, that auth status reports. type authStatusRow struct { Host string `json:"host"` diff --git a/internal/app/config.go b/internal/app/config.go index 1fef896d..414f9f3c 100644 --- a/internal/app/config.go +++ b/internal/app/config.go @@ -194,10 +194,7 @@ ssh_certificate_principal, ssh_key_fingerprint.`} get := &cobra.Command{ Use: "get ", Short: "Print the value of a setting", Args: argsExact(1), ValidArgsFunction: a.completeConfig, Example: `$ tea config get editor -# Show where the value comes from $ tea config get default_host --show-source -# Read an instance or account setting -$ tea config get ssh_host --host https://gitea.com $ tea config get ssh_key --host https://gitea.com --user alice`, RunE: func(cmd *cobra.Command, args []string) error { snapshot, err := a.settings() @@ -259,11 +256,6 @@ $ tea config list --host https://gitea.com`, Use: "set ", Short: "Change a setting", Args: argsExact(2), ValidArgsFunction: a.completeConfig, Annotations: textOutput("config set does not return a resource"), Example: `$ tea config set editor "code --wait" -$ tea config set git_protocol ssh -# Keep raw markdown and plain IDs on a terminal -$ tea config set markdown raw -$ tea config set hyperlinks never -# Change an instance or account setting $ tea config set ssh_host git.example.com:2222 --host https://example.com $ tea config set ssh_key ~/.ssh/id_ed25519 --host https://example.com --user alice`, RunE: func(cmd *cobra.Command, args []string) error { diff --git a/internal/app/errors.go b/internal/app/errors.go index 0ce3a69b..370208a7 100644 --- a/internal/app/errors.go +++ b/internal/app/errors.go @@ -145,9 +145,8 @@ func unreachable(err error) string { return "" } -// timeoutError words an expired --timeout as "timed out after 4s waiting for run 34" instead -// of "context deadline exceeded". Other errors are returned unchanged; the result still matches -// context.DeadlineExceeded, so the exit code stays the same. +// timeoutError words an expired --timeout as "timed out after 4s waiting for run 34" instead of +// "context deadline exceeded", keeping the exit code. Other errors are returned unchanged. func timeoutError(err error, d time.Duration, format string, args ...any) error { if !errors.Is(err, context.DeadlineExceeded) { return err @@ -156,7 +155,7 @@ func timeoutError(err error, d time.Duration, format string, args ...any) error if format != "" { text += " " + fmt.Sprintf(format, args...) } - return &explainedError{text: text, err: err} + return explain(err, "%s", text) } // shortDuration formats d without zero units, such as 1h, 1h30m or 45s. diff --git a/internal/app/help_topics.go b/internal/app/help_topics.go index 209cccc5..40554ad7 100644 --- a/internal/app/help_topics.go +++ b/internal/app/help_topics.go @@ -153,9 +153,8 @@ func availableCommands(cmd *cobra.Command) []commandList { return append(lists, other, topics) } -// helpFlags renders the flags of cmd and the global flags that apply to it: --repo, --remote and --cwd -// unless it never selects a repository, --json and --jq when it outputs JSON (--jq alone when it -// filters raw JSON), and --yes when it asks for confirmation. Persistent flags of parent commands, such as --org, count as its own. +// helpFlags renders the flags of cmd, counting parents' persistent flags such as --org as its own, and +// the global flags that apply to it: --repo, --remote and --cwd, --json and --jq, and --yes. func helpFlags(cmd *cobra.Command) (local, inherited string) { globals := cmd.Root().PersistentFlags() var own []*pflag.Flag diff --git a/internal/app/human_output.go b/internal/app/human_output.go index fd2ac993..27fa788e 100644 --- a/internal/app/human_output.go +++ b/internal/app/human_output.go @@ -42,8 +42,7 @@ func (a *application) display() terminal.Display { } } -// writeHuman pages multi-line human list/view text. JSON callers must not use it. -// --no-input does not disable paging. +// writeHuman pages human list and view text; --no-input does not disable paging. func (a *application) writeHuman(cmd *cobra.Command, text string) error { preferences, _ := a.preferences() return terminal.WriteHuman(cmd.Context(), a.deps.Streams, preferences.Pager, a.deps.Pager, text) @@ -76,10 +75,8 @@ type listPage struct { Counted bool } -// writeTable writes items as a JSON array, or as a table through the pager. On a TTY the table has -// a header row and, with a spec Context, a context line that also points at the next page; an empty -// list prints the spec's Empty message to stderr instead. Piped tables are tab-separated, and the -// next-page hint goes to stderr. +// writeTable writes items as a JSON array, or as a table through the pager: on a TTY with a header, +// the spec's Context line, or its Empty message on stderr; piped as TSV with the next-page hint on stderr. func writeTable[T any](a *application, cmd *cobra.Command, items []T, spec listSpec[T], page listPage) error { if a.options.json != "" { return a.writeJSON(cmd, items)