use std::path::PathBuf;
use regex::Regex;
use thiserror::Error;
use super::ref_list::RefList;
pub type ReqId = String;
#[derive(Debug, Default, PartialEq, Eq, Clone)]
pub struct Req {
pub head: ReqHeading,
pub ref_list: RefList,
pub filepath: PathBuf,
pub line_nr: usize,
pub wiki_link: Option<String>,
}
#[derive(Debug, Default, PartialEq, Eq, Clone)]
pub struct ReqHeading {
pub id: ReqId,
pub lvl: usize,
pub title: String,
}
static REQ_HEADING_MATCHER: std::sync::OnceLock<Regex> = std::sync::OnceLock::new();
pub fn get_req_heading(possible_heading: &str) -> Result<ReqHeading, ReqMatchingError> {
let regex = REQ_HEADING_MATCHER.get_or_init(|| {
Regex::new(r"^(?<lvl>#+)\s(?<id>[^\s:]+):(?<title>.+)")
.expect("Regex to match the requirement heading could **not** be created.")
});
match regex.captures(possible_heading) {
Some(captures) => Ok(ReqHeading {
id: captures
.name("id")
.expect("`id` capture group was not in heading match.")
.as_str()
.to_string(),
lvl: captures
.name("lvl")
.expect("`lvl` capture group was not in heading match.")
.len(),
title: captures
.name("title")
.expect("`title` capture group was not in heading match.")
.as_str()
.trim()
.to_string(),
}),
None => Err(ReqMatchingError::NoMatchFound),
}
}
#[derive(Debug, Error, PartialEq, Eq)]
pub enum ReqMatchingError {
#[error("No match was found in the given input.")]
NoMatchFound,
#[error(
"Entry in the *references* list is marked as *deprecated*, but has reference counters."
)]
DeprecatedHasCnt,
#[error("Optional count after `manual` flag is not separated by `+`.")]
ManualCntFailingPlus,
#[error("A direct references counter was set without the general counter before.")]
DirectCntWithoutGeneralCnt,
#[error("The matched counter could not be converted to a number.")]
CntIsNoNumber,
#[error("The matched direct counter is higher than the general counter.")]
DirectCntAboveGeneralCnt,
}
#[cfg(test)]
mod test {
use super::get_req_heading;
#[test]
fn get_high_lvl_req() {
let act_heading = get_req_heading("# req_id: Some Title").unwrap();
assert_eq!(
act_heading.id.as_str(),
"req_id",
"Requirement ID was not retrieved correctly."
);
assert_eq!(
act_heading.lvl, 1,
"Heading level was not retrieved correctly."
);
assert_eq!(
act_heading.title.as_str(),
"Some Title",
"Heading title was not retrieved correctly."
);
}
#[test]
fn get_low_lvl_req() {
let act_heading = get_req_heading("# req_id.sub_req: Some Title").unwrap();
assert_eq!(
act_heading.id.as_str(),
"req_id.sub_req",
"Requirement ID was not retrieved correctly."
);
assert_eq!(
act_heading.lvl, 1,
"Heading level was not retrieved correctly."
);
assert_eq!(
act_heading.title.as_str(),
"Some Title",
"Heading title was not retrieved correctly."
);
}
#[test]
fn get_req_in_sub_heading() {
let act_heading = get_req_heading("## req_id.sub_req: Some Title").unwrap();
assert_eq!(
act_heading.id.as_str(),
"req_id.sub_req",
"Requirement ID was not retrieved correctly."
);
assert_eq!(
act_heading.lvl, 2,
"Heading level was not retrieved correctly."
);
assert_eq!(
act_heading.title.as_str(),
"Some Title",
"Heading title was not retrieved correctly."
);
}
#[test]
fn ignore_req_id_with_whitespace() {
let act_heading = get_req_heading("# req id: Some Title");
assert_eq!(
act_heading.unwrap_err(),
super::ReqMatchingError::NoMatchFound,
"Requirement ID with whitespace was extracted as valid ID."
);
}
}