Skip to main content

meta_ast/interface/
args.rs

1use std::str::FromStr;
2
3use clap::Parser;
4
5/// Polyglot static analyzer that builds symbol surfaces and cross-file dependency graphs.
6#[derive(Parser)]
7#[command(name = "meta-ast", version, about = "Polyglot static analyzer")]
8pub enum Cli {
9    /// Inspect a project directory/file and extract all symbol definitions
10    ///
11    /// Examples:
12    ///   meta-ast inspect ./my-project
13    ///   meta-ast inspect ./my-project/main.py -f yaml -o symbols.yaml
14    ///   meta-ast inspect ./my-project --language python
15    Inspect(InspectArgs),
16
17    /// Build a cross-file dependency graph and analyze Strongly Connected Components (SCCs)
18    ///
19    /// Examples:
20    ///   meta-ast graph ./my-project
21    ///   meta-ast graph ./my-project --html -o project_graph.html
22    ///   meta-ast graph ./my-project -f yaml -o graph.yaml
23    Graph(GraphArgs),
24
25    /// Scan cross-language call sites and generate MetaCall deployment manifests
26    ///
27    /// Examples:
28    ///   meta-ast deploy ./my-project --out ./deploy-dir
29    ///   meta-ast deploy ./my-project --check
30    #[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    /// Root directory or source file to inspect
57    pub path: std::path::PathBuf,
58
59    /// Output file path (prints to stdout if omitted)
60    #[arg(short, long)]
61    pub output: Option<std::path::PathBuf>,
62
63    /// Only analyze files detected as this language
64    #[arg(short, long, value_parser = parse_language)]
65    pub language: Option<crate::language::LangId>,
66
67    /// Output format for the extracted symbols
68    #[arg(short = 'f', long, default_value = "json", value_parser = parse_format)]
69    pub format: crate::output::OutputFormat,
70}
71
72#[derive(Parser)]
73pub struct GraphArgs {
74    /// Root directory to analyze
75    pub path: std::path::PathBuf,
76
77    /// Output file path (prints to stdout if omitted)
78    #[arg(short, long)]
79    pub output: Option<std::path::PathBuf>,
80
81    /// Only analyze files detected as this language
82    #[arg(short, long, value_parser = parse_language)]
83    pub language: Option<crate::language::LangId>,
84
85    /// Output serialization format for the graph structure
86    #[arg(short = 'f', long, default_value = "json", value_parser = parse_format)]
87    pub format: crate::output::OutputFormat,
88
89    /// Generate an interactive HTML dashboard with graph visualization
90    #[arg(long)]
91    pub html: bool,
92
93    /// Also emit a portable datagraph.json export (requires --features dataflow)
94    #[cfg(feature = "dataflow")]
95    #[arg(long)]
96    pub datagraph: bool,
97
98    /// Enter watch mode: monitor the project and re-analyze on file changes
99    #[cfg(feature = "watch")]
100    #[arg(long)]
101    pub watch: bool,
102
103    /// Debounce duration in milliseconds for watch mode (default: 200)
104    #[cfg(feature = "watch")]
105    #[arg(long, default_value = "200")]
106    pub watch_debounce: u64,
107}
108
109#[cfg(feature = "metacall-deploy")]
110#[derive(Parser)]
111pub struct DeployArgs {
112    /// Root directory of the project to analyze
113    pub path: std::path::PathBuf,
114
115    /// Output format for generated manifests
116    #[arg(short = 'f', long, default_value = "json", value_parser = parse_format)]
117    pub format: crate::output::OutputFormat,
118
119    /// Check mode: diff generated manifests against existing metacall.json
120    #[arg(long)]
121    pub check: bool,
122
123    /// Output directory for generated manifests and mesh annotation
124    #[arg(short, long, default_value = ".")]
125    pub out: std::path::PathBuf,
126
127    /// Maximum number of files in a single pod before rebalancing is triggered
128    #[arg(long, default_value_t = crate::deploy::cut::DEFAULT_MAX_POD_SIZE)]
129    pub max_pod_size: usize,
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn parse_language_valid_lowercase() {
138        assert_eq!(
139            parse_language("python"),
140            Ok(crate::language::LangId::Python)
141        );
142        assert_eq!(
143            parse_language("typescript"),
144            Ok(crate::language::LangId::TypeScript)
145        );
146    }
147
148    #[test]
149    fn parse_language_case_insensitive() {
150        assert_eq!(
151            parse_language("Python"),
152            Ok(crate::language::LangId::Python)
153        );
154        assert_eq!(
155            parse_language("TYPESCRIPT"),
156            Ok(crate::language::LangId::TypeScript)
157        );
158    }
159
160    #[test]
161    fn parse_language_invalid_returns_all_names() {
162        let err = parse_language("pytho").unwrap_err();
163        for id in crate::language::LangId::all() {
164            let name: &str = id.as_ref();
165            assert!(
166                err.contains(name),
167                "error {err:?} should list the valid name {name:?}"
168            );
169        }
170    }
171
172    #[test]
173    fn parse_language_empty_returns_err() {
174        assert!(parse_language("").is_err());
175    }
176}