1pub mod cli;
8pub mod commands;
9pub mod contract_walk;
10pub mod json_obj;
11pub mod query_args;
12
13use std::path::PathBuf;
17use std::str::FromStr;
18
19use clap::Parser;
20use cli::Commands;
21
22const SHORT_VERSION: &str = concat!(
29 env!("CARGO_PKG_VERSION"),
30 " (aprender provable-contracts verifier)"
31);
32
33const LONG_VERSION: &str = concat!(
43 env!("CARGO_PKG_VERSION"),
44 " (aprender provable-contracts verifier)\n",
45 "crate aprender-contracts-cli — ",
46 env!("CARGO_PKG_REPOSITORY"),
47 "\n",
48 "Verifies YAML contracts under contracts/; run `pv --help` for the command surface.\n",
49 "This is NOT pv(1), the pipe viewer (distro package `pv`, or the `pv` crate on crates.io)."
50);
51
52#[derive(Parser)]
54#[command(
55 name = "pv",
56 about = "provable-contracts — papers to provable Rust kernels",
57 version = SHORT_VERSION,
58 long_version = LONG_VERSION
59)]
60pub struct Cli {
61 #[command(subcommand)]
63 pub command: Commands,
64
65 #[arg(short, long, global = true)]
67 pub quiet: bool,
68
69 #[arg(short, long, global = true)]
71 pub verbose: bool,
72}
73
74#[allow(clippy::too_many_lines)]
84pub fn dispatch(command: Commands) -> Result<(), Box<dyn std::error::Error>> {
85 match command {
86 Commands::Explain {
87 contract,
88 format,
89 binding,
90 } => commands::explain::run(&contract, binding.as_deref(), &format),
91 Commands::Validate { contract } => commands::validate::run(&contract),
92 Commands::CheckParity { contract } => commands::check_parity::run(&contract),
93 Commands::Scaffold {
94 contract,
95 r#trait,
96 output,
97 } => commands::scaffold::run(&contract, r#trait, output.as_deref()),
98 Commands::ExtractPytorch { target, output } => {
99 commands::extract::run(&target, output.as_deref())
100 }
101 Commands::Codegen {
102 contract_dir,
103 output,
104 } => commands::codegen::run(&contract_dir, output.as_deref()),
105 Commands::Kani { contract } => commands::kani::run(&contract),
106 Commands::Probar { contract, binding } => {
107 commands::probar::run(&contract, binding.as_deref())
108 }
109 Commands::Status { contract } => commands::status::run(&contract),
110 Commands::Audit {
111 contract, binding, ..
112 } => commands::audit::run(&contract, binding.as_deref()),
113 Commands::Diff { old, new } => commands::diff::run(&old, &new),
114 Commands::Coverage {
115 contract_dir,
116 binding,
117 fuzz,
118 reverse,
119 enforcement,
120 } => commands::coverage::run(
121 &contract_dir,
122 binding.as_deref(),
123 fuzz,
124 reverse.as_deref(),
125 enforcement.as_deref(),
126 ),
127 Commands::Generate {
128 contract,
129 output,
130 binding,
131 readme,
132 ci,
133 } => commands::generate::run(&contract, &output, binding.as_deref(), readme, ci),
134 Commands::Graph {
135 contract_dir,
136 format,
137 } => match commands::graph::GraphFormat::from_str(&format) {
138 Ok(fmt) => commands::graph::run(&contract_dir, fmt),
139 Err(e) => Err(e.into()),
140 },
141 Commands::Equations { contract, format } => {
142 match commands::equations::OutputFormat::from_str(&format) {
143 Ok(fmt) => commands::equations::run(&contract, fmt),
144 Err(e) => Err(e.into()),
145 }
146 }
147 Commands::Lean {
148 contract,
149 output_dir,
150 } => commands::lean::run(&contract, output_dir.as_deref()),
151 Commands::LeanStatus { path } => commands::lean_status::run(&path),
152 Commands::ProofStatus {
153 path,
154 binding,
155 verify_bindings,
156 format,
157 table,
158 kind,
159 } => commands::proof_status::run(
160 &path,
161 binding.as_deref(),
162 verify_bindings.as_deref(),
163 &format,
164 table,
165 kind.as_deref(),
166 ),
167 Commands::Lint {
168 contract_dir,
169 min_score,
170 binding,
171 format,
172 severity,
173 strict,
174 suppress,
175 suppress_rule,
176 suppress_file,
177 rule,
178 config,
179 diff_ref,
180 trend,
181 show_trend,
182 no_cache,
183 cache_stats,
184 coverage,
185 min_coverage,
186 crate_dir,
187 min_level,
188 explain,
189 watch,
190 strict_test_binding,
191 ..
192 } => {
193 if let Some(ref rule_id) = explain {
194 commands::lint::explain_rule(rule_id);
195 return Ok(());
196 }
197 commands::lint::run(
198 &contract_dir,
199 binding.as_deref(),
200 min_score,
201 format.as_deref(),
202 severity.as_deref(),
203 strict,
204 suppress.as_deref(),
205 suppress_rule.as_deref(),
206 suppress_file.as_deref(),
207 &rule,
208 config.as_deref(),
209 diff_ref.as_deref(),
210 trend,
211 show_trend,
212 no_cache,
213 cache_stats,
214 coverage,
215 min_coverage,
216 crate_dir.as_deref(),
217 min_level.as_deref(),
218 watch,
219 strict_test_binding,
220 )
221 }
222 Commands::Score {
223 path,
224 binding,
225 format,
226 min_score,
227 summary,
228 top_gaps,
229 weights,
230 pvscore,
231 ..
232 } => commands::score::run(
233 &path,
234 binding.as_deref(),
235 &format,
236 min_score,
237 summary,
238 top_gaps,
239 weights.as_deref(),
240 pvscore,
241 ),
242 Commands::Query(q) => commands::query::run(&commands::query::QueryCliParams {
243 contract_dir: &q.contract_dir,
244 query_str: &q.query,
245 regex: q.regex,
246 literal: q.literal,
247 case_sensitive: q.case_sensitive,
248 limit: q.limit,
249 obligation: q.obligation.as_deref(),
250 min_score: q.min_score,
251 min_level: q.min_level,
252 depends_on: q.depends_on.as_deref(),
253 depended_by: q.depended_by.as_deref(),
254 unproven: q.unproven,
255 show_score: q.score,
256 show_graph: q.graph,
257 show_paper: q.paper,
258 show_proof_status: q.proof_status,
259 show_binding: q.binding_info,
260 binding_gaps: q.binding_gaps,
261 show_diff: q.diff,
262 show_pagerank: q.pagerank,
263 show_call_sites: q.call_sites,
264 show_violations: q.violations,
265 show_coverage_map: q.coverage_map,
266 project_filter: q.project.as_deref(),
267 include_project: q.include_project.as_deref(),
268 tier: q.tier,
269 class: q.class,
270 kind: q.kind.as_deref(),
271 all_projects: q.all_projects,
272 rebuild_index: q.rebuild_index,
273 binding: q.binding.as_deref(),
274 format: &q.format,
275 exit_code: q.exit_code,
276 }),
277 Commands::Invariants { contract } => commands::invariants::run(&contract),
278 Commands::Coq { contract } => commands::coq::run(&contract),
279 Commands::Fuzz { contract } => commands::fuzz::run(&contract),
280 Commands::Mirai { contract } => commands::mirai::run(&contract),
281 Commands::Flux { contract } => commands::flux::run(&contract),
282 Commands::Tla { contract_dir } => commands::tla::run(&contract_dir),
283 Commands::Book {
284 contract_dir,
285 output,
286 update_summary,
287 summary_path,
288 } => commands::book::run(
289 &contract_dir,
290 &output,
291 update_summary,
292 summary_path.as_deref(),
293 ),
294 Commands::Infer {
295 crate_dir,
296 binding,
297 contract_dir,
298 top,
299 } => commands::infer::run(&crate_dir, &binding, &contract_dir, top),
300 Commands::Unlock { contract, reason } => commands::unlock::run(&contract, &reason),
301 Commands::Roofline {
302 contract_dir,
303 params,
304 bits,
305 hardware,
306 format,
307 } => commands::roofline::run(&contract_dir, params, bits, &hardware, &format),
308 Commands::Pipeline { pipeline, format } => commands::pipeline::run(&pipeline, &format),
309 Commands::Kaizen {
310 contract_dir,
311 src_root,
312 repo,
313 dry_run,
314 codegen,
315 fix,
316 json,
317 min_score,
318 } => {
319 let default_root = PathBuf::from("..");
320 let root = src_root.as_deref().unwrap_or(&default_root);
321 commands::kaizen::run(
322 &contract_dir,
323 root,
324 repo.as_deref(),
325 dry_run || !fix, codegen || fix, fix,
328 json,
329 min_score,
330 )
331 }
332 Commands::VerifyBindings {
333 binding,
334 output,
335 crate_name,
336 } => commands::verify_bindings::run(&binding, output.as_deref(), crate_name.as_deref()),
337 Commands::Certify {
338 contract_dir,
339 config,
340 output,
341 } => commands::certify::run(&contract_dir, config.as_deref(), output.as_deref()),
342 Commands::VerifyStructure {
343 contract_dir,
344 config,
345 model,
346 } => commands::verify_structure::run(&contract_dir, config.as_deref(), model.as_deref()),
347 Commands::VerifyPipeline {
348 contract_dir,
349 format,
350 } => {
351 commands::verify_pipeline::run(&contract_dir, &format);
352 Ok(())
353 }
354 Commands::Migrate {
355 contract_dir,
356 dry_run,
357 } => commands::migrate::run(&contract_dir, dry_run),
358 }
359}
360
361pub fn run() {
364 let cli = Cli::parse();
365 let _ = (cli.quiet, cli.verbose); if let Err(e) = dispatch(cli.command) {
368 eprintln!("error: {e}");
369 std::process::exit(1);
370 }
371}
372
373#[cfg(test)]
374#[path = "../tests/includes/version_identity_unit.rs"]
375mod version_identity_unit;
376
377#[cfg(test)]
378#[path = "../tests/includes/dispatch_tests.rs"]
379mod dispatch_tests;
380
381#[cfg(test)]
382#[path = "../tests/includes/dispatch_query_tests.rs"]
383mod dispatch_query_tests;