···2929### Install
30303131```bash
3232-cargo install --path mlf-cli
3232+# Install with all code generators (default: TypeScript, Go, Rust)
3333+cargo install --path mlf-cli --all-features
3434+3535+# Install with only specific generators
3636+cargo install --path mlf-cli --no-default-features --features typescript,go
3737+3838+# Install with JSON generation only
3939+cargo install --path mlf-cli --no-default-features
3340```
34413535-### Generate JSON lexicons from MLF
4242+### Generate code from MLF
36433744```bash
4545+# Generate TypeScript types
4646+mlf generate code -g typescript -i examples/**/*.mlf -o output/
4747+4848+# Generate Go structs
4949+mlf generate code -g go -i examples/**/*.mlf -o output/
5050+5151+# Generate Rust structs with serde
5252+mlf generate code -g rust -i examples/**/*.mlf -o output/
5353+5454+# Generate JSON lexicons (always available)
5555+mlf generate code -g json -i examples/**/*.mlf -o output/
5656+# Or use the legacy command:
3857mlf generate lexicon -i examples/**/*.mlf -o output/
3958```
4059···4463mlf check examples/app.bsky.feed.post.mlf
4564```
46654747-### Validate generated lexicons
6666+### Validate JSON records
48674968```bash
5050-mlf validate output/app.bsky.feed.post.json
6969+mlf validate examples/app.bsky.feed.post.mlf record.json
5170```
52715372## Project layout
54735574```
5675mlf/
5757-├── mlf-cli/ # Command-line app
5858-├── mlf-lang/ # Parser and lexer (no_std compatible)
5959-├── mlf-codegen/ # JSON lexicon code generation
6060-├── mlf-validation/ # Lexicon validation
6161-├── mlf-diagnostics/ # Fancy error reporting
6262-├── mlf-wasm/ # WASM bindings for browser use
6363-├── tree-sitter-mlf/ # Tree-sitter grammar for syntax highlighting
6464-└── website/ # Docs and playground
7676+├── mlf-cli/ # Command-line app
7777+├── mlf-lang/ # Parser and lexer (no_std compatible)
7878+├── mlf-codegen/ # Core code generation with plugin system
7979+├── codegen-plugins/ # Language-specific code generators
8080+│ ├── mlf-codegen-typescript/ # TypeScript generator
8181+│ ├── mlf-codegen-go/ # Go generator
8282+│ └── mlf-codegen-rust/ # Rust generator
8383+├── mlf-validation/ # Lexicon validation
8484+├── mlf-diagnostics/ # Fancy error reporting
8585+├── mlf-wasm/ # WASM bindings for browser use
8686+├── tree-sitter-mlf/ # Tree-sitter grammar for syntax highlighting
8787+└── website/ # Docs and playground
8888+ └── mlf-playground-wasm/ # Playground WASM with all generators
6589```
66906791## Documentation
-12
codegen-plugins/mlf-codegen-go/src/lib.rs
···77impl GoGenerator {
88 pub const NAME: &'static str = "go";
991010- fn to_snake_case(&self, s: &str) -> String {
1111- // Simple camelCase to snake_case conversion
1212- let mut result = String::new();
1313- for (i, ch) in s.chars().enumerate() {
1414- if ch.is_uppercase() && i > 0 {
1515- result.push('_');
1616- }
1717- result.push(ch.to_lowercase().next().unwrap());
1818- }
1919- result
2020- }
2121-2210 fn generate_type(&self, ty: &Type, optional: bool, ctx: &GeneratorContext) -> Result<String, String> {
2311 let base_type = match ty {
2412 Type::Primitive { kind, .. } => match kind {
+1-1
codegen-plugins/mlf-codegen-rust/src/lib.rs
···6767 // Otherwise use serde_json::Value for flexibility
6868 "serde_json::Value".to_string()
6969 }
7070- Type::Object { fields, .. } => {
7070+ Type::Object { .. } => {
7171 // Inline struct types aren't idiomatic in Rust
7272 // We'd need to generate a named type
7373 // For now, use serde_json::Value
+30-7
mlf-cli/src/generate/code.rs
···11use miette::Diagnostic;
22-use mlf_codegen::plugin::{CodeGenerator, GeneratorContext};
22+use mlf_codegen::plugin::GeneratorContext;
33use std::path::{Path, PathBuf};
44use thiserror::Error;
55···46464747 #[error("Code generation failed: {0}")]
4848 #[diagnostic(code(mlf::generate::generation_failed))]
4949+ #[allow(dead_code)]
4950 GenerationFailed(String),
5051}
5152···216217}
217218218219fn extract_namespace(file_path: &Path) -> String {
219219- // Namespace is derived solely from the filename
220220- file_path
221221- .file_stem()
222222- .and_then(|s| s.to_str())
223223- .unwrap_or("unknown")
224224- .to_string()
220220+ // Extract namespace from path components
221221+ // e.g., com/atproto/admin/defs.mlf -> com.atproto.admin.defs
222222+223223+ let mut components = Vec::new();
224224+225225+ for component in file_path.components() {
226226+ match component {
227227+ std::path::Component::Normal(os_str) => {
228228+ if let Some(s) = os_str.to_str() {
229229+ components.push(s);
230230+ }
231231+ }
232232+ _ => continue, // Skip ., .., /, etc.
233233+ }
234234+ }
235235+236236+ // Remove the .mlf extension from the last component if present
237237+ if let Some(last) = components.last_mut() {
238238+ if let Some(stem) = last.strip_suffix(".mlf") {
239239+ *last = stem;
240240+ }
241241+ }
242242+243243+ if components.is_empty() {
244244+ return "unknown".to_string();
245245+ }
246246+247247+ components.join(".")
225248}
+29-6
mlf-cli/src/generate/lexicon.rs
···66pub enum GenerateError {
77 #[error("Failed to read file: {path}")]
88 #[diagnostic(code(mlf::generate::read_file))]
99+ #[allow(dead_code)]
910 ReadFile {
1011 path: String,
1112 #[source]
···153154}
154155155156fn extract_namespace(file_path: &Path) -> String {
156156- // Namespace is derived solely from the filename
157157- file_path
158158- .file_stem()
159159- .and_then(|s| s.to_str())
160160- .unwrap_or("unknown")
161161- .to_string()
157157+ // Extract namespace from path components
158158+ // e.g., com/atproto/admin/defs.mlf -> com.atproto.admin.defs
159159+160160+ let mut components = Vec::new();
161161+162162+ for component in file_path.components() {
163163+ match component {
164164+ std::path::Component::Normal(os_str) => {
165165+ if let Some(s) = os_str.to_str() {
166166+ components.push(s);
167167+ }
168168+ }
169169+ _ => continue, // Skip ., .., /, etc.
170170+ }
171171+ }
172172+173173+ // Remove the .mlf extension from the last component if present
174174+ if let Some(last) = components.last_mut() {
175175+ if let Some(stem) = last.strip_suffix(".mlf") {
176176+ *last = stem;
177177+ }
178178+ }
179179+180180+ if components.is_empty() {
181181+ return "unknown".to_string();
182182+ }
183183+184184+ components.join(".")
162185}