meta_ast/interface/
args.rs1use std::str::FromStr;
2
3use clap::Parser;
4
5#[derive(Parser)]
7#[command(name = "meta-ast", version, about = "Polyglot static analyzer")]
8pub enum Cli {
9 Inspect(InspectArgs),
16
17 Graph(GraphArgs),
24
25 #[cfg(feature = "metacall-deploy")]
31 Deploy(DeployArgs),
32}
33
34fn parse_format(s: &str) -> Result<crate::output::OutputFormat, String> {
35 match s.to_lowercase().as_str() {
36 "json" => Ok(crate::output::OutputFormat::Json),
37 "yaml" | "yml" => Ok(crate::output::OutputFormat::Yaml),
38 _ => Err(format!("invalid format '{s}': expected 'json' or 'yaml'")),
39 }
40}
41
42fn parse_language(s: &str) -> Result<crate::language::LangId, String> {
43 let normalized = s.to_lowercase();
44 crate::language::LangId::from_str(&normalized).map_err(|_| {
45 let all = crate::language::LangId::all();
46 let names: Vec<_> = all.iter().map(|l| l.as_ref()).collect();
47 format!(
48 "invalid language '{s}': expected one of {}",
49 names.join(", ")
50 )
51 })
52}
53
54#[derive(Parser)]
55pub struct InspectArgs {
56 pub path: std::path::PathBuf,
58
59 #[arg(short, long)]
61 pub output: Option<std::path::PathBuf>,
62
63 #[arg(short, long, value_parser = parse_language)]
65 pub language: Option<crate::language::LangId>,
66
67 #[arg(short = 'f', long, default_value = "json", value_parser = parse_format)]
69 pub format: crate::output::OutputFormat,
70
71 #[arg(long, value_enum, default_value_t = crate::interface::report::FailOn::Error)]
73 pub fail_on: crate::interface::report::FailOn,
74}
75
76#[derive(Parser)]
77pub struct GraphArgs {
78 pub path: std::path::PathBuf,
80
81 #[arg(short, long)]
83 pub output: Option<std::path::PathBuf>,
84
85 #[arg(short, long, value_parser = parse_language)]
87 pub language: Option<crate::language::LangId>,
88
89 #[arg(short = 'f', long, default_value = "json", value_parser = parse_format)]
91 pub format: crate::output::OutputFormat,
92
93 #[arg(long, value_enum, default_value_t = crate::interface::report::FailOn::Error)]
95 pub fail_on: crate::interface::report::FailOn,
96
97 #[arg(long)]
99 pub html: bool,
100
101 #[arg(long, requires = "html")]
103 pub open: bool,
104
105 #[cfg(feature = "dataflow")]
107 #[arg(long)]
108 pub datagraph: bool,
109
110 #[cfg(feature = "dataflow")]
112 #[arg(long)]
113 pub datagraph_output: Option<std::path::PathBuf>,
114
115 #[cfg(feature = "watch")]
117 #[arg(long)]
118 pub watch: bool,
119
120 #[cfg(feature = "watch")]
122 #[arg(long, default_value = "200")]
123 pub watch_debounce: u64,
124}
125
126#[cfg(feature = "metacall-deploy")]
127#[derive(Parser)]
128pub struct DeployArgs {
129 pub path: std::path::PathBuf,
131
132 #[arg(short = 'f', long, default_value = "json", value_parser = parse_format)]
134 pub format: crate::output::OutputFormat,
135
136 #[arg(long)]
138 pub check: bool,
139
140 #[arg(short, long, default_value = ".")]
142 pub out: std::path::PathBuf,
143
144 #[arg(long, default_value_t = crate::deploy::cut::DEFAULT_MAX_POD_SIZE)]
146 pub max_pod_size: usize,
147
148 #[arg(long, value_enum, default_value_t = crate::interface::report::FailOn::Error)]
150 pub fail_on: crate::interface::report::FailOn,
151}
152
153impl From<&InspectArgs> for crate::output::emitter::EmitConfig {
155 fn from(args: &InspectArgs) -> Self {
156 Self {
157 output: args.output.clone(),
158 format: args.format,
159 html: false,
160 open_browser: false,
161 }
162 }
163}
164
165impl From<&GraphArgs> for crate::output::emitter::EmitConfig {
166 fn from(args: &GraphArgs) -> Self {
167 Self {
168 output: args.output.clone(),
169 format: args.format,
170 html: args.html,
171 open_browser: true,
172 }
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn parse_language_valid_lowercase() {
182 assert_eq!(
183 parse_language("python"),
184 Ok(crate::language::LangId::Python)
185 );
186 assert_eq!(
187 parse_language("typescript"),
188 Ok(crate::language::LangId::TypeScript)
189 );
190 }
191
192 #[test]
193 fn parse_language_case_insensitive() {
194 assert_eq!(
195 parse_language("Python"),
196 Ok(crate::language::LangId::Python)
197 );
198 assert_eq!(
199 parse_language("TYPESCRIPT"),
200 Ok(crate::language::LangId::TypeScript)
201 );
202 }
203
204 #[test]
205 fn parse_language_invalid_returns_all_names() {
206 let err = parse_language("pytho").unwrap_err();
207 for id in crate::language::LangId::all() {
208 let name: &str = id.as_ref();
209 assert!(
210 err.contains(name),
211 "error {err:?} should list the valid name {name:?}"
212 );
213 }
214 }
215
216 #[test]
217 fn parse_language_empty_returns_err() {
218 assert!(parse_language("").is_err());
219 }
220
221 #[test]
222 fn opening_the_dashboard_is_an_opt_in() {
223 let parsed = Cli::try_parse_from(["meta-ast", "graph", "demo", "--html", "--open"]);
224 assert!(
225 parsed.is_ok(),
226 "graph --html --open parses: {:?}",
227 parsed.as_ref().err()
228 );
229 assert!(
230 matches!(parsed, Ok(Cli::Graph(ref args)) if args.open),
231 "the flag reaches the parsed arguments"
232 );
233 assert!(
234 Cli::try_parse_from(["meta-ast", "graph", "demo", "--open"]).is_err(),
235 "--open without --html is a usage error"
236 );
237 }
238}