github.com/hirochachacha/plua@v0.0.0-20170217012138-c82f520cc725/cmd/luafmt/luafmt.go (about) 1 // Original: src/cmd/gofmt/gofmt.go 2 // 3 // Copyright 2009 The Go Authors. All rights reserved. 4 // Portions Copyright 2016 Hiroshi Ioka. All rights reserved. 5 // 6 // Redistribution and use in source and binary forms, with or without 7 // modification, are permitted provided that the following conditions are 8 // met: 9 // 10 // * Redistributions of source code must retain the above copyright 11 // notice, this list of conditions and the following disclaimer. 12 // * Redistributions in binary form must reproduce the above 13 // copyright notice, this list of conditions and the following disclaimer 14 // in the documentation and/or other materials provided with the 15 // distribution. 16 // * Neither the name of Google Inc. nor the names of its 17 // contributors may be used to endorse or promote products derived from 18 // this software without specific prior written permission. 19 // 20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 32 package main 33 34 import ( 35 "bytes" 36 "flag" 37 "fmt" 38 "io" 39 "io/ioutil" 40 "os" 41 "os/exec" 42 "path/filepath" 43 "runtime" 44 "strings" 45 46 "github.com/hirochachacha/plua/compiler/ast/printer" 47 "github.com/hirochachacha/plua/compiler/parser" 48 "github.com/hirochachacha/plua/compiler/scanner" 49 ) 50 51 var ( 52 // main operation modes 53 list = flag.Bool("l", false, "list files whose formatting differs from luafmt's") 54 write = flag.Bool("w", false, "write result to (source) file instead of stdout") 55 doDiff = flag.Bool("d", false, "display diffs instead of rewriting files") 56 ) 57 58 const ( 59 // tabWidth = 2 60 // printerMode = printer.UseSpaces | printer.TabIndent 61 ) 62 63 var ( 64 exitCode = 0 65 66 buf bytes.Buffer 67 ) 68 69 func report(err error) { 70 fmt.Fprintln(os.Stderr, err) 71 exitCode = 2 72 } 73 74 func usage() { 75 fmt.Fprintf(os.Stderr, "usage: luafmt [flags] [path ...]\n") 76 flag.PrintDefaults() 77 } 78 79 func isLuaFile(f os.FileInfo) bool { 80 // ignore non-Go files 81 name := f.Name() 82 return !f.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".lua") 83 } 84 85 func processFile(filename string) error { 86 var srcname string 87 var r io.Reader 88 var src []byte 89 var perm os.FileMode = 0644 90 91 if filename == "" { 92 srcname = "=stdin" 93 r = os.Stdin 94 } else { 95 srcname = "@" + filename 96 f, err := os.Open(filename) 97 if err != nil { 98 return err 99 } 100 defer f.Close() 101 fi, err := f.Stat() 102 if err != nil { 103 return err 104 } 105 src, err = ioutil.ReadAll(f) 106 if err != nil { 107 return err 108 } 109 r = bytes.NewReader(src) 110 perm = fi.Mode().Perm() 111 } 112 113 ast, err := parser.Parse(scanner.Scan(r, srcname, scanner.ScanComments), parser.ParseComments) 114 if err != nil { 115 return err 116 } 117 118 buf.Reset() 119 120 err = printer.Fprint(&buf, ast) 121 // err = (&printer.Config{Mode: printerMode, Tabwidth: tabWidth}).Fprint(&buf, ast) 122 if err != nil { 123 return err 124 } 125 126 res := buf.Bytes() 127 128 if !bytes.Equal(src, res) { 129 // formatting has changed 130 if *list { 131 fmt.Fprintln(os.Stdout, filename) 132 } 133 if *write { 134 // make a temporary backup before overwriting original 135 bakname, err := backupFile(filename+".", src, perm) 136 if err != nil { 137 return err 138 } 139 err = ioutil.WriteFile(filename, res, perm) 140 if err != nil { 141 os.Rename(bakname, filename) 142 return err 143 } 144 err = os.Remove(bakname) 145 if err != nil { 146 return err 147 } 148 } 149 if *doDiff { 150 data, err := diff(src, res) 151 if err != nil { 152 return fmt.Errorf("computing diff: %s", err) 153 } 154 fmt.Printf("diff %s luafmt/%s\n", filename, filename) 155 os.Stdout.Write(data) 156 } 157 } 158 159 if !*list && !*write && !*doDiff { 160 _, err = os.Stdout.Write(res) 161 } 162 163 return err 164 } 165 166 func visitFile(path string, f os.FileInfo, err error) error { 167 if err == nil && isLuaFile(f) { 168 err = processFile(path) 169 } 170 // Don't complain if a file was deleted in the meantime (i.e. 171 // the directory changed concurrently while running luafmt). 172 if err != nil && !os.IsNotExist(err) { 173 report(err) 174 } 175 return nil 176 } 177 178 func walkDir(path string) { 179 filepath.Walk(path, visitFile) 180 } 181 182 func main() { 183 // call luafmtMain in a separate function 184 // so that it can use defer and have them 185 // run before the exit. 186 luafmtMain() 187 os.Exit(exitCode) 188 } 189 190 func luafmtMain() { 191 flag.Usage = usage 192 flag.Parse() 193 194 if flag.NArg() == 0 { 195 if *write { 196 fmt.Fprintln(os.Stderr, "error: cannot use -w with standard input") 197 exitCode = 2 198 return 199 } 200 if err := processFile(""); err != nil { 201 report(err) 202 } 203 return 204 } 205 206 for i := 0; i < flag.NArg(); i++ { 207 path := flag.Arg(i) 208 switch dir, err := os.Stat(path); { 209 case err != nil: 210 report(err) 211 case dir.IsDir(): 212 walkDir(path) 213 default: 214 if err := processFile(path); err != nil { 215 report(err) 216 } 217 } 218 } 219 } 220 221 func diff(b1, b2 []byte) (data []byte, err error) { 222 f1, err := ioutil.TempFile("", "luafmt") 223 if err != nil { 224 return 225 } 226 defer os.Remove(f1.Name()) 227 defer f1.Close() 228 229 f2, err := ioutil.TempFile("", "luafmt") 230 if err != nil { 231 return 232 } 233 defer os.Remove(f2.Name()) 234 defer f2.Close() 235 236 f1.Write(b1) 237 f2.Write(b2) 238 239 data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput() 240 if len(data) > 0 { 241 // diff exits with a non-zero status when the files don't match. 242 // Ignore that failure as long as we get output. 243 err = nil 244 } 245 return 246 247 } 248 249 const chmodSupported = runtime.GOOS != "windows" 250 251 // backupFile writes data to a new file named filename<number> with permissions perm, 252 // with <number randomly chosen such that the file name is unique. backupFile returns 253 // the chosen file name. 254 func backupFile(filename string, data []byte, perm os.FileMode) (string, error) { 255 // create backup file 256 f, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename)) 257 if err != nil { 258 return "", err 259 } 260 bakname := f.Name() 261 if chmodSupported { 262 err = f.Chmod(perm) 263 if err != nil { 264 f.Close() 265 os.Remove(bakname) 266 return bakname, err 267 } 268 } 269 270 // write data to backup file 271 n, err := f.Write(data) 272 if err == nil && n < len(data) { 273 err = io.ErrShortWrite 274 } 275 if err1 := f.Close(); err == nil { 276 err = err1 277 } 278 279 return bakname, err 280 }