Skip to main content

gitprint/
lib.rs

1pub mod cli;
2pub mod defaults;
3pub mod error;
4pub mod filter;
5pub mod git;
6pub mod highlight;
7pub mod pdf;
8pub mod types;
9
10use std::path::Path;
11
12use crate::error::Error;
13use crate::types::Config;
14
15/// Run the full pipeline: git → filter → highlight → PDF.
16/// Files are processed one at a time to keep memory usage low.
17pub fn run(config: &Config) -> Result<(), Error> {
18    let repo_path = git::verify_repo(&config.repo_path)?;
19    let mut metadata = git::get_metadata(&repo_path, config)?;
20
21    let all_paths = git::list_tracked_files(&repo_path, config)?;
22    let file_filter = filter::FileFilter::new(&config.include_patterns, &config.exclude_patterns)?;
23    let mut paths: Vec<_> = file_filter.filter_paths(all_paths).collect();
24    paths.sort();
25
26    let highlighter = highlight::Highlighter::new(&config.theme)?;
27    let mut doc = pdf::create_document(config)?;
28
29    // First pass: collect line counts per file for TOC and cover page.
30    // Reads each file once but only keeps the line count, not the content.
31    let line_counts: Vec<usize> = paths
32        .iter()
33        .map(|p| count_lines(&repo_path, p, config))
34        .collect();
35
36    let toc_entries: Vec<(&Path, usize)> = paths
37        .iter()
38        .zip(&line_counts)
39        .map(|(p, &n)| (p.as_path(), n))
40        .collect();
41
42    metadata.file_count = toc_entries.len();
43    metadata.total_lines = line_counts.iter().sum();
44
45    // Render front matter
46    pdf::cover::render(&mut doc, &metadata);
47
48    if config.toc {
49        pdf::toc::render(&mut doc, &toc_entries);
50    }
51    if config.file_tree {
52        pdf::tree::render(&mut doc, &paths);
53    }
54
55    // Second pass: read, highlight, and render each file one at a time.
56    // Only one file's content + highlighted tokens live in memory at once.
57    paths.iter().zip(&line_counts).for_each(|(path, &lines)| {
58        if let Some(content) = read_text_file(&repo_path, path, config) {
59            let highlighted = highlighter.highlight_lines(&content, path);
60            let display_path = path.display().to_string();
61            pdf::code::render_file(&mut doc, &display_path, highlighted, lines, !config.no_line_numbers, config.font_size as u8);
62            // `content` and highlighted iterator are dropped here
63        }
64    });
65
66    pdf::write_pdf(doc, &config.output_path)?;
67
68    eprintln!(
69        "wrote {} files ({} lines) to {}",
70        metadata.file_count,
71        metadata.total_lines,
72        config.output_path.display()
73    );
74
75    Ok(())
76}
77
78/// Read a file and return its content only if it's valid text (not binary/minified).
79fn read_text_file(repo_path: &Path, path: &Path, config: &Config) -> Option<String> {
80    git::read_file_content(repo_path, path, config)
81        .ok()
82        .filter(|c| !filter::is_binary(c.as_bytes()))
83        .filter(|c| !filter::is_minified(c))
84}
85
86/// Count lines in a file without keeping its content in memory.
87fn count_lines(repo_path: &Path, path: &Path, config: &Config) -> usize {
88    read_text_file(repo_path, path, config)
89        .map(|c| c.lines().count())
90        .unwrap_or(0)
91}