From cd07753d63334496a66890ba68a6d8629d0dcdc8 Mon Sep 17 00:00:00 2001 From: Matt Stavola Date: Wed, 8 Oct 2025 23:59:25 -0400 Subject: [PATCH] Format fixes, init command, and updated docs --- mlf-cli/src/config.rs | 22 +- mlf-cli/src/fetch.rs | 10 +- mlf-cli/src/init.rs | 58 +++++ mlf-cli/src/main.rs | 9 + mlf.toml | 4 +- website/content/docs/cli/01-installation.md | 30 ++- website/content/docs/cli/03-init.md | 202 ++++++++++++++++++ .../docs/cli/{03-check.md => 04-check.md} | 2 +- .../cli/{04-validate.md => 05-validate.md} | 2 +- .../cli/{05-generate.md => 06-generate.md} | 2 +- .../docs/cli/{06-fetch.md => 07-fetch.md} | 2 +- .../docs/cli/{07-errors.md => 08-errors.md} | 2 +- website/content/docs/cli/_index.md | 13 +- 13 files changed, 333 insertions(+), 25 deletions(-) create mode 100644 mlf-cli/src/init.rs create mode 100644 website/content/docs/cli/03-init.md rename website/content/docs/cli/{03-check.md => 04-check.md} (99%) rename website/content/docs/cli/{04-validate.md => 05-validate.md} (99%) rename website/content/docs/cli/{05-generate.md => 06-generate.md} (99%) rename website/content/docs/cli/{06-fetch.md => 07-fetch.md} (99%) rename website/content/docs/cli/{07-errors.md => 08-errors.md} (99%) diff --git a/mlf-cli/src/config.rs b/mlf-cli/src/config.rs index 6f33803..20e099e 100644 --- a/mlf-cli/src/config.rs +++ b/mlf-cli/src/config.rs @@ -19,11 +19,11 @@ pub struct MlfConfig { #[serde(default)] pub source: SourceConfig, - #[serde(default)] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub output: Vec, #[serde(default)] - pub dependencies: Vec, + pub dependencies: DependenciesConfig, } #[derive(Debug, Serialize, Deserialize)] @@ -50,12 +50,26 @@ pub struct OutputConfig { pub directory: String, } +#[derive(Debug, Serialize, Deserialize)] +pub struct DependenciesConfig { + #[serde(default)] + pub dependencies: Vec, +} + +impl Default for DependenciesConfig { + fn default() -> Self { + Self { + dependencies: vec![], + } + } +} + impl Default for MlfConfig { fn default() -> Self { Self { source: SourceConfig::default(), output: vec![], - dependencies: vec![], + dependencies: DependenciesConfig::default(), } } } @@ -140,6 +154,6 @@ mod tests { let config = MlfConfig::default(); assert_eq!(config.source.directory, "./lexicons"); assert!(config.output.is_empty()); - assert!(config.dependencies.is_empty()); + assert!(config.dependencies.dependencies.is_empty()); } } diff --git a/mlf-cli/src/fetch.rs b/mlf-cli/src/fetch.rs index ee0639a..86175e2 100644 --- a/mlf-cli/src/fetch.rs +++ b/mlf-cli/src/fetch.rs @@ -162,17 +162,17 @@ fn fetch_all_dependencies(project_root: &std::path::Path) -> Result<(), FetchErr let config_path = project_root.join("mlf.toml"); let config = MlfConfig::load(&config_path).map_err(FetchError::NoProjectRoot)?; - if config.dependencies.is_empty() { + if config.dependencies.dependencies.is_empty() { println!("No dependencies found in mlf.toml"); return Ok(()); } - println!("Fetching {} dependencies...", config.dependencies.len()); + println!("Fetching {} dependencies...", config.dependencies.dependencies.len()); let mut errors = Vec::new(); let mut success_count = 0; - for dep in &config.dependencies { + for dep in &config.dependencies.dependencies { println!("\nFetching: {}", dep); match fetch_lexicon(dep, project_root) { Ok(()) => { @@ -207,12 +207,12 @@ fn save_dependency(project_root: &std::path::Path, nsid: &str) -> Result<(), Fet let config_path = project_root.join("mlf.toml"); let mut config = MlfConfig::load(&config_path).map_err(FetchError::NoProjectRoot)?; - if config.dependencies.contains(&nsid.to_string()) { + if config.dependencies.dependencies.contains(&nsid.to_string()) { println!("Dependency '{}' already in mlf.toml", nsid); return Ok(()); } - config.dependencies.push(nsid.to_string()); + config.dependencies.dependencies.push(nsid.to_string()); config.save(&config_path).map_err(FetchError::NoProjectRoot)?; println!("Added '{}' to dependencies in mlf.toml", nsid); diff --git a/mlf-cli/src/init.rs b/mlf-cli/src/init.rs new file mode 100644 index 0000000..c622c84 --- /dev/null +++ b/mlf-cli/src/init.rs @@ -0,0 +1,58 @@ +use crate::config::{init_mlf_cache, MlfConfig}; +use std::io::Write; + +pub fn run_init(skip_prompts: bool) -> Result<(), std::io::Error> { + let current_dir = std::env::current_dir()?; + let config_path = current_dir.join("mlf.toml"); + + // Check if mlf.toml already exists + if config_path.exists() { + eprintln!("mlf.toml already exists in current directory"); + eprintln!("Remove it first if you want to reinitialize"); + return Err(std::io::Error::new( + std::io::ErrorKind::AlreadyExists, + "mlf.toml already exists", + )); + } + + if !skip_prompts { + println!("Initialize MLF project in {}?", current_dir.display()); + println!("This will create:"); + println!(" - mlf.toml (project configuration)"); + println!(" - .mlf/ (cache directory for fetched lexicons)"); + println!(); + print!("Continue? (y/n): "); + std::io::stdout().flush()?; + + let mut input = String::new(); + std::io::stdin().read_line(&mut input)?; + + if input.trim().to_lowercase() != "y" { + println!("Cancelled"); + return Ok(()); + } + } + + // Create default mlf.toml + let config = MlfConfig::default(); + config + .save(&config_path) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + + println!("✓ Created mlf.toml"); + + // Initialize .mlf directory + init_mlf_cache(¤t_dir)?; + println!("✓ Initialized .mlf/ directory"); + + println!(); + println!("Project initialized successfully!"); + println!(); + println!("Next steps:"); + println!(" 1. Create MLF files in ./lexicons/"); + println!(" 2. Fetch dependencies: mlf fetch --save"); + println!(" 3. Check your lexicons: mlf check"); + println!(" 4. Generate code: mlf generate"); + + Ok(()) +} diff --git a/mlf-cli/src/main.rs b/mlf-cli/src/main.rs index fd7c67b..0429568 100644 --- a/mlf-cli/src/main.rs +++ b/mlf-cli/src/main.rs @@ -7,6 +7,7 @@ mod check; mod config; mod fetch; mod generate; +mod init; mod workspace_ext; // Import optional code generator plugins @@ -30,6 +31,11 @@ struct Cli { #[derive(Subcommand)] enum Commands { + Init { + #[arg(long, help = "Skip prompts and use defaults")] + yes: bool, + }, + Check { #[arg(help = "MLF lexicon file(s) to validate (glob patterns supported). If omitted, checks source directory from mlf.toml")] input: Vec, @@ -95,6 +101,9 @@ fn main() { let cli = Cli::parse(); let result: Result<(), miette::Report> = match cli.command { + Commands::Init { yes } => { + init::run_init(yes).into_diagnostic() + } Commands::Check { input } => { check::run_check(input).into_diagnostic() } diff --git a/mlf.toml b/mlf.toml index d4c5995..72c1f66 100644 --- a/mlf.toml +++ b/mlf.toml @@ -1,5 +1,7 @@ output = [] -dependencies = [] [source] directory = "./lexicons" + +[dependencies] +dependencies = [] diff --git a/website/content/docs/cli/01-installation.md b/website/content/docs/cli/01-installation.md index 8674445..491c854 100644 --- a/website/content/docs/cli/01-installation.md +++ b/website/content/docs/cli/01-installation.md @@ -67,11 +67,31 @@ mlf --version mlf --help ``` +## Initialize a Project + +After installation, initialize a new MLF project: + +```bash +mlf init +``` + +This creates: +- `mlf.toml` - Project configuration file +- `.mlf/` - Cache directory for fetched lexicons (automatically gitignored) +- `./lexicons/` - Default source directory for your MLF files + +Use `--yes` to skip the confirmation prompt: + +```bash +mlf init --yes +``` + ## Next Steps -Once installed, you can: +Once initialized, you can: -1. [Configure your project](02-configuration.md) with an `mlf.toml` file -2. [Check MLF files](03-check.md) for syntax and type errors -3. [Generate code](05-generate.md) in your preferred language -4. [Fetch remote lexicons](06-fetch.md) from ATProto repositories +1. Review your [project configuration](02-configuration.md) in `mlf.toml` +2. [Fetch remote lexicons](06-fetch.md) from ATProto repositories +3. Create MLF files in `./lexicons/` +4. [Check MLF files](03-check.md) for syntax and type errors +5. [Generate code](05-generate.md) in your preferred language diff --git a/website/content/docs/cli/03-init.md b/website/content/docs/cli/03-init.md new file mode 100644 index 0000000..43ab2a7 --- /dev/null +++ b/website/content/docs/cli/03-init.md @@ -0,0 +1,202 @@ ++++ +title = "Init Command" +description = "Initialize a new MLF project" +weight = 3 ++++ + +The `mlf init` command creates a new MLF project with default configuration and directory structure. + +## Usage + +```bash +# Interactive initialization (prompts for confirmation) +mlf init + +# Skip prompts +mlf init --yes +``` + +**Options:** +- `--yes` - Skip confirmation prompts and use defaults + +## What It Does + +Running `mlf init` creates: + +1. **mlf.toml** - Project configuration file with defaults: + ```toml + [source] + directory = "./lexicons" + + [dependencies] + dependencies = [] + ``` + +2. **.mlf/** - Cache directory structure: + ``` + .mlf/ + ├── .gitignore # Ignores all files except itself + ├── .lexicon-cache.toml # Metadata about fetched lexicons + └── lexicons/ + ├── json/ # Original JSON lexicons + └── mlf/ # Converted MLF format + ``` + +The `.mlf` directory is automatically added to `.gitignore` so fetched lexicons aren't committed to version control. + +## Interactive Mode + +Without `--yes`, init prompts for confirmation: + +```bash +$ mlf init +Initialize MLF project in /path/to/project? +This will create: + - mlf.toml (project configuration) + - .mlf/ (cache directory for fetched lexicons) + +Continue? (y/n): y +✓ Created mlf.toml +✓ Initialized .mlf/ directory + +Project initialized successfully! + +Next steps: + 1. Create MLF files in ./lexicons/ + 2. Fetch dependencies: mlf fetch --save + 3. Check your lexicons: mlf check + 4. Generate code: mlf generate +``` + +## Non-Interactive Mode + +Use `--yes` to skip prompts (useful for scripts and CI/CD): + +```bash +$ mlf init --yes +✓ Created mlf.toml +✓ Initialized .mlf/ directory + +Project initialized successfully! + +Next steps: + 1. Create MLF files in ./lexicons/ + 2. Fetch dependencies: mlf fetch --save + 3. Check your lexicons: mlf check + 4. Generate code: mlf generate +``` + +## Safety + +Init won't overwrite existing files: + +```bash +$ mlf init --yes +mlf.toml already exists in current directory +Remove it first if you want to reinitialize + × mlf.toml already exists +``` + +If you need to reinitialize, remove `mlf.toml` first: + +```bash +rm mlf.toml +mlf init --yes +``` + +## Typical Workflow + +After initialization, a typical workflow looks like: + +```bash +# 1. Initialize project +mlf init --yes + +# 2. Fetch dependencies +mlf fetch app.bsky --save +mlf fetch com.atproto --save + +# 3. Create your lexicons +mkdir -p lexicons/com/example +cat > lexicons/com/example/post.mlf << 'EOF' +record main { + text!: string constrained { + maxLength: 300, + }, + createdAt!: Datetime, +} +EOF + +# 4. Add output configurations to mlf.toml +cat >> mlf.toml << 'EOF' + +[[output]] +type = "lexicon" +directory = "./dist/lexicons" + +[[output]] +type = "typescript" +directory = "./src/types" +EOF + +# 5. Check for errors +mlf check + +# 6. Generate code +mlf generate +``` + +## Manual Setup Alternative + +If you prefer manual setup, you can create these files yourself: + +**mlf.toml:** +```toml +[source] +directory = "./lexicons" + +[dependencies] +dependencies = [] + +[[output]] +type = "lexicon" +directory = "./dist/lexicons" +``` + +**Directory structure:** +```bash +mkdir -p lexicons +mkdir -p .mlf/lexicons/{json,mlf} +echo "*\n!.gitignore\n" > .mlf/.gitignore +``` + +However, `mlf init` is recommended as it ensures everything is set up correctly. + +## CI/CD Integration + +Use `mlf init --yes` in CI pipelines: + +```yaml +# GitHub Actions example +- name: Initialize MLF Project + run: mlf init --yes + +- name: Fetch Dependencies + run: mlf fetch + +- name: Generate Code + run: mlf generate +``` + +## Exit Codes + +- `0` - Success +- `1` - Error (e.g., mlf.toml already exists, permission denied) + +## Tips + +1. **Always start with init** - It sets up the correct structure +2. **Use --yes in scripts** - Avoids hanging on prompts +3. **Commit mlf.toml** - Track your project configuration +4. **Don't commit .mlf/** - Let each developer fetch dependencies +5. **Customize after init** - Edit `mlf.toml` to add outputs and dependencies diff --git a/website/content/docs/cli/03-check.md b/website/content/docs/cli/04-check.md similarity index 99% rename from website/content/docs/cli/03-check.md rename to website/content/docs/cli/04-check.md index fdf338f..2930004 100644 --- a/website/content/docs/cli/03-check.md +++ b/website/content/docs/cli/04-check.md @@ -1,7 +1,7 @@ +++ title = "Check Command" description = "Validate MLF lexicon files" -weight = 3 +weight = 4 +++ The `mlf check` command validates MLF lexicon files for syntax and type errors. diff --git a/website/content/docs/cli/04-validate.md b/website/content/docs/cli/05-validate.md similarity index 99% rename from website/content/docs/cli/04-validate.md rename to website/content/docs/cli/05-validate.md index e8ce37c..972db81 100644 --- a/website/content/docs/cli/04-validate.md +++ b/website/content/docs/cli/05-validate.md @@ -1,7 +1,7 @@ +++ title = "Validate Command" description = "Validate JSON records against lexicons" -weight = 4 +weight = 5 +++ The `mlf validate` command validates JSON record data against an MLF lexicon schema. diff --git a/website/content/docs/cli/05-generate.md b/website/content/docs/cli/06-generate.md similarity index 99% rename from website/content/docs/cli/05-generate.md rename to website/content/docs/cli/06-generate.md index 71a300d..f78418e 100644 --- a/website/content/docs/cli/05-generate.md +++ b/website/content/docs/cli/06-generate.md @@ -1,7 +1,7 @@ +++ title = "Generate Commands" description = "Generate code and lexicons from MLF" -weight = 5 +weight = 6 +++ The `mlf generate` command converts MLF files to various output formats including JSON lexicons and code in multiple programming languages. diff --git a/website/content/docs/cli/06-fetch.md b/website/content/docs/cli/07-fetch.md similarity index 99% rename from website/content/docs/cli/06-fetch.md rename to website/content/docs/cli/07-fetch.md index c30d360..71000e7 100644 --- a/website/content/docs/cli/06-fetch.md +++ b/website/content/docs/cli/07-fetch.md @@ -1,7 +1,7 @@ +++ title = "Fetch Command" description = "Download lexicons from remote repositories" -weight = 6 +weight = 7 +++ The `mlf fetch` command downloads ATProto lexicons from remote repositories and converts them to MLF format. diff --git a/website/content/docs/cli/07-errors.md b/website/content/docs/cli/08-errors.md similarity index 99% rename from website/content/docs/cli/07-errors.md rename to website/content/docs/cli/08-errors.md index b18f865..f4b1cdc 100644 --- a/website/content/docs/cli/07-errors.md +++ b/website/content/docs/cli/08-errors.md @@ -1,7 +1,7 @@ +++ title = "Error Messages" description = "Understanding MLF error diagnostics" -weight = 7 +weight = 8 +++ MLF provides rich, helpful error messages with source code context and suggestions for fixing issues. diff --git a/website/content/docs/cli/_index.md b/website/content/docs/cli/_index.md index b77bdf7..8859ee7 100644 --- a/website/content/docs/cli/_index.md +++ b/website/content/docs/cli/_index.md @@ -21,15 +21,15 @@ The `mlf` command-line tool provides: ## Quick Start ```bash -# Check MLF files -mlf check - -# Generate TypeScript types -mlf generate code -g typescript -i "lexicons/**/*.mlf" -o src/types/ +# Initialize a new project +mlf init # Fetch remote lexicons mlf fetch stream.place --save +# Check MLF files +mlf check + # Generate all configured outputs mlf generate ``` @@ -42,6 +42,9 @@ See the [Configuration](02-configuration.md) section for details. ## Command Categories +### Project Setup +- `mlf init` - Initialize a new MLF project + ### Validation Commands - `mlf check` - Validate MLF syntax and types - `mlf validate` - Validate JSON records against lexicons -- 2.51.2