1use std::fs;
2use std::path::Path;
3
4use anyhow::{Context, Result};
5
6fn detect_formats(dir: &Path) -> Vec<&'static str> {
8 let mut formats = Vec::new();
9 let check = |ext: &str| -> bool {
10 walkdir(dir)
11 .into_iter()
12 .any(|e| e.path().extension().and_then(|e| e.to_str()) == Some(ext))
13 };
14 fn walkdir(dir: &Path) -> Vec<fs::DirEntry> {
16 let mut entries = Vec::new();
17 if let Ok(rd) = fs::read_dir(dir) {
18 for entry in rd.flatten() {
19 let path = entry.path();
20 if path.is_dir()
21 && !path
22 .file_name()
23 .is_some_and(|n| n.to_string_lossy().starts_with('.'))
24 {
25 entries.extend(walkdir(&path));
26 } else {
27 entries.push(entry);
28 }
29 }
30 }
31 entries
32 }
33
34 if check("org") {
35 formats.push("org");
36 }
37 if check("tex") || check("latex") {
38 formats.push("latex");
39 }
40 if check("md") || check("markdown") {
41 formats.push("markdown");
42 }
43 formats
44}
45
46fn generate_config(formats: &[&str]) -> String {
48 let default_format = formats.first().copied().unwrap_or("plaintext");
49 format!(
50 r#"# snapper project configuration
51# https://snapper.turtletech.us/docs/reference/config/
52
53# Extra abbreviations (merged with built-in list)
54# extra_abbreviations = ["GROMACS", "LAMMPS", "DFT"]
55
56# File patterns to ignore
57# ignore = ["*.bib", "*.cls", "*.sty"]
58
59# Default format (auto-detected from extension if omitted)
60format = "{default_format}"
61
62# Maximum line width (0 = unlimited)
63max_width = 0
64"#
65 )
66}
67
68fn generate_gitattributes(formats: &[&str]) -> String {
70 let mut lines = String::from("# snapper semantic line break filter\n");
71 for fmt in formats {
72 let ext = match *fmt {
73 "org" => "*.org",
74 "latex" => "*.tex",
75 "markdown" => "*.md",
76 _ => continue,
77 };
78 lines.push_str(&format!("{ext} filter=snapper\n"));
79 }
80 lines
81}
82
83fn generate_precommit() -> &'static str {
85 r#"# Add to .pre-commit-config.yaml:
86- repo: https://github.com/TurtleTech-ehf/snapper
87 rev: v0.1.0
88 hooks:
89 - id: snapper
90"#
91}
92
93fn generate_apheleia(formats: &[&str]) -> String {
95 let mut s = String::from(";; Add to your Emacs config:\n(with-eval-after-load 'apheleia\n");
96 s.push_str(" (push '(snapper . (\"snapper\")) apheleia-formatters)\n");
97 for fmt in formats {
98 let mode = match *fmt {
99 "org" => "org-mode",
100 "latex" => "latex-mode",
101 "markdown" => "markdown-mode",
102 _ => continue,
103 };
104 s.push_str(&format!(
105 " (push '({mode} . snapper) apheleia-mode-alist)\n"
106 ));
107 }
108 s.push_str(")\n");
109 s
110}
111
112pub fn run_init(dry_run: bool) -> Result<()> {
114 let cwd = std::env::current_dir()?;
115 let formats = detect_formats(&cwd);
116
117 eprintln!(
118 "Detected formats: {}",
119 if formats.is_empty() {
120 "none (will use plaintext defaults)".to_string()
121 } else {
122 formats.join(", ")
123 }
124 );
125
126 let config_content = generate_config(&formats);
128 let config_path = cwd.join(".snapperrc.toml");
129 if config_path.exists() {
130 eprintln!(" .snapperrc.toml already exists, skipping");
131 } else if dry_run {
132 eprintln!("\n--- .snapperrc.toml ---");
133 eprint!("{config_content}");
134 } else {
135 fs::write(&config_path, &config_content).context("failed to write .snapperrc.toml")?;
136 eprintln!(" Created .snapperrc.toml");
137 }
138
139 if !formats.is_empty() {
141 let ga_content = generate_gitattributes(&formats);
142 let ga_path = cwd.join(".gitattributes");
143 if dry_run {
144 eprintln!("\n--- .gitattributes (append) ---");
145 eprint!("{ga_content}");
146 } else if ga_path.exists() {
147 let existing = fs::read_to_string(&ga_path)?;
148 if !existing.contains("filter=snapper") {
149 fs::write(&ga_path, format!("{existing}\n{ga_content}"))
150 .context("failed to append .gitattributes")?;
151 eprintln!(" Appended to .gitattributes");
152 } else {
153 eprintln!(" .gitattributes already has snapper filter, skipping");
154 }
155 } else {
156 fs::write(&ga_path, &ga_content).context("failed to write .gitattributes")?;
157 eprintln!(" Created .gitattributes");
158 }
159 }
160
161 eprintln!("\n{}", generate_precommit());
163 eprintln!("{}", generate_apheleia(&formats));
164
165 eprintln!("To enable the git smudge/clean filter, run:");
167 eprintln!(" git config filter.snapper.clean \"snapper\"");
168 eprintln!(" git config filter.snapper.smudge cat");
169
170 Ok(())
171}