diff options
| author | A Farzat <a@farzat.xyz> | 2026-06-20 14:41:13 +0300 |
|---|---|---|
| committer | A Farzat <a@farzat.xyz> | 2026-06-20 15:59:54 +0300 |
| commit | 4ec209d3863e913cffbe8c6e0a14a292820a2715 (patch) | |
| tree | aeb79461140bd2fb7266b7c0a1bc0fda7d378f56 /src/md_generator.rs | |
| parent | 0b679b0b5d81ad8c5f61031ffe56b3f646ba9234 (diff) | |
| download | repo2markdown-4ec209d3863e913cffbe8c6e0a14a292820a2715.tar.gz repo2markdown-4ec209d3863e913cffbe8c6e0a14a292820a2715.zip | |
Move input parsing from md_generator to run
Diffstat (limited to 'src/md_generator.rs')
| -rw-r--r-- | src/md_generator.rs | 113 |
1 files changed, 45 insertions, 68 deletions
diff --git a/src/md_generator.rs b/src/md_generator.rs index 48f066c..7740247 100644 --- a/src/md_generator.rs +++ b/src/md_generator.rs @@ -1,15 +1,8 @@ -//! Generate Markdown from null-separated path lists. +//! Generate Markdown from path lists. //! -//! This module reads a stream of NUL-separated file paths, normalizes each path, skips duplicates, -//! and renders selected files into Markdown. +//! This module normalizes paths, skips duplicates, and renders selected files into Markdown. -use std::{ - collections::HashSet, - ffi::OsStr, - io::{Read, Write}, - os::unix::ffi::OsStrExt, - path::Path, -}; +use std::{collections::HashSet, io::Write, path::Path}; use crate::{ logger::Logger, @@ -20,20 +13,17 @@ use crate::{ const DEFAULT_PROJECT_NAME: &str = "Project Outline"; -/// Generates Markdown for the files listed in `input`. -/// -/// `input` is expected to contain a NUL-separated sequence of paths, such as output from -/// `find -print0` or `git ls-files -z`. +/// Generates Markdown for `paths`. /// -/// Relative input paths are resolved against `origin_base`. File headings use paths made relative -/// to `root`. +/// Relative paths are resolved against `origin_base`. File headings use paths made relative to +/// `root`. /// /// Duplicate files are skipped after normalization, while preserving the first occurrence order. /// /// Rendering behavior such as file-size limits and placeholder output is controlled by /// `render_options`. -pub fn generate_markdown_from_paths<R: Read, W: Write>( - mut input: R, +pub fn generate_markdown_from_paths<W: Write>( + paths: &[&Path], output: W, render_options: RenderOptions, root: &Path, @@ -41,9 +31,6 @@ pub fn generate_markdown_from_paths<R: Read, W: Write>( project_title: Option<&str>, logger: Logger, ) -> Result<(), Box<dyn std::error::Error>> { - let mut buf = Vec::new(); - input.read_to_end(&mut buf)?; - let normalizer = Normalizer::new(root, origin_base)?; let mut renderer = Renderer::new(output, render_options).with_logger(logger); @@ -51,12 +38,7 @@ pub fn generate_markdown_from_paths<R: Read, W: Write>( renderer.render_header(project_title)?; let mut seen_paths = HashSet::new(); - for segment in buf.split(|b| *b == 0) { - if segment.is_empty() { - continue; - } - - let path = Path::new(OsStr::from_bytes(segment)); + for path in paths { let normalized_path = normalizer.normalize(path)?; if !seen_paths.insert(normalized_path.clone()) { logger.warn(format!( @@ -84,7 +66,7 @@ fn derive_project_title(root: &Path) -> &str { mod tests { use std::ffi::OsStr; use std::fs; - use std::io::{Cursor, Read, Write}; + use std::io::Write; use std::os::unix::ffi::OsStrExt; use std::path::Path; @@ -94,17 +76,8 @@ mod tests { use super::{DEFAULT_PROJECT_NAME, derive_project_title, generate_markdown_from_paths}; - fn paths_to_null_sep_bytes(file_paths: &[&Path]) -> Vec<u8> { - let mut output = Vec::new(); - for path in file_paths { - output.extend(path.as_os_str().as_encoded_bytes()); - output.push(0); - } - output - } - - fn generate_with_defaults<R: Read, W: Write>( - input: R, + fn generate_with_defaults<W: Write>( + paths: &[&Path], output: W, root: &Path, origin_base: &Path, @@ -113,7 +86,7 @@ mod tests { let logger = Logger::default(); let render_options = RenderOptions::default(); generate_markdown_from_paths( - input, + paths, output, render_options, root, @@ -124,30 +97,30 @@ mod tests { } #[test] - fn cli_with_empty_input_produces_empty_project_with_specified_project_title() { + fn empty_path_list_produces_header_with_specified_project_title() { let temp_dir = tempdir().unwrap(); - let input = Cursor::new(b""); + let paths = []; let mut output = Vec::new(); let root = temp_dir.path(); let origin_base = temp_dir.path(); - generate_with_defaults(input, &mut output, root, origin_base, Some("Project name")) + generate_with_defaults(&paths, &mut output, root, origin_base, Some("Project name")) .unwrap(); assert_eq!(String::from_utf8(output).unwrap(), "# Project name\n"); } #[test] - fn cli_reads_single_file_from_stdin() { + fn renders_single_file_from_path_list() { let temp_dir = tempdir().unwrap(); let origin_base = temp_dir.path(); - let input = Cursor::new(b"test_main.rs\0"); + let paths = [Path::new("test_main.rs")]; let mut output = Vec::new(); let root = temp_dir.path(); fs::write(origin_base.join("test_main.rs"), "fn main() {}").unwrap(); - generate_with_defaults(input, &mut output, root, origin_base, None).unwrap(); + generate_with_defaults(&paths, &mut output, root, origin_base, None).unwrap(); let output_str = String::from_utf8(output).unwrap(); @@ -156,17 +129,17 @@ mod tests { } #[test] - fn cli_reads_multiple_files_in_order() { + fn renders_multiple_files_in_order() { let temp_dir = tempdir().unwrap(); let origin_base = temp_dir.path(); - let input = Cursor::new(b"a.rs\0b.rs\0"); + let paths = [Path::new("a.rs"), Path::new("b.rs")]; let mut output = Vec::new(); let root = temp_dir.path(); fs::write(origin_base.join("a.rs"), "A").unwrap(); fs::write(origin_base.join("b.rs"), "B").unwrap(); - generate_with_defaults(input, &mut output, root, origin_base, None).unwrap(); + generate_with_defaults(&paths, &mut output, root, origin_base, None).unwrap(); let output = String::from_utf8(output).unwrap(); @@ -177,10 +150,10 @@ mod tests { } #[test] - fn cli_normalizes_paths_before_rendering() { + fn normalizes_paths_before_rendering() { let temp_dir = tempdir().unwrap(); let origin_base = temp_dir.path(); - let input = Cursor::new(b"test/./main.rs\0"); + let paths = [Path::new("test/./main.rs")]; let mut output = Vec::new(); let root = temp_dir.path(); @@ -188,7 +161,7 @@ mod tests { fs::create_dir_all(&write_dir).unwrap(); fs::write(write_dir.join("main.rs"), "fn main() {}").unwrap(); - generate_with_defaults(input, &mut output, root, origin_base, None).unwrap(); + generate_with_defaults(&paths, &mut output, root, origin_base, None).unwrap(); let output = String::from_utf8(output).unwrap(); @@ -196,17 +169,17 @@ mod tests { } #[test] - fn cli_reads_from_origin_but_outputs_relative_to_root() { + fn resolves_paths_from_origin_but_outputs_root_relative_paths() { let temp_dir = tempdir().unwrap(); let origin_base = temp_dir.path().join("sandbox/src"); - let input = Cursor::new(b"main.rs\0"); + let paths = [Path::new("main.rs")]; let mut output = Vec::new(); let root = temp_dir.path().join("project"); fs::create_dir_all(&origin_base).unwrap(); fs::write(origin_base.join("main.rs"), "fn main() {}").unwrap(); - generate_with_defaults(input, &mut output, &root, &origin_base, None).unwrap(); + generate_with_defaults(&paths, &mut output, &root, &origin_base, None).unwrap(); let output = String::from_utf8(output).unwrap(); @@ -218,17 +191,17 @@ mod tests { } #[test] - fn cli_ignores_origin_when_input_path_is_absolute() { + fn absolute_paths_are_not_resolved_against_origin() { let temp_dir1 = tempdir().unwrap(); let temp_dir2 = tempdir().unwrap(); let origin_base = temp_dir2.path(); let filepath = temp_dir1.path().join("test_main.rs"); - let input = Cursor::new(paths_to_null_sep_bytes(&[&filepath])); + let paths = [filepath.as_ref()]; let mut output = Vec::new(); let root = temp_dir2.path(); fs::write(&filepath, "fn main() {}").unwrap(); - generate_with_defaults(input, &mut output, root, origin_base, None).unwrap(); + generate_with_defaults(&paths, &mut output, root, origin_base, None).unwrap(); let output = String::from_utf8(output).unwrap(); @@ -244,10 +217,10 @@ mod tests { fs::write(origin.join("a.rs"), "A").unwrap(); - let input = Cursor::new(b"a.rs\0a.rs\0"); + let paths = [Path::new("a.rs"), Path::new("a.rs")]; let mut output = Vec::new(); - generate_with_defaults(input, &mut output, root, origin, None).unwrap(); + generate_with_defaults(&paths, &mut output, root, origin, None).unwrap(); let output = String::from_utf8(output).unwrap(); @@ -263,10 +236,10 @@ mod tests { fs::write(origin.join("a.rs"), "A").unwrap(); fs::write(origin.join("b.rs"), "B").unwrap(); - let input = Cursor::new(b"a.rs\0b.rs\0a.rs\0"); + let paths = [Path::new("a.rs"), Path::new("b.rs"), Path::new("a.rs")]; let mut output = Vec::new(); - generate_with_defaults(input, &mut output, root, origin, None).unwrap(); + generate_with_defaults(&paths, &mut output, root, origin, None).unwrap(); let output = String::from_utf8(output).unwrap(); assert_eq!(output.matches("## File: a.rs").count(), 1); @@ -286,10 +259,14 @@ mod tests { fs::write(origin.join("a.rs"), "A").unwrap(); fs::write(origin.join("b.rs"), "B").unwrap(); - let input = Cursor::new(b"a.rs\0b.rs\0bla/../a.rs\0"); + let paths = [ + Path::new("a.rs"), + Path::new("b.rs"), + Path::new("bla/../a.rs"), + ]; let mut output = Vec::new(); - generate_with_defaults(input, &mut output, root, origin, None).unwrap(); + generate_with_defaults(&paths, &mut output, root, origin, None).unwrap(); let output = String::from_utf8(output).unwrap(); assert_eq!(output.matches("## File: a.rs").count(), 1); @@ -299,11 +276,11 @@ mod tests { fn project_title_is_derived_from_root_by_default_even_if_directory_does_not_exist() { let temp_dir = tempdir().unwrap(); let origin_base = temp_dir.path(); - let input = Cursor::new(b""); + let paths = []; let mut output = Vec::new(); let root = temp_dir.path().join("repo2markdown"); - generate_with_defaults(input, &mut output, &root, origin_base, None).unwrap(); + generate_with_defaults(&paths, &mut output, &root, origin_base, None).unwrap(); let output_str = String::from_utf8(output).unwrap(); @@ -336,7 +313,7 @@ mod tests { let file_path = origin_base.join("big.txt"); fs::write(&file_path, "A".repeat(10)).unwrap(); - let input = Cursor::new(b"big.txt\0"); + let paths = [Path::new("big.txt")]; let mut output = Vec::new(); let config = RenderOptions { @@ -346,7 +323,7 @@ mod tests { }; generate_markdown_from_paths( - input, + &paths, &mut output, config, root, |
