use mingling::{ Grouped, Routable, ShellContext, StructuralData, Suggest, SuggestItem, macros::{ arg, buffer, chain, completion, dispatcher, metadata, pack, pack_err_structural, r_println, renderer, routeify, }, metadata::Description, picker::EntryPicker, }; use serde::{Deserialize, Serialize}; use std::sync::OnceLock; use crate::Next; dispatcher!("explain"); #[metadata(EntryExplain)] pub fn desc_explain() -> Description { "Explain the meaning of the specified Lint".into() } pack!(StateExplainLint = String); pack_err_structural!(ErrorNoExplainLintProvided); pack_err_structural!(ErrorNoSuchLint = String); #[derive(Debug, Default, Grouped, StructuralData, Serialize)] pub struct ResultExplainLint { pub lint_name: String, pub title: String, pub summary: String, pub active_on: String, pub author: String, pub default: String, } #[chain(routeify)] pub fn handle_explain(args: EntryExplain) -> Next { let lint_name = args .pick_or_route(&arg![String], || { ErrorNoExplainLintProvided::default().to_chain() }) .to_result()?; StateExplainLint::new(lint_name).into() } /// Mirror of the lint registry JSON generated by `build.rs` (`registry.json`). #[derive(Debug, Deserialize)] struct LintRegistry { lints: Vec, } #[derive(Debug, Deserialize)] struct LintEntry { name: String, title: String, summary: String, metadata: LintMetadata, } #[derive(Debug, Deserialize)] struct LintMetadata { active_on: String, author: String, default: String, } /// The lint registry, embedded at compile time via `include_str!`. /// /// `registry.json` is regenerated by `build.rs` on every build, so the /// embedded copy always reflects the lints in `src/lints/`. fn lint_registry() -> &'static LintRegistry { static REGISTRY: OnceLock = OnceLock::new(); REGISTRY.get_or_init(|| { serde_json::from_str(include_str!("../../registry.json")) .expect("failed to parse embedded registry.json") }) } #[chain] pub fn handle_state_explain_lint(p: StateExplainLint) -> Next { let lint_name = p.inner; let Some(entry) = lint_registry().lints.iter().find(|l| l.name == lint_name) else { return ErrorNoSuchLint::new(lint_name).to_chain(); }; ResultExplainLint { lint_name: entry.name.clone(), title: entry.title.clone(), summary: entry.summary.clone(), active_on: entry.metadata.active_on.clone(), author: entry.metadata.author.clone(), default: entry.metadata.default.clone(), } .to_chain() } #[renderer(buffer)] pub fn render_explain_lint(r: ResultExplainLint) { r_println!("{}", r.title); r_println!(""); r_println!(" Name: {}", r.lint_name); r_println!(" Active on: {}", r.active_on); r_println!(" Default: {}", r.default); r_println!(" Author: {}", r.author); r_println!(""); r_println!("{}", r.summary); } #[renderer(buffer)] pub fn render_error_no_explain_lint_provided(_: ErrorNoExplainLintProvided) { r_println!("No lint name provided"); r_println!(""); r_println!("Usage: mling explain "); } #[renderer(buffer)] pub fn render_error_no_such_lint(err: ErrorNoSuchLint) { r_println!("No such lint: \"{}\"", err.info); r_println!(""); r_println!("Available lints:"); for entry in &lint_registry().lints { r_println!(" {}", entry.name); } } #[completion(EntryExplain)] pub fn complete_explain(ctx: &ShellContext) -> Suggest { if ctx.previous_word != "explain" { return Suggest::FileCompletion; } let lints: Vec = lint_registry() .lints .iter() .map(|l| l.name.clone()) .collect(); let mut suggest = Suggest::new(); for lint in lints { suggest.insert(SuggestItem::Simple(lint)); } suggest }