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) }