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  }