Neale Pickett
·
2023-04-11
main.go
1package main
2
3import (
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
18type 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
28type Command func() error
29
30func nothing() error {
31 return nil
32}
33
34func 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.
53func (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
101func (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.
123func (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.
139func (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.
160func (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.
188func (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
199func (t *T) Markdown() error {
200 return transpile.Markdown(t.Stdin, t.Stdout)
201}
202
203func 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}