1use std::fs::OpenOptions;
5use std::io::Write;
6use std::path::PathBuf;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum CloneBackend {
11 Apfs,
12 Ficlone,
13 Refs,
14 None,
15}
16
17impl CloneBackend {
18 fn as_str(self) -> &'static str {
19 match self {
20 CloneBackend::Apfs => "apfs",
21 CloneBackend::Ficlone => "ficlone",
22 CloneBackend::Refs => "refs",
23 CloneBackend::None => "none",
24 }
25 }
26}
27
28#[derive(Debug, Clone)]
30pub struct Stats {
31 pub cloned: usize,
33 pub cloned_directories: usize,
35 pub skipped: usize,
37 pub checked_out_by_git: usize,
39 pub source: Option<PathBuf>,
41 pub clone_backend: CloneBackend,
42 pub fell_back: bool,
44 pub fallback_reason: Option<String>,
45}
46
47impl Default for Stats {
48 fn default() -> Self {
49 Stats {
50 cloned: 0,
51 cloned_directories: 0,
52 skipped: 0,
53 checked_out_by_git: 0,
54 source: None,
55 clone_backend: CloneBackend::None,
56 fell_back: false,
57 fallback_reason: None,
58 }
59 }
60}
61
62impl Stats {
63 pub fn fall_back(&mut self, reason: impl Into<String>) {
65 self.fell_back = true;
66 self.fallback_reason = Some(reason.into());
67 }
68
69 fn to_json(&self) -> String {
70 let value = serde_json::json!({
71 "cloned": self.cloned,
72 "cloned_directories": self.cloned_directories,
73 "skipped": self.skipped,
74 "checked_out_by_git": self.checked_out_by_git,
75 "source": self.source.as_ref().map(|path| path.to_string_lossy()),
76 "clone_backend": self.clone_backend.as_str(),
77 "fell_back": self.fell_back,
78 "fallback_reason": self.fallback_reason,
79 });
80 value.to_string()
81 }
82
83 pub fn emit(&self) {
88 if std::env::var_os("SPROUT_STATS").is_none_or(|value| value != "1") {
89 return;
90 }
91 let json = self.to_json();
92 match std::env::var_os("SPROUT_STATS_FILE") {
93 Some(path) => {
94 if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(path) {
95 let _ = writeln!(file, "{json}");
96 }
97 }
98 None => {
99 let _ = writeln!(std::io::stderr(), "sprout-stats: {json}");
100 }
101 }
102 }
103}