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 }