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 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 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 if Path::new(&args.output).exists() {
56 if !prompter.confirm_overwrite(&args.output)? {
58 if !silent {
59 println!("Operation cancelled.");
60 }
61 return Ok(());
62 }
63 }
64
65 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 let file_tree = build_file_tree(&files, base_path);
72
73 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 if !prompter.confirm_processing(files.len())? {
84 if !silent {
85 println!("Operation cancelled.");
86 }
87 return Ok(());
88 }
89
90 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}