1use std::collections::BTreeSet;
2use std::fs;
3use std::path::{Path, PathBuf};
4
5use crate::error::{Error, Result};
6use crate::side_repo::SideRepo;
7
8pub struct TrackedPaths {
10 file_path: PathBuf,
11 paths: BTreeSet<PathBuf>,
12}
13
14impl TrackedPaths {
15 pub fn load(repo: &SideRepo) -> Result<Self> {
21 let file_path = repo.tracked_file();
22 let paths = if file_path.exists() {
23 let content = fs::read_to_string(&file_path).map_err(|e| Error::ReadFile {
24 path: file_path.clone(),
25 source: e,
26 })?;
27 content
28 .lines()
29 .filter(|line| !line.trim().is_empty())
30 .map(PathBuf::from)
31 .collect()
32 } else {
33 BTreeSet::new()
34 };
35
36 Ok(Self { file_path, paths })
37 }
38
39 pub fn save(&self) -> Result<()> {
45 if let Some(parent) = self.file_path.parent()
47 && !parent.exists()
48 {
49 fs::create_dir_all(parent).map_err(|e| Error::CreateDir {
50 path: parent.to_path_buf(),
51 source: e,
52 })?;
53 }
54
55 let content: String = self
56 .paths
57 .iter()
58 .map(|p| p.to_string_lossy().to_string())
59 .collect::<Vec<_>>()
60 .join("\n");
61
62 fs::write(&self.file_path, content).map_err(|e| Error::WriteFile {
63 path: self.file_path.clone(),
64 source: e,
65 })
66 }
67
68 pub fn add(&mut self, path: &Path) -> bool {
70 self.paths.insert(path.to_path_buf())
71 }
72
73 pub fn remove(&mut self, path: &Path) -> bool {
75 self.paths.remove(path)
76 }
77
78 #[must_use]
80 pub fn contains(&self, path: &Path) -> bool {
81 self.paths.contains(path)
82 }
83
84 #[must_use]
86 pub fn is_empty(&self) -> bool {
87 self.paths.is_empty()
88 }
89
90 #[must_use]
92 pub const fn paths(&self) -> &BTreeSet<PathBuf> {
93 &self.paths
94 }
95
96 #[must_use]
99 pub fn expand(&self, work_tree: &Path) -> Vec<PathBuf> {
100 let mut files = Vec::new();
101
102 for path in &self.paths {
103 let full_path = work_tree.join(path);
104 if full_path.is_file() {
105 files.push(path.clone());
106 } else if full_path.is_dir() {
107 Self::walk_dir(&full_path, path, &mut files);
108 }
109 }
111
112 files
113 }
114
115 fn walk_dir(dir: &Path, relative_base: &Path, files: &mut Vec<PathBuf>) {
117 if let Ok(entries) = fs::read_dir(dir) {
118 for entry in entries.flatten() {
119 let entry_path = entry.path();
120 let relative = relative_base.join(entry.file_name());
121
122 if entry_path.is_file() {
123 files.push(relative);
124 } else if entry_path.is_dir() {
125 Self::walk_dir(&entry_path, &relative, files);
126 }
127 }
128 }
129 }
130}