package cmd import ( "fmt" "os" "path/filepath" "github.com/spf13/cobra" "dirmd/internal/config" "dirmd/internal/renderer" "dirmd/internal/walker" ) var ( rootCmd = &cobra.Command{ Use: "dirmd [flags] ", Short: "Generate markdown documentation from directory structure", Long: `dirmd walks a directory and generates a markdown file with: - Directory tree structure - All readable text files with their contents Examples: # Create new documentation dirmd -o output.md ~/repos/myproject # Append a single file dirmd -a -i README.md -o existing.md # Append another directory dirmd -a -o existing.md ~/repos/anotherproject`, RunE: run, } outputPath string appendMode bool singleFile string maxSize int64 maxDepth int ignorePatterns []string extensions []string ) func Execute() { if err := rootCmd.Execute(); err != nil { os.Exit(1) } } func init() { rootCmd.PersistentFlags().StringVarP(&outputPath, "output", "o", "", "output markdown file path (required)") rootCmd.Flags().BoolVarP(&appendMode, "append", "a", false, "append to existing output file") rootCmd.Flags().StringVarP(&singleFile, "input-file", "i", "", "single file input (append mode only)") rootCmd.Flags().Int64Var(&maxSize, "max-size", config.DefaultMaxSize, "max file size in bytes") rootCmd.Flags().IntVar(&maxDepth, "max-depth", config.DefaultMaxDepth, "max directory recursion depth") rootCmd.Flags().StringArrayVar(&ignorePatterns, "ignore", nil, "additional ignore patterns (glob)") rootCmd.Flags().StringArrayVar(&extensions, "extensions", nil, "additional file extensions to skip (repeatable)") rootCmd.MarkPersistentFlagRequired("output") } func run(cmd *cobra.Command, args []string) error { cfg := &config.Config{ OutputPath: outputPath, AppendMode: appendMode, MaxSize: maxSize, MaxDepth: maxDepth, Ignores: append(config.DefaultIgnores, ignorePatterns...), Extensions: append(config.DefaultExts, extensions...), } if singleFile != "" { cfg.InputPath = singleFile cfg.SingleFile = true if !appendMode { return fmt.Errorf("single file input (-i) requires append mode (-a)") } } else { if len(args) < 1 { return 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 fmt.Errorf("failed to resolve absolute path: %w", err) } cfg.AbsRoot = abs // Non-append mode: output must not exist if !appendMode { if _, err := os.Stat(outputPath); err == nil { return fmt.Errorf("output file %s already exists (use -a to append)", outputPath) } } result, err := walker.Run(cfg) if err != nil { return err } if len(result.Entries) == 0 { return fmt.Errorf("no readable text files found in %s", cfg.AbsRoot) } if err := renderer.Write(result, outputPath, appendMode, cfg.SingleFile); err != nil { return err } fmt.Fprintf(os.Stderr, "wrote %d entries to %s\n", len(result.Entries), outputPath) return nil }