Skip to main content

gitprint/
types.rs

1use std::path::PathBuf;
2
3/// Paper size for PDF output.
4#[derive(Debug, Clone, Copy, clap::ValueEnum)]
5pub enum PaperSize {
6    A4,
7    Letter,
8    Legal,
9}
10
11/// Configuration for a gitprint run.
12#[derive(Debug, Clone)]
13pub struct Config {
14    pub repo_path: PathBuf,
15    pub output_path: PathBuf,
16    pub include_patterns: Vec<String>,
17    pub exclude_patterns: Vec<String>,
18    pub theme: String,
19    pub font_size: f64,
20    pub no_line_numbers: bool,
21    pub toc: bool,
22    pub file_tree: bool,
23    pub branch: Option<String>,
24    pub commit: Option<String>,
25    pub paper_size: PaperSize,
26    pub landscape: bool,
27    /// Original remote URL when input was a remote repository, used for GitHub links.
28    pub remote_url: Option<String>,
29}
30
31impl Config {
32    #[cfg(test)]
33    pub(crate) fn test_default() -> Self {
34        Self {
35            repo_path: PathBuf::from("."),
36            output_path: PathBuf::from("/tmp/gitprint-test.pdf"),
37            include_patterns: vec![],
38            exclude_patterns: vec![],
39            theme: "InspiredGitHub".to_string(),
40            font_size: 8.0,
41            no_line_numbers: false,
42            toc: true,
43            file_tree: true,
44            branch: None,
45            commit: None,
46            paper_size: PaperSize::A4,
47            landscape: false,
48            remote_url: None,
49        }
50    }
51}
52
53/// Metadata extracted from a git repository.
54#[derive(Debug, Clone)]
55pub struct RepoMetadata {
56    pub name: String,
57    pub branch: String,
58    pub commit_hash: String,
59    pub commit_hash_short: String,
60    pub commit_date: String,
61    pub commit_message: String,
62    pub commit_author: String,
63    /// Email address of the last committer.
64    pub commit_author_email: String,
65    pub file_count: usize,
66    pub total_lines: usize,
67    /// Filesystem owner of the input path (local paths only).
68    pub fs_owner: Option<String>,
69    /// Filesystem group of the input path (local paths only).
70    pub fs_group: Option<String>,
71    /// UTC timestamp when this PDF was generated.
72    pub generated_at: String,
73    /// Human-readable size of the git-tracked content (e.g. "4.2 MB").
74    /// Computed from `git ls-tree -r -l`; empty for non-git paths.
75    pub repo_size: String,
76    /// Human-readable filesystem disk usage of the input path (e.g. "5.1 MB").
77    /// Computed from `du -sh`; empty for remote repos.
78    pub fs_size: String,
79    /// Remote URL detected from git config (e.g. `git remote get-url origin`).
80    /// Used to generate commit/author links even when `Config::remote_url` is None.
81    pub detected_remote_url: Option<String>,
82    /// Absolute filesystem path to the repo root (local repos only, `None` for remote clones).
83    /// Used to generate `file://` links on the cover page.
84    pub repo_absolute_path: Option<PathBuf>,
85}
86
87/// An RGB color value.
88#[derive(Debug, Clone, Copy)]
89pub struct RgbColor {
90    pub r: u8,
91    pub g: u8,
92    pub b: u8,
93}
94
95/// A single syntax-highlighted token with styling information.
96#[derive(Debug, Clone)]
97pub struct HighlightedToken {
98    pub text: String,
99    pub color: RgbColor,
100    pub bold: bool,
101    pub italic: bool,
102}
103
104/// A line of syntax-highlighted tokens.
105#[derive(Debug, Clone)]
106pub struct HighlightedLine {
107    pub line_number: usize,
108    pub tokens: Vec<HighlightedToken>,
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn test_config_test_default() {
117        let config = Config::test_default();
118        assert_eq!(config.repo_path, PathBuf::from("."));
119        assert_eq!(config.theme, "InspiredGitHub");
120        assert_eq!(config.font_size, 8.0);
121        assert!(config.toc);
122        assert!(config.file_tree);
123        assert!(!config.no_line_numbers);
124        assert!(!config.landscape);
125        assert!(config.branch.is_none());
126        assert!(config.commit.is_none());
127    }
128
129    #[test]
130    fn test_repo_metadata_clone() {
131        let meta = RepoMetadata {
132            name: "test".to_string(),
133            branch: "main".to_string(),
134            commit_hash: "abc123".to_string(),
135            commit_hash_short: "abc1234".to_string(),
136            commit_date: "2024-01-01".to_string(),
137            commit_message: "init".to_string(),
138            commit_author: "Alice".to_string(),
139            commit_author_email: "alice@example.com".to_string(),
140            file_count: 10,
141            total_lines: 500,
142            fs_owner: None,
143            fs_group: None,
144            generated_at: "2024-01-15 10:00:00 UTC".to_string(),
145            repo_size: "1.2 MB".to_string(),
146            fs_size: "1.5 MB".to_string(),
147            detected_remote_url: None,
148            repo_absolute_path: None,
149        };
150        let cloned = meta.clone();
151        assert_eq!(cloned.name, "test");
152        assert_eq!(cloned.file_count, 10);
153    }
154
155    #[test]
156    fn test_rgb_color_copy() {
157        let color = RgbColor {
158            r: 255,
159            g: 128,
160            b: 0,
161        };
162        let copied = color;
163        assert_eq!(copied.r, 255);
164        assert_eq!(copied.g, 128);
165        assert_eq!(copied.b, 0);
166        // Original still usable (Copy trait)
167        assert_eq!(color.r, 255);
168    }
169
170    #[test]
171    fn test_highlighted_line_structure() {
172        let line = HighlightedLine {
173            line_number: 42,
174            tokens: vec![
175                HighlightedToken {
176                    text: "fn".to_string(),
177                    color: RgbColor { r: 0, g: 0, b: 255 },
178                    bold: true,
179                    italic: false,
180                },
181                HighlightedToken {
182                    text: " main".to_string(),
183                    color: RgbColor { r: 0, g: 0, b: 0 },
184                    bold: false,
185                    italic: false,
186                },
187            ],
188        };
189        assert_eq!(line.line_number, 42);
190        assert_eq!(line.tokens.len(), 2);
191        assert!(line.tokens[0].bold);
192        assert!(!line.tokens[1].bold);
193    }
194}