1pub mod version;
3
4use anyhow::Result;
5
6pub mod gitignore {
8 use std::path::Path;
9
10 pub fn load_patterns(base_path: &Path) -> Vec<String> {
12 let mut patterns = vec![
13 ".git".to_string(),
15 ".git/**".to_string(),
16 ];
17
18 let gitignore_path = base_path.join(".gitignore");
20 if gitignore_path.exists() {
21 if let Ok(content) = std::fs::read_to_string(&gitignore_path) {
22 for line in content.lines() {
23 let line = line.trim();
24 if !line.is_empty() && !line.starts_with('#') {
25 patterns.push(line.to_string());
26 }
27 }
28 }
29 }
30
31 patterns
32 }
33
34 pub fn should_ignore(path: &Path, patterns: &[String]) -> bool {
36 let path_str = path.to_string_lossy();
37
38 for pattern in patterns {
39 if pattern.ends_with("/**") {
41 let prefix = &pattern[..pattern.len() - 3];
42 if path_str.starts_with(prefix) {
43 return true;
44 }
45 } else if pattern.contains('*') {
46 if glob_match(pattern, &path_str) {
47 return true;
48 }
49 } else {
50 if &*path_str == pattern || path_str.starts_with(&format!("{}/", pattern)) {
52 return true;
53 }
54 }
55 }
56
57 false
58 }
59
60 fn glob_match(pattern: &str, text: &str) -> bool {
62 let parts: Vec<&str> = pattern.split('*').collect();
63
64 if parts.is_empty() {
65 return false;
66 }
67
68 let mut pos = 0;
69 for (i, part) in parts.iter().enumerate() {
70 if i == 0 && !part.is_empty() {
71 if !text.starts_with(part) {
73 return false;
74 }
75 pos = part.len();
76 } else if i == parts.len() - 1 && !part.is_empty() {
77 if !text[pos..].ends_with(part) {
79 return false;
80 }
81 } else if !part.is_empty() {
82 if let Some(idx) = text[pos..].find(part) {
84 pos += idx + part.len();
85 } else {
86 return false;
87 }
88 }
89 }
90
91 true
92 }
93}
94
95pub fn format_size(bytes: usize) -> String {
97 const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
98 let mut size = bytes as f64;
99 let mut unit_index = 0;
100
101 while size >= 1024.0 && unit_index < UNITS.len() - 1 {
102 size /= 1024.0;
103 unit_index += 1;
104 }
105
106 if unit_index == 0 {
107 format!("{} {}", size as usize, UNITS[unit_index])
108 } else {
109 format!("{:.1} {}", size, UNITS[unit_index])
110 }
111}
112
113pub fn validate_name(name: &str) -> Result<()> {
115 if name.is_empty() {
116 anyhow::bail!("Name cannot be empty");
117 }
118
119 if name.contains('/') || name.contains('\\') {
121 anyhow::bail!("Name cannot contain path separators");
122 }
123
124 if name.contains('\0') {
126 anyhow::bail!("Name cannot contain null characters");
127 }
128
129 Ok(())
130}