From 2bbbf4e81e8ecb0b24646b2d7816b79a60f53d52 Mon Sep 17 00:00:00 2001 From: Bartal Laearsson Date: Thu, 27 Aug 2026 14:20:33 +0100 Subject: [PATCH] add only frontend --- README.md | 34 +++++++++++++++++++++++++--------- cmd/root.go | 11 ++++++++++- internal/config/config.go | 11 +++++++++-- internal/filter/filter.go | 38 +++++++++++++++++++++++++++----------- internal/walker/walker.go | 18 ++++++++++-------- 5 files changed, 81 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 2102631..be10709 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Generate markdown documentation from directory structure. Optimized for LLM inge - Configurable depth, file size limits, ignore patterns - Optional upload to Proton Drive after generating output - Skip frontend files (`.html`, `.css`, `.js`, etc.) +- Frontend only mode (include only frontend files and `README.md`) - Omit test files and inline test blocks (Go, Java, Rust) ## Build @@ -40,6 +41,9 @@ Generate markdown documentation from directory structure. Optimized for LLM inge # Skip frontend files (.html, .css, .js, etc.) dirmd -o output.md ~/repos/myproject --skip-frontend + # Frontend only (include only frontend files and README.md) + dirmd -o output.md ~/repos/myproject --frontend-only + # Omit test files and inline test blocks # Go: excludes *_test.go # Java: excludes *Test.java, *Tests.java, src/test/java/* @@ -47,7 +51,7 @@ Generate markdown documentation from directory structure. Optimized for LLM inge dirmd -o output.md ~/repos/myproject --omit-test # Combine filters - dirmd -o output.md ~/repos/myproject --skip-frontend --omit-test + dirmd -o output.md ~/repos/myproject --frontend-only --omit-test # Generate and upload to Proton Drive (default: /my-files/md/) dirmd -o output.md ~/repos/myproject --proton-drive @@ -67,18 +71,26 @@ The `--omit-test` flag removes test files and inline test blocks to reduce token The Rust stripping works by detecting the `#[cfg(test)]` attribute, then tracking brace depth until the module closes. Nested braces in string literals may occasionally throw off the count, but this is acceptable for RAG use cases. -Frontend files can be excluded separately with `--skip-frontend`: +## Frontend Filtering -| Extension Type | Excluded With `--skip-frontend` | -|---------------|-------------------------------| +Two complementary flags control frontend file inclusion: + +| Flag | Effect | +|------|--------| +| `--skip-frontend` | Exclude frontend files, keep everything else | +| `--frontend-only` | Include only frontend files and `README.md`, exclude everything else | + +Frontend extensions covered: + +| Type | Extensions | +|------|------------| | Templates | `.html`, `.htm`, `.gohtml`, `.tmpl` | | Stylesheets | `.css`, `.scss`, `.sass`, `.less` | -| Scripts | `.js`, `.jsx`, `.mjs`, `.cjs`, `.ts`, `.tsx` | +| JavaScript | `.js`, `.jsx`, `.mjs`, `.cjs` | +| TypeScript | `.ts`, `.tsx` | | Frameworks | `.vue`, `.svelte` | -These flags can be combined freely: - - dirmd -o docs.md ~/repos/myproject --skip-frontend --omit-test +`--skip-frontend` and `--frontend-only` are mutually exclusive. Both can be combined with `--omit-test`. ## Vertical Slices Mode @@ -123,13 +135,14 @@ Output is designed for RAG ingestion and LLM context windows, not human presenta | `--proton-drive` | Upload output to Proton Drive after writing locally | false | | `--drive-path` | Full remote path on Proton Drive (e.g. /my-files/md/report.md) | /my-files/md/ | | `--skip-frontend` | Omit frontend file types (.html, .css, .js, .ts, .vue, .svelte, etc.) | false | +| `--frontend-only` | Include only frontend file types and README.md | false | | `--omit-test` | Exclude test files (Go *_test.go, Java *Test.java) and strip #[cfg(test)] blocks from Rust | false | ## Proton Drive Integration dirmd can optionally upload the generated markdown file(s) to Proton Drive using the [Proton Drive CLI](https://proton.me/support/drive-cli). The upload is performed only when `--proton-drive` is passed. Without it, dirmd behaves exactly as before — no Proton Drive dependency is required. -In vertical slices mode, all generated files (root + slices) are uploaded to `/my-files/md/`. The `--drive-path` flag is ignored in slices mode; each file is uploaded by its basename. +In vertical slices mode, all generated files (root + slices) are uploaded to `/my-files/md/`. The `--drive-path` flag is ignored in slices mode ; each file is uploaded by its basename. ### Prerequisites @@ -158,6 +171,9 @@ For full documentation on the Proton Drive CLI, including installation, authenti # Skip frontend and omit tests dirmd -o docs.md ~/repos/project -f --skip-frontend --omit-test + # Frontend only with test omission + dirmd -o docs.md ~/repos/project -f --frontend-only --omit-test + # Generate, then upload to default remote location dirmd -o docs.md ~/repos/project --proton-drive diff --git a/cmd/root.go b/cmd/root.go index b121696..806cf87 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,4 +1,3 @@ -// cmd/root.go package cmd import ( @@ -42,6 +41,9 @@ Examples: # Skip frontend files dirmd -o ./docs ~/repos/myproject --skip-frontend + # Frontend only + dirmd -o ./docs ~/repos/myproject --frontend-only + # Omit test files and test blocks dirmd -o ./docs ~/repos/myproject --omit-test @@ -65,6 +67,7 @@ Examples: drivePath string verticalSlices bool skipFrontend bool + frontendOnly bool omitTest bool ) @@ -87,6 +90,7 @@ func init() { rootCmd.Flags().StringVar(&drivePath, "drive-path", "", "full remote directory on Proton Drive (e.g. /my-files/docs/api). Requires --proton-drive") rootCmd.Flags().BoolVar(&verticalSlices, "vertical-slices", false, "split output into root index.md + one markdown per top-level directory") rootCmd.Flags().BoolVar(&skipFrontend, "skip-frontend", false, "omit frontend file types (.html, .css, .js, .ts, .vue, .svelte, etc.)") + rootCmd.Flags().BoolVar(&frontendOnly, "frontend-only", false, "include only frontend file types and README.md") rootCmd.Flags().BoolVar(&omitTest, "omit-test", false, "exclude test files (Go *_test.go, Java *Test.java) and strip #[cfg(test)] blocks from Rust") rootCmd.MarkPersistentFlagRequired("output") @@ -105,6 +109,10 @@ func run(cmd *cobra.Command, args []string) error { return fmt.Errorf("--vertical-slices cannot be used with --input-file") } + if skipFrontend && frontendOnly { + return fmt.Errorf("--skip-frontend and --frontend-only are mutually exclusive") + } + excludedExts := append(config.DefaultExts, extensions...) if skipFrontend { excludedExts = append(excludedExts, config.FrontendExts...) @@ -122,6 +130,7 @@ func run(cmd *cobra.Command, args []string) error { DrivePath: drivePath, VerticalSlices: verticalSlices, SkipFrontend: skipFrontend, + FrontendOnly: frontendOnly, OmitTest: omitTest, } diff --git a/internal/config/config.go b/internal/config/config.go index 95cd3d6..c16a096 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,4 +1,3 @@ -// internal/config/config.go package config import ( @@ -80,6 +79,7 @@ type Config struct { DrivePath string VerticalSlices bool SkipFrontend bool + FrontendOnly bool OmitTest bool } @@ -106,6 +106,7 @@ func Parse() (*Config, error) { drivePath string verticalSlices bool skipFrontend bool + frontendOnly bool omitTest bool ) @@ -119,9 +120,10 @@ func Parse() (*Config, error) { flag.Var(&exts, "extensions", "additional file extensions to skip (repeatable)") flag.BoolVar(&showVer, "version", false, "print version and exit") flag.BoolVar(&protonDrive, "proton-drive", false, "upload output to Proton Drive after writing locally") - flag.StringVar(&drivePath, "drive-path", "", "full remote path on Proton Drive (requires --proton-drive)") + flag.StringVar(&drivePath, "drive-path", "", "full remote path on Proton Drive (requires --proton-drive") flag.BoolVar(&verticalSlices, "vertical-slices", false, "split output into root file + one markdown per top-level directory") flag.BoolVar(&skipFrontend, "skip-frontend", false, "omit frontend file types (.html, .css, .js, .ts, .vue, .svelte, etc.)") + flag.BoolVar(&frontendOnly, "frontend-only", false, "include only frontend file types and README.md") flag.BoolVar(&omitTest, "omit-test", false, "exclude test files (Go *_test.go, Java *Test.java) and strip #[cfg(test)] blocks from Rust") flag.Usage = PrintUsage @@ -148,6 +150,10 @@ func Parse() (*Config, error) { return nil, fmt.Errorf("--vertical-slices cannot be used with --input-file") } + if skipFrontend && frontendOnly { + return nil, fmt.Errorf("--skip-frontend and --frontend-only are mutually exclusive") + } + excludedExts := append(DefaultExts, exts...) if skipFrontend { excludedExts = append(excludedExts, FrontendExts...) @@ -165,6 +171,7 @@ func Parse() (*Config, error) { DrivePath: drivePath, VerticalSlices: verticalSlices, SkipFrontend: skipFrontend, + FrontendOnly: frontendOnly, OmitTest: omitTest, } diff --git a/internal/filter/filter.go b/internal/filter/filter.go index 1321e04..ec083e2 100644 --- a/internal/filter/filter.go +++ b/internal/filter/filter.go @@ -1,17 +1,19 @@ -// internal/filter/filter.go package filter import ( "os" "path/filepath" "strings" + + "dirmd/internal/config" ) type Filter struct { - MaxSize int64 - Extensions []string - Ignores []string - OmitTest bool + MaxSize int64 + Extensions []string + Ignores []string + OmitTest bool + FrontendOnly bool } func (f *Filter) ShouldSkipDir(name string) bool { @@ -39,14 +41,11 @@ func (f *Filter) ShouldSkipFile(path, name string, info os.FileInfo) bool { } } - ext := filepath.Ext(path) - for _, bad := range f.Extensions { - if strings.EqualFold(ext, bad) { - return true - } + if info.Size() > f.MaxSize { + return true } - if info.Size() > f.MaxSize { + if f.FrontendOnly && !isFrontendOrReadme(path, name) { return true } @@ -54,9 +53,26 @@ func (f *Filter) ShouldSkipFile(path, name string, info os.FileInfo) bool { return true } + ext := filepath.Ext(path) + for _, bad := range f.Extensions { + if strings.EqualFold(ext, bad) { + return true + } + } + return false } +func isFrontendOrReadme(path, name string) bool { + ext := strings.ToLower(filepath.Ext(path)) + for _, fe := range config.FrontendExts { + if strings.EqualFold(ext, fe) { + return true + } + } + return strings.EqualFold(name, "README.md") +} + func isTestFile(path, name string) bool { ext := strings.ToLower(filepath.Ext(path)) diff --git a/internal/walker/walker.go b/internal/walker/walker.go index eedf70e..1e7aaa2 100644 --- a/internal/walker/walker.go +++ b/internal/walker/walker.go @@ -42,10 +42,11 @@ func processSingleFile(cfg *config.Config) (*Result, error) { } flt := &filter.Filter{ - MaxSize: cfg.MaxSize, - Extensions: cfg.Extensions, - Ignores: cfg.Ignores, - OmitTest: cfg.OmitTest, + MaxSize: cfg.MaxSize, + Extensions: cfg.Extensions, + Ignores: cfg.Ignores, + OmitTest: cfg.OmitTest, + FrontendOnly: cfg.FrontendOnly, } if flt.ShouldSkipFile(cfg.AbsRoot, filepath.Base(cfg.AbsRoot), info) { @@ -62,10 +63,11 @@ func processSingleFile(cfg *config.Config) (*Result, error) { func walkDirectory(cfg *config.Config) (*Result, error) { flt := &filter.Filter{ - MaxSize: cfg.MaxSize, - Extensions: cfg.Extensions, - Ignores: cfg.Ignores, - OmitTest: cfg.OmitTest, + MaxSize: cfg.MaxSize, + Extensions: cfg.Extensions, + Ignores: cfg.Ignores, + OmitTest: cfg.OmitTest, + FrontendOnly: cfg.FrontendOnly, } result := &Result{AbsRoot: cfg.AbsRoot, OmitTest: cfg.OmitTest}