2022-09-04 23:33:35 +00:00
|
|
|
use colored::Colorize;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use std::fmt;
|
2022-11-23 14:33:20 +00:00
|
|
|
use std::io::{BufRead, BufReader, Error, ErrorKind, Seek};
|
2022-09-04 23:33:35 +00:00
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
|
|
|
|
pub struct Pmd {
|
|
|
|
root: PathBuf,
|
|
|
|
data: PmdData,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
pub struct PmdData {
|
|
|
|
#[serde(rename = "$value")]
|
|
|
|
files: Option<Vec<File>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
pub struct File {
|
|
|
|
name: String,
|
|
|
|
#[serde(rename = "$value")]
|
|
|
|
violations: Vec<Violation>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
pub struct Violation {
|
|
|
|
#[serde(rename = "beginline")]
|
|
|
|
begin_line: usize,
|
|
|
|
#[serde(rename = "endline")]
|
|
|
|
end_line: usize,
|
|
|
|
|
|
|
|
rule: String,
|
|
|
|
#[serde(rename = "$value")]
|
|
|
|
message: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Pmd {
|
|
|
|
pub fn open(root: impl Into<PathBuf>, path: impl Into<PathBuf>) -> std::io::Result<Self> {
|
|
|
|
let file = std::fs::File::open(path.into())?;
|
|
|
|
let data = serde_xml_rs::from_reader(file)
|
|
|
|
.map_err(|_| Error::new(ErrorKind::Other, "Failed to parse XML data"))?;
|
|
|
|
Ok(Pmd {
|
|
|
|
root: root.into(),
|
|
|
|
data,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn contains_violations(&self) -> bool {
|
|
|
|
self.data.files.is_some()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn violation_count(&self) -> usize {
|
|
|
|
let mut count = 0;
|
|
|
|
if let Some(files) = self.data.files.as_ref() {
|
|
|
|
for file in files.iter() {
|
|
|
|
count += file.violations.len();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
count
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl File {
|
|
|
|
fn display(&self, root: &Path, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
let mut file = std::fs::File::open(&self.name).map_err(|_| fmt::Error)?;
|
|
|
|
for violation in self.violations.iter() {
|
|
|
|
writeln!(f, "{}: {}", "warning".yellow(), violation.message)?;
|
|
|
|
let max_digits = violation.end_line.to_string().len();
|
|
|
|
let name = Path::new(&self.name)
|
|
|
|
.strip_prefix(root)
|
|
|
|
.map_err(|_| fmt::Error)?;
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"{}{} {}",
|
|
|
|
" ".repeat(max_digits),
|
|
|
|
"-->".blue().bold(),
|
|
|
|
name.to_string_lossy().bold()
|
|
|
|
)?;
|
2022-11-23 14:33:20 +00:00
|
|
|
file.rewind().map_err(|_| fmt::Error)?;
|
2022-09-04 23:33:35 +00:00
|
|
|
let reader = BufReader::new(&file);
|
|
|
|
for (num, text) in reader
|
|
|
|
.lines()
|
|
|
|
.enumerate()
|
|
|
|
.skip(violation.begin_line - 1)
|
|
|
|
.take(violation.end_line - violation.begin_line + 1)
|
|
|
|
{
|
|
|
|
let mut text = text.map_err(|_| fmt::Error)?;
|
|
|
|
if text.len() > 117 {
|
|
|
|
text.truncate(117);
|
|
|
|
text.push_str("...");
|
|
|
|
}
|
|
|
|
let num_str = (num + 1).to_string();
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"{}{}{} {}",
|
|
|
|
num_str.blue().bold(),
|
|
|
|
" ".repeat(max_digits - num_str.len() + 1),
|
|
|
|
"|".blue().bold(),
|
|
|
|
text
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"{}{} {}: rule {} is enabled\n",
|
|
|
|
" ".repeat(max_digits + 1),
|
|
|
|
"=".blue().bold(),
|
|
|
|
"note".bold(),
|
|
|
|
violation.rule.bold()
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for Pmd {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
if let Some(files) = self.data.files.as_ref() {
|
|
|
|
for file in files.iter() {
|
|
|
|
file.display(&self.root, f)?;
|
|
|
|
}
|
|
|
|
write!(f, "{} total warnings emitted", self.violation_count())?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|