Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions builtins/cat/cat.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,25 +94,25 @@ const (
)

func registerFlags(fs *builtins.FlagSet) builtins.HandlerFunc {
help := fs.Bool("help", false, "print usage and exit")
number := fs.BoolP("number", "n", false, "number all output lines")
numberNonblank := fs.BoolP("number-nonblank", "b", false, "number non-blank output lines, overrides -n")
squeezeBlank := fs.BoolP("squeeze-blank", "s", false, "suppress repeated empty output lines")
showEnds := fs.BoolP("show-ends", "E", false, "display $ at end of each line")
showTabs := fs.BoolP("show-tabs", "T", false, "display TAB characters as ^I")
showNonprinting := fs.BoolP("show-nonprinting", "v", false, "use ^ and M- notation, except for LFD and TAB")
showAll := fs.BoolP("show-all", "A", false, "equivalent to -vET")
flagE := fs.BoolP("show-nonprinting-ends", "e", false, "equivalent to -vE")
flagT := fs.BoolP("show-nonprinting-tabs", "t", false, "equivalent to -vT")
_ = fs.BoolP("unbuffered", "u", false, "ignored")
help := builtins.NoArgBool(fs, "help", "", "print usage and exit")
number := builtins.NoArgBool(fs, "number", "n", "number all output lines")
numberNonblank := builtins.NoArgBool(fs, "number-nonblank", "b", "number non-blank output lines, overrides -n")
squeezeBlank := builtins.NoArgBool(fs, "squeeze-blank", "s", "suppress repeated empty output lines")
showEnds := builtins.NoArgBool(fs, "show-ends", "E", "display $ at end of each line")
showTabs := builtins.NoArgBool(fs, "show-tabs", "T", "display TAB characters as ^I")
showNonprinting := builtins.NoArgBool(fs, "show-nonprinting", "v", "use ^ and M- notation, except for LFD and TAB")
showAll := builtins.NoArgBool(fs, "show-all", "A", "equivalent to -vET")
flagE := builtins.NoArgBool(fs, "show-nonprinting-ends", "e", "equivalent to -vE")
flagT := builtins.NoArgBool(fs, "show-nonprinting-tabs", "t", "equivalent to -vT")
_ = builtins.NoArgBool(fs, "unbuffered", "u", "ignored")

return func(ctx context.Context, callCtx *builtins.CallContext, files []string) builtins.Result {
if *help {
callCtx.Out("Usage: cat [OPTION]... [FILE]...\n")
callCtx.Out("Concatenate FILE(s) to standard output.\n")
callCtx.Out("With no FILE, or when FILE is -, read standard input.\n\n")
fs.SetOutput(callCtx.Stdout)
fs.PrintDefaults()
builtins.PrintFlagDefaults(fs)
return builtins.Result{}
}

Expand Down
2 changes: 1 addition & 1 deletion builtins/cd/cd.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ var errBoolSeqValue = errors.New("option doesn't allow an argument")
const boolSeqSentinel = "\x00rshell:cd:bare\x00"

func makeFlags(fs *builtins.FlagSet) builtins.HandlerFunc {
helpFlag := fs.BoolP("help", "h", false, "print usage and exit")
helpFlag := builtins.NoArgBool(fs, "help", "h", "print usage and exit")

// Disable interspersed flag parsing: once the directory operand
// is seen, any following token (including flag-shaped ones like
Expand Down
10 changes: 5 additions & 5 deletions builtins/cut/cut.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,14 @@ const (
// registerFlags registers all cut flags on the framework-provided FlagSet and
// returns a bound handler whose flag variables are captured by closure.
func registerFlags(fs *builtins.FlagSet) builtins.HandlerFunc {
help := fs.Bool("help", false, "print usage and exit")
help := builtins.NoArgBool(fs, "help", "", "print usage and exit")
bytesListStr := fs.StringP("bytes", "b", "", "select only these bytes")
charsListStr := fs.StringP("characters", "c", "", "select only these characters")
fieldsListStr := fs.StringP("fields", "f", "", "select only these fields")
delimiter := fs.StringP("delimiter", "d", "\t", "use DELIM instead of TAB for field delimiter")
onlyDelimited := fs.BoolP("only-delimited", "s", false, "do not print lines not containing delimiters")
_ = fs.BoolP("", "n", false, "do not split multi-byte characters")
complement := fs.Bool("complement", false, "complement the set of selected bytes, characters, or fields")
onlyDelimited := builtins.NoArgBool(fs, "only-delimited", "s", "do not print lines not containing delimiters")
_ = builtins.NoArgBool(fs, "", "n", "do not split multi-byte characters")
complement := builtins.NoArgBool(fs, "complement", "", "complement the set of selected bytes, characters, or fields")
outputDelimiter := fs.String("output-delimiter", "", "use STRING as the output delimiter")

return func(ctx context.Context, callCtx *builtins.CallContext, files []string) builtins.Result {
Expand All @@ -128,7 +128,7 @@ func registerFlags(fs *builtins.FlagSet) builtins.HandlerFunc {
callCtx.Out("Print selected parts of lines from each FILE to standard output.\n")
callCtx.Out("With no FILE, or when FILE is -, read standard input.\n\n")
fs.SetOutput(callCtx.Stdout)
fs.PrintDefaults()
builtins.PrintFlagDefaults(fs)
return builtins.Result{}
}

Expand Down
104 changes: 18 additions & 86 deletions builtins/df/df.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,18 +106,6 @@ const (
unitsHuman1000 // -H: powers of 1000
)

// noArgSentinel is the NoOptDefVal we set on every no-argument flag
// (unitFlag and noArgBool). pflag passes this exact string to Set when
// the user writes the bare flag (`-h`, `--all`); for the explicit-value
// form (`--all=true`) pflag passes the user's literal value instead.
//
// We use a single NUL byte so the two cases are distinguishable: NUL
// is the C-string terminator and POSIX execve(2) refuses to pass argv
// elements containing it, so the user cannot forge this string from a
// shell. That lets Set reject every `=value` form — including `=true`
// — to match GNU df's "doesn't allow an argument" exit-1 error.
const noArgSentinel = "\x00"

// unitFlag is a pflag.Value that writes a fixed unitMode into a shared
// target each time the flag is set. We use one instance for -h (writes
// unitsHuman1024) and one for -H (writes unitsHuman1000) sharing a
Expand All @@ -136,7 +124,7 @@ func (u *unitFlag) String() string { return "" }
func (u *unitFlag) Type() string { return "bool" }

// Set is called by pflag once per occurrence of the flag. It receives:
// - noArgSentinel for a bare flag (e.g. `-h`, `--human-readable`);
// - builtins.NoArgSentinel for a bare flag (e.g. `-h`, `--human-readable`);
// - the user's literal value for `--name=value` / `-name=value`.
//
// GNU df rejects every explicit-value form (`gdf --human-readable=false`
Expand All @@ -145,7 +133,7 @@ func (u *unitFlag) Type() string { return "bool" }
// argv (NUL bytes can't be passed through execve), so any non-sentinel
// value here means the user wrote `--flag=value` and must be rejected.
func (u *unitFlag) Set(s string) error {
if s != noArgSentinel {
if s != builtins.NoArgSentinel {
return errors.New("flag does not allow an argument")
}
*u.target = u.value
Expand All @@ -158,45 +146,7 @@ func (u *unitFlag) Set(s string) error {
// and rejects `-h` with "flag needs an argument".
func registerUnitFlag(fs *builtins.FlagSet, target *unitMode, value unitMode, name, shorthand, usage string) {
flag := fs.VarPF(&unitFlag{target: target, value: value}, name, shorthand, usage)
flag.NoOptDefVal = noArgSentinel
}

// noArgBool is a pflag.Value that mirrors GNU getopt's no_argument
// behaviour: bare `--flag` and `-f` work, but `--flag=value` and
// `-f=value` are rejected with "flag does not allow an argument" for
// every value (including `=true`). pflag.BoolP treats the
// explicit-value form as a successful parse, which silently diverges
// from GNU.
//
// Like unitFlag, this relies on noArgSentinel (a NUL byte) to
// distinguish a bare flag from an explicit `=value`.
type noArgBool struct {
target *bool
}

func (b *noArgBool) String() string {
if b.target != nil && *b.target {
return "true"
}
return "false"
}
func (b *noArgBool) Type() string { return "bool" }
func (b *noArgBool) Set(s string) error {
if s != noArgSentinel {
return errors.New("flag does not allow an argument")
}
*b.target = true
return nil
}

// registerNoArgBool installs a noArgBool flag and returns the *bool
// target so the caller can read it like an ordinary fs.Bool result.
// Pass an empty shorthand for long-only flags (e.g. --total).
func registerNoArgBool(fs *builtins.FlagSet, name, shorthand, usage string) *bool {
target := new(bool)
flag := fs.VarPF(&noArgBool{target: target}, name, shorthand, usage)
flag.NoOptDefVal = noArgSentinel
return target
flag.NoOptDefVal = builtins.NoArgSentinel
}

// flags carries the parsed flag state. It is constructed once per
Expand All @@ -217,20 +167,20 @@ type flags struct {

func makeFlags(fs *builtins.FlagSet) builtins.HandlerFunc {
mode := unitsK
// All boolean options use registerNoArgBool so explicit-value
// All boolean options use builtins.NoArgBool so explicit-value
// forms (`--all=false`, `--portability=false`, etc.) are
// rejected with "flag does not allow an argument" — matching GNU
// df's getopt(1)-style refusal for no-argument options.
f := &flags{
help: registerNoArgBool(fs, "help", "", "print usage and exit"),
help: builtins.NoArgBool(fs, "help", "", "print usage and exit"),
mode: &mode,
posix: registerNoArgBool(fs, "portability", "P", "use the POSIX output format"),
printType: registerNoArgBool(fs, "print-type", "T", "print file system type"),
inodes: registerNoArgBool(fs, "inodes", "i", "list inode information instead of block usage"),
all: registerNoArgBool(fs, "all", "a", "include pseudo, duplicate, inaccessible file systems"),
local: registerNoArgBool(fs, "local", "l", "limit listing to local file systems"),
total: registerNoArgBool(fs, "total", "", "append a grand total row"),
noSync: registerNoArgBool(fs, "no-sync", "", "do not invoke sync before getting usage info (default; accepted for compatibility)"),
posix: builtins.NoArgBool(fs, "portability", "P", "use the POSIX output format"),
printType: builtins.NoArgBool(fs, "print-type", "T", "print file system type"),
inodes: builtins.NoArgBool(fs, "inodes", "i", "list inode information instead of block usage"),
all: builtins.NoArgBool(fs, "all", "a", "include pseudo, duplicate, inaccessible file systems"),
local: builtins.NoArgBool(fs, "local", "l", "limit listing to local file systems"),
total: builtins.NoArgBool(fs, "total", "", "append a grand total row"),
noSync: builtins.NoArgBool(fs, "no-sync", "", "do not invoke sync before getting usage info (default; accepted for compatibility)"),
includeTypes: fs.StringArrayP("type", "t", nil, "limit listing to file systems of type TYPE"),
excludeTypes: fs.StringArrayP("exclude-type", "x", nil, "limit listing to file systems not of type TYPE"),
}
Expand All @@ -252,7 +202,7 @@ func makeFlags(fs *builtins.FlagSet) builtins.HandlerFunc {
// from the auto-generated help and handle the line manually in
// printHelp.
kFlag := fs.VarPF(&unitFlag{target: &mode, value: unitsK}, "", "k", "use 1024-byte blocks (POSIX default)")
kFlag.NoOptDefVal = noArgSentinel
kFlag.NoOptDefVal = builtins.NoArgSentinel
kFlag.Hidden = true

return func(ctx context.Context, callCtx *builtins.CallContext, args []string) builtins.Result {
Expand Down Expand Up @@ -892,33 +842,15 @@ func minColumnWidths(withType bool) []int {
// skipped by PrintDefaults; we append the line manually so it still
// appears in --help.
//
// Every no-argument flag uses noArgSentinel (a NUL byte) as its
// NoOptDefVal so that explicit-value forms (--all=true, etc.) are
// rejected. PrintDefaults would happily render that NUL byte into the
// help text as `--all[= ]\x00…`, producing binary garbage. Clear the
// NoOptDefVal of every flag before printing — Parse has already run, so
// the cleared value cannot affect parsing for this invocation. The
// defer-restore is defensive: makeFlags currently builds a fresh
// FlagSet per invocation, but a future refactor that reuses or caches
// the FlagSet would silently regress the explicit-value rejection
// without it (--all=true would start being accepted).
// The no-argument flags carry builtins.NoArgSentinel (a NUL byte) as their
// NoOptDefVal so that explicit-value forms (--all=true, etc.) are rejected;
// builtins.PrintFlagDefaults clears those sentinels before printing so the
// NUL is not rendered into the help text as binary garbage.
func printHelp(callCtx *builtins.CallContext, fs *builtins.FlagSet) {
callCtx.Out("Usage: df [OPTION]...\n")
callCtx.Out("Show information about the file system on which each FILE resides,\n")
callCtx.Out("or all file systems by default.\n\n")
saved := make(map[*builtins.Flag]string)
fs.VisitAll(func(flag *builtins.Flag) {
if flag.NoOptDefVal == noArgSentinel {
saved[flag] = flag.NoOptDefVal
flag.NoOptDefVal = ""
}
})
defer func() {
for f, v := range saved {
f.NoOptDefVal = v
}
}()
fs.SetOutput(callCtx.Stdout)
fs.PrintDefaults()
builtins.PrintFlagDefaults(fs)
callCtx.Out(" -k use 1024-byte blocks (POSIX default)\n")
}
16 changes: 8 additions & 8 deletions builtins/du/du.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,10 @@ func registerFlags(fs *builtins.FlagSet) builtins.HandlerFunc {
// shape rather than alphabetical.
fs.SortFlags = false

all := fs.BoolP("all", "a", false, "write counts for all files, not just directories")
summarize := fs.BoolP("summarize", "s", false, "display only a total for each argument")
total := fs.BoolP("total", "c", false, "produce a grand total")
separateDirs := fs.BoolP("separate-dirs", "S", false, "for directories, do not include size of subdirectories")
all := builtins.NoArgBool(fs, "all", "a", "write counts for all files, not just directories")
summarize := builtins.NoArgBool(fs, "summarize", "s", "display only a total for each argument")
total := builtins.NoArgBool(fs, "total", "c", "produce a grand total")
separateDirs := builtins.NoArgBool(fs, "separate-dirs", "S", "for directories, do not include size of subdirectories")

// Mutually-exclusive last-wins groups (-L vs -P, and the size-format
// flags -b/-h/--si/-k/-m). Each Set() call increments a shared
Expand All @@ -248,24 +248,24 @@ func registerFlags(fs *builtins.FlagSet) builtins.HandlerFunc {
derefL := registerSeq("dereference", "L", "dereference all symbolic links")
derefP := registerSeq("no-dereference", "P", "don't follow any symbolic links (default)")

apparentSize := fs.Bool("apparent-size", false, "print apparent sizes rather than device usage")
apparentSize := builtins.NoArgBool(fs, "apparent-size", "", "print apparent sizes rather than device usage")
bytesFlag := registerSeq("bytes", "b", "equivalent to --apparent-size --block-size=1")
humanFlag := registerSeq("human-readable", "h", "print sizes in human-readable format")
siFlag := registerSeq("si", "", "like -h, but use powers of 1000")
kiloFlag := registerSeq("kilobytes", "k", "use 1024-byte blocks (default)")
megaFlag := registerSeq("megabytes", "m", "use 1 MiB (1024*1024) blocks")

null := fs.BoolP("null", "0", false, "end each output line with NUL, not newline")
null := builtins.NoArgBool(fs, "null", "0", "end each output line with NUL, not newline")
maxDepth := fs.IntP("max-depth", "d", -1, "print the total for a directory only if it is N or fewer levels deep")
helpFlag := fs.Bool("help", false, "print usage and exit")
helpFlag := builtins.NoArgBool(fs, "help", "", "print usage and exit")

return func(ctx context.Context, callCtx *builtins.CallContext, paths []string) builtins.Result {
if *helpFlag {
fs.SetOutput(callCtx.Stdout)
callCtx.Out("Usage: du [OPTION]... [FILE]...\n")
callCtx.Out("Summarize device usage of the set of FILEs, recursively for directories.\n")
callCtx.Out("With no FILE, du operates on the current directory.\n\n")
fs.PrintDefaults()
builtins.PrintFlagDefaults(fs)
return builtins.Result{}
}

Expand Down
Loading