github.com/vugu/vugu@v0.3.6-0.20240430171613-3f6f402e014b/cmd/vugufmt/main.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"flag"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  	"runtime"
    11  	"strings"
    12  
    13  	"github.com/vugu/vugu/vugufmt"
    14  )
    15  
    16  var (
    17  	exitCode    = 0
    18  	list        = flag.Bool("l", false, "list files whose formatting differs from vugufmt's")
    19  	write       = flag.Bool("w", false, "write result to (source) file instead of stdout")
    20  	simplifyAST = flag.Bool("s", false, "simplify code")
    21  	imports     = flag.Bool("i", false, "run goimports instead of gofmt")
    22  	doDiff      = flag.Bool("d", false, "display diffs instead of rewriting files")
    23  )
    24  
    25  func main() {
    26  	vugufmtMain()
    27  	os.Exit(exitCode)
    28  }
    29  
    30  func vugufmtMain() {
    31  	// Handle input flags
    32  	flag.Usage = func() {
    33  		fmt.Fprintf(os.Stderr, "usage: vugufmt [flags] [path ...]\n")
    34  		flag.PrintDefaults()
    35  	}
    36  	flag.Parse()
    37  
    38  	// If no file paths given, we are reading from stdin.
    39  	if flag.NArg() == 0 {
    40  		if err := processFile("<standard input>", os.Stdin, os.Stdout); err != nil {
    41  			report(err)
    42  		}
    43  		return
    44  	}
    45  
    46  	// Otherwise, we need to read a bunch of files
    47  	for i := 0; i < flag.NArg(); i++ {
    48  		path := flag.Arg(i)
    49  		switch dir, err := os.Stat(path); {
    50  		case err != nil:
    51  			report(err)
    52  		case dir.IsDir():
    53  			walkDir(path)
    54  		default:
    55  			if err := processFile(path, nil, os.Stdout); err != nil {
    56  				report(err)
    57  			}
    58  		}
    59  	}
    60  }
    61  
    62  func walkDir(path string) {
    63  	err := filepath.Walk(path, visitFile)
    64  	if err != nil {
    65  		report(err)
    66  	}
    67  }
    68  
    69  func visitFile(path string, f os.FileInfo, err error) error {
    70  	if err == nil && isVuguFile(f) {
    71  		err = processFile(path, nil, os.Stdout)
    72  	}
    73  
    74  	// Don't complain if a file was deleted in the meantime (i.e.
    75  	// the directory changed concurrently while running gofmt).
    76  	if err != nil && !os.IsNotExist(err) {
    77  		report(err)
    78  	}
    79  	return nil
    80  }
    81  
    82  func isVuguFile(f os.FileInfo) bool {
    83  	// ignore non-Vugu files (except html)
    84  	name := f.Name()
    85  	return !f.IsDir() &&
    86  		!strings.HasPrefix(name, ".") &&
    87  		(strings.HasSuffix(name, ".vugu") || (strings.HasSuffix(name, ".html")))
    88  }
    89  
    90  func report(err error) {
    91  	fmt.Fprintf(os.Stderr, "%s\n", strings.TrimSpace(err.Error()))
    92  	exitCode = 2
    93  }
    94  
    95  func processFile(filename string, in io.Reader, out io.Writer) error {
    96  	var perm os.FileMode = 0644
    97  	// open the file if needed
    98  	if in == nil {
    99  		f, err := os.Open(filename)
   100  		if err != nil {
   101  			return err
   102  		}
   103  		defer f.Close()
   104  
   105  		fi, err := f.Stat()
   106  		if err != nil {
   107  			return err
   108  		}
   109  		in = f
   110  		perm = fi.Mode().Perm()
   111  	}
   112  
   113  	src, err := io.ReadAll(in)
   114  	if err != nil {
   115  		return err
   116  	}
   117  
   118  	var resBuff bytes.Buffer
   119  
   120  	var formatter *vugufmt.Formatter
   121  	if *imports {
   122  		formatter = vugufmt.NewFormatter(vugufmt.UseGoImports)
   123  	} else {
   124  		formatter = vugufmt.NewFormatter(vugufmt.UseGoFmt(*simplifyAST))
   125  	}
   126  
   127  	if !*list && !*doDiff {
   128  		if err := formatter.FormatHTML(filename, bytes.NewReader(src), &resBuff); err != nil {
   129  			return err
   130  		}
   131  		res := resBuff.Bytes()
   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 = os.WriteFile(filename, res, perm)
   140  			if err != nil {
   141  				err = os.Rename(bakname, filename)
   142  				return err
   143  			}
   144  			err = os.Remove(bakname)
   145  			if err != nil {
   146  				return err
   147  			}
   148  		} else {
   149  			// just write to stdout
   150  			_, err = out.Write(res)
   151  			if err != nil {
   152  				return err
   153  			}
   154  		}
   155  	} else {
   156  		different, err := formatter.Diff(filename, bytes.NewReader(src), &resBuff)
   157  		if err != nil {
   158  			return fmt.Errorf("computing diff: %s", err)
   159  		}
   160  		if *list {
   161  			if different {
   162  				fmt.Fprintln(out, filename)
   163  			}
   164  		} else if *doDiff {
   165  			_, err := out.Write(resBuff.Bytes())
   166  			if err != nil {
   167  				return err
   168  			}
   169  		}
   170  	}
   171  
   172  	return nil
   173  }
   174  
   175  const chmodSupported = runtime.GOOS != "windows"
   176  
   177  // backupFile writes data to a new file named filename<number> with permissions perm,
   178  // with <number randomly chosen such that the file name is unique. backupFile returns
   179  // the chosen file name.
   180  func backupFile(filename string, data []byte, perm os.FileMode) (string, error) {
   181  
   182  	// create backup file
   183  	f, err := os.CreateTemp(filepath.Dir(filename), filepath.Base(filename))
   184  	if err != nil {
   185  		return "", err
   186  	}
   187  
   188  	bakname := f.Name()
   189  
   190  	if chmodSupported {
   191  		err = f.Chmod(perm)
   192  		if err != nil {
   193  			f.Close()
   194  			os.Remove(bakname)
   195  			return bakname, err
   196  		}
   197  	}
   198  
   199  	// write data to backup file
   200  	_, err = f.Write(data)
   201  	if err1 := f.Close(); err == nil {
   202  		err = err1
   203  	}
   204  	return bakname, err
   205  }