Add rendering of rule markdown for terminal output (#2747)

Add rendering of rule markdown for terminal output
    
This is achieved by making use of the `mdcat` crate.
    
See the following links for details:
    
- https://crates.io/crates/mdcat
- https://github.com/swsnr/mdcat
- https://docs.rs/mdcat/latest/mdcat/
This commit is contained in:
Nick Pope 2023-02-12 02:32:45 +00:00 committed by GitHub
parent 1b61d4e18b
commit 551b810aeb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 922 additions and 49 deletions

873
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -896,7 +896,7 @@ mod tests {
for rule in Rule::iter() {
let code = rule.code();
let (linter, rest) =
Linter::parse_code(code).unwrap_or_else(|| panic!("couldn't parse {:?}", code));
Linter::parse_code(code).unwrap_or_else(|| panic!("couldn't parse {code:?}"));
assert_eq!(code, format!("{}{rest}", linter.common_prefix()));
}
}

View file

@ -94,7 +94,7 @@ fn has_duplicates(s: &str) -> bool {
for ch in s.chars() {
if escaped {
escaped = false;
let pair = format!("\\{}", ch);
let pair = format!("\\{ch}");
if !seen.insert(pair) {
return true;
}

View file

@ -915,7 +915,7 @@ impl<'a> Generator<'a> {
}
Constant::None => self.p("None"),
Constant::Bool(b) => self.p(if *b { "True" } else { "False" }),
Constant::Int(i) => self.p(&format!("{}", i)),
Constant::Int(i) => self.p(&format!("{i}")),
Constant::Tuple(tup) => {
if let [elt] = &**tup {
self.p("(");

View file

@ -41,8 +41,10 @@ glob = { version = "0.3.0" }
ignore = { version = "0.4.18" }
itertools = { version = "0.10.5" }
log = { version = "0.4.17" }
mdcat = { version = "1.0.0", default-features = false }
notify = { version = "5.0.0" }
path-absolutize = { version = "3.0.14", features = ["once_cell_cache"] }
pulldown-cmark = { version = "0.9.2", default-features = false, features = ['simd'] }
quick-junit = { version = "0.3.2" }
rayon = { version = "1.5.3" }
regex = { version = "1.6.0" }
@ -50,9 +52,10 @@ rustc-hash = { version = "1.1.0" }
serde = { version = "1.0.147", features = ["derive"] }
serde_json = { version = "1.0.87" }
similar = { version = "2.2.1" }
strum = { version = "0.24.1" }
syntect = { version = "5.0.0", default-features = false, features = ["parsing", "regex-fancy", "default-themes", "default-syntaxes"] }
textwrap = { version = "0.16.0" }
walkdir = { version = "2.3.2" }
strum = "0.24.1"
[dev-dependencies]
assert_cmd = { version = "2.0.4" }

View file

@ -41,7 +41,7 @@ pub enum Command {
rule: Rule,
/// Output format
#[arg(long, value_enum, default_value = "text")]
#[arg(long, value_enum, default_value = "markdown")]
format: HelpFormat,
},
/// List all supported upstream linters
@ -284,6 +284,7 @@ pub struct CheckArgs {
pub enum HelpFormat {
Text,
Json,
Markdown,
}
#[allow(clippy::module_name_repetitions)]

View file

@ -4,11 +4,15 @@ use std::path::{Path, PathBuf};
use std::time::Instant;
use anyhow::{bail, Result};
use colored::control::SHOULD_COLORIZE;
use colored::Colorize;
use ignore::Error;
use itertools::Itertools;
use log::{debug, error};
use mdcat::terminal::{TerminalProgram, TerminalSize};
use mdcat::{Environment, ResourceAccess, Settings};
use path_absolutize::path_dedot;
use pulldown_cmark::{Options, Parser};
#[cfg(not(target_family = "wasm"))]
use rayon::prelude::*;
use ruff::cache::CACHE_DIR_NAME;
@ -20,6 +24,7 @@ use ruff::resolver::PyprojectDiscovery;
use ruff::settings::flags;
use ruff::{fix, fs, packaging, resolver, warn_user_once, AutofixAvailability, IOError};
use serde::Serialize;
use syntect::parsing::SyntaxSet;
use walkdir::WalkDir;
use crate::args::{HelpFormat, Overrides};
@ -281,9 +286,10 @@ struct Explanation<'a> {
pub fn rule(rule: &Rule, format: HelpFormat) -> Result<()> {
let (linter, _) = Linter::parse_code(rule.code()).unwrap();
let mut stdout = BufWriter::new(io::stdout().lock());
let mut output = String::new();
match format {
HelpFormat::Text => {
let mut output = String::new();
HelpFormat::Text | HelpFormat::Markdown => {
output.push_str(&format!("# {} ({})", rule.as_ref(), rule.code()));
output.push('\n');
output.push('\n');
@ -308,22 +314,46 @@ pub fn rule(rule: &Rule, format: HelpFormat) -> Result<()> {
output.push_str("Message formats:");
for format in rule.message_formats() {
output.push('\n');
output.push_str(&format!("* {}", format));
output.push_str(&format!("* {format}"));
}
}
writeln!(stdout, "{}", output)?;
}
HelpFormat::Json => {
writeln!(
stdout,
"{}",
serde_json::to_string_pretty(&Explanation {
code: rule.code(),
linter: linter.name(),
summary: rule.message_formats()[0],
})?
)?;
output.push_str(&serde_json::to_string_pretty(&Explanation {
code: rule.code(),
linter: linter.name(),
summary: rule.message_formats()[0],
})?);
}
};
match format {
HelpFormat::Json | HelpFormat::Text => {
writeln!(stdout, "{output}")?;
}
HelpFormat::Markdown => {
let parser = Parser::new_ext(
&output,
Options::ENABLE_TASKLISTS | Options::ENABLE_STRIKETHROUGH,
);
let cwd = std::env::current_dir()?;
let env = &Environment::for_local_directory(&cwd)?;
let terminal = if SHOULD_COLORIZE.should_colorize() {
TerminalProgram::detect()
} else {
TerminalProgram::Dumb
};
let settings = &Settings {
resource_access: ResourceAccess::LocalOnly,
syntax_set: SyntaxSet::load_defaults_newlines(),
terminal_capabilities: terminal.capabilities(),
terminal_size: TerminalSize::detect().unwrap_or_default(),
};
mdcat::push_tty(settings, env, &mut stdout, parser)?;
}
};
Ok(())

View file

@ -47,6 +47,30 @@ pub fn linter(format: HelpFormat) {
println!("{}", serde_json::to_string_pretty(&linters).unwrap());
}
}
HelpFormat::Markdown => {
#[allow(clippy::print_stdout)]
{
println!("| {:>6} | {:<27} |", "Prefix", "Name");
println!("| {:>6} | {:<27} |", "------", "-".repeat(27));
}
for linter in Linter::iter() {
let prefix = match linter.common_prefix() {
"" => linter
.upstream_categories()
.unwrap()
.iter()
.map(|UpstreamCategory(prefix, ..)| prefix.as_ref())
.join("/"),
prefix => prefix.to_string(),
};
#[allow(clippy::print_stdout)]
{
println!("| {:>6} | {:<27} |", prefix, linter.name());
}
}
}
}
}

View file

@ -40,7 +40,7 @@ pub fn main(args: &Args) -> Result<()> {
output.push_str(explanation.trim());
if args.dry_run {
println!("{}", output);
println!("{output}");
} else {
fs::create_dir_all("docs/rules")?;
fs::write(format!("docs/rules/{}.md", rule.as_ref()), output)?;