feat(release): beelloo v0.1

This commit is contained in:
2026-02-11 11:09:45 +01:00
parent 75ad1e7cee
commit 0dc9eda240
26 changed files with 1918 additions and 0 deletions

142
internal/cli/run.go Normal file
View File

@ -0,0 +1,142 @@
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 <file.md>")
fmt.Fprintln(w, " beelloo build <file.md> [--css=style.css]")
fmt.Fprintln(w, " beelloo serve <file.md> [--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))
}