pub(crate) mod corpus;
pub(crate) mod csv;
pub(crate) mod dotenv;
pub(crate) mod format;
pub(crate) mod ini;
pub(crate) mod json;
pub(crate) mod locate;
pub(crate) mod log;
pub(crate) mod policy;
pub(crate) mod position;
pub(crate) mod scanner;
pub(crate) mod toml;
pub(crate) mod yaml;
use serde::Serialize;
pub(crate) use format::{FALLBACK_FORMAT, SUPPORTED_FORMATS, resolve_format};
pub(crate) use locate::find;
pub(crate) use policy::{Block, Class, Kind, Refusal};
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub(crate) struct Found {
pub(crate) kind: Option<Kind>,
pub(crate) text: String,
pub(crate) line: usize,
pub(crate) column: usize,
pub(crate) key: Option<String>,
pub(crate) normalized: Option<String>,
pub(crate) class: Option<Class>,
pub(crate) cidr: Option<Block>,
pub(crate) refused: Option<Refusal>,
}
impl Found {
pub(crate) const fn is_refusal(&self) -> bool {
self.refused.is_some()
}
pub(crate) fn survives(&self, kinds: &[Kind], classes: &[Class]) -> bool {
if self.is_refusal() {
return true;
}
let kind_ok = kinds.is_empty() || self.kind.is_some_and(|kind| kinds.contains(&kind));
let class_ok =
classes.is_empty() || self.class.is_some_and(|class| classes.contains(&class));
kind_ok && class_ok
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct KeySpan {
pub(crate) start: usize,
pub(crate) end: usize,
pub(crate) path: String,
}
pub(crate) fn lines(text: &str) -> impl Iterator<Item = (usize, &str)> {
let mut offset = 0;
text.split_inclusive('\n').map(move |line| {
let start = offset;
offset += line.len();
(start, line.trim_end_matches(['\n', '\r']))
})
}
pub(crate) fn strip_comment<'a>(line: &'a str, markers: &[char], after_space: bool) -> &'a str {
let mut quote: Option<char> = None;
let mut previous = ' ';
for (index, character) in line.char_indices() {
match (quote, character) {
(Some(open), _) if character == open => quote = None,
(None, '"' | '\'') => quote = Some(character),
(None, _)
if markers.contains(&character) && (!after_space || previous.is_whitespace()) =>
{
return &line[..index];
}
_ => {}
}
previous = character;
}
line
}
pub(crate) fn dotted(prefix: &str, key: &str) -> String {
if prefix.is_empty() {
return key.to_string();
}
format!("{prefix}.{key}")
}
pub(crate) fn parse_error(text: &str, format: &str) -> Option<String> {
match format::canonical(format) {
"json" => json::parse_error(text),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lines_carry_the_offset_they_start_at() {
let text = "a\nbb\nccc";
let found: Vec<(usize, &str)> = lines(text).collect();
assert_eq!(found, [(0, "a"), (2, "bb"), (5, "ccc")]);
for (offset, line) in found {
assert_eq!(&text[offset..offset + line.len()], line);
}
}
#[test]
fn a_windows_line_ending_is_not_part_of_the_line() {
assert_eq!(
lines("a\r\nb\r\n").collect::<Vec<_>>(),
[(0, "a"), (3, "b")]
);
}
#[test]
fn only_json_can_fail_to_parse() {
assert!(parse_error("{not json", "json").is_some());
for format in ["yaml", "toml", "ini", "env", "csv", "log", "unknown"] {
assert!(parse_error("{not json", format).is_none(), "{format}");
}
}
}