context_builder/
lib.rs

1use clap::Parser;
2use log::info;
3use std::io;
4use std::path::Path;
5use std::time::Instant;
6
7pub mod cli;
8pub mod file_utils;
9pub mod markdown;
10pub mod tree;
11
12use cli::Args;
13use file_utils::{collect_files, confirm_overwrite, confirm_processing};
14use markdown::generate_markdown;
15use tree::{build_file_tree, print_tree};
16
17pub trait Prompter {
18    fn confirm_processing(&self, file_count: usize) -> io::Result<bool>;
19    fn confirm_overwrite(&self, file_path: &str) -> io::Result<bool>;
20}
21
22pub struct DefaultPrompter;
23
24impl Prompter for DefaultPrompter {
25    fn confirm_processing(&self, file_count: usize) -> io::Result<bool> {
26        confirm_processing(file_count)
27    }
28    fn confirm_overwrite(&self, file_path: &str) -> io::Result<bool> {
29        confirm_overwrite(file_path)
30    }
31}
32
33pub fn run_with_args(args: Args, prompter: &impl Prompter) -> io::Result<()> {
34    let start_time = Instant::now();
35
36    // If CB_SILENT is set to "1" or "true" (case-insensitive), suppress user-facing prints.
37    let silent = std::env::var("CB_SILENT")
38        .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
39        .unwrap_or(false);
40
41    let base_path = Path::new(&args.input);
42
43    // Pre-run checks
44    if !base_path.exists() || !base_path.is_dir() {
45        if !silent {
46            eprintln!(
47                "Error: The specified input directory '{}' does not exist or is not a directory.",
48                args.input
49            );
50        }
51        return Ok(());
52    }
53
54    // Check if an output file already exists
55    if Path::new(&args.output).exists() {
56        // Ask for user confirmation to overwrite
57        if !prompter.confirm_overwrite(&args.output)? {
58            if !silent {
59                println!("Operation cancelled.");
60            }
61            return Ok(());
62        }
63    }
64
65    // --- 1. Collect files --- //
66    info!("Starting file collection...");
67    let files = collect_files(base_path, &args.filter, &args.ignore)?;
68    info!("Found {} files to process.", files.len());
69
70    // --- 2. Build file tree --- //
71    let file_tree = build_file_tree(&files, base_path);
72
73    // --- 3. Handle preview mode --- //
74    if args.preview {
75        if !silent {
76            println!("\n# File Tree Structure (Preview)\n");
77            print_tree(&file_tree, 0);
78        }
79        return Ok(());
80    }
81
82    // --- 4. Get user confirmation --- //
83    if !prompter.confirm_processing(files.len())? {
84        if !silent {
85            println!("Operation cancelled.");
86        }
87        return Ok(());
88    }
89
90    // --- 5. Generate the markdown file --- //
91    generate_markdown(
92        &args.output,
93        &args.input,
94        &args.filter,
95        &args.ignore,
96        &file_tree,
97        &files,
98        base_path,
99        args.line_numbers,
100    )?;
101
102    let duration = start_time.elapsed();
103
104    if !silent {
105        println!("Documentation created successfully: {}", args.output);
106        println!("Processing time: {:.2?}", duration);
107    }
108
109    Ok(())
110}
111
112pub fn run() -> io::Result<()> {
113    env_logger::init();
114    let args = Args::parse();
115    run_with_args(args, &DefaultPrompter)
116}