1package cmd
2
3import (
4 "fmt"
5 "os"
6 "path/filepath"
7 "time"
8
9 "github.com/spf13/cobra"
10 "github.com/spf13/cobra/doc"
11
12 xdgadapter "github.com/bnema/dumber/internal/infrastructure/xdg"
13)
14
15const dirPerm = 0o755
16
17var (
18 genDocsOutputDir string
19 genDocsFormat string
20)
21
22var genDocsCmd = &cobra.Command{
23 Use: "gen-docs",
24 Short: "Generate documentation from CLI commands",
25 Long: `Generate documentation (man pages or markdown) from CLI command definitions.
26
27The documentation is auto-generated from the command structure, including:
28- Command names and aliases
29- Short and long descriptions
30- Flags and their descriptions
31- Usage examples
32
33Supported formats:
34 man Unix manual pages (groff format)
35 markdown Markdown files (for websites/wikis)
36
37By default, man pages are installed to ~/.local/share/man/man1/ so they
38are immediately available via 'man dumber'. You may need to run 'mandb'
39to update the man page index.
40
41Examples:
42 dumber gen-docs # Install man pages to ~/.local/share/man/man1/
43 dumber gen-docs --format markdown # Generate markdown docs
44 dumber gen-docs --output ./man # Generate to local directory`,
45 RunE: runGenDocs,
46}
47
48func init() {
49 rootCmd.AddCommand(genDocsCmd)
50 genDocsCmd.Flags().StringVarP(&genDocsOutputDir, "output", "o", "", "Output directory for generated docs")
51 genDocsCmd.Flags().StringVarP(&genDocsFormat, "format", "f", "man", "Output format: man, markdown")
52}
53
54func runGenDocs(_ *cobra.Command, _ []string) error {
55 // Resolve output directory
56 outputDir := genDocsOutputDir
57 if outputDir == "" {
58 switch genDocsFormat {
59 case "man":
60 xdg := xdgadapter.New(false, "")
61 manDir, err := xdg.ManDir()
62 if err != nil {
63 return fmt.Errorf("resolve man directory: %w", err)
64 }
65 outputDir = manDir
66 case "markdown":
67 outputDir = "./docs"
68 }
69 }
70
71 // Create output directory if it doesn't exist
72 if err := os.MkdirAll(outputDir, dirPerm); err != nil {
73 return fmt.Errorf("create output directory: %w", err)
74 }
75
76 switch genDocsFormat {
77 case "man":
78 return generateManPages(outputDir)
79 case "markdown":
80 return generateMarkdown(outputDir)
81 default:
82 return fmt.Errorf("unsupported format %q (use: man, markdown)", genDocsFormat)
83 }
84}
85
86func generateManPages(outputDir string) error {
87 header := &doc.GenManHeader{
88 Title: "DUMBER",
89 Section: "1",
90 Source: "dumber " + buildInfo.Version,
91 Manual: "Dumber Manual",
92 Date: func() *time.Time { t := time.Now(); return &t }(),
93 }
94
95 // Disable auto-generation timestamp in the footer for reproducible builds
96 rootCmd.DisableAutoGenTag = true
97
98 if err := doc.GenManTree(rootCmd, header, outputDir); err != nil {
99 return fmt.Errorf("generate man pages: %w", err)
100 }
101
102 fmt.Printf("Installed man pages to %s\n", outputDir)
103 fmt.Println("Run 'mandb' if 'man dumber' doesn't work immediately.")
104
105 // List generated files
106 entries, err := os.ReadDir(outputDir)
107 if err != nil {
108 return nil // Non-fatal
109 }
110 for _, e := range entries {
111 if filepath.Ext(e.Name()) == ".1" {
112 fmt.Printf(" - %s\n", e.Name())
113 }
114 }
115
116 return nil
117}
118
119func generateMarkdown(outputDir string) error {
120 // Disable auto-generation timestamp for reproducible builds
121 rootCmd.DisableAutoGenTag = true
122
123 if err := doc.GenMarkdownTree(rootCmd, outputDir); err != nil {
124 return fmt.Errorf("generate markdown docs: %w", err)
125 }
126
127 fmt.Printf("Generated markdown docs in %s\n", outputDir)
128
129 // List generated files
130 entries, err := os.ReadDir(outputDir)
131 if err != nil {
132 return nil // Non-fatal
133 }
134 for _, e := range entries {
135 if filepath.Ext(e.Name()) == ".md" {
136 fmt.Printf(" - %s\n", e.Name())
137 }
138 }
139
140 return nil
141}