use std::{io, path::PathBuf}; use clap::Parser; use repo2markdown::{ fenced_md_generator::generate_fenced_markdown, logger::{Logger, Verbosity}, md_generator::generate_markdown_from_paths, renderer::{DEFAULT_MAX_FILE_SIZE, RenderOptions}, }; #[derive(Debug, Parser)] #[command(version, about, long_about = None)] struct Cli { /// Root path to which file paths displayed in the generated markdown are relative. #[arg(long, default_value = ".")] root: PathBuf, /// Base path used to resolve relative input paths. #[arg(long, default_value = ".")] origin: PathBuf, /// Project title to use in the markdown header. #[arg(long)] name: Option, /// Wrap the entire output in a markdown code fence. #[arg(long)] fenced: bool, /// Maximum file size, in bytes, to include in the generated markdown. #[arg(long, default_value_t = DEFAULT_MAX_FILE_SIZE, value_name = "BYTES")] max_file_size: u64, /// Include a placeholder entry when a binary file is skipped. #[arg(long)] placeholder_binary_files: bool, /// Include a placeholder entry when a large file is skipped. #[arg(long)] placeholder_large_files: bool, /// Suppress warnings and informational logs. #[arg(long, conflicts_with = "verbose")] quiet: bool, /// Include informational logs. #[arg(long, conflicts_with = "quiet")] verbose: bool, } impl Cli { fn render_options(&self) -> RenderOptions { RenderOptions { max_file_size: self.max_file_size, placeholder_for_binary_files: self.placeholder_binary_files, placeholder_for_large_files: self.placeholder_large_files, } } fn verbosity(&self) -> Verbosity { if self.quiet { Verbosity::Quiet } else if self.verbose { Verbosity::Verbose } else { Verbosity::Normal } } } fn main() -> Result<(), Box> { let cli = Cli::parse(); let stdin = io::stdin(); let stdout = io::stdout(); let logger = Logger::new(cli.verbosity()); if cli.fenced { generate_fenced_markdown( stdin.lock(), stdout.lock(), cli.render_options(), &cli.root, &cli.origin, cli.name.as_deref(), logger, ) } else { generate_markdown_from_paths( stdin.lock(), stdout.lock(), cli.render_options(), &cli.root, &cli.origin, cli.name.as_deref(), logger, ) } } #[cfg(test)] mod tests { use std::path::PathBuf; use clap::Parser; use repo2markdown::logger::Verbosity; use super::{Cli, RenderOptions}; #[test] fn cli_uses_default_paths_and_normal_verbosity() { let cli = Cli::try_parse_from(["repo2markdown"]).unwrap(); assert_eq!(cli.root, PathBuf::from(".")); assert_eq!(cli.origin, PathBuf::from(".")); assert_eq!(cli.name, None); assert_eq!(cli.verbosity(), Verbosity::Normal); } #[test] fn cli_accepts_root_origin_and_name() { let cli = Cli::try_parse_from([ "repo2markdown", "--root", "/repo", "--origin", "/repo/src", "--name", "My Project", ]) .unwrap(); assert_eq!(cli.root, PathBuf::from("/repo")); assert_eq!(cli.origin, PathBuf::from("/repo/src")); assert_eq!(cli.name.as_deref(), Some("My Project")); } #[test] fn cli_accepts_render_options() { let cli = Cli::try_parse_from([ "repo2markdown", "--max-file-size", "5", "--placeholder-binary-files", "--placeholder-large-files", ]) .unwrap(); let options = cli.render_options(); assert_eq!(options.max_file_size, 5); assert!(options.placeholder_for_binary_files); assert!(options.placeholder_for_large_files); } #[test] fn cli_defaults_unspecified_render_options() { let cli = Cli::try_parse_from(["repo2markdown"]).unwrap(); assert_eq!(cli.render_options(), RenderOptions::default()); } #[test] fn cli_accepts_quiet_verbosity() { let cli = Cli::try_parse_from(["repo2markdown", "--quiet"]).unwrap(); assert_eq!(cli.verbosity(), Verbosity::Quiet); } #[test] fn cli_accepts_verbose_verbosity() { let cli = Cli::try_parse_from(["repo2markdown", "--verbose"]).unwrap(); assert_eq!(cli.verbosity(), Verbosity::Verbose); } #[test] fn cli_rejects_quiet_and_verbose_together() { let result = Cli::try_parse_from(["repo2markdown", "--quiet", "--verbose"]); assert!(result.is_err()); } #[test] fn cli_rejects_invalid_max_file_size() { let result = Cli::try_parse_from(["repo2markdown", "--max-file-size", "not-a-number"]); assert!(result.is_err()); } #[test] fn cli_does_not_use_fenced_output_by_default() { let cli = Cli::try_parse_from(["repo2markdown"]).unwrap(); assert!(!cli.fenced); } #[test] fn cli_accepts_fenced_output_option() { let cli = Cli::try_parse_from(["repo2markdown", "--fenced"]).unwrap(); assert!(cli.fenced); } }