acorn_lib/analyzer/
vale.rs

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