Files

191 lines
4.8 KiB
Go

package config
import (
"flag"
"fmt"
"os"
"path/filepath"
"strings"
)
const (
DefaultMaxSize = 512 * 1024
DefaultMaxDepth = 20
Version = "0.1.0"
)
var DefaultIgnores = []string{
".git",
"node_modules",
"vendor",
"bin",
"target",
"build",
"dist",
".idea",
".vscode",
"dependency-reduced-pom.xml",
}
var DefaultExts = []string{
".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".ico",
".mp4", ".avi", ".mov", ".mkv", ".webm",
".mp3", ".wav", ".flac", ".ogg",
".zip", ".tar", ".gz", ".bz2", ".xz", ".7z", ".rar",
".exe", ".dll", ".so", ".dylib", ".wasm",
".pdf", ".doc", ".docx", ".xls", ".xlsx",
".pem", ".key", ".cert",
".db", ".sqlite", ".sqlite3",
".class",
".jar",
".war",
".ear",
}
var FrontendExts = []string{
".html", ".htm", ".gohtml", ".tmpl",
".css", ".scss", ".sass", ".less",
".js", ".jsx", ".mjs", ".cjs",
".ts", ".tsx",
".vue", ".svelte",
}
type stringSlice []string
func (s *stringSlice) String() string {
return strings.Join(*s, ", ")
}
func (s *stringSlice) Set(v string) error {
*s = append(*s, v)
return nil
}
type Config struct {
InputPath string
OutputPath string
AppendMode bool
Force bool
SingleFile bool
MaxSize int64
MaxDepth int
Ignores []string
Extensions []string
AbsRoot string
ProtonDrive bool
DrivePath string
VerticalSlices bool
SkipFrontend bool
}
func PrintUsage() {
fmt.Fprintf(os.Stderr, "dirmd v%s\n\n", Version)
fmt.Fprintf(os.Stderr, "Usage: dirmd [flags] <directory>\n")
fmt.Fprintf(os.Stderr, " dirmd -a -i <file> -o <output>\n\n")
fmt.Fprintf(os.Stderr, "Flags:\n")
flag.PrintDefaults()
}
func Parse() (*Config, error) {
var (
input string
output string
appendMd bool
force bool
maxSize int64
maxDepth int
ignores stringSlice
exts stringSlice
showVer bool
protonDrive bool
drivePath string
verticalSlices bool
skipFrontend bool
)
flag.StringVar(&input, "i", "", "single file input (append mode only)")
flag.StringVar(&output, "o", "", "output markdown file path (required)")
flag.BoolVar(&appendMd, "a", false, "append to existing output file")
flag.BoolVar(&force, "f", false, "overwrite existing output file (non-append mode)")
flag.Int64Var(&maxSize, "max-size", DefaultMaxSize, "max file size in bytes")
flag.IntVar(&maxDepth, "max-depth", DefaultMaxDepth, "max directory recursion depth")
flag.Var(&ignores, "ignore", "additional ignore patterns (glob, repeatable)")
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.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.Usage = PrintUsage
flag.Parse()
if showVer {
fmt.Printf("dirmd v%s\n", Version)
os.Exit(0)
}
if output == "" {
return nil, fmt.Errorf("-o is required")
}
if drivePath != "" && !protonDrive {
return nil, fmt.Errorf("--drive-path requires --proton-drive")
}
if verticalSlices && appendMd {
return nil, fmt.Errorf("--vertical-slices cannot be used with --append")
}
if verticalSlices && input != "" {
return nil, fmt.Errorf("--vertical-slices cannot be used with --input-file")
}
excludedExts := append(DefaultExts, exts...)
if skipFrontend {
excludedExts = append(excludedExts, FrontendExts...)
}
cfg := &Config{
OutputPath: output,
AppendMode: appendMd,
Force: force,
MaxSize: maxSize,
MaxDepth: maxDepth,
Ignores: append(DefaultIgnores, ignores...),
Extensions: excludedExts,
ProtonDrive: protonDrive,
DrivePath: drivePath,
VerticalSlices: verticalSlices,
SkipFrontend: skipFrontend,
}
if input != "" {
cfg.SingleFile = true
cfg.InputPath = input
if !appendMd {
return nil, fmt.Errorf("single file input (-i) requires append mode (-a)")
}
} else {
args := flag.Args()
if len(args) < 1 {
return nil, fmt.Errorf("directory argument is required when -i is not used")
}
cfg.InputPath = args[0]
}
abs, err := filepath.Abs(cfg.InputPath)
if err != nil {
return nil, fmt.Errorf("failed to resolve absolute path: %w", err)
}
cfg.AbsRoot = abs
if !appendMd {
if _, err := os.Stat(output); err == nil && !force {
return nil, fmt.Errorf("output file %s already exists (use -f to overwrite or -a to append)", output)
}
}
return cfg, nil
}