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; use crate::GlobalSettings; #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] pub(crate) fn scan_for_in_buffer_settings<'s>( input: OrgSource<'s>, ) -> Res, Vec>> { // 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>> { 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) } pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>( keywords: Vec>, original_settings: &'g GlobalSettings<'g, 's>, ) -> Result, String> { let mut new_settings = original_settings.clone(); // Todo Keywords 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())?; 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; } } Ok(new_settings) } #[cfg(test)] mod tests { use super::*; #[test] fn scan_test() -> Result<(), Box> { 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(()) } }