dumpfiles 0.3.0

A CLI and library for generating structured YAML representations of directory contents, optimized for efficiently sharing codebases with LLMs.
Documentation
extern crate anyhow;
extern crate clap;
extern crate dumpfiles;
extern crate env_logger;
extern crate log;

use anyhow::{Context, Result};
use clap::Parser;
use dumpfiles::{write_directory_contents_yaml, GitignoreMode};
use std::path::PathBuf;

#[derive(Parser)]
#[command(
    author,
    version,
    about,
    long_about = None,
    after_help = "EXAMPLES:\n  \
    dumpfiles src/\n    \
        Process the 'src' directory and output to 'output.yaml'\n\n  \
    dumpfiles . -o project_dump.yaml -i \"*.log\"\n    \
        Process current directory, save to 'project_dump.yaml', and ignore log files too"
)]
struct Cli {
    /// Path to the directory to process
    #[arg(value_name = "DIRECTORY")]
    directory: PathBuf,

    /// Path to the output file
    #[arg(short, long, default_value = "output.yaml")]
    output: PathBuf,

    /// Patterns to ignore (can be used multiple times)
    #[arg(short, long, default_values = [".git*"])]
    ignore: Vec<String>,

    /// Path to a custom .gitignore file
    #[arg(short, long, value_name = "GITIGNORE")]
    gitignore: Option<PathBuf>,

    /// Disable automatic .gitignore processing
    #[arg(long)]
    no_gitignore: bool,

    /// Path to .dumpignore file (defaults to .dumpignore in the directory)
    #[arg(short = 'd', long, value_name = "DUMPIGNORE")]
    dumpignore: Option<PathBuf>,
}

fn main() -> Result<()> {
    env_logger::init();

    let cli = Cli::parse();
    let ignore_patterns: Vec<String> = cli
        .ignore
        .iter()
        .map(|p| p.replace("\\", "/"))
        .map(|p| p.trim_end_matches('/').to_string())
        .collect();

    // If no dumpignore path is specified, check for .dumpignore in the directory.
    let dumpignore_path = match &cli.dumpignore {
        Some(path) => Some(path.clone()),
        None => {
            let default_path = cli.directory.join(".dumpignore");
            if default_path.exists() {
                Some(default_path.clone())
            } else {
                None
            }
        }
    };

    let git_mode = if cli.no_gitignore {
        GitignoreMode::Disabled
    } else if let Some(p) = &cli.gitignore {
        GitignoreMode::Path(p.clone())
    } else {
        GitignoreMode::Auto
    };

    log::info!("Starting to process directory: {}", cli.directory.display());
    write_directory_contents_yaml(
        &cli.directory,
        &cli.output,
        &ignore_patterns,
        git_mode,
        dumpignore_path.as_deref(),
    )
    .context("Failed to write directory contents in YAML format")?;

    log::info!(
        "Directory contents have been written to {}",
        cli.output.display()
    );
    Ok(())
}