1use std::path::PathBuf;
10
11use nom::branch::alt;
12use nom::character::complete::multispace0;
13use nom::combinator::{eof, map, recognize};
14use nom::error::{ErrorKind, ParseError};
15use nom::sequence::tuple;
16
17use crate::db::{Entry, DB};
18use crate::error::Error;
19use crate::linter::lint::lint_db;
20use crate::load::RuleFrom::{Disk, Mem};
21use crate::load::RuleSource;
22use crate::parser::parse::{StrTrace, TraceResult};
23use crate::parser::{comment, rule, set};
24use crate::read::Line::*;
25use crate::{load, Rule, Set};
26
27#[derive(Debug)]
28enum Line {
29 Blank,
30 Comment(String),
31 SetDef(Set),
32 RuleDef(Rule),
33 Malformed(String, String),
34 MalformedSet(String, String),
35}
36
37enum LineError<I> {
38 CannotParseSet(I, String),
39 CannotParse(I, String),
40 Nom(I, ErrorKind),
41}
42
43impl<I> ParseError<I> for LineError<I> {
44 fn from_error_kind(input: I, kind: ErrorKind) -> Self {
45 LineError::Nom(input, kind)
46 }
47
48 fn append(_: I, _: ErrorKind, other: Self) -> Self {
49 other
50 }
51}
52
53fn parser(i: &str) -> nom::IResult<StrTrace, Line, LineError<&str>> {
54 alt((
55 map(blank_line, |_| Blank),
56 map(comment::parse, Comment),
57 map(set::parse, SetDef),
58 map(rule::parse, RuleDef),
59 ))(StrTrace::new(i))
60 .map_err(|e| {
61 let details = match e {
62 nom::Err::Error(e) => e.to_string(),
63 e => format!("{:?}", e),
64 };
65 let f = if i.starts_with('%') {
67 LineError::CannotParseSet
68 } else {
69 LineError::CannotParse
70 };
71
72 nom::Err::Error(f(i, details))
73 })
74}
75
76fn blank_line(i: StrTrace) -> TraceResult<StrTrace> {
77 recognize(tuple((multispace0, eof)))(i)
78}
79
80pub fn deserialize_rules_db(text: &str) -> Result<DB, Error> {
81 read_rules_db(load::rules_from(Mem(text.to_string()))?)
82}
83
84pub fn load_rules_db(path: &str) -> Result<DB, Error> {
85 read_rules_db(load::rules_from(Disk(PathBuf::from(path)))?)
86}
87
88fn read_rules_db(xs: Vec<RuleSource>) -> Result<DB, Error> {
89 let lookup: Vec<(String, Entry)> = xs
90 .iter()
91 .map(relativized_path)
92 .map(|(source, l)| (source, parser(l)))
93 .flat_map(|(source, r)| match r {
94 Ok((t, rule)) if t.current.is_empty() => Some((source, rule)),
95 Ok((_, _)) => None,
96 Err(nom::Err::Error(LineError::CannotParse(i, why))) => {
97 Some((source, Malformed(i.to_string(), why)))
98 }
99 Err(nom::Err::Error(LineError::CannotParseSet(i, why))) => {
100 Some((source, MalformedSet(i.to_string(), why)))
101 }
102 Err(_) => None,
103 })
104 .filter_map(|(source, line)| match line {
105 RuleDef(r) => Some((source, Entry::ValidRule(r))),
106 SetDef(s) => Some((source, Entry::ValidSet(s))),
107 Malformed(text, error) => Some((source, Entry::Invalid { text, error })),
108 MalformedSet(text, error) => Some((source, Entry::InvalidSet { text, error })),
109 Comment(text) => Some((source, Entry::Comment(text))),
110 _ => None,
111 })
112 .collect();
113
114 Ok(lint_db(DB::from_sources(lookup)))
115}
116
117fn relativized_path(i: &(PathBuf, String)) -> (String, &String) {
118 (
119 i.0.display()
121 .to_string()
122 .rsplit_once('/')
123 .map(|(_, rhs)| rhs.to_string())
124 .unwrap_or_else(|| i.0.display().to_string()),
126 &i.1,
127 )
128}
129
130#[cfg(test)]
131mod tests {
132 use crate::read::relativized_path;
133 use std::path::PathBuf;
134
135 #[test]
136 fn test_relativize_path() {
137 let i = (PathBuf::from("/foo/bar.baz"), "boom".to_string());
139 let r = relativized_path(&i);
140 assert_eq!(r.0, "bar.baz");
141 assert_eq!(r.1, "boom");
142
143 let i = (PathBuf::from("bar.baz"), "boom2".to_string());
145 let r = relativized_path(&i);
146 assert_eq!(r.0, "bar.baz");
147 assert_eq!(r.1, "boom2");
148 }
149}