A human-friendly DSL for ATProto Lexicons
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Allow .mlf/lexicons to be part of the workspace

+156 -6
+7 -3
mlf-cli/src/generate/code.rs
··· 118 118 119 119 let namespace = extract_namespace(&file_path); 120 120 121 - // Create workspace with standard library 122 - let mut workspace = match mlf_lang::Workspace::with_std() { 121 + // Create workspace with standard library and .mlf cache 122 + let mlf_cache_dir = crate::config::find_project_root(&std::env::current_dir().unwrap()) 123 + .ok() 124 + .map(|root| crate::config::get_mlf_cache_dir(&root)); 125 + 126 + let mut workspace = match crate::workspace_ext::workspace_with_std_and_cache(mlf_cache_dir.as_deref()) { 123 127 Ok(ws) => ws, 124 128 Err(e) => { 125 129 errors.push(( 126 130 file_path.display().to_string(), 127 - format!("Failed to load standard library: {:?}", e), 131 + format!("Failed to load workspace: {}", e), 128 132 )); 129 133 continue; 130 134 }
+7 -3
mlf-cli/src/generate/lexicon.rs
··· 89 89 90 90 let namespace = extract_namespace(&file_path); 91 91 92 - // Create workspace with standard library for inline type resolution 93 - let mut workspace = match mlf_lang::Workspace::with_std() { 92 + // Create workspace with standard library and .mlf cache for inline type resolution 93 + let mlf_cache_dir = crate::config::find_project_root(&std::env::current_dir().unwrap()) 94 + .ok() 95 + .map(|root| crate::config::get_mlf_cache_dir(&root)); 96 + 97 + let mut workspace = match crate::workspace_ext::workspace_with_std_and_cache(mlf_cache_dir.as_deref()) { 94 98 Ok(ws) => ws, 95 99 Err(e) => { 96 - errors.push((file_path.display().to_string(), format!("Failed to load standard library: {:?}", e))); 100 + errors.push((file_path.display().to_string(), format!("Failed to load workspace: {}", e))); 97 101 continue; 98 102 } 99 103 };
+1
mlf-cli/src/main.rs
··· 7 7 mod config; 8 8 mod fetch; 9 9 mod generate; 10 + mod workspace_ext; 10 11 11 12 // Import optional code generator plugins 12 13 // These are automatically registered via inventory when the feature is enabled
+141
mlf-cli/src/workspace_ext.rs
··· 1 + /// Extensions to Workspace for CLI-specific functionality like loading from filesystem 2 + use mlf_lang::Workspace; 3 + use std::path::Path; 4 + 5 + /// Load MLF files from a directory into the workspace 6 + pub fn load_mlf_directory(workspace: &mut Workspace, dir: &Path) -> Result<(), String> { 7 + if !dir.exists() { 8 + // Directory doesn't exist, that's ok - just return 9 + return Ok(()); 10 + } 11 + 12 + // Recursively find all .mlf files 13 + let mlf_files = find_mlf_files(dir)?; 14 + 15 + for file_path in mlf_files { 16 + // Read the file 17 + let content = std::fs::read_to_string(&file_path) 18 + .map_err(|e| format!("Failed to read {}: {}", file_path.display(), e))?; 19 + 20 + // Convert file path to namespace 21 + // e.g., ".mlf/lexicons/mlf/stream.place.mlf" -> "stream.place" 22 + let namespace = extract_namespace_from_path(&file_path, dir)?; 23 + 24 + // Parse the lexicon 25 + let lexicon = mlf_lang::parse_lexicon(&content) 26 + .map_err(|e| format!("Failed to parse {}: {:?}", file_path.display(), e))?; 27 + 28 + // Add to workspace (merge if already exists) 29 + if workspace.has_module(&namespace) { 30 + // Module already exists, we could merge or skip 31 + // For now, skip to avoid conflicts 32 + continue; 33 + } 34 + 35 + workspace 36 + .add_module(namespace.clone(), lexicon) 37 + .map_err(|e| format!("Failed to add module {}: {:?}", namespace, e))?; 38 + } 39 + 40 + Ok(()) 41 + } 42 + 43 + /// Find all .mlf files recursively in a directory 44 + fn find_mlf_files(dir: &Path) -> Result<Vec<std::path::PathBuf>, String> { 45 + let mut files = Vec::new(); 46 + 47 + let entries = std::fs::read_dir(dir) 48 + .map_err(|e| format!("Failed to read directory {}: {}", dir.display(), e))?; 49 + 50 + for entry in entries { 51 + let entry = entry.map_err(|e| format!("Failed to read entry: {}", e))?; 52 + let path = entry.path(); 53 + 54 + if path.is_dir() { 55 + // Recurse into subdirectory 56 + files.extend(find_mlf_files(&path)?); 57 + } else if path.extension().and_then(|s| s.to_str()) == Some("mlf") { 58 + files.push(path); 59 + } 60 + } 61 + 62 + Ok(files) 63 + } 64 + 65 + /// Extract namespace from file path relative to base directory 66 + /// e.g., base=".mlf/lexicons/mlf", path=".mlf/lexicons/mlf/stream.place.mlf" -> "stream.place" 67 + fn extract_namespace_from_path(path: &Path, base: &Path) -> Result<String, String> { 68 + let relative = path 69 + .strip_prefix(base) 70 + .map_err(|e| format!("Failed to strip prefix: {}", e))?; 71 + 72 + // Convert path to string 73 + let path_str = relative 74 + .to_str() 75 + .ok_or_else(|| "Non-UTF8 path".to_string())?; 76 + 77 + // Remove .mlf extension 78 + let without_ext = path_str 79 + .strip_suffix(".mlf") 80 + .unwrap_or(path_str); 81 + 82 + // Replace path separators with dots 83 + // e.g., "stream/place/foo.mlf" -> "stream.place.foo" 84 + let namespace = without_ext.replace(std::path::MAIN_SEPARATOR, "."); 85 + 86 + Ok(namespace) 87 + } 88 + 89 + /// Create a workspace with std library AND .mlf cache if it exists 90 + pub fn workspace_with_std_and_cache( 91 + mlf_cache_dir: Option<&Path>, 92 + ) -> Result<Workspace, String> { 93 + // Start with std library 94 + let mut workspace = Workspace::with_std() 95 + .map_err(|e| format!("Failed to load std library: {:?}", e))?; 96 + 97 + // Load from .mlf cache if provided 98 + if let Some(cache_dir) = mlf_cache_dir { 99 + let mlf_dir = cache_dir.join("lexicons/mlf"); 100 + load_mlf_directory(&mut workspace, &mlf_dir)?; 101 + } 102 + 103 + Ok(workspace) 104 + } 105 + 106 + /// Extension trait for Workspace to check if a module exists 107 + pub trait WorkspaceExt { 108 + fn has_module(&self, namespace: &str) -> bool; 109 + } 110 + 111 + impl WorkspaceExt for Workspace { 112 + fn has_module(&self, namespace: &str) -> bool { 113 + // This requires exposing the modules field or adding a method to mlf-lang 114 + // For now, we'll just try to add and catch the error 115 + // TODO: Add a proper has_module method to Workspace 116 + false 117 + } 118 + } 119 + 120 + #[cfg(test)] 121 + mod tests { 122 + use super::*; 123 + 124 + #[test] 125 + fn test_extract_namespace() { 126 + let base = Path::new(".mlf/lexicons/mlf"); 127 + let path = Path::new(".mlf/lexicons/mlf/stream.place.mlf"); 128 + let namespace = extract_namespace_from_path(path, base).unwrap(); 129 + assert_eq!(namespace, "stream.place"); 130 + } 131 + 132 + #[test] 133 + fn test_extract_namespace_nested() { 134 + let base = Path::new(".mlf/lexicons/mlf"); 135 + let path = Path::new(".mlf/lexicons/mlf/com/atproto/admin/defs.mlf"); 136 + let namespace = extract_namespace_from_path(path, base).unwrap(); 137 + // On Unix: "com/atproto/admin/defs" -> "com.atproto.admin.defs" 138 + // Note: This depends on the directory structure 139 + assert!(namespace.contains("com")); 140 + } 141 + }