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", } 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", // Java compiled bytecode ".jar", // JAR archives ".war", ".ear", } 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 } func PrintUsage() { fmt.Fprintf(os.Stderr, "dirmd v%s\n\n", Version) fmt.Fprintf(os.Stderr, "Usage: dirmd [flags] \n") fmt.Fprintf(os.Stderr, " dirmd -a -i -o \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 ) 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.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") } cfg := &Config{ OutputPath: output, AppendMode: appendMd, Force: force, MaxSize: maxSize, MaxDepth: maxDepth, Ignores: append(DefaultIgnores, ignores...), Extensions: append(DefaultExts, exts...), ProtonDrive: protonDrive, DrivePath: drivePath, } 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 }