//! Runtime orchestration for the `repo2markdown` binary. //! //! This module connects parsed CLI options with stdin path-list parsing and Markdown generation. //! It reads a NUL-separated list of paths from input, then renders either plain Markdown or a //! fenced Markdown document depending on the CLI configuration. use std::io::{Read, Write}; use repo2markdown::{ fenced_md_generator::generate_fenced_markdown, logger::Logger, md_generator::generate_markdown_from_paths, path_list::paths_from_null_separated_bytes, }; use crate::cli::Cli; /// Runs the main `repo2markdown` pipeline. /// /// The input stream is read as a NUL-separated path list. Empty path segments are ignored by the /// path-list parser. Paths are resolved and rendered according to `cli` options. /// /// When `cli.fenced` is set, the full generated Markdown document is wrapped in an outer Markdown /// fence. Otherwise, plain Markdown is written directly to `output`. pub fn run( cli: &Cli, mut input: R, output: W, ) -> Result<(), Box> { let logger = Logger::new(cli.verbosity()); let mut input_buf = Vec::new(); input.read_to_end(&mut input_buf)?; let paths = paths_from_null_separated_bytes(&input_buf); if cli.fenced { generate_fenced_markdown( &paths, output, cli.render_options(), &cli.root, &cli.origin, cli.name.as_deref(), logger, ) } else { generate_markdown_from_paths( &paths, output, cli.render_options(), &cli.root, &cli.origin, cli.name.as_deref(), logger, ) } } #[cfg(test)] mod tests { use std::{fs, io::Cursor}; use clap::Parser; use tempfile::tempdir; use crate::cli::Cli; use super::run; #[test] fn run_does_not_fence_output_by_default() { let cli = Cli::try_parse_from(["repo2markdown", "--name", "Project name"]).unwrap(); let input = Cursor::new(b""); let mut output = Vec::new(); run(&cli, input, &mut output).unwrap(); assert_eq!(String::from_utf8(output).unwrap(), "# Project name\n"); } #[test] fn run_uses_fenced_output_when_requested() { let cli = Cli::try_parse_from(["repo2markdown", "--fenced", "--name", "Project name"]).unwrap(); let input = Cursor::new(b""); let mut output = Vec::new(); run(&cli, input, &mut output).unwrap(); assert_eq!( String::from_utf8(output).unwrap(), "```markdown\n# Project name\n```\n" ); } #[test] fn run_reads_null_separated_paths_from_stdin() { let temp_dir = tempdir().unwrap(); fs::write(temp_dir.path().join("b.rs"), "B").unwrap(); fs::write(temp_dir.path().join("a.rs"), "A").unwrap(); let cli = Cli::try_parse_from([ "repo2markdown", "--origin", temp_dir.path().to_str().unwrap(), "--root", temp_dir.path().to_str().unwrap(), "--name", "Project name", ]) .unwrap(); let input = Cursor::new(b"a.rs\0b.rs\0"); let mut output = Vec::new(); run(&cli, input, &mut output).unwrap(); let output = String::from_utf8(output).unwrap(); assert!(output.contains("## File: a.rs")); assert!(output.contains("## File: b.rs")); assert!(output.contains("```rust\nA\n```")); assert!(output.contains("```rust\nB\n```")); let a_pos = output.find("a.rs").unwrap(); let b_pos = output.find("b.rs").unwrap(); assert!(a_pos < b_pos); } #[test] fn run_ignores_empty_path_segments() { let temp_dir = tempdir().unwrap(); fs::write(temp_dir.path().join("a.rs"), "A").unwrap(); let cli = Cli::try_parse_from([ "repo2markdown", "--origin", temp_dir.path().to_str().unwrap(), "--root", temp_dir.path().to_str().unwrap(), "--name", "Project name", ]) .unwrap(); let input = Cursor::new(b"\0a.rs\0\0"); let mut output = Vec::new(); run(&cli, input, &mut output).unwrap(); let output = String::from_utf8(output).unwrap(); assert!(output.contains("## File: a.rs")); assert!(output.contains("```rust\nA\n```")); assert_eq!(output.matches("## File: a.rs").count(), 1); } #[test] fn run_accepts_final_path_without_trailing_nul() { let temp_dir = tempdir().unwrap(); fs::write(temp_dir.path().join("a.rs"), "A").unwrap(); let cli = Cli::try_parse_from([ "repo2markdown", "--origin", temp_dir.path().to_str().unwrap(), "--root", temp_dir.path().to_str().unwrap(), "--name", "Project name", ]) .unwrap(); let input = Cursor::new(b"a.rs"); let mut output = Vec::new(); run(&cli, input, &mut output).unwrap(); let output = String::from_utf8(output).unwrap(); assert!(output.contains("## File: a.rs")); assert!(output.contains("```rust\nA\n```")); } }