github.com/dirtbags/moth/v4@v4.6.3-0.20240418162859-a93bc5be85d4/cmd/transpile/main.go (about) 1 package main 2 3 import ( 4 "encoding/json" 5 "flag" 6 "fmt" 7 "io" 8 "log" 9 "os" 10 "sort" 11 12 "github.com/dirtbags/moth/v4/pkg/transpile" 13 14 "github.com/spf13/afero" 15 ) 16 17 // T represents the state of things 18 type T struct { 19 Stdin io.Reader 20 Stdout io.Writer 21 Stderr io.Writer 22 Args []string 23 BaseFs afero.Fs 24 fs afero.Fs 25 } 26 27 // Command is a function invoked by the user 28 type Command func() error 29 30 func nothing() error { 31 return nil 32 } 33 34 func usage(w io.Writer) { 35 fmt.Fprintln(w, " Usage: transpile mothball [FLAGS] [MOTHBALL]") 36 fmt.Fprintln(w, " Compile a mothball") 37 fmt.Fprintln(w, " Usage: inventory [FLAGS]") 38 fmt.Fprintln(w, " Show category inventory") 39 fmt.Fprintln(w, " Usage: puzzle [FLAGS]") 40 fmt.Fprintln(w, " Print puzzle JSON") 41 fmt.Fprintln(w, " Usage: file [FLAGS] FILENAME") 42 fmt.Fprintln(w, " Open a file for a puzzle") 43 fmt.Fprintln(w, " Usage: answer [FLAGS] ANSWER") 44 fmt.Fprintln(w, " Check correctness of an answer") 45 fmt.Fprintln(w, " Usage: markdown [FLAGS]") 46 fmt.Fprintln(w, " Format stdin with markdown") 47 fmt.Fprintln(w, "") 48 fmt.Fprintln(w, "-dir DIRECTORY") 49 fmt.Fprintln(w, " Use puzzle in DIRECTORY") 50 } 51 52 // ParseArgs parses arguments and runs the appropriate action. 53 func (t *T) ParseArgs() (Command, error) { 54 var cmd Command 55 56 if len(t.Args) == 1 { 57 usage(t.Stderr) 58 return nothing, nil 59 } 60 61 flags := flag.NewFlagSet(t.Args[1], flag.ContinueOnError) 62 flags.SetOutput(t.Stderr) 63 directory := flags.String("dir", "", "Work directory") 64 65 switch t.Args[1] { 66 case "mothball": 67 cmd = t.DumpMothball 68 case "inventory": 69 cmd = t.PrintInventory 70 case "puzzle": 71 cmd = t.DumpPuzzle 72 case "file": 73 cmd = t.DumpFile 74 case "answer": 75 cmd = t.CheckAnswer 76 case "markdown": 77 cmd = t.Markdown 78 case "help": 79 usage(t.Stderr) 80 return nothing, nil 81 default: 82 fmt.Fprintln(t.Stderr, "ERROR:", t.Args[1], "is not a valid command") 83 usage(t.Stderr) 84 return nothing, fmt.Errorf("invalid command") 85 } 86 87 if err := flags.Parse(t.Args[2:]); err != nil { 88 return nothing, err 89 } 90 if *directory != "" { 91 t.fs = afero.NewBasePathFs(t.BaseFs, *directory) 92 } else { 93 t.fs = t.BaseFs 94 } 95 t.Args = flags.Args() 96 97 return cmd, nil 98 } 99 100 // PrintInventory prints a puzzle inventory to stdout 101 func (t *T) PrintInventory() error { 102 c := transpile.NewFsCategory(t.fs, "") 103 104 inv, err := c.Inventory() 105 if err != nil { 106 return err 107 } 108 sort.Ints(inv) 109 jinv, err := json.Marshal( 110 transpile.InventoryResponse{ 111 Puzzles: inv, 112 }, 113 ) 114 if err != nil { 115 return err 116 } 117 118 t.Stdout.Write(jinv) 119 return nil 120 } 121 122 // DumpPuzzle writes a puzzle's JSON to the writer. 123 func (t *T) DumpPuzzle() error { 124 puzzle := transpile.NewFsPuzzle(t.fs) 125 126 p, err := puzzle.Puzzle() 127 if err != nil { 128 return err 129 } 130 jp, err := json.Marshal(p) 131 if err != nil { 132 return err 133 } 134 t.Stdout.Write(jp) 135 return nil 136 } 137 138 // DumpFile writes a file to the writer. 139 func (t *T) DumpFile() error { 140 filename := "puzzle.json" 141 if len(t.Args) > 0 { 142 filename = t.Args[0] 143 } 144 145 puzzle := transpile.NewFsPuzzle(t.fs) 146 147 f, err := puzzle.Open(filename) 148 if err != nil { 149 return err 150 } 151 defer f.Close() 152 if _, err := io.Copy(t.Stdout, f); err != nil { 153 return err 154 } 155 156 return nil 157 } 158 159 // DumpMothball writes a mothball to the writer, or an output file if specified. 160 func (t *T) DumpMothball() error { 161 var w io.Writer 162 c := transpile.NewFsCategory(t.fs, "") 163 164 filename := "" 165 if len(t.Args) == 0 { 166 w = t.Stdout 167 } else { 168 filename = t.Args[0] 169 outf, err := t.BaseFs.Create(filename) 170 if err != nil { 171 return err 172 } 173 defer outf.Close() 174 w = outf 175 log.Println("Writing mothball to", filename) 176 } 177 178 if err := transpile.Mothball(c, w); err != nil { 179 if filename != "" { 180 t.BaseFs.Remove(filename) 181 } 182 return err 183 } 184 return nil 185 } 186 187 // CheckAnswer prints whether an answer is correct. 188 func (t *T) CheckAnswer() error { 189 answer := "" 190 if len(t.Args) > 0 { 191 answer = t.Args[0] 192 } 193 c := transpile.NewFsPuzzle(t.fs) 194 _, err := fmt.Fprintf(t.Stdout, `{"Correct":%v}`, c.Answer(answer)) 195 return err 196 } 197 198 // Markdown runs stdin through a Markdown engine 199 func (t *T) Markdown() error { 200 return transpile.Markdown(t.Stdin, t.Stdout) 201 } 202 203 func main() { 204 t := &T{ 205 Stdin: os.Stdin, 206 Stdout: os.Stdout, 207 Stderr: os.Stderr, 208 Args: os.Args, 209 BaseFs: afero.NewOsFs(), 210 } 211 cmd, err := t.ParseArgs() 212 if err != nil { 213 log.Fatal(err) 214 } 215 if err := cmd(); err != nil { 216 log.Fatal(err) 217 } 218 }