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  }