Files
iknowyou/back/cmd/gendocs/main.go
2026-04-06 15:12:34 +02:00

156 lines
4.1 KiB
Go

package main
import (
"flag"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/anotherhadi/iknowyou/internal/registry"
"github.com/anotherhadi/iknowyou/internal/tools"
)
func main() {
out := flag.String("out", "../.github/docs", "output directory for generated docs")
flag.Parse()
toolsDir := filepath.Join(*out, "tools")
if _, err := os.Stat(toolsDir); err == nil {
if err := os.RemoveAll(toolsDir); err != nil {
fatalf("removing tools dir: %v", err)
}
}
if err := os.MkdirAll(toolsDir, 0o755); err != nil {
fatalf("mkdir: %v", err)
}
runners := make([]tools.ToolRunner, len(registry.Factories))
for i, f := range registry.Factories {
runners[i] = f()
}
if err := writeIndex(*out, runners); err != nil {
fatalf("index: %v", err)
}
for _, r := range runners {
if err := writeTool(*out, r); err != nil {
fatalf("tool %s: %v", r.Name(), err)
}
}
fmt.Printf("✓ generated docs for %d tools → %s\n", len(runners), *out)
}
// writeIndex writes the tools.md index table.
func writeIndex(outDir string, runners []tools.ToolRunner) error {
var b strings.Builder
b.WriteString("# Tools\n\n")
fmt.Fprintf(&b, "_%d tools registered._\n\n", len(runners))
b.WriteString("| Tool | Input types | Description | Link |\n")
b.WriteString("|------|-------------|-------------|------|\n")
for _, r := range runners {
types := make([]string, len(r.InputTypes()))
for i, t := range r.InputTypes() {
types[i] = fmt.Sprintf("`%s`", t)
}
link := fmt.Sprintf("[`%s`](tools/%s.md)", r.Name(), r.Name())
projectLink := ""
if r.Link() != "" {
projectLink = fmt.Sprintf("[Link](%s)", r.Link())
}
fmt.Fprintf(&b, "| %s | %s | %s | %s |\n",
link,
strings.Join(types, ", "),
r.Description(),
projectLink,
)
}
return writeFile(filepath.Join(outDir, "tools.md"), b.String())
}
// writeTool writes the per-tool detail page.
func writeTool(outDir string, r tools.ToolRunner) error {
var b strings.Builder
fmt.Fprintf(&b, "# `%s`\n\n", r.Name())
fmt.Fprintf(&b, "%s\n\n", r.Description())
if r.Link() != "" {
fmt.Fprintf(&b, "**Source / documentation:** [%s](%s)\n\n", r.Link(), r.Link())
}
// Input types
b.WriteString("## Input types\n\n")
for _, t := range r.InputTypes() {
fmt.Fprintf(&b, "- `%s`\n", t)
}
b.WriteString("\n")
// External binary dependencies
if lister, ok := r.(tools.DependencyLister); ok {
if deps := lister.Dependencies(); len(deps) > 0 {
b.WriteString("## External dependencies\n\n")
b.WriteString("The following binaries must be installed and available in `$PATH`:\n\n")
for _, dep := range deps {
fmt.Fprintf(&b, "- `%s`\n", dep)
}
b.WriteString("\n")
}
}
// Configuration
if d, ok := r.(tools.ConfigDescriber); ok {
fields := d.ConfigFields()
if len(fields) > 0 {
b.WriteString("## Configuration\n\n")
b.WriteString("Configure globally via the Tools page or override per profile.\n\n")
b.WriteString("| Field | Type | Required | Default | Description |\n")
b.WriteString("|-------|------|:--------:|---------|-------------|\n")
for _, f := range fields {
req := "-"
if f.Required {
req = "**yes**"
}
def := "-"
if f.Default != nil && fmt.Sprintf("%v", f.Default) != "" {
def = fmt.Sprintf("`%v`", f.Default)
}
desc := f.Description
if desc == "" {
desc = "-"
}
fmt.Fprintf(&b, "| `%s` | `%s` | %s | %s | %s |\n",
f.Name, f.Type, req, def, desc,
)
}
b.WriteString("\n")
} else {
b.WriteString("## Configuration\n\nThis tool has no configuration fields.\n\n")
}
} else if _, ok := r.(tools.Configurable); ok {
b.WriteString("## Configuration\n\nThis tool is configurable but does not expose field metadata.\n\n")
} else {
b.WriteString("## Configuration\n\nThis tool requires no configuration.\n\n")
}
b.WriteString("---\n\n")
b.WriteString("[← Back to tools index](../tools.md)\n")
return writeFile(filepath.Join(outDir, "tools", r.Name()+".md"), b.String())
}
func writeFile(path, content string) error {
return os.WriteFile(path, []byte(content), 0o644)
}
func fatalf(format string, args ...any) {
fmt.Fprintf(os.Stderr, "gendocs: "+format+"\n", args...)
os.Exit(1)
}