feat(release): beelloo v0.1
This commit is contained in:
142
internal/cli/run.go
Normal file
142
internal/cli/run.go
Normal 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))
|
||||
}
|
||||
56
internal/cli/run_test.go
Normal file
56
internal/cli/run_test.go
Normal file
@ -0,0 +1,56 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"beelloo/internal/invoice"
|
||||
)
|
||||
|
||||
func TestRunNewCreatesScaffold(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
var errOut bytes.Buffer
|
||||
tmp := t.TempDir()
|
||||
path := filepath.Join(tmp, "invoice.md")
|
||||
|
||||
code := Run([]string{"new", path}, &out, &errOut)
|
||||
if code != 0 {
|
||||
t.Fatalf("expected exit code 0, got %d: %s", code, errOut.String())
|
||||
}
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("read scaffold: %v", err)
|
||||
}
|
||||
if string(data) != invoice.ScaffoldMarkdown {
|
||||
t.Fatalf("scaffold content mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunBuildErrors(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
var errOut bytes.Buffer
|
||||
code := Run([]string{"build"}, &out, &errOut)
|
||||
if code != 2 {
|
||||
t.Fatalf("expected exit code 2, got %d", code)
|
||||
}
|
||||
code = Run([]string{"build", "missing.md"}, &out, &errOut)
|
||||
if code != 1 {
|
||||
t.Fatalf("expected exit code 1 for missing file, got %d", code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunBuildValidationError(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
var errOut bytes.Buffer
|
||||
tmp := t.TempDir()
|
||||
path := filepath.Join(tmp, "bad.md")
|
||||
if err := os.WriteFile(path, []byte("# Facture\n\n## Vendeur\nNom: \n"), 0644); err != nil {
|
||||
t.Fatalf("write bad markdown: %v", err)
|
||||
}
|
||||
code := Run([]string{"build", path}, &out, &errOut)
|
||||
if code != 1 {
|
||||
t.Fatalf("expected exit code 1, got %d", code)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user