Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol. wisp.place
at main 4.7 kB view raw
1use globset::{Glob, GlobSet, GlobSetBuilder}; 2use serde::{Deserialize, Serialize}; 3use std::path::Path; 4use miette::IntoDiagnostic; 5 6#[derive(Debug, Deserialize, Serialize)] 7struct IgnoreConfig { 8 patterns: Vec<String>, 9} 10 11/// Load ignore patterns from the default .wispignore.json file 12fn load_default_patterns() -> miette::Result<Vec<String>> { 13 // Path to the default ignore patterns JSON file (in the monorepo root) 14 let default_json_path = concat!(env!("CARGO_MANIFEST_DIR"), "/../.wispignore.json"); 15 16 match std::fs::read_to_string(default_json_path) { 17 Ok(contents) => { 18 let config: IgnoreConfig = serde_json::from_str(&contents).into_diagnostic()?; 19 Ok(config.patterns) 20 } 21 Err(_) => { 22 // If the default file doesn't exist, return hardcoded patterns as fallback 23 eprintln!("⚠️ Default .wispignore.json not found, using hardcoded patterns"); 24 Ok(get_hardcoded_patterns()) 25 } 26 } 27} 28 29/// Hardcoded fallback patterns (same as in .wispignore.json) 30fn get_hardcoded_patterns() -> Vec<String> { 31 vec![ 32 ".git".to_string(), 33 ".git/**".to_string(), 34 ".github".to_string(), 35 ".github/**".to_string(), 36 ".gitlab".to_string(), 37 ".gitlab/**".to_string(), 38 ".DS_Store".to_string(), 39 ".wisp.metadata.json".to_string(), 40 ".env".to_string(), 41 ".env.*".to_string(), 42 "node_modules".to_string(), 43 "node_modules/**".to_string(), 44 "Thumbs.db".to_string(), 45 "desktop.ini".to_string(), 46 "._*".to_string(), 47 ".Spotlight-V100".to_string(), 48 ".Spotlight-V100/**".to_string(), 49 ".Trashes".to_string(), 50 ".Trashes/**".to_string(), 51 ".fseventsd".to_string(), 52 ".fseventsd/**".to_string(), 53 ".cache".to_string(), 54 ".cache/**".to_string(), 55 ".temp".to_string(), 56 ".temp/**".to_string(), 57 ".tmp".to_string(), 58 ".tmp/**".to_string(), 59 "__pycache__".to_string(), 60 "__pycache__/**".to_string(), 61 "*.pyc".to_string(), 62 ".venv".to_string(), 63 ".venv/**".to_string(), 64 "venv".to_string(), 65 "venv/**".to_string(), 66 "env".to_string(), 67 "env/**".to_string(), 68 "*.swp".to_string(), 69 "*.swo".to_string(), 70 "*~".to_string(), 71 ".tangled".to_string(), 72 ".tangled/**".to_string(), 73 ] 74} 75 76/// Load custom ignore patterns from a .wispignore file in the given directory 77fn load_wispignore_file(dir_path: &Path) -> miette::Result<Vec<String>> { 78 let wispignore_path = dir_path.join(".wispignore"); 79 80 if !wispignore_path.exists() { 81 return Ok(Vec::new()); 82 } 83 84 let contents = std::fs::read_to_string(&wispignore_path).into_diagnostic()?; 85 86 // Parse gitignore-style file (one pattern per line, # for comments) 87 let patterns: Vec<String> = contents 88 .lines() 89 .filter_map(|line| { 90 let line = line.trim(); 91 // Skip empty lines and comments 92 if line.is_empty() || line.starts_with('#') { 93 None 94 } else { 95 Some(line.to_string()) 96 } 97 }) 98 .collect(); 99 100 if !patterns.is_empty() { 101 println!("Loaded {} custom patterns from .wispignore", patterns.len()); 102 } 103 104 Ok(patterns) 105} 106 107/// Build a GlobSet from a list of patterns 108fn build_globset(patterns: Vec<String>) -> miette::Result<GlobSet> { 109 let mut builder = GlobSetBuilder::new(); 110 111 for pattern in patterns { 112 let glob = Glob::new(&pattern).into_diagnostic()?; 113 builder.add(glob); 114 } 115 116 let globset = builder.build().into_diagnostic()?; 117 Ok(globset) 118} 119 120/// IgnoreMatcher handles checking if paths should be ignored 121pub struct IgnoreMatcher { 122 globset: GlobSet, 123} 124 125impl IgnoreMatcher { 126 /// Create a new IgnoreMatcher for the given directory 127 /// Loads default patterns and any custom .wispignore file 128 pub fn new(dir_path: &Path) -> miette::Result<Self> { 129 let mut all_patterns = load_default_patterns()?; 130 131 // Load custom patterns from .wispignore 132 let custom_patterns = load_wispignore_file(dir_path)?; 133 all_patterns.extend(custom_patterns); 134 135 let globset = build_globset(all_patterns)?; 136 137 Ok(Self { globset }) 138 } 139 140 /// Check if the given path (relative to site root) should be ignored 141 pub fn is_ignored(&self, path: &str) -> bool { 142 self.globset.is_match(path) 143 } 144 145 /// Check if a filename should be ignored (checks just the filename, not full path) 146 pub fn is_filename_ignored(&self, filename: &str) -> bool { 147 self.globset.is_match(filename) 148 } 149}