package cli import ( "fmt" "io" "os" "path/filepath" "strings" "beelloo/internal/invoice" "beelloo/internal/render" "beelloo/internal/server" ) func Run(args []string, out io.Writer, errOut io.Writer) int { if len(args) == 0 { usage(errOut) return 2 } switch args[0] { case "new": return runNew(args[1:], out, errOut) case "emit": return runBuild(args[1:], out, errOut) case "build": return runBuild(args[1:], out, errOut) case "serve": return runServe(args[1:], out, errOut) case "-h", "--help", "help": usage(out) return 0 default: fmt.Fprintf(errOut, "unknown command: %s\n", args[0]) usage(errOut) return 2 } } func usage(w io.Writer) { fmt.Fprintln(w, "Usage:") fmt.Fprintln(w, " beelloo new ") fmt.Fprintln(w, " beelloo build [--css=style.css]") fmt.Fprintln(w, " beelloo serve [--addr=127.0.0.1:0] [--css=style.css]") } func runNew(args []string, out io.Writer, errOut io.Writer) int { if len(args) != 1 { fmt.Fprintln(errOut, "new requires a single file path") return 2 } path := args[0] if err := os.WriteFile(path, []byte(invoice.ScaffoldMarkdown), 0644); err != nil { fmt.Fprintf(errOut, "failed to write scaffold: %v\n", err) return 1 } fmt.Fprintf(out, "scaffold written to %s\n", path) return 0 } func runBuild(args []string, out io.Writer, errOut io.Writer) int { if len(args) < 1 { fmt.Fprintln(errOut, "build requires a markdown file path") return 2 } path := args[0] cssPath := "" for _, arg := range args[1:] { if strings.HasPrefix(arg, "--css=") { cssPath = strings.TrimPrefix(arg, "--css=") } } file, err := os.Open(path) if err != nil { fmt.Fprintf(errOut, "failed to open %s: %v\n", path, err) return 1 } defer file.Close() doc, err := invoice.ParseMarkdown(file) if err != nil { fmt.Fprintf(errOut, "parse error: %v\n", err) return 1 } if err := invoice.Validate(&doc); err != nil { fmt.Fprintf(errOut, "validation error: %v\n", err) return 1 } html, err := renderWithCSS(doc, cssPath) if err != nil { fmt.Fprintf(errOut, "render error: %v\n", err) return 1 } outputPath := strings.TrimSuffix(path, filepath.Ext(path)) + ".html" if err := os.WriteFile(outputPath, []byte(html), 0644); err != nil { fmt.Fprintf(errOut, "failed to write html: %v\n", err) return 1 } fmt.Fprintf(out, "HTML generated at %s\n", outputPath) return 0 } func runServe(args []string, out io.Writer, errOut io.Writer) int { if len(args) < 1 { fmt.Fprintln(errOut, "serve requires a markdown file path") return 2 } path := args[0] addr := "127.0.0.1:0" cssPath := "" for _, arg := range args[1:] { if strings.HasPrefix(arg, "--addr=") { addr = strings.TrimPrefix(arg, "--addr=") } if strings.HasPrefix(arg, "--css=") { cssPath = strings.TrimPrefix(arg, "--css=") } } err := server.Serve(server.Config{ Addr: addr, MDPath: path, CSSPath: cssPath, }, out, errOut) if err != nil { fmt.Fprintf(errOut, "%v\n", err) return 1 } return 0 } func renderWithCSS(doc invoice.Document, cssPath string) (string, error) { if cssPath == "" { return render.RenderHTML(doc) } css, err := os.ReadFile(cssPath) if err != nil { return "", err } return render.RenderHTMLWithCSS(doc, string(css)) }