use crate::comment::Comment;
#[derive(Debug, Clone, PartialEq)]
pub struct ParsedAnnotation {
pub name: String,
pub args: Vec<(String, String)>,
}
impl ParsedAnnotation {
pub fn arg(&self, key: &str) -> Option<&str> {
self.args
.iter()
.find(|(k, _)| k == key)
.map(|(_, v)| v.as_str())
}
pub fn is_bare(&self) -> bool {
self.args.is_empty()
}
}
pub fn parse_annotation(comment: &Comment) -> Option<ParsedAnnotation> {
let text = comment.text.trim();
let rest = text.strip_prefix('@')?;
let rest = rest.trim_start();
if rest.is_empty() {
return None;
}
Some(parse_annotation_text(rest))
}
fn parse_annotation_text(text: &str) -> ParsedAnnotation {
if let Some(paren_pos) = text.find(':') {
let name = text[..paren_pos].trim().to_string();
let args_text = text[paren_pos + 1..].trim_end_matches('\n').trim();
let args = parse_args(args_text);
ParsedAnnotation { name, args }
} else if let Some(paren_pos) = text.find('\n') {
let name = text[..paren_pos].trim().to_string();
let args_text = text[paren_pos + 1..].trim_end_matches('\n').trim();
let args = parse_args(args_text);
ParsedAnnotation { name, args }
} else {
ParsedAnnotation {
name: text.trim().to_string(),
args: vec![],
}
}
}
fn parse_args(text: &str) -> Vec<(String, String)> {
if text.is_empty() {
return vec![];
}
text.split('\n')
.filter_map(|pair| {
let pair = pair.trim();
if pair.is_empty() {
return None;
}
if let Some(eq_pos) = pair.find('=') {
let key = pair[..eq_pos].trim_start_matches('@').trim().to_string();
let val = pair[eq_pos + 1..].trim().trim_matches('"').to_string();
Some((key, val))
} else {
Some((
pair.trim_matches('@').trim().to_string(),
"true".to_string(),
))
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::comment::Comment;
use crate::error::{Location, Span};
fn make_comment(text: &str) -> Comment {
Comment {
text: text.to_string(),
raw: format!("#{}", text),
span: Span {
start: Location::default(),
end: Location::default(),
},
line: 1,
column: 1,
}
}
#[test]
fn test_bare_annotation() {
let c = make_comment("@ sparnatural");
let ann = parse_annotation(&c).unwrap();
assert_eq!(ann.name, "sparnatural");
assert!(ann.args.is_empty());
}
#[test]
fn test_annotation_with_args() {
let c = make_comment("@ sparnatural:\n@ widget=list\n");
let ann = parse_annotation(&c).unwrap();
assert_eq!(ann.name, "sparnatural");
assert_eq!(ann.arg("widget"), Some("list"));
}
#[test]
fn test_annotation_with_quoted_value() {
let c = make_comment("@ sparnatural:\n@ widget = date_range\n@ label = \"Birth date\"");
let ann = parse_annotation(&c).unwrap();
assert_eq!(ann.name, "sparnatural");
assert_eq!(ann.arg("widget"), Some("date_range"));
assert_eq!(ann.arg("label"), Some("Birth date"));
}
#[test]
fn test_not_an_annotation() {
let c = make_comment(" regular comment");
assert!(parse_annotation(&c).is_none());
}
#[test]
fn test_bare_flag_arg() {
let c = make_comment("@ sparnatural:\n@ searchable\n");
let ann = parse_annotation(&c).unwrap();
assert_eq!(ann.arg("searchable"), Some("true"));
}
}