Skip to content

Commit ac0ddd0

Browse files
authored
cli: support "-" and "@" for pipes and files (#365)
This PR adds support for flags to optionally allow the user to specify special syntaxes that tell the CLI to read the contents from a pipe, prompt, or file. This behavior is off by default and must be enabled on a per-flag basis. - If a flag sets `AllowFromFile` to true, then if the value for the flag starts with an "@" symbol, the argument is interpreted as a filepath and the contents are read from disk. This is theoretically possible today with shellisms (e.g. `-foo=$(cat bar.txt)`), but this provides a shell-agnostic implementation. If the file does not start with "@", then the value is read as a string as normal. As a special case where the user really wanted the value to be the literal "@", "\\@" can be used to escape the input. - If a flag sets `AllowFromPrompt` to true, then if the value for the flag is exactly the string "-", then the flag expects input from stdin or a tty. If neither are present, then the user is prompted. As a special case where the user really wanted the literal value "-", "\\-" can be used to escape the input. Again, both of these behaviors are off by default and must be enabled on a per-flag basis. In general, I don't expect many flags will use this configuration, but it's useful to be consistent where necessary. Finally, it looks like there are some new linting errors, since the linter was failing locally for me, so I fixed those up.
1 parent ea610d9 commit ac0ddd0

File tree

9 files changed

+614
-277
lines changed

9 files changed

+614
-277
lines changed

.golangci.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,15 @@ linters:
3333
- 'bidichk'
3434
- 'bodyclose'
3535
- 'containedctx'
36+
# - 'copyloopvar' TODO: enable after everything is upgraded to Go 1.22
3637
- 'depguard'
3738
- 'dupword'
3839
- 'durationcheck'
3940
- 'errcheck'
4041
- 'errchkjson'
4142
- 'errname'
4243
- 'errorlint'
43-
- 'execinquery'
4444
- 'exhaustive'
45-
- 'exportloopref'
4645
- 'forcetypeassert'
4746
- 'gci'
4847
- 'gocheckcompilerdirectives'
@@ -170,7 +169,7 @@ linters-settings:
170169
171170
sloglint:
172171
# default: false
173-
context-only: true
172+
context: 'all'
174173
# default: false
175174
static-msg: false
176175
# default: '' (snake, kebab, camel, pascal)

cache/cache.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ func New[T any](expireAfter time.Duration) *Cache[T] {
9090
// largely defeat the purpose. In this case, 50ms is somewhat arbitrary, but
9191
// it ensures the CPU is not entirely bound by the sweep operation.
9292
sweep := expireAfter / 4.0
93-
if min := 50 * time.Millisecond; sweep < min {
94-
sweep = min
93+
if minimum := 50 * time.Millisecond; sweep < minimum {
94+
sweep = minimum
9595
}
9696
go c.start(sweep)
9797

cli/command.go

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -262,8 +262,15 @@ type BaseCommand struct {
262262
}
263263

264264
// NewFlagSet creates a new flag set that inherits properties from the command.
265-
func (c *BaseCommand) NewFlagSet(opts ...Option) *FlagSet {
266-
return NewFlagSet(WithLookupEnv(c.LookupEnv))
265+
func (c *BaseCommand) NewFlagSet(o ...Option) *FlagSet {
266+
opts := []Option{
267+
WithLookupEnv(c.LookupEnv),
268+
WithPromptAll(c.PromptAll),
269+
WithWorkingDir(c.WorkingDir),
270+
}
271+
opts = append(opts, o...)
272+
273+
return NewFlagSet(opts...)
267274
}
268275

269276
// Flags returns the base command flags, which is always nil.
@@ -483,22 +490,7 @@ func (c *BaseCommand) GetEnv(key string) string {
483490
// WorkingDir returns the absolute path of current working directory from where
484491
// the command was started. All symlinks are resolved to their real paths.
485492
func (c *BaseCommand) WorkingDir() (string, error) {
486-
cwd, err := os.Getwd()
487-
if err != nil {
488-
return "", fmt.Errorf("failed to get current working directory: %w", err)
489-
}
490-
491-
symCwd, err := filepath.EvalSymlinks(cwd)
492-
if err != nil {
493-
return "", fmt.Errorf("failed to resolve symlinks for current working directory: %w", err)
494-
}
495-
496-
abs, err := filepath.Abs(symCwd)
497-
if err != nil {
498-
return "", fmt.Errorf("failed to compute absolute path for current working directory: %w", err)
499-
}
500-
501-
return abs, nil
493+
return workingDir()
502494
}
503495

504496
// ExecutablePath returns the absolute path of the CLI executable binary. All
@@ -575,3 +567,22 @@ func buildCompleteCommands(cmd Command) *complete.Command {
575567

576568
return completer
577569
}
570+
571+
func workingDir() (string, error) {
572+
cwd, err := os.Getwd()
573+
if err != nil {
574+
return "", fmt.Errorf("failed to get current working directory: %w", err)
575+
}
576+
577+
symCwd, err := filepath.EvalSymlinks(cwd)
578+
if err != nil {
579+
return "", fmt.Errorf("failed to resolve symlinks for current working directory: %w", err)
580+
}
581+
582+
abs, err := filepath.Abs(symCwd)
583+
if err != nil {
584+
return "", fmt.Errorf("failed to compute absolute path for current working directory: %w", err)
585+
}
586+
587+
return abs, nil
588+
}

cli/command_doc_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,13 @@ func (c *CountCommand) Run(ctx context.Context, args []string) error {
7474
return fmt.Errorf("expected 1 argument, got %q", args)
7575
}
7676

77-
maxStr := args[0]
78-
max, err := strconv.ParseInt(maxStr, 10, 64)
77+
maximumStr := args[0]
78+
maximum, err := strconv.ParseInt(maximumStr, 10, 64)
7979
if err != nil {
8080
return fmt.Errorf("failed to parse max: %w", err)
8181
}
8282

83-
for i := int64(0); i <= max; i += c.flagStep {
83+
for i := int64(0); i <= maximum; i += c.flagStep {
8484
c.Outf("%d", i)
8585
}
8686

0 commit comments

Comments
 (0)