package main import ( "context" "errors" "os" "os/signal" "sync" "syscall" "git.golem15.com/golem15/summercms/internal/docsite" "git.golem15.com/golem15/summercms/modules/backpack" "git.golem15.com/golem15/summercms/modules/bonfire" "git.golem15.com/golem15/summercms/modules/cabana" "git.golem15.com/golem15/summercms/modules/compass" "git.golem15.com/golem15/summercms/modules/conga" "git.golem15.com/golem15/summercms/modules/flare" "git.golem15.com/golem15/summercms/modules/lagoon" "git.golem15.com/golem15/summercms/modules/lighthouse/centrifugo" "git.golem15.com/golem15/summercms/modules/surf" ) func docsBuildCommand() bonfire.Command { return bonfire.Command{ Name: "docs:build", Description: "Build the documentation site", Flags: []bonfire.Flag{ {Name: "root", Description: "Repository root; src= paths and modules/ resolve against it", Default: "."}, {Name: "src", Description: "Docs source directory (default /docs)"}, {Name: "out", Description: "Output directory (default /site)"}, {Name: "base-url", Description: "Base URL for site links (overrides site.yaml base_url)"}, {Name: "check", Description: "Validate the docs and write nothing", Bare: true}, }, Run: func(ctx context.Context, in bonfire.Input, out bonfire.Output) error { opts := docsOptions(in) if flagTrue(in, "check") { problems, err := docsite.Check(opts) if err != nil { return err } if len(problems) > 0 { return reportDocsProblems(out, "docs:build", problems) } out.Printf("docs:build: no problems found\n") return nil } result, problems, err := docsite.Build(opts) if err != nil { return err } if len(problems) > 0 { return reportDocsProblems(out, "docs:build", problems) } out.Printf("docs:build: wrote %d pages to %s\n", result.Pages, result.Out) return nil }, } } func docsSyncCommand() bonfire.Command { return bonfire.Command{ Name: "docs:sync", Description: "Rewrite src= code blocks from their sources", Flags: []bonfire.Flag{ {Name: "root", Description: "Repository root; src= paths resolve against it", Default: "."}, {Name: "src", Description: "Docs source directory (default /docs)"}, }, Run: func(ctx context.Context, in bonfire.Input, out bonfire.Output) error { result, problems, err := docsite.Sync(docsOptions(in)) if err != nil { return err } if len(problems) > 0 { return reportDocsProblems(out, "docs:sync", problems) } if result.Snippets == 0 { out.Printf("docs:sync: all snippets up to date\n") return nil } out.Printf("docs:sync: updated %d snippets in %d files\n", result.Snippets, result.Files) return nil }, } } func docsServeCommand() bonfire.Command { return bonfire.Command{ Name: "docs:serve", Description: "Build the documentation site and preview it on a local address", Flags: []bonfire.Flag{ {Name: "root", Description: "Repository root; src= paths and modules/ resolve against it", Default: "."}, {Name: "src", Description: "Docs source directory (default /docs)"}, {Name: "base-url", Description: "Base URL for site links (overrides site.yaml base_url)"}, {Name: "addr", Description: "Listen address; must be loopback unless --allow-remote", Default: docsite.DefaultServeAddr}, {Name: "allow-remote", Description: "Allow a non-loopback --addr (serves the docs on the network)", Bare: true}, }, Run: func(ctx context.Context, in bonfire.Input, out bonfire.Output) error { addr, _ := in.Flag("addr") if addr == "" { addr = docsite.DefaultServeAddr } ctx, stop := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM) defer stop() return docsite.Serve(ctx, docsOptions(in), addr, flagTrue(in, "allow-remote"), outputWriter{mu: &sync.Mutex{}, out: out}) }, } } // outputWriter adapts bonfire.Output to io.Writer for docsite.Serve, which // writes from its watch goroutine too. type outputWriter struct { mu *sync.Mutex out bonfire.Output } func (w outputWriter) Write(p []byte) (int, error) { w.mu.Lock() defer w.mu.Unlock() w.out.Printf("%s", p) return len(p), nil } func docsOptions(in bonfire.Input) docsite.Options { opts := docsite.Options{Commands: docsCommands()} opts.Root, _ = in.Flag("root") opts.Src, _ = in.Flag("src") opts.Out, _ = in.Flag("out") opts.BaseURL, _ = in.Flag("base-url") return opts } // docsCommands collects the command names docs pages may show: the summer // tool's own commands, and every command the generated application main // registers (TestDocsCommandsMirrorGeneratedMain keeps this list in step // with internal/build) plus the realtime and push commands applications // append. The constructors only capture the app, so an empty config is // enough; nothing runs. func docsCommands() *docsite.Commands { var tool []string for _, c := range toolCommands() { tool = append(tool, c.Name) } app := backpack.New(&compass.Config{}) var appCmds []bonfire.Command appCmds = append(appCmds, lagoon.RuntimeCommands(app, nil)...) appCmds = append(appCmds, lagoon.KeyGenerateCommand()) appCmds = append(appCmds, conga.RuntimeCommands(app, nil)...) appCmds = append(appCmds, surf.ServeCommand(app, nil)) appCmds = append(appCmds, surf.RouteListCommand(app, nil)) appCmds = append(appCmds, cabana.RuntimeCommands(app)...) appCmds = append(appCmds, centrifugo.Commands(app)...) appCmds = append(appCmds, flare.Commands(app)...) names := make([]string, 0, len(appCmds)) for _, c := range appCmds { names = append(names, c.Name) } return &docsite.Commands{Tool: tool, App: names} } // reportDocsProblems prints one line per problem and a summary, and returns // a short error so the binary exits 1. func reportDocsProblems(out bonfire.Output, cmd string, problems []docsite.Problem) error { for _, p := range problems { out.Printf("%s\n", p) } out.Printf("%s: %d problems, nothing written\n", cmd, len(problems)) return errors.New(cmd + " failed") }