feat(cli): add gen-docs command for auto-generated man pages
- commit
- 4ddee70026fb862e2d81f55ed1a908b8562cb69b
- author
- bnema · · 2025-12-27 19:31 UTC
- parent
- 02bf2da
- signature
- GPG Unverified
2B82207D370127A0The key is unknown
7 files · +190 -3
M Makefile +6 -1
| @@ -1,6 +1,6 @@ | |||
| 1 | 1 | # Makefile for dumber (Clean Architecture - puregotk) | |
| 2 | 2 | ||
| 3 | - | .PHONY: build build-frontend test lint clean install-tools dev generate help init check-docs | |
| 3 | + | .PHONY: build build-frontend test lint clean install-tools dev generate help init check-docs man | |
| 4 | 4 | ||
| 5 | 5 | # Load local overrides from .env.local if present (Makefile syntax) | |
| 6 | 6 | ifneq (,$(wildcard .env.local)) | |
| @@ -137,6 +137,11 @@ check: ## Check that all tools and dependencies are working | |||
| 137 | 137 | @$(MAKE) test | |
| 138 | 138 | @echo "\nAll checks passed!" | |
| 139 | 139 | ||
| 140 | + | # Documentation | |
| 141 | + | man: build-quick ## Install man pages to ~/.local/share/man/man1/ | |
| 142 | + | @echo "Installing man pages..." | |
| 143 | + | $(DIST_DIR)/$(BINARY_NAME) gen-docs | |
| 144 | + | ||
| 140 | 145 | # Native release targets | |
| 141 | 146 | .PHONY: release-snapshot release | |
| 142 | 147 | ||
| @@ -1,6 +1,6 @@ | |||||
| 1 | # Makefile for dumber (Clean Architecture - puregotk) | 1 | # Makefile for dumber (Clean Architecture - puregotk) | ||
| 2 | 2 | ||||
| 3 | - | .PHONY: build build-frontend test lint clean install-tools dev generate help init check-docs | 3 | + | .PHONY: build build-frontend test lint clean install-tools dev generate help init check-docs man |
| 4 | 4 | ||||
| 5 | # Load local overrides from .env.local if present (Makefile syntax) | 5 | # Load local overrides from .env.local if present (Makefile syntax) | ||
| 6 | ifneq (,$(wildcard .env.local)) | 6 | ifneq (,$(wildcard .env.local)) | ||
| @@ -137,6 +137,11 @@ check: ## Check that all tools and dependencies are working | |||||
| 137 | @$(MAKE) test | 137 | @$(MAKE) test | ||
| 138 | @echo "\nAll checks passed!" | 138 | @echo "\nAll checks passed!" | ||
| 139 | 139 | ||||
| 140 | + | # Documentation | |||
| 141 | + | man: build-quick ## Install man pages to ~/.local/share/man/man1/ | |||
| 142 | + | @echo "Installing man pages..." | |||
| 143 | + | $(DIST_DIR)/$(BINARY_NAME) gen-docs | |||
| 144 | + | ||||
| 140 | # Native release targets | 145 | # Native release targets | ||
| 141 | .PHONY: release-snapshot release | 146 | .PHONY: release-snapshot release | ||
| 142 | 147 | ||||
M go.mod +2 -0
| @@ -30,6 +30,7 @@ require ( | |||
| 30 | 30 | github.com/charmbracelet/x/ansi v0.10.1 // indirect | |
| 31 | 31 | github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect | |
| 32 | 32 | github.com/charmbracelet/x/term v0.2.1 // indirect | |
| 33 | + | github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect | |
| 33 | 34 | github.com/davecgh/go-spew v1.1.1 // indirect | |
| 34 | 35 | github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect | |
| 35 | 36 | github.com/go-viper/mapstructure/v2 v2.4.0 // indirect | |
| @@ -47,6 +48,7 @@ require ( | |||
| 47 | 48 | github.com/ncruces/julianday v1.0.0 // indirect | |
| 48 | 49 | github.com/pmezard/go-difflib v1.0.0 // indirect | |
| 49 | 50 | github.com/rivo/uniseg v0.4.7 // indirect | |
| 51 | + | github.com/russross/blackfriday/v2 v2.1.0 // indirect | |
| 50 | 52 | github.com/sagikazarmark/locafero v0.11.0 // indirect | |
| 51 | 53 | github.com/sahilm/fuzzy v0.1.1 // indirect | |
| 52 | 54 | github.com/sethvargo/go-retry v0.3.0 // indirect | |
| @@ -30,6 +30,7 @@ require ( | |||||
| 30 | github.com/charmbracelet/x/ansi v0.10.1 // indirect | 30 | github.com/charmbracelet/x/ansi v0.10.1 // indirect | ||
| 31 | github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect | 31 | github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect | ||
| 32 | github.com/charmbracelet/x/term v0.2.1 // indirect | 32 | github.com/charmbracelet/x/term v0.2.1 // indirect | ||
| 33 | + | github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect | |||
| 33 | github.com/davecgh/go-spew v1.1.1 // indirect | 34 | github.com/davecgh/go-spew v1.1.1 // indirect | ||
| 34 | github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect | 35 | github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect | ||
| 35 | github.com/go-viper/mapstructure/v2 v2.4.0 // indirect | 36 | github.com/go-viper/mapstructure/v2 v2.4.0 // indirect | ||
| @@ -47,6 +48,7 @@ require ( | |||||
| 47 | github.com/ncruces/julianday v1.0.0 // indirect | 48 | github.com/ncruces/julianday v1.0.0 // indirect | ||
| 48 | github.com/pmezard/go-difflib v1.0.0 // indirect | 49 | github.com/pmezard/go-difflib v1.0.0 // indirect | ||
| 49 | github.com/rivo/uniseg v0.4.7 // indirect | 50 | github.com/rivo/uniseg v0.4.7 // indirect | ||
| 51 | + | github.com/russross/blackfriday/v2 v2.1.0 // indirect | |||
| 50 | github.com/sagikazarmark/locafero v0.11.0 // indirect | 52 | github.com/sagikazarmark/locafero v0.11.0 // indirect | ||
| 51 | github.com/sahilm/fuzzy v0.1.1 // indirect | 53 | github.com/sahilm/fuzzy v0.1.1 // indirect | ||
| 52 | github.com/sethvargo/go-retry v0.3.0 // indirect | 54 | github.com/sethvargo/go-retry v0.3.0 // indirect | ||
M go.sum +2 -0 generated file: collapsed
M internal/application/port/xdg.go +4 -0
| @@ -10,4 +10,8 @@ type XDGPaths interface { | |||
| 10 | 10 | FilterJSONDir() (string, error) | |
| 11 | 11 | FilterStoreDir() (string, error) | |
| 12 | 12 | FilterCacheDir() (string, error) | |
| 13 | + | ||
| 14 | + | // ManDir returns the user man page directory (man1 section). | |
| 15 | + | // Typically $XDG_DATA_HOME/man/man1 or ~/.local/share/man/man1. | |
| 16 | + | ManDir() (string, error) | |
| 13 | 17 | } | |
| @@ -10,4 +10,8 @@ type XDGPaths interface { | |||||
| 10 | FilterJSONDir() (string, error) | 10 | FilterJSONDir() (string, error) | ||
| 11 | FilterStoreDir() (string, error) | 11 | FilterStoreDir() (string, error) | ||
| 12 | FilterCacheDir() (string, error) | 12 | FilterCacheDir() (string, error) | ||
| 13 | + | ||||
| 14 | + | // ManDir returns the user man page directory (man1 section). | |||
| 15 | + | // Typically $XDG_DATA_HOME/man/man1 or ~/.local/share/man/man1. | |||
| 16 | + | ManDir() (string, error) | |||
| 13 | } | 17 | } | ||
A internal/cli/cmd/gen_docs.go +141 -0
| @@ -0,0 +1,141 @@ | |||
| 1 | + | package cmd | |
| 2 | + | ||
| 3 | + | import ( | |
| 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 | + | ||
| 15 | + | const dirPerm = 0o755 | |
| 16 | + | ||
| 17 | + | var ( | |
| 18 | + | genDocsOutputDir string | |
| 19 | + | genDocsFormat string | |
| 20 | + | ) | |
| 21 | + | ||
| 22 | + | var 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 | + | ||
| 27 | + | The 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 | + | ||
| 33 | + | Supported formats: | |
| 34 | + | man Unix manual pages (groff format) | |
| 35 | + | markdown Markdown files (for websites/wikis) | |
| 36 | + | ||
| 37 | + | By default, man pages are installed to ~/.local/share/man/man1/ so they | |
| 38 | + | are immediately available via 'man dumber'. You may need to run 'mandb' | |
| 39 | + | to update the man page index. | |
| 40 | + | ||
| 41 | + | Examples: | |
| 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 | + | ||
| 48 | + | func 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 | + | ||
| 54 | + | func 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() | |
| 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 | + | ||
| 86 | + | func 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 | + | ||
| 119 | + | func 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 | + | } | |
| @@ -0,0 +1,141 @@ | |||||
| 1 | + | package cmd | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 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 | + | ||||
| 15 | + | const dirPerm = 0o755 | |||
| 16 | + | ||||
| 17 | + | var ( | |||
| 18 | + | genDocsOutputDir string | |||
| 19 | + | genDocsFormat string | |||
| 20 | + | ) | |||
| 21 | + | ||||
| 22 | + | var 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 | + | ||||
| 27 | + | The 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 | + | ||||
| 33 | + | Supported formats: | |||
| 34 | + | man Unix manual pages (groff format) | |||
| 35 | + | markdown Markdown files (for websites/wikis) | |||
| 36 | + | ||||
| 37 | + | By default, man pages are installed to ~/.local/share/man/man1/ so they | |||
| 38 | + | are immediately available via 'man dumber'. You may need to run 'mandb' | |||
| 39 | + | to update the man page index. | |||
| 40 | + | ||||
| 41 | + | Examples: | |||
| 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 | + | ||||
| 48 | + | func 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 | + | ||||
| 54 | + | func 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() | |||
| 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 | + | ||||
| 86 | + | func 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 | + | ||||
| 119 | + | func 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 | + | } | |||
M internal/cli/cmd/root.go +18 -2
| @@ -16,8 +16,24 @@ var ( | |||
| 16 | 16 | buildInfo build.Info | |
| 17 | 17 | rootCmd = &cobra.Command{ | |
| 18 | 18 | Use: "dumber", | |
| 19 | - | Short: "A minimal web browser", | |
| 20 | - | Long: `Dumber is a minimal, keyboard-driven web browser built with GTK4 and WebKitGTK.`, | |
| 19 | + | Short: "A fully unfeatured unbloated browser for tiling WMs", | |
| 20 | + | Long: `Dumber - a dumb browser that works like your favorite terminal multiplexer. | |
| 21 | + | ||
| 22 | + | A fully unfeatured unbloated browser for tiling WMs, built with GTK4 and WebKitGTK. | |
| 23 | + | ||
| 24 | + | Features: | |
| 25 | + | - Wayland native (Sway, Hyprland, River, Niri, etc.) | |
| 26 | + | - Tabs and workspaces with split or stacked panes | |
| 27 | + | - Keyboard-driven workflow inspired by Zellij | |
| 28 | + | - GPU rendering with automatic VA-API/VDPAU detection | |
| 29 | + | - Built-in ad blocking (UBlock-based network + cosmetic filtering) | |
| 30 | + | - Launcher integration (rofi/fuzzel) with favicons | |
| 31 | + | - Search shortcuts via bangs (!g, !gi, etc.) | |
| 32 | + | - Session management with auto-save and restore | |
| 33 | + | ||
| 34 | + | Use 'dumber browse' to launch the graphical browser, or explore the | |
| 35 | + | subcommands for CLI-based operations like history search and session | |
| 36 | + | management.`, | |
| 21 | 37 | PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { | |
| 22 | 38 | // Skip initialization for help commands | |
| 23 | 39 | if cmd.Name() == "help" || cmd.Name() == "completion" { | |
| @@ -16,8 +16,24 @@ var ( | |||||
| 16 | buildInfo build.Info | 16 | buildInfo build.Info | ||
| 17 | rootCmd = &cobra.Command{ | 17 | rootCmd = &cobra.Command{ | ||
| 18 | Use: "dumber", | 18 | Use: "dumber", | ||
| 19 | - | Short: "A minimal web browser", | 19 | + | Short: "A fully unfeatured unbloated browser for tiling WMs", |
| 20 | - | Long: `Dumber is a minimal, keyboard-driven web browser built with GTK4 and WebKitGTK.`, | 20 | + | Long: `Dumber - a dumb browser that works like your favorite terminal multiplexer. |
| 21 | + | ||||
| 22 | + | A fully unfeatured unbloated browser for tiling WMs, built with GTK4 and WebKitGTK. | |||
| 23 | + | ||||
| 24 | + | Features: | |||
| 25 | + | - Wayland native (Sway, Hyprland, River, Niri, etc.) | |||
| 26 | + | - Tabs and workspaces with split or stacked panes | |||
| 27 | + | - Keyboard-driven workflow inspired by Zellij | |||
| 28 | + | - GPU rendering with automatic VA-API/VDPAU detection | |||
| 29 | + | - Built-in ad blocking (UBlock-based network + cosmetic filtering) | |||
| 30 | + | - Launcher integration (rofi/fuzzel) with favicons | |||
| 31 | + | - Search shortcuts via bangs (!g, !gi, etc.) | |||
| 32 | + | - Session management with auto-save and restore | |||
| 33 | + | ||||
| 34 | + | Use 'dumber browse' to launch the graphical browser, or explore the | |||
| 35 | + | subcommands for CLI-based operations like history search and session | |||
| 36 | + | management.`, | |||
| 21 | PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { | 37 | PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { | ||
| 22 | // Skip initialization for help commands | 38 | // Skip initialization for help commands | ||
| 23 | if cmd.Name() == "help" || cmd.Name() == "completion" { | 39 | if cmd.Name() == "help" || cmd.Name() == "completion" { | ||
M internal/infrastructure/xdg/adapter.go +17 -0
| @@ -1,6 +1,7 @@ | |||
| 1 | 1 | package xdg | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "os" | |
| 4 | 5 | "path/filepath" | |
| 5 | 6 | ||
| 6 | 7 | "github.com/bnema/dumber/internal/application/port" | |
| @@ -55,4 +56,20 @@ func (a *Adapter) FilterCacheDir() (string, error) { | |||
| 55 | 56 | return config.GetFilterCacheDir() | |
| 56 | 57 | } | |
| 57 | 58 | ||
| 59 | + | func (a *Adapter) ManDir() (string, error) { | |
| 60 | + | // Man pages go to the user's XDG_DATA_HOME/man/man1, not the dumber-specific dir. | |
| 61 | + | // This allows 'man dumber' to work without custom MANPATH configuration. | |
| 62 | + | home, err := os.UserHomeDir() | |
| 63 | + | if err != nil { | |
| 64 | + | return "", err | |
| 65 | + | } | |
| 66 | + | ||
| 67 | + | dataHome := os.Getenv("XDG_DATA_HOME") | |
| 68 | + | if dataHome == "" { | |
| 69 | + | dataHome = filepath.Join(home, ".local", "share") | |
| 70 | + | } | |
| 71 | + | ||
| 72 | + | return filepath.Join(dataHome, "man", "man1"), nil | |
| 73 | + | } | |
| 74 | + | ||
| 58 | 75 | var _ port.XDGPaths = (*Adapter)(nil) | |
| @@ -1,6 +1,7 @@ | |||||
| 1 | package xdg | 1 | package xdg | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "os" | |||
| 4 | "path/filepath" | 5 | "path/filepath" | ||
| 5 | 6 | ||||
| 6 | "github.com/bnema/dumber/internal/application/port" | 7 | "github.com/bnema/dumber/internal/application/port" | ||
| @@ -55,4 +56,20 @@ func (a *Adapter) FilterCacheDir() (string, error) { | |||||
| 55 | return config.GetFilterCacheDir() | 56 | return config.GetFilterCacheDir() | ||
| 56 | } | 57 | } | ||
| 57 | 58 | ||||
| 59 | + | func (a *Adapter) ManDir() (string, error) { | |||
| 60 | + | // Man pages go to the user's XDG_DATA_HOME/man/man1, not the dumber-specific dir. | |||
| 61 | + | // This allows 'man dumber' to work without custom MANPATH configuration. | |||
| 62 | + | home, err := os.UserHomeDir() | |||
| 63 | + | if err != nil { | |||
| 64 | + | return "", err | |||
| 65 | + | } | |||
| 66 | + | ||||
| 67 | + | dataHome := os.Getenv("XDG_DATA_HOME") | |||
| 68 | + | if dataHome == "" { | |||
| 69 | + | dataHome = filepath.Join(home, ".local", "share") | |||
| 70 | + | } | |||
| 71 | + | ||||
| 72 | + | return filepath.Join(dataHome, "man", "man1"), nil | |||
| 73 | + | } | |||
| 74 | + | ||||
| 58 | var _ port.XDGPaths = (*Adapter)(nil) | 75 | var _ port.XDGPaths = (*Adapter)(nil) | ||