From fceab2fab9dac68f213a5d29b7f15050b05fa552 Mon Sep 17 00:00:00 2001 From: Sebastian Davids Date: Tue, 1 Oct 2024 17:00:39 +0200 Subject: [PATCH] cmd/shfmt: support -l=0 and -f=0 flags To separate output filenames with null bytes rather than newlines, matching the format expected by tools such as `xargs -0`. Fixes #1096. Signed-off-by: Sebastian Davids --- cmd/shfmt/main.go | 68 +++++++++++++++++++++++++++++++++---------- cmd/shfmt/shfmt.1.scd | 10 ++++--- 2 files changed, 58 insertions(+), 20 deletions(-) diff --git a/cmd/shfmt/main.go b/cmd/shfmt/main.go index 18e5bee9..437bfc76 100644 --- a/cmd/shfmt/main.go +++ b/cmd/shfmt/main.go @@ -27,6 +27,16 @@ import ( "mvdan.cc/sh/v3/syntax/typedjson" ) +type boolString string + +func (s *boolString) Set(val string) error { + *s = boolString(val) + return nil +} +func (s *boolString) Get() any { return string(*s) } +func (s *boolString) String() string { return string(*s) } +func (*boolString) IsBoolFlag() bool { return true } + type multiFlag[T any] struct { short, long string val T @@ -34,12 +44,12 @@ type multiFlag[T any] struct { var ( versionFlag = &multiFlag[bool]{"", "version", false} - list = &multiFlag[bool]{"l", "list", false} + list = &multiFlag[boolString]{"l", "list", "false"} write = &multiFlag[bool]{"w", "write", false} simplify = &multiFlag[bool]{"s", "simplify", false} minify = &multiFlag[bool]{"mn", "minify", false} - find = &multiFlag[bool]{"f", "find", false} + find = &multiFlag[boolString]{"f", "find", "false"} diff = &multiFlag[bool]{"d", "diff", false} applyIgnore = &multiFlag[bool]{"", "apply-ignore", false} @@ -89,6 +99,13 @@ func init() { if name := f.long; name != "" { flag.BoolVar(&f.val, name, f.val, "") } + case *multiFlag[boolString]: + if name := f.short; name != "" { + flag.Var(&f.val, name, "") + } + if name := f.long; name != "" { + flag.Var(&f.val, name, "") + } case *multiFlag[string]: if name := f.short; name != "" { flag.StringVar(&f.val, name, f.val, "") @@ -130,13 +147,14 @@ directory, all shell scripts found under that directory will be used. --version show version and exit - -l, --list list files whose formatting differs from shfmt's - -w, --write write result to file instead of stdout - -d, --diff error with a diff when the formatting differs - -s, --simplify simplify the code - -mn, --minify minify the code to reduce its size (implies -s) - --apply-ignore always apply EditorConfig ignore rules - --filename str provide a name for the standard input file + -l[=0], --list[=0] list files whose formatting differs from shfmt; + paths are separated by a newline or a null character if -l=0 + -w, --write write result to file instead of stdout + -d, --diff error with a diff when the formatting differs + -s, --simplify simplify the code + -mn, --minify minify the code to reduce its size (implies -s) + --apply-ignore always apply EditorConfig ignore rules + --filename str provide a name for the standard input file Parser options: @@ -154,9 +172,10 @@ Printer options: Utilities: - -f, --find recursively find all shell files and print the paths - --to-json print syntax tree to stdout as a typed JSON - --from-json read syntax tree from stdin as a typed JSON + -f[=0], --find[=0] recursively find all shell files and print the paths; + paths are separated by a newline or a null character if -f=0 + --to-json print syntax tree to stdout as a typed JSON + --from-json read syntax tree from stdin as a typed JSON Formatting options can also be read from EditorConfig files; see 'man shfmt' for a detailed description of the tool's behavior. @@ -181,6 +200,14 @@ For more information and to report bugs, see https://github.com/mvdan/sh. fmt.Fprintf(os.Stderr, "-p and -ln=lang cannot coexist\n") return 1 } + if list.val != "true" && list.val != "false" && list.val != "0" { + fmt.Fprintf(os.Stderr, "only -l and -l=0 allowed\n") + return 1 + } + if find.val != "true" && find.val != "false" && find.val != "0" { + fmt.Fprintf(os.Stderr, "only -f and -f=0 allowed\n") + return 1 + } if minify.val { simplify.val = true } @@ -248,7 +275,7 @@ For more information and to report bugs, see https://github.com/mvdan/sh. } status := 0 for _, path := range flag.Args() { - if info, err := os.Stat(path); err == nil && !info.IsDir() && !applyIgnore.val && !find.val { + if info, err := os.Stat(path); err == nil && !info.IsDir() && !applyIgnore.val && find.val == "false" { // When given paths to files directly, always format them, // no matter their extension or shebang. // @@ -424,9 +451,14 @@ func formatPath(path string, checkShebang bool) error { } readBuf.Write(copyBuf[:n]) } - if find.val { + switch find.val { + case "true": fmt.Println(path) return nil + case "0": + fmt.Print(path) + fmt.Print("\000") + return nil } if _, err := io.CopyBuffer(&readBuf, f, copyBuf); err != nil { return err @@ -492,8 +524,12 @@ func formatBytes(src []byte, path string, fileLang syntax.LangVariant) error { printer.Print(&writeBuf, node) res := writeBuf.Bytes() if !bytes.Equal(src, res) { - if list.val { + switch list.val { + case "true": fmt.Println(path) + case "0": + fmt.Print(path) + fmt.Print("\000") } if write.val { info, err := os.Lstat(path) @@ -537,7 +573,7 @@ func formatBytes(src []byte, path string, fileLang syntax.LangVariant) error { return errChangedWithDiff } } - if !list.val && !write.val && !diff.val { + if list.val == "false" && !write.val && !diff.val { os.Stdout.Write(res) } return nil diff --git a/cmd/shfmt/shfmt.1.scd b/cmd/shfmt/shfmt.1.scd index c490c138..50734749 100644 --- a/cmd/shfmt/shfmt.1.scd +++ b/cmd/shfmt/shfmt.1.scd @@ -28,8 +28,9 @@ predictable. Some aspects of the format can be configured via printer flags. *--version* Show version and exit. -*-l*, *--list* - List files whose formatting differs from shfmt's. +*-l[=0]*, *--list[=0]* + List files whose formatting differs from shfmt's; + paths are separated by a newline or a null character if -l=0 *-w*, *--write* Write result to file instead of stdout. @@ -101,8 +102,9 @@ predictable. Some aspects of the format can be configured via printer flags. ## Utility flags -*-f*, *--find* - Recursively find all shell files and print the paths. +*-f[=0]*, *--find[=0]* + Recursively find all shell files and print the paths; + paths are separated by a newline or a null character if -f=0. *--to-json* Print syntax tree to stdout as a typed JSON.