github.com/upm-org/ush-sandbox@v2.6.4+incompatible/cmd/shfmt/main.go (about) 1 // Copyright (c) 2016, Daniel Martà <mvdan@mvdan.cc> 2 // See LICENSE for licensing information 3 4 package main 5 6 import ( 7 "bytes" 8 "flag" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "os" 13 "os/exec" 14 "path/filepath" 15 "regexp" 16 17 "mvdan.cc/sh/fileutil" 18 "mvdan.cc/sh/syntax" 19 ) 20 21 var ( 22 showVersion = flag.Bool("version", false, "") 23 24 list = flag.Bool("l", false, "") 25 write = flag.Bool("w", false, "") 26 simple = flag.Bool("s", false, "") 27 find = flag.Bool("f", false, "") 28 diff = flag.Bool("d", false, "") 29 30 langStr = flag.String("ln", "", "") 31 posix = flag.Bool("p", false, "") 32 33 indent = flag.Uint("i", 0, "") 34 binNext = flag.Bool("bn", false, "") 35 caseIndent = flag.Bool("ci", false, "") 36 spaceRedirs = flag.Bool("sr", false, "") 37 keepPadding = flag.Bool("kp", false, "") 38 minify = flag.Bool("mn", false, "") 39 40 toJSON = flag.Bool("tojson", false, "") 41 42 parser *syntax.Parser 43 printer *syntax.Printer 44 readBuf, writeBuf bytes.Buffer 45 46 copyBuf = make([]byte, 32*1024) 47 48 in io.Reader = os.Stdin 49 out io.Writer = os.Stdout 50 51 version = "v2.6.4" 52 ) 53 54 func main() { 55 flag.Usage = func() { 56 fmt.Fprint(os.Stderr, `usage: shfmt [flags] [path ...] 57 58 If no arguments are given, standard input will be used. If a given path 59 is a directory, it will be recursively searched for shell files - both 60 by filename extension and by shebang. 61 62 -version show version and exit 63 64 -l list files whose formatting differs from shfmt's 65 -w write result to file instead of stdout 66 -d error with a diff when the formatting differs 67 -s simplify the code 68 69 Parser options: 70 71 -ln str language variant to parse (bash/posix/mksh, default "bash") 72 -p shorthand for -ln=posix 73 74 Printer options: 75 76 -i uint indent: 0 for tabs (default), >0 for number of spaces 77 -bn binary ops like && and | may start a line 78 -ci switch cases will be indented 79 -sr redirect operators will be followed by a space 80 -kp keep column alignment paddings 81 -mn minify program to reduce its size (implies -s) 82 83 Utilities: 84 85 -f recursively find all shell files and print the paths 86 -tojson print syntax tree to stdout as a typed JSON 87 `) 88 } 89 flag.Parse() 90 91 if *showVersion { 92 fmt.Println(version) 93 return 94 } 95 if *posix && *langStr != "" { 96 fmt.Fprintf(os.Stderr, "-p and -ln=lang cannot coexist\n") 97 os.Exit(1) 98 } 99 lang := syntax.LangBash 100 switch *langStr { 101 case "bash", "": 102 case "posix": 103 lang = syntax.LangPOSIX 104 case "mksh": 105 lang = syntax.LangMirBSDKorn 106 default: 107 fmt.Fprintf(os.Stderr, "unknown shell language: %s\n", *langStr) 108 os.Exit(1) 109 } 110 if *posix { 111 lang = syntax.LangPOSIX 112 } 113 if *minify { 114 *simple = true 115 } 116 parser = syntax.NewParser(syntax.KeepComments, syntax.Variant(lang)) 117 printer = syntax.NewPrinter(func(p *syntax.Printer) { 118 syntax.Indent(*indent)(p) 119 if *binNext { 120 syntax.BinaryNextLine(p) 121 } 122 if *caseIndent { 123 syntax.SwitchCaseIndent(p) 124 } 125 if *spaceRedirs { 126 syntax.SpaceRedirects(p) 127 } 128 if *keepPadding { 129 syntax.KeepPadding(p) 130 } 131 if *minify { 132 syntax.Minify(p) 133 } 134 }) 135 if flag.NArg() == 0 { 136 if err := formatStdin(); err != nil { 137 if err != errChangedWithDiff { 138 fmt.Fprintln(os.Stderr, err) 139 } 140 os.Exit(1) 141 } 142 return 143 } 144 if *toJSON { 145 fmt.Fprintln(os.Stderr, "-tojson can only be used with stdin/out") 146 os.Exit(1) 147 } 148 anyErr := false 149 for _, path := range flag.Args() { 150 walk(path, func(err error) { 151 if err != errChangedWithDiff { 152 fmt.Fprintln(os.Stderr, err) 153 } 154 anyErr = true 155 }) 156 } 157 if anyErr { 158 os.Exit(1) 159 } 160 } 161 162 var errChangedWithDiff = fmt.Errorf("") 163 164 func formatStdin() error { 165 if *write { 166 return fmt.Errorf("-w cannot be used on standard input") 167 } 168 src, err := ioutil.ReadAll(in) 169 if err != nil { 170 return err 171 } 172 return formatBytes(src, "<standard input>") 173 } 174 175 var vcsDir = regexp.MustCompile(`^\.(git|svn|hg)$`) 176 177 func walk(path string, onError func(error)) { 178 info, err := os.Stat(path) 179 if err != nil { 180 onError(err) 181 return 182 } 183 if !info.IsDir() { 184 if err := formatPath(path, false); err != nil { 185 onError(err) 186 } 187 return 188 } 189 filepath.Walk(path, func(path string, info os.FileInfo, err error) error { 190 if info.IsDir() && vcsDir.MatchString(info.Name()) { 191 return filepath.SkipDir 192 } 193 if err != nil { 194 onError(err) 195 return nil 196 } 197 conf := fileutil.CouldBeScript(info) 198 if conf == fileutil.ConfNotScript { 199 return nil 200 } 201 err = formatPath(path, conf == fileutil.ConfIfShebang) 202 if err != nil && !os.IsNotExist(err) { 203 onError(err) 204 } 205 return nil 206 }) 207 } 208 209 func formatPath(path string, checkShebang bool) error { 210 f, err := os.Open(path) 211 if err != nil { 212 return err 213 } 214 defer f.Close() 215 readBuf.Reset() 216 if checkShebang { 217 n, err := f.Read(copyBuf[:32]) 218 if err != nil { 219 return err 220 } 221 if !fileutil.HasShebang(copyBuf[:n]) { 222 return nil 223 } 224 readBuf.Write(copyBuf[:n]) 225 } 226 if *find { 227 fmt.Fprintln(out, path) 228 return nil 229 } 230 if _, err := io.CopyBuffer(&readBuf, f, copyBuf); err != nil { 231 return err 232 } 233 f.Close() 234 return formatBytes(readBuf.Bytes(), path) 235 } 236 237 func formatBytes(src []byte, path string) error { 238 prog, err := parser.Parse(bytes.NewReader(src), path) 239 if err != nil { 240 return err 241 } 242 if *simple { 243 syntax.Simplify(prog) 244 } 245 if *toJSON { 246 // must be standard input; fine to return 247 return writeJSON(out, prog, true) 248 } 249 writeBuf.Reset() 250 printer.Print(&writeBuf, prog) 251 res := writeBuf.Bytes() 252 if !bytes.Equal(src, res) { 253 if *list { 254 if _, err := fmt.Fprintln(out, path); err != nil { 255 return err 256 } 257 } 258 if *write { 259 f, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0) 260 if err != nil { 261 return err 262 } 263 if _, err := f.Write(res); err != nil { 264 return err 265 } 266 if err := f.Close(); err != nil { 267 return err 268 } 269 } 270 if *diff { 271 data, err := diffBytes(src, res, path) 272 if err != nil { 273 return fmt.Errorf("computing diff: %s", err) 274 } 275 out.Write(data) 276 return errChangedWithDiff 277 } 278 } 279 if !*list && !*write && !*diff { 280 if _, err := out.Write(res); err != nil { 281 return err 282 } 283 } 284 return nil 285 } 286 287 func writeTempFile(dir, prefix string, data []byte) (string, error) { 288 file, err := ioutil.TempFile(dir, prefix) 289 if err != nil { 290 return "", err 291 } 292 _, err = file.Write(data) 293 if err1 := file.Close(); err == nil { 294 err = err1 295 } 296 if err != nil { 297 os.Remove(file.Name()) 298 return "", err 299 } 300 return file.Name(), nil 301 } 302 303 func diffBytes(b1, b2 []byte, path string) ([]byte, error) { 304 fmt.Fprintf(out, "diff -u %s %s\n", 305 filepath.ToSlash(path+".orig"), 306 filepath.ToSlash(path)) 307 f1, err := writeTempFile("", "shfmt", b1) 308 if err != nil { 309 return nil, err 310 } 311 defer os.Remove(f1) 312 313 f2, err := writeTempFile("", "shfmt", b2) 314 if err != nil { 315 return nil, err 316 } 317 defer os.Remove(f2) 318 319 data, err := exec.Command("diff", "-u", f1, f2).Output() 320 if len(data) == 0 { 321 // No diff, or something went wrong; don't check for err 322 // as diff will return non-zero if the files differ. 323 return nil, err 324 } 325 // We already print the filename, so remove the 326 // temporary filenames printed by diff. 327 lines := bytes.Split(data, []byte("\n")) 328 for i, line := range lines { 329 switch { 330 case bytes.HasPrefix(line, []byte("---")): 331 case bytes.HasPrefix(line, []byte("+++")): 332 default: 333 return bytes.Join(lines[i:], []byte("\n")), nil 334 } 335 } 336 return data, nil 337 }