acorn_lib/analyzer/
vale.rs

1//! # Vale interface
2//!
3//! Provides programmtic access to [Vale prose analyzer](https://vale.sh/).
4use crate::util::{Label, SemanticVersion};
5use bon::Builder;
6use color_eyre::owo_colors::OwoColorize;
7use derive_more::Display;
8use serde::{Deserialize, Serialize};
9use std::path::PathBuf;
10use tracing::error;
11
12/// Vale output severity
13///
14/// See <https://vale.sh/docs/keys/minalertlevel>
15#[derive(Clone, Debug, Default, Display, Serialize, Deserialize)]
16#[serde(rename_all = "lowercase")]
17pub enum ValeOutputItemSeverity {
18    /// Warning
19    ///
20    /// Should strongly consider fixing
21    #[display("warning")]
22    Warning,
23    /// Error
24    ///
25    /// Should fix
26    #[display("error")]
27    Error,
28    /// Suggestion
29    ///
30    /// Should consider fixing or changing
31    #[default]
32    #[display("suggestion")]
33    Suggestion,
34}
35/// Vale installation details
36#[derive(Builder, Clone, Debug, Default, Display)]
37#[display("{:?}", version)]
38#[builder(start_fn = init)]
39pub struct Vale {
40    /// Vale version
41    pub version: Option<SemanticVersion>,
42    /// Path to `vale` binary
43    pub binary: Option<PathBuf>,
44    /// Path to Vale configuration
45    pub config: Option<ValeConfig>,
46}
47/// Vale configuration
48///
49/// See <https://vale.sh/docs/vale-ini>
50#[derive(Builder, Clone, Debug, Display)]
51#[display("{:?}", path)]
52#[builder(start_fn = init)]
53pub struct ValeConfig {
54    /// Path to Vale configuration
55    #[builder(default = PathBuf::from("./.vale/.vale.ini"))]
56    pub path: PathBuf,
57    /// List of Vale packages
58    ///
59    /// See <https://vale.sh/docs/keys/packages>
60    #[builder(default = Vec::<String>::new())]
61    pub packages: Vec<String>,
62    /// List of Vale vocabularies
63    ///
64    /// See <https://vale.sh/docs/keys/vocab>
65    #[builder(default = Vec::<String>::new())]
66    pub vocabularies: Vec<String>,
67    /// List of Vale rules to disable
68    ///
69    /// See <https://vale.sh/docs/styles#rules>
70    #[builder(default = Vec::<String>::new())]
71    pub disabled: Vec<String>,
72}
73/// Vale output
74#[derive(Clone, Debug, Serialize, Deserialize)]
75pub struct ValeOutput {
76    /// List of output items
77    pub items: Vec<ValeOutputItem>,
78}
79/// Vale output item
80///
81/// The primary purpose of this struct is to enable presenting a custom view of Vale output
82#[derive(Clone, Debug, Serialize, Deserialize)]
83#[serde(rename_all = "PascalCase")]
84pub struct ValeOutputItem {
85    action: ValeOutputItemAction,
86    span: Vec<u32>,
87    check: String,
88    description: String,
89    link: String,
90    message: String,
91    severity: ValeOutputItemSeverity,
92    #[serde(rename = "Match")]
93    word_match: String,
94    /// Line number
95    pub line: u32,
96}
97/// Vale output item action
98#[derive(Clone, Debug, Serialize, Deserialize)]
99#[serde(rename_all = "PascalCase")]
100pub struct ValeOutputItemAction {
101    /// Action name
102    name: String,
103    /// Action parameters
104    params: Option<Vec<String>>,
105}
106impl ValeOutputItemSeverity {
107    /// Returns colored output based on severity
108    pub fn colored(&self) -> String {
109        match self {
110            | ValeOutputItemSeverity::Warning => self.to_string().yellow().to_string(),
111            | ValeOutputItemSeverity::Error => self.to_string().red().to_string(),
112            | ValeOutputItemSeverity::Suggestion => self.to_string().blue().to_string(),
113        }
114    }
115}
116/// Preprocess Vale output
117#[cfg(any(unix, target_os = "wasi", target_os = "redox"))]
118pub fn preprocess_vale_output(path: PathBuf, output: &str) -> String {
119    output.replace(path.to_str().unwrap(), "items")
120}
121/// Preprocess Vale output
122#[cfg(windows)]
123pub fn preprocess_vale_output(path: PathBuf, output: &str) -> String {
124    let input = path.as_path().display().to_string().replace("\\", "/");
125    output.replace("\\\\", "/").replace(&input, "items")
126}
127/// Parse Vale output
128pub fn parse_vale_output(path: PathBuf, output: &str) -> Vec<ValeOutputItem> {
129    let processed = preprocess_vale_output(path, output);
130    if processed != "{}" {
131        let parsed: serde_json::Result<ValeOutput> = serde_json::from_str(&processed);
132        match parsed {
133            | Ok(ValeOutput { items }) => items,
134            | Err(why) => {
135                error!("=> {} Parse Vale output - {why}", Label::fail());
136                vec![]
137            }
138        }
139    } else {
140        vec![]
141    }
142}
143/// Print Vale output
144pub fn print_vale_output(items: Vec<ValeOutputItem>) {
145    for item in items {
146        let ValeOutputItem {
147            check,
148            line,
149            message,
150            severity,
151            span,
152            ..
153        } = item;
154        let location = format!("Line {}, Character {}", line, span[0]);
155        println!("{:<24} {:<21} {} {}", location, severity.colored(), message, check.dimmed());
156    }
157}