forked from
stavola.xyz/mlf
A human-friendly DSL for ATProto Lexicons
1use clap::{Parser, Subcommand};
2use miette::IntoDiagnostic;
3use std::path::PathBuf;
4use std::process;
5
6mod check;
7mod config;
8mod fetch;
9mod generate;
10mod init;
11mod workspace_ext;
12
13// Import optional code generator plugins
14// These are automatically registered via inventory when the feature is enabled
15#[cfg(feature = "typescript")]
16use mlf_codegen_typescript as _;
17
18#[cfg(feature = "go")]
19use mlf_codegen_go as _;
20
21#[cfg(feature = "rust")]
22use mlf_codegen_rust as _;
23
24#[derive(Parser)]
25#[command(name = "mlf")]
26#[command(about = "MLF (Matt's Lexicon Format) CLI tool", long_about = None)]
27struct Cli {
28 #[command(subcommand)]
29 command: Commands,
30}
31
32#[derive(Subcommand)]
33enum Commands {
34 Init {
35 #[arg(long, help = "Skip prompts and use defaults")]
36 yes: bool,
37 },
38
39 Check {
40 #[arg(help = "MLF lexicon file(s) or directory to validate. If omitted, checks source directory from mlf.toml")]
41 input: Vec<PathBuf>,
42
43 #[arg(long, help = "Root directory for namespace calculation (defaults to mlf.toml source directory or current directory)")]
44 root: Option<PathBuf>,
45 },
46
47 Validate {
48 #[arg(help = "MLF lexicon file")]
49 lexicon: PathBuf,
50
51 #[arg(help = "JSON record file to validate against the lexicon")]
52 record: PathBuf,
53 },
54
55 Generate {
56 #[command(subcommand)]
57 command: Option<GenerateCommands>,
58 },
59
60 Fetch {
61 #[arg(help = "Namespace to fetch (e.g., stream.place). If omitted, fetches all dependencies from mlf.toml")]
62 nsid: Option<String>,
63
64 #[arg(long, help = "Add namespace to dependencies in mlf.toml")]
65 save: bool,
66
67 #[arg(long, help = "Update dependencies to latest versions (ignores lockfile)")]
68 update: bool,
69
70 #[arg(long, help = "Require lockfile and fail if dependencies need updating")]
71 locked: bool,
72 },
73}
74
75#[derive(Subcommand)]
76enum GenerateCommands {
77 Lexicon {
78 #[arg(short, long, help = "Input MLF file(s) or directory. If omitted, uses source directory from mlf.toml")]
79 input: Vec<PathBuf>,
80
81 #[arg(short, long, help = "Output directory. If omitted, uses first lexicon output from mlf.toml")]
82 output: Option<PathBuf>,
83
84 #[arg(long, help = "Root directory for namespace calculation (defaults to mlf.toml source directory or current directory)")]
85 root: Option<PathBuf>,
86
87 #[arg(long, help = "Use flat file structure (e.g., app.bsky.post.json)")]
88 flat: bool,
89 },
90 Code {
91 #[arg(short, long, help = "Generator to use (typescript, go, rust, etc.). If omitted, uses first code output from mlf.toml")]
92 generator: Option<String>,
93
94 #[arg(short, long, help = "Input MLF file(s) or directory. If omitted, uses source directory from mlf.toml")]
95 input: Vec<PathBuf>,
96
97 #[arg(short, long, help = "Output directory. If omitted, uses matching output from mlf.toml")]
98 output: Option<PathBuf>,
99
100 #[arg(long, help = "Root directory for namespace calculation (defaults to mlf.toml source directory or current directory)")]
101 root: Option<PathBuf>,
102
103 #[arg(long, help = "Use flat file structure (e.g., app.bsky.post.ts)")]
104 flat: bool,
105 },
106 Mlf {
107 #[arg(short, long, help = "Input JSON lexicon files (glob patterns supported)")]
108 input: Vec<String>,
109
110 #[arg(short, long, help = "Output directory. If omitted, uses first mlf output from mlf.toml")]
111 output: Option<PathBuf>,
112 },
113}
114
115#[tokio::main]
116async fn main() {
117 let cli = Cli::parse();
118
119 let result: Result<(), miette::Report> = match cli.command {
120 Commands::Init { yes } => {
121 init::run_init(yes).into_diagnostic()
122 }
123 Commands::Check { input, root } => {
124 check::run_check(input, root).into_diagnostic()
125 }
126 Commands::Validate { lexicon, record } => {
127 check::validate(lexicon, record).into_diagnostic()
128 }
129 Commands::Generate { command } => match command {
130 Some(GenerateCommands::Lexicon { input, output, root, flat }) => {
131 generate::lexicon::run(input, output, root, flat).into_diagnostic()
132 }
133 Some(GenerateCommands::Code { generator, input, output, root, flat }) => {
134 generate::code::run(generator, input, output, root, flat).into_diagnostic()
135 }
136 Some(GenerateCommands::Mlf { input, output }) => {
137 generate::mlf::run(input, output).into_diagnostic()
138 }
139 None => {
140 // Run all outputs from mlf.toml
141 generate::run_all().into_diagnostic()
142 }
143 },
144 Commands::Fetch { nsid, save, update, locked } => {
145 fetch::run_fetch(nsid, save, update, locked).await.into_diagnostic()
146 }
147 };
148
149 if let Err(e) = result {
150 eprintln!("{:?}", e);
151 process::exit(1);
152 }
153}