dkdc_common/
lib.rs

1/// Common utilities for dkdc
2pub mod version;
3
4use anyhow::Result;
5
6/// Gitignore pattern handling
7pub mod gitignore {
8    use std::path::Path;
9
10    /// Load gitignore patterns from a directory
11    pub fn load_patterns(base_path: &Path) -> Vec<String> {
12        let mut patterns = vec![
13            // Always ignore .git
14            ".git".to_string(),
15            ".git/**".to_string(),
16        ];
17
18        // Load .gitignore if it exists
19        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    /// Check if a path should be ignored based on patterns
35    pub fn should_ignore(path: &Path, patterns: &[String]) -> bool {
36        let path_str = path.to_string_lossy();
37
38        for pattern in patterns {
39            // Simple pattern matching (not full gitignore syntax)
40            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                // Exact match or prefix
51                if &*path_str == pattern || path_str.starts_with(&format!("{}/", pattern)) {
52                    return true;
53                }
54            }
55        }
56
57        false
58    }
59
60    /// Basic glob matching for * only
61    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                // Pattern doesn't start with *, must match beginning
72                if !text.starts_with(part) {
73                    return false;
74                }
75                pos = part.len();
76            } else if i == parts.len() - 1 && !part.is_empty() {
77                // Pattern doesn't end with *, must match end
78                if !text[pos..].ends_with(part) {
79                    return false;
80                }
81            } else if !part.is_empty() {
82                // Find the part in the remaining text
83                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
95/// Format byte sizes for human readability
96pub 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
113/// Validate paths and names
114pub fn validate_name(name: &str) -> Result<()> {
115    if name.is_empty() {
116        anyhow::bail!("Name cannot be empty");
117    }
118
119    // Disallow path separators in names
120    if name.contains('/') || name.contains('\\') {
121        anyhow::bail!("Name cannot contain path separators");
122    }
123
124    // Disallow special characters that could cause issues
125    if name.contains('\0') {
126        anyhow::bail!("Name cannot contain null characters");
127    }
128
129    Ok(())
130}