add omit test files and only frontend
This commit is contained in:
@@ -13,8 +13,8 @@ Generate markdown documentation from directory structure. Optimized for LLM inge
|
|||||||
- Force overwrite of existing output files
|
- Force overwrite of existing output files
|
||||||
- Configurable depth, file size limits, ignore patterns
|
- Configurable depth, file size limits, ignore patterns
|
||||||
- Optional upload to Proton Drive after generating output
|
- Optional upload to Proton Drive after generating output
|
||||||
|
- Skip frontend files (`.html`, `.css`, `.js`, etc.)
|
||||||

|
- Omit test files and inline test blocks (Go, Java, Rust)
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
@@ -37,12 +37,49 @@ Generate markdown documentation from directory structure. Optimized for LLM inge
|
|||||||
# Generate with vertical slices
|
# Generate with vertical slices
|
||||||
dirmd -o output.md ~/repos/myproject --vertical-slices
|
dirmd -o output.md ~/repos/myproject --vertical-slices
|
||||||
|
|
||||||
|
# Skip frontend files (.html, .css, .js, etc.)
|
||||||
|
dirmd -o output.md ~/repos/myproject --skip-frontend
|
||||||
|
|
||||||
|
# Omit test files and inline test blocks
|
||||||
|
# Go: excludes *_test.go
|
||||||
|
# Java: excludes *Test.java, *Tests.java, src/test/java/*
|
||||||
|
# Rust: strips #[cfg(test)] mod tests { ... } blocks
|
||||||
|
dirmd -o output.md ~/repos/myproject --omit-test
|
||||||
|
|
||||||
|
# Combine filters
|
||||||
|
dirmd -o output.md ~/repos/myproject --skip-frontend --omit-test
|
||||||
|
|
||||||
# Generate and upload to Proton Drive (default: /my-files/md/<filename>)
|
# Generate and upload to Proton Drive (default: /my-files/md/<filename>)
|
||||||
dirmd -o output.md ~/repos/myproject --proton-drive
|
dirmd -o output.md ~/repos/myproject --proton-drive
|
||||||
|
|
||||||
# Generate and upload to a specific remote path
|
# Generate and upload to a specific remote path
|
||||||
dirmd -o output.md ~/repos/myproject --proton-drive --drive-path /my-files/docs/api.md
|
dirmd -o output.md ~/repos/myproject --proton-drive --drive-path /my-files/docs/api.md
|
||||||
|
|
||||||
|
## Test File Omission
|
||||||
|
|
||||||
|
The `--omit-test` flag removes test files and inline test blocks to reduce token overhead in LLM context windows. Behavior varies by language:
|
||||||
|
|
||||||
|
| Language | Exclusion Strategy | What Gets Removed |
|
||||||
|
|----------|-------------------|-------------------|
|
||||||
|
| **Go** | File exclusion | All `*_test.go` files |
|
||||||
|
| **Java** | File exclusion | `*Test.java`, `*Tests.java`, files under `src/test/java/` |
|
||||||
|
| **Rust** | Content stripping | Entire `#[cfg(test)] mod tests { ... }` blocks removed from `.rs` files |
|
||||||
|
|
||||||
|
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`:
|
||||||
|
|
||||||
|
| Extension Type | Excluded With `--skip-frontend` |
|
||||||
|
|---------------|-------------------------------|
|
||||||
|
| Templates | `.html`, `.htm`, `.gohtml`, `.tmpl` |
|
||||||
|
| Stylesheets | `.css`, `.scss`, `.sass`, `.less` |
|
||||||
|
| Scripts | `.js`, `.jsx`, `.mjs`, `.cjs`, `.ts`, `.tsx` |
|
||||||
|
| Frameworks | `.vue`, `.svelte` |
|
||||||
|
|
||||||
|
These flags can be combined freely:
|
||||||
|
|
||||||
|
dirmd -o docs.md ~/repos/myproject --skip-frontend --omit-test
|
||||||
|
|
||||||
## Vertical Slices Mode
|
## Vertical Slices Mode
|
||||||
|
|
||||||
When `--vertical-slices` is passed, dirmd splits output into multiple files instead of one monolithic document:
|
When `--vertical-slices` is passed, dirmd splits output into multiple files instead of one monolithic document:
|
||||||
@@ -84,7 +121,9 @@ Output is designed for RAG ingestion and LLM context windows, not human presenta
|
|||||||
| `--ignore` | Additional ignore patterns (glob) | |
|
| `--ignore` | Additional ignore patterns (glob) | |
|
||||||
| `--extensions` | Additional file extensions to skip | |
|
| `--extensions` | Additional file extensions to skip | |
|
||||||
| `--proton-drive` | Upload output to Proton Drive after writing locally | false |
|
| `--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/<filename> |
|
| `--drive-path` | Full remote path on Proton Drive (e.g. /my-files/md/report.md) | /my-files/md/<filename> |
|
||||||
|
| `--skip-frontend` | Omit frontend file types (.html, .css, .js, .ts, .vue, .svelte, etc.) | false |
|
||||||
|
| `--omit-test` | Exclude test files (Go *_test.go, Java *Test.java) and strip #[cfg(test)] blocks from Rust | false |
|
||||||
|
|
||||||
## Proton Drive Integration
|
## Proton Drive Integration
|
||||||
|
|
||||||
@@ -96,7 +135,7 @@ In vertical slices mode, all generated files (root + slices) are uploaded to `/m
|
|||||||
|
|
||||||
1. Install the Proton Drive CLI — see the [official guide](https://proton.me/support/drive-cli) for download and installation instructions.
|
1. Install the Proton Drive CLI — see the [official guide](https://proton.me/support/drive-cli) for download and installation instructions.
|
||||||
2. Authenticate by running `proton-drive auth login`.
|
2. Authenticate by running `proton-drive auth login`.
|
||||||
3. Ensure `proton-drive` is in your `PATH`.
|
3. Ensure `proton-drive` is in your PATH.
|
||||||
|
|
||||||
If the CLI is not installed or you are not logged in, dirmd will report the error to stderr and exit with a non-zero code. Local files are always written successfully before the upload is attempted.
|
If the CLI is not installed or you are not logged in, dirmd will report the error to stderr and exit with a non-zero code. Local files are always written successfully before the upload is attempted.
|
||||||
|
|
||||||
@@ -116,6 +155,9 @@ For full documentation on the Proton Drive CLI, including installation, authenti
|
|||||||
# Vertical slices with overwrite
|
# Vertical slices with overwrite
|
||||||
dirmd -o docs.md ~/repos/project -f --vertical-slices
|
dirmd -o docs.md ~/repos/project -f --vertical-slices
|
||||||
|
|
||||||
|
# Skip frontend and omit tests
|
||||||
|
dirmd -o docs.md ~/repos/project -f --skip-frontend --omit-test
|
||||||
|
|
||||||
# Generate, then upload to default remote location
|
# Generate, then upload to default remote location
|
||||||
dirmd -o docs.md ~/repos/project --proton-drive
|
dirmd -o docs.md ~/repos/project --proton-drive
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// cmd/root.go
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -41,6 +42,9 @@ Examples:
|
|||||||
# Skip frontend files
|
# Skip frontend files
|
||||||
dirmd -o ./docs ~/repos/myproject --skip-frontend
|
dirmd -o ./docs ~/repos/myproject --skip-frontend
|
||||||
|
|
||||||
|
# Omit test files and test blocks
|
||||||
|
dirmd -o ./docs ~/repos/myproject --omit-test
|
||||||
|
|
||||||
# Generate and upload to Proton Drive
|
# Generate and upload to Proton Drive
|
||||||
dirmd -o ./docs ~/repos/myproject --proton-drive
|
dirmd -o ./docs ~/repos/myproject --proton-drive
|
||||||
|
|
||||||
@@ -61,6 +65,7 @@ Examples:
|
|||||||
drivePath string
|
drivePath string
|
||||||
verticalSlices bool
|
verticalSlices bool
|
||||||
skipFrontend bool
|
skipFrontend bool
|
||||||
|
omitTest bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func Execute() {
|
func Execute() {
|
||||||
@@ -82,6 +87,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().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(&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(&skipFrontend, "skip-frontend", false, "omit frontend file types (.html, .css, .js, .ts, .vue, .svelte, etc.)")
|
||||||
|
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")
|
rootCmd.MarkPersistentFlagRequired("output")
|
||||||
}
|
}
|
||||||
@@ -116,6 +122,7 @@ func run(cmd *cobra.Command, args []string) error {
|
|||||||
DrivePath: drivePath,
|
DrivePath: drivePath,
|
||||||
VerticalSlices: verticalSlices,
|
VerticalSlices: verticalSlices,
|
||||||
SkipFrontend: skipFrontend,
|
SkipFrontend: skipFrontend,
|
||||||
|
OmitTest: omitTest,
|
||||||
}
|
}
|
||||||
|
|
||||||
if singleFile != "" {
|
if singleFile != "" {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// internal/config/config.go
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -79,6 +80,7 @@ type Config struct {
|
|||||||
DrivePath string
|
DrivePath string
|
||||||
VerticalSlices bool
|
VerticalSlices bool
|
||||||
SkipFrontend bool
|
SkipFrontend bool
|
||||||
|
OmitTest bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func PrintUsage() {
|
func PrintUsage() {
|
||||||
@@ -104,6 +106,7 @@ func Parse() (*Config, error) {
|
|||||||
drivePath string
|
drivePath string
|
||||||
verticalSlices bool
|
verticalSlices bool
|
||||||
skipFrontend bool
|
skipFrontend bool
|
||||||
|
omitTest bool
|
||||||
)
|
)
|
||||||
|
|
||||||
flag.StringVar(&input, "i", "", "single file input (append mode only)")
|
flag.StringVar(&input, "i", "", "single file input (append mode only)")
|
||||||
@@ -119,6 +122,7 @@ func Parse() (*Config, error) {
|
|||||||
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(&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(&skipFrontend, "skip-frontend", false, "omit frontend file types (.html, .css, .js, .ts, .vue, .svelte, etc.)")
|
||||||
|
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
|
flag.Usage = PrintUsage
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
@@ -161,6 +165,7 @@ func Parse() (*Config, error) {
|
|||||||
DrivePath: drivePath,
|
DrivePath: drivePath,
|
||||||
VerticalSlices: verticalSlices,
|
VerticalSlices: verticalSlices,
|
||||||
SkipFrontend: skipFrontend,
|
SkipFrontend: skipFrontend,
|
||||||
|
OmitTest: omitTest,
|
||||||
}
|
}
|
||||||
|
|
||||||
if input != "" {
|
if input != "" {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// internal/filter/filter.go
|
||||||
package filter
|
package filter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -10,6 +11,7 @@ type Filter struct {
|
|||||||
MaxSize int64
|
MaxSize int64
|
||||||
Extensions []string
|
Extensions []string
|
||||||
Ignores []string
|
Ignores []string
|
||||||
|
OmitTest bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Filter) ShouldSkipDir(name string) bool {
|
func (f *Filter) ShouldSkipDir(name string) bool {
|
||||||
@@ -48,6 +50,28 @@ func (f *Filter) ShouldSkipFile(path, name string, info os.FileInfo) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if f.OmitTest && isTestFile(path, name) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isTestFile(path, name string) bool {
|
||||||
|
ext := strings.ToLower(filepath.Ext(path))
|
||||||
|
|
||||||
|
switch ext {
|
||||||
|
case ".go":
|
||||||
|
return strings.HasSuffix(strings.ToLower(name), "_test.go")
|
||||||
|
case ".java":
|
||||||
|
base := strings.TrimSuffix(name, filepath.Ext(name))
|
||||||
|
if strings.HasSuffix(base, "Test") || strings.HasSuffix(base, "Tests") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
slashPath := filepath.ToSlash(path)
|
||||||
|
return strings.Contains(slashPath, "/src/test/")
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// internal/renderer/renderer.go
|
||||||
package renderer
|
package renderer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -14,7 +15,6 @@ import (
|
|||||||
|
|
||||||
const fenceChars = "~~~"
|
const fenceChars = "~~~"
|
||||||
|
|
||||||
// Write writes a single markdown file with tree and file contents.
|
|
||||||
func Write(result *walker.Result, outputPath string, appendMode bool, singleFile bool) error {
|
func Write(result *walker.Result, outputPath string, appendMode bool, singleFile bool) error {
|
||||||
flags := os.O_CREATE | os.O_WRONLY
|
flags := os.O_CREATE | os.O_WRONLY
|
||||||
if appendMode {
|
if appendMode {
|
||||||
@@ -55,7 +55,7 @@ func Write(result *walker.Result, outputPath string, appendMode bool, singleFile
|
|||||||
for _, entry := range result.Entries {
|
for _, entry := range result.Entries {
|
||||||
fullPath := filepath.Join(result.AbsRoot, entry.RelPath)
|
fullPath := filepath.Join(result.AbsRoot, entry.RelPath)
|
||||||
|
|
||||||
content, err := readFile(fullPath)
|
content, err := readFileForOutput(fullPath, result.OmitTest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "warning: cannot read %s: %v\n", entry.RelPath, err)
|
fmt.Fprintf(os.Stderr, "warning: cannot read %s: %v\n", entry.RelPath, err)
|
||||||
continue
|
continue
|
||||||
@@ -82,8 +82,6 @@ func Write(result *walker.Result, outputPath string, appendMode bool, singleFile
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteSlices generates a root markdown file plus one markdown per top-level directory.
|
|
||||||
// Returns the list of generated file paths.
|
|
||||||
func WriteSlices(result *walker.Result, outputDir, repoName string) ([]string, error) {
|
func WriteSlices(result *walker.Result, outputDir, repoName string) ([]string, error) {
|
||||||
entries := result.Entries
|
entries := result.Entries
|
||||||
if len(entries) == 0 {
|
if len(entries) == 0 {
|
||||||
@@ -109,16 +107,14 @@ func WriteSlices(result *walker.Result, outputDir, repoName string) ([]string, e
|
|||||||
|
|
||||||
var generated []string
|
var generated []string
|
||||||
|
|
||||||
// Write root file as index_<reponame>.md
|
|
||||||
indexBasename := "index_" + repoName + ".md"
|
indexBasename := "index_" + repoName + ".md"
|
||||||
rootPath := filepath.Join(outputDir, indexBasename)
|
rootPath := filepath.Join(outputDir, indexBasename)
|
||||||
generated = append(generated, rootPath)
|
generated = append(generated, rootPath)
|
||||||
|
|
||||||
if err := writeRootSlice(rootPath, absRoot, rootName, repoName, rootFiles, topLevelDirs); err != nil {
|
if err := writeRootSlice(rootPath, absRoot, rootName, repoName, rootFiles, topLevelDirs, result.OmitTest); err != nil {
|
||||||
return nil, fmt.Errorf("failed to write root slice: %w", err)
|
return nil, fmt.Errorf("failed to write root slice: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write each top-level directory slice into a mirroring subdirectory
|
|
||||||
var dirNames []string
|
var dirNames []string
|
||||||
for k := range topLevelDirs {
|
for k := range topLevelDirs {
|
||||||
dirNames = append(dirNames, k)
|
dirNames = append(dirNames, k)
|
||||||
@@ -137,7 +133,6 @@ func WriteSlices(result *walker.Result, outputDir, repoName string) ([]string, e
|
|||||||
absDir := filepath.Join(absRoot, dirName)
|
absDir := filepath.Join(absRoot, dirName)
|
||||||
dirEntries := topLevelDirs[dirName]
|
dirEntries := topLevelDirs[dirName]
|
||||||
|
|
||||||
// Strip dirName prefix for tree building only
|
|
||||||
treeEntries := make([]walker.Entry, len(dirEntries))
|
treeEntries := make([]walker.Entry, len(dirEntries))
|
||||||
for i, e := range dirEntries {
|
for i, e := range dirEntries {
|
||||||
sepIdx := strings.Index(e.RelPath, string(filepath.Separator))
|
sepIdx := strings.Index(e.RelPath, string(filepath.Separator))
|
||||||
@@ -151,7 +146,7 @@ func WriteSlices(result *walker.Result, outputDir, repoName string) ([]string, e
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := writeDirSlice(slicePath, absDir, absRoot, dirName, treeEntries, dirEntries, indexBasename); err != nil {
|
if err := writeDirSlice(slicePath, absDir, absRoot, dirName, treeEntries, dirEntries, indexBasename, result.OmitTest); err != nil {
|
||||||
return nil, fmt.Errorf("failed to write slice %s: %w", sliceName, err)
|
return nil, fmt.Errorf("failed to write slice %s: %w", sliceName, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,7 +154,7 @@ func WriteSlices(result *walker.Result, outputDir, repoName string) ([]string, e
|
|||||||
return generated, nil
|
return generated, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeRootSlice(path, absRoot, rootName, repoName string, rootFiles []walker.Entry, topLevelDirs map[string][]walker.Entry) error {
|
func writeRootSlice(path, absRoot, rootName, repoName string, rootFiles []walker.Entry, topLevelDirs map[string][]walker.Entry, omitTest bool) error {
|
||||||
f, err := os.Create(path)
|
f, err := os.Create(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot create root file: %w", err)
|
return fmt.Errorf("cannot create root file: %w", err)
|
||||||
@@ -170,14 +165,12 @@ func writeRootSlice(path, absRoot, rootName, repoName string, rootFiles []walker
|
|||||||
|
|
||||||
fmt.Fprintf(&sb, "# %s [ROOT]\n\n", absRoot)
|
fmt.Fprintf(&sb, "# %s [ROOT]\n\n", absRoot)
|
||||||
|
|
||||||
// Build combined tree with references
|
|
||||||
treeLines := buildTreeWithRefs(rootName, repoName, topLevelDirs, rootFiles)
|
treeLines := buildTreeWithRefs(rootName, repoName, topLevelDirs, rootFiles)
|
||||||
sb.WriteString("## Directory Tree\n\n")
|
sb.WriteString("## Directory Tree\n\n")
|
||||||
sb.WriteString(fenceChars + "\n")
|
sb.WriteString(fenceChars + "\n")
|
||||||
sb.WriteString(treeLines)
|
sb.WriteString(treeLines)
|
||||||
sb.WriteString(fenceChars + "\n\n")
|
sb.WriteString(fenceChars + "\n\n")
|
||||||
|
|
||||||
// Vertical slices index for LLM navigation
|
|
||||||
sb.WriteString("## Vertical Slices Index\n\n")
|
sb.WriteString("## Vertical Slices Index\n\n")
|
||||||
sb.WriteString("These files contain the full contents of each top-level directory.\n")
|
sb.WriteString("These files contain the full contents of each top-level directory.\n")
|
||||||
sb.WriteString("Read the relevant slice file for detailed source code.\n\n")
|
sb.WriteString("Read the relevant slice file for detailed source code.\n\n")
|
||||||
@@ -211,7 +204,7 @@ func writeRootSlice(path, absRoot, rootName, repoName string, rootFiles []walker
|
|||||||
|
|
||||||
for _, entry := range rootFiles {
|
for _, entry := range rootFiles {
|
||||||
fullPath := filepath.Join(absRoot, entry.RelPath)
|
fullPath := filepath.Join(absRoot, entry.RelPath)
|
||||||
content, err := readFile(fullPath)
|
content, err := readFileForOutput(fullPath, omitTest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "warning: cannot read %s: %v\n", entry.RelPath, err)
|
fmt.Fprintf(os.Stderr, "warning: cannot read %s: %v\n", entry.RelPath, err)
|
||||||
continue
|
continue
|
||||||
@@ -238,7 +231,7 @@ func writeRootSlice(path, absRoot, rootName, repoName string, rootFiles []walker
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeDirSlice(path, absDir, absRoot, dirName string, treeEntries, fileEntries []walker.Entry, rootFileName string) error {
|
func writeDirSlice(path, absDir, absRoot, dirName string, treeEntries, fileEntries []walker.Entry, rootFileName string, omitTest bool) error {
|
||||||
f, err := os.Create(path)
|
f, err := os.Create(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot create slice file: %w", err)
|
return fmt.Errorf("cannot create slice file: %w", err)
|
||||||
@@ -266,7 +259,7 @@ func writeDirSlice(path, absDir, absRoot, dirName string, treeEntries, fileEntri
|
|||||||
|
|
||||||
for _, entry := range fileEntries {
|
for _, entry := range fileEntries {
|
||||||
fullPath := filepath.Join(absRoot, entry.RelPath)
|
fullPath := filepath.Join(absRoot, entry.RelPath)
|
||||||
content, err := readFile(fullPath)
|
content, err := readFileForOutput(fullPath, omitTest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "warning: cannot read %s: %v\n", entry.RelPath, err)
|
fmt.Fprintf(os.Stderr, "warning: cannot read %s: %v\n", entry.RelPath, err)
|
||||||
continue
|
continue
|
||||||
@@ -298,19 +291,16 @@ func buildTreeWithRefs(rootName, repoName string, topLevelDirs map[string][]walk
|
|||||||
sb.WriteString(rootName)
|
sb.WriteString(rootName)
|
||||||
sb.WriteString("/\n")
|
sb.WriteString("/\n")
|
||||||
|
|
||||||
// Collect and sort directory names
|
|
||||||
var dirNames []string
|
var dirNames []string
|
||||||
for k := range topLevelDirs {
|
for k := range topLevelDirs {
|
||||||
dirNames = append(dirNames, k)
|
dirNames = append(dirNames, k)
|
||||||
}
|
}
|
||||||
sort.Strings(dirNames)
|
sort.Strings(dirNames)
|
||||||
|
|
||||||
// Sort root files
|
|
||||||
sort.Slice(rootFiles, func(i, j int) bool {
|
sort.Slice(rootFiles, func(i, j int) bool {
|
||||||
return rootFiles[i].RelPath < rootFiles[j].RelPath
|
return rootFiles[i].RelPath < rootFiles[j].RelPath
|
||||||
})
|
})
|
||||||
|
|
||||||
// Merge directories and files into a single sorted list for display
|
|
||||||
type treeItem struct {
|
type treeItem struct {
|
||||||
name string
|
name string
|
||||||
isDir bool
|
isDir bool
|
||||||
@@ -441,6 +431,68 @@ func readFile(path string) (string, error) {
|
|||||||
return content, nil
|
return content, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readFileForOutput(fullPath string, omitTest bool) (string, error) {
|
||||||
|
content, err := readFile(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if omitTest && strings.EqualFold(filepath.Ext(fullPath), ".rs") {
|
||||||
|
content = stripTestBlocks(content)
|
||||||
|
}
|
||||||
|
return content, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripTestBlocks(content string) string {
|
||||||
|
lines := strings.Split(content, "\n")
|
||||||
|
var result []string
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
for i < len(lines) {
|
||||||
|
line := lines[i]
|
||||||
|
trimmed := strings.TrimSpace(line)
|
||||||
|
|
||||||
|
if isCfgTestAttr(trimmed) {
|
||||||
|
braceDepth := 0
|
||||||
|
foundBrace := false
|
||||||
|
|
||||||
|
for i < len(lines) {
|
||||||
|
line := lines[i]
|
||||||
|
|
||||||
|
if !foundBrace {
|
||||||
|
if strings.Contains(line, "{") {
|
||||||
|
braceDepth += strings.Count(line, "{") - strings.Count(line, "}")
|
||||||
|
foundBrace = true
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
braceDepth += strings.Count(line, "{") - strings.Count(line, "}")
|
||||||
|
i++
|
||||||
|
|
||||||
|
if braceDepth <= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, line)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.Join(result, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCfgTestAttr(line string) bool {
|
||||||
|
return strings.Contains(line, "#[cfg(test)]") ||
|
||||||
|
strings.Contains(line, "#[cfg(all(test") ||
|
||||||
|
strings.Contains(line, "#[cfg(any(test")
|
||||||
|
}
|
||||||
|
|
||||||
func extToLang(ext string) string {
|
func extToLang(ext string) string {
|
||||||
mapping := map[string]string{
|
mapping := map[string]string{
|
||||||
".go": "go",
|
".go": "go",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// internal/walker/walker.go
|
||||||
package walker
|
package walker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -20,6 +21,7 @@ type Result struct {
|
|||||||
Entries []Entry
|
Entries []Entry
|
||||||
AbsRoot string
|
AbsRoot string
|
||||||
SingleFile bool
|
SingleFile bool
|
||||||
|
OmitTest bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func Run(cfg *config.Config) (*Result, error) {
|
func Run(cfg *config.Config) (*Result, error) {
|
||||||
@@ -43,6 +45,7 @@ func processSingleFile(cfg *config.Config) (*Result, error) {
|
|||||||
MaxSize: cfg.MaxSize,
|
MaxSize: cfg.MaxSize,
|
||||||
Extensions: cfg.Extensions,
|
Extensions: cfg.Extensions,
|
||||||
Ignores: cfg.Ignores,
|
Ignores: cfg.Ignores,
|
||||||
|
OmitTest: cfg.OmitTest,
|
||||||
}
|
}
|
||||||
|
|
||||||
if flt.ShouldSkipFile(cfg.AbsRoot, filepath.Base(cfg.AbsRoot), info) {
|
if flt.ShouldSkipFile(cfg.AbsRoot, filepath.Base(cfg.AbsRoot), info) {
|
||||||
@@ -53,8 +56,8 @@ func processSingleFile(cfg *config.Config) (*Result, error) {
|
|||||||
Entries: []Entry{{RelPath: filepath.Base(cfg.AbsRoot), Size: info.Size()}},
|
Entries: []Entry{{RelPath: filepath.Base(cfg.AbsRoot), Size: info.Size()}},
|
||||||
AbsRoot: filepath.Dir(cfg.AbsRoot),
|
AbsRoot: filepath.Dir(cfg.AbsRoot),
|
||||||
SingleFile: true,
|
SingleFile: true,
|
||||||
|
OmitTest: cfg.OmitTest,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func walkDirectory(cfg *config.Config) (*Result, error) {
|
func walkDirectory(cfg *config.Config) (*Result, error) {
|
||||||
@@ -62,9 +65,10 @@ func walkDirectory(cfg *config.Config) (*Result, error) {
|
|||||||
MaxSize: cfg.MaxSize,
|
MaxSize: cfg.MaxSize,
|
||||||
Extensions: cfg.Extensions,
|
Extensions: cfg.Extensions,
|
||||||
Ignores: cfg.Ignores,
|
Ignores: cfg.Ignores,
|
||||||
|
OmitTest: cfg.OmitTest,
|
||||||
}
|
}
|
||||||
|
|
||||||
result := &Result{AbsRoot: cfg.AbsRoot}
|
result := &Result{AbsRoot: cfg.AbsRoot, OmitTest: cfg.OmitTest}
|
||||||
|
|
||||||
err := filepath.WalkDir(cfg.AbsRoot, func(path string, d fs.DirEntry, err error) error {
|
err := filepath.WalkDir(cfg.AbsRoot, func(path string, d fs.DirEntry, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user