moth

Monarch Of The Hill game server
git clone https://git.woozle.org/neale/moth.git

moth / cmd / transpile
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}