organic/src/parser/in_buffer_settings.rs

146 lines
4.5 KiB
Rust
Raw Normal View History

use nom::branch::alt;
use nom::bytes::complete::is_not;
use nom::bytes::complete::tag_no_case;
use nom::bytes::complete::take_until;
use nom::character::complete::space1;
use nom::multi::separated_list0;
use super::keyword::filtered_keyword;
use super::keyword_todo::todo_keywords;
use super::OrgSource;
use crate::context::HeadlineLevelFilter;
use crate::error::CustomError;
use crate::error::Res;
use crate::types::Keyword;
2023-09-06 15:00:19 +00:00
use crate::GlobalSettings;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
2023-09-11 17:13:28 +00:00
pub(crate) fn scan_for_in_buffer_settings<'s>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Keyword<'s>>> {
// TODO: Write some tests to make sure this is functioning properly.
let mut keywords = Vec::new();
let mut remaining = input;
loop {
// Skip text until possible in_buffer_setting
let start_of_pound = take_until::<_, _, CustomError<_>>("#+")(remaining);
let start_of_pound = if let Ok((start_of_pound, _)) = start_of_pound {
start_of_pound
} else {
break;
};
// Go backwards to the start of the line and run the filtered_keyword parser
let start_of_line = start_of_pound.get_start_of_line();
let (remain, maybe_kw) = match filtered_keyword(in_buffer_settings_key)(start_of_line) {
Ok((remain, kw)) => (remain, Some(kw)),
Err(_) => {
let end_of_line = take_until::<_, _, CustomError<_>>("\n")(start_of_pound);
if let Ok((end_of_line, _)) = end_of_line {
(end_of_line, None)
} else {
break;
}
}
};
if let Some(kw) = maybe_kw {
keywords.push(kw);
}
remaining = remain;
}
Ok((remaining, keywords))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn in_buffer_settings_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt((
tag_no_case("archive"),
tag_no_case("category"),
tag_no_case("columns"),
tag_no_case("filetags"),
tag_no_case("link"),
tag_no_case("priorities"),
tag_no_case("property"),
tag_no_case("seq_todo"),
tag_no_case("setupfile"),
tag_no_case("startup"),
tag_no_case("tags"),
tag_no_case("todo"),
tag_no_case("typ_todo"),
))(input)
}
2023-09-06 15:00:19 +00:00
2023-09-11 17:13:28 +00:00
pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
2023-09-06 15:00:19 +00:00
keywords: Vec<Keyword<'sf>>,
original_settings: &'g GlobalSettings<'g, 's>,
) -> Result<GlobalSettings<'g, 's>, String> {
let mut new_settings = original_settings.clone();
// Todo Keywords
2023-09-06 15:00:19 +00:00
for kw in keywords.iter().filter(|kw| {
kw.key.eq_ignore_ascii_case("todo")
|| kw.key.eq_ignore_ascii_case("seq_todo")
|| kw.key.eq_ignore_ascii_case("typ_todo")
}) {
let (_, (in_progress_words, complete_words)) =
todo_keywords(kw.value).map_err(|err| err.to_string())?;
2023-09-06 15:00:19 +00:00
new_settings
.in_progress_todo_keywords
.extend(in_progress_words.into_iter().map(str::to_string));
new_settings
.complete_todo_keywords
.extend(complete_words.into_iter().map(str::to_string));
}
// Startup settings
for kw in keywords
.iter()
.filter(|kw| kw.key.eq_ignore_ascii_case("startup"))
{
let (_remaining, settings) =
separated_list0(space1::<&str, nom::error::Error<_>>, is_not(" \t"))(kw.value)
.map_err(|err: nom::Err<_>| err.to_string())?;
if settings.contains(&"odd") {
new_settings.odd_levels_only = HeadlineLevelFilter::Odd;
}
if settings.contains(&"oddeven") {
new_settings.odd_levels_only = HeadlineLevelFilter::OddEven;
}
}
2023-09-06 15:00:19 +00:00
Ok(new_settings)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scan_test() -> Result<(), Box<dyn std::error::Error>> {
let input = OrgSource::new(
r#"
foo
#+archive: bar
baz #+category: lorem
#+label: ipsum
#+todo: dolar
cat
"#,
);
let (remaining, settings) = scan_for_in_buffer_settings(input)?;
assert_eq!(Into::<&str>::into(remaining), "cat\n");
let keys: Vec<_> = settings.iter().map(|kw| kw.key).collect();
// category is skipped because it is not the first non-whitespace on the line.
//
// label is skipped because it is not an in-buffer setting.
assert_eq!(keys, vec!["archive", "todo"]);
Ok(())
}
}