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 {
92 contract,
93 check_ids,
94 } => commands::validate::run(&contract, check_ids),
95 Commands::CheckParity { contract } => commands::check_parity::run(&contract),
96 Commands::Scaffold {
97 contract,
98 r#trait,
99 output,
100 } => commands::scaffold::run(&contract, r#trait, output.as_deref()),
101 Commands::ExtractPytorch { target, output } => {
102 commands::extract::run(&target, output.as_deref())
103 }
104 Commands::Codegen {
105 contract_dir,
106 output,
107 } => commands::codegen::run(&contract_dir, output.as_deref()),
108 Commands::Kani { contract } => commands::kani::run(&contract),
109 Commands::Probar { contract, binding } => {
110 commands::probar::run(&contract, binding.as_deref())
111 }
112 Commands::Status { contract } => commands::status::run(&contract),
113 Commands::Audit {
114 contract, binding, ..
115 } => commands::audit::run(&contract, binding.as_deref()),
116 Commands::Diff { old, new } => commands::diff::run(&old, &new),
117 Commands::Census {
118 contract_dir,
119 format,
120 json,
121 } => {
122 let as_json = json || matches!(format, cli::CensusFormat::Json);
123 commands::census::run(&contract_dir, as_json)
124 }
125 Commands::Extract {
126 contract_dir,
127 check,
128 out,
129 release,
130 } => {
131 let subject = release
132 .subject()
133 .map_err(crate::contract_walk::ReleaseArgsRefused)?;
134 commands::extract_rdf::run(&contract_dir, check, subject.as_ref(), out.as_deref())
135 }
136 Commands::Coverage {
137 contract_dir,
138 binding,
139 fuzz,
140 reverse,
141 enforcement,
142 } => commands::coverage::run(
143 &contract_dir,
144 binding.as_deref(),
145 fuzz,
146 reverse.as_deref(),
147 enforcement.as_deref(),
148 ),
149 Commands::Generate {
150 contract,
151 output,
152 binding,
153 readme,
154 ci,
155 } => commands::generate::run(&contract, &output, binding.as_deref(), readme, ci),
156 Commands::Graph {
157 contract_dir,
158 format,
159 } => match commands::graph::GraphFormat::from_str(&format) {
160 Ok(fmt) => commands::graph::run(&contract_dir, fmt),
161 Err(e) => Err(e.into()),
162 },
163 Commands::Equations { contract, format } => {
164 match commands::equations::OutputFormat::from_str(&format) {
165 Ok(fmt) => commands::equations::run(&contract, fmt),
166 Err(e) => Err(e.into()),
167 }
168 }
169 Commands::Lean {
170 contract,
171 output_dir,
172 } => commands::lean::run(&contract, output_dir.as_deref()),
173 Commands::LeanStatus { path } => commands::lean_status::run(&path),
174 Commands::ProofStatus {
175 path,
176 binding,
177 verify_bindings,
178 format,
179 table,
180 kind,
181 } => commands::proof_status::run(
182 &path,
183 binding.as_deref(),
184 verify_bindings.as_deref(),
185 &format,
186 table,
187 kind.as_deref(),
188 ),
189 Commands::Lint {
190 contract_dir,
191 min_score,
192 binding,
193 format,
194 severity,
195 strict,
196 suppress,
197 suppress_rule,
198 suppress_file,
199 rule,
200 config,
201 diff_ref,
202 trend,
203 show_trend,
204 no_cache,
205 cache_stats,
206 coverage,
207 min_coverage,
208 crate_dir,
209 min_level,
210 explain,
211 watch,
212 strict_test_binding,
213 armed_baseline_ref,
214 gate,
215 shape,
216 release,
217 ..
218 } => {
219 if let Some(ref rule_id) = explain {
220 commands::lint::explain_rule(rule_id);
221 return Ok(());
222 }
223 commands::lint::run(
224 &contract_dir,
225 binding.as_deref(),
226 min_score,
227 format.as_deref(),
228 severity.as_deref(),
229 strict,
230 suppress.as_deref(),
231 suppress_rule.as_deref(),
232 suppress_file.as_deref(),
233 &rule,
234 config.as_deref(),
235 diff_ref.as_deref(),
236 trend,
237 show_trend,
238 no_cache,
239 cache_stats,
240 coverage,
241 min_coverage,
242 crate_dir.as_deref(),
243 min_level.as_deref(),
244 watch,
245 strict_test_binding,
246 armed_baseline_ref.as_deref(),
247 gate.as_deref(),
248 commands::lint::shapes_options(gate.as_deref(), shape, &release)?,
249 )
250 }
251 Commands::Score {
252 path,
253 binding,
254 format,
255 min_score,
256 summary,
257 top_gaps,
258 weights,
259 pvscore,
260 ..
261 } => commands::score::run(
262 &path,
263 binding.as_deref(),
264 &format,
265 min_score,
266 summary,
267 top_gaps,
268 weights.as_deref(),
269 pvscore,
270 ),
271 Commands::Query(q) => commands::query::run(&commands::query::QueryCliParams {
272 contract_dir: &q.contract_dir,
273 query_str: &q.query,
274 regex: q.regex,
275 literal: q.literal,
276 case_sensitive: q.case_sensitive,
277 limit: q.limit,
278 obligation: q.obligation.as_deref(),
279 min_score: q.min_score,
280 min_level: q.min_level,
281 depends_on: q.depends_on.as_deref(),
282 depended_by: q.depended_by.as_deref(),
283 unproven: q.unproven,
284 show_score: q.score,
285 show_graph: q.graph,
286 show_paper: q.paper,
287 show_proof_status: q.proof_status,
288 show_binding: q.binding_info,
289 binding_gaps: q.binding_gaps,
290 show_diff: q.diff,
291 show_pagerank: q.pagerank,
292 show_call_sites: q.call_sites,
293 show_violations: q.violations,
294 show_coverage_map: q.coverage_map,
295 project_filter: q.project.as_deref(),
296 include_project: q.include_project.as_deref(),
297 tier: q.tier,
298 class: q.class,
299 kind: q.kind.as_deref(),
300 all_projects: q.all_projects,
301 rebuild_index: q.rebuild_index,
302 binding: q.binding.as_deref(),
303 format: &q.format,
304 exit_code: q.exit_code,
305 }),
306 Commands::Invariants { contract } => commands::invariants::run(&contract),
307 Commands::Coq { contract } => commands::coq::run(&contract),
308 Commands::Fuzz { contract } => commands::fuzz::run(&contract),
309 Commands::Mirai { contract } => commands::mirai::run(&contract),
310 Commands::Flux { contract } => commands::flux::run(&contract),
311 Commands::Tla { contract_dir } => commands::tla::run(&contract_dir),
312 Commands::Book {
313 contract_dir,
314 output,
315 update_summary,
316 summary_path,
317 } => commands::book::run(
318 &contract_dir,
319 &output,
320 update_summary,
321 summary_path.as_deref(),
322 ),
323 Commands::Infer {
324 crate_dir,
325 binding,
326 contract_dir,
327 top,
328 } => commands::infer::run(&crate_dir, &binding, &contract_dir, top),
329 Commands::Unlock { contract, reason } => commands::unlock::run(&contract, &reason),
330 Commands::Roofline {
331 contract_dir,
332 params,
333 bits,
334 hardware,
335 format,
336 } => commands::roofline::run(&contract_dir, params, bits, &hardware, &format),
337 Commands::Pipeline { pipeline, format } => commands::pipeline::run(&pipeline, &format),
338 Commands::Kaizen {
339 contract_dir,
340 src_root,
341 repo,
342 dry_run,
343 codegen,
344 fix,
345 json,
346 min_score,
347 } => {
348 let default_root = PathBuf::from("..");
349 let root = src_root.as_deref().unwrap_or(&default_root);
350 commands::kaizen::run(
351 &contract_dir,
352 root,
353 repo.as_deref(),
354 dry_run || !fix, codegen || fix, fix,
357 json,
358 min_score,
359 )
360 }
361 Commands::VerifyBindings {
362 binding,
363 output,
364 crate_name,
365 } => commands::verify_bindings::run(&binding, output.as_deref(), crate_name.as_deref()),
366 Commands::Certify {
367 contract_dir,
368 config,
369 output,
370 } => commands::certify::run(&contract_dir, config.as_deref(), output.as_deref()),
371 Commands::VerifyStructure {
372 contract_dir,
373 config,
374 model,
375 } => commands::verify_structure::run(&contract_dir, config.as_deref(), model.as_deref()),
376 Commands::VerifyPipeline {
377 contract_dir,
378 format,
379 } => commands::verify_pipeline::run(&contract_dir, &format),
380 Commands::Migrate {
381 contract_dir,
382 dry_run,
383 } => commands::migrate::run(&contract_dir, dry_run),
384 }
385}
386
387pub fn run() {
390 let cli = Cli::parse();
391 let _ = (cli.quiet, cli.verbose); if let Err(e) = dispatch(cli.command) {
394 let code = contract_walk::exit_code_for(e.as_ref());
399 let verdict = contract_walk::verdict_for(e.as_ref());
400 eprintln!("{verdict}: {e}");
401 std::process::exit(code);
402 }
403}
404
405#[cfg(test)]
406#[path = "../tests/includes/version_identity_unit.rs"]
407mod version_identity_unit;
408
409#[cfg(test)]
410#[path = "../tests/includes/dispatch_tests.rs"]
411mod dispatch_tests;
412
413#[cfg(test)]
414#[path = "../tests/includes/dispatch_query_tests.rs"]
415mod dispatch_query_tests;