use serde_json::Value;
pub(super) fn args(attrs: &[Value], name: &str) -> Vec<String> {
let mut out = Vec::new();
for attr in attrs {
let text = match attr {
Value::String(s) => s.as_str(),
Value::Object(o) => match o.get("other").and_then(|v| v.as_str()) {
Some(s) => s,
None => continue,
},
_ => continue,
};
if let Some(inner) = strip_wrapper(text, name) {
out.push(inner.to_string());
}
}
out
}
fn strip_wrapper<'a>(text: &'a str, name: &str) -> Option<&'a str> {
let t = text.trim();
let t = t.strip_prefix("#[")?.strip_suffix(']')?.trim();
let t = t.strip_prefix(name)?.trim();
t.strip_prefix('(')?.strip_suffix(')')
}
pub(super) fn split_items(args: &str) -> Vec<&str> {
let (mut out, mut depth, mut in_str, mut escaped, mut start) =
(Vec::new(), 0i32, false, false, 0);
for (i, c) in args.char_indices() {
if in_str {
match c {
_ if escaped => escaped = false,
'\\' => escaped = true,
'"' => in_str = false,
_ => {}
}
continue;
}
match c {
'"' => in_str = true,
'(' | '[' => depth += 1,
')' | ']' => depth -= 1,
',' if depth == 0 => {
out.push(args[start..i].trim());
start = i + 1;
}
_ => {}
}
}
let tail = args[start..].trim();
if !tail.is_empty() {
out.push(tail);
}
out
}
pub(super) fn key_value(item: &str) -> (&str, Option<String>) {
match item.split_once('=') {
None => (item.trim(), None),
Some((k, v)) => {
let v = v.trim();
let unquoted = v.strip_prefix('"').and_then(|s| s.strip_suffix('"'));
(k.trim(), Some(unquoted.unwrap_or(v).to_string()))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn reads_rustdoc_object_and_string_attr_shapes() {
let mixed = vec![
json!({"other": "#[serde(flatten)]"}),
json!("#[serde(default)]"),
];
assert_eq!(args(&mixed, "serde"), vec!["flatten", "default"]);
}
#[test]
fn only_the_named_attribute_is_returned() {
let mixed = vec![
json!({"other": "#[serde(default)]"}),
json!({"other": "#[graphql(skip)]"}),
json!({"other": "#[doc = \"hi\"]"}),
];
assert_eq!(args(&mixed, "graphql"), vec!["skip"]);
}
#[test]
fn nested_parens_do_not_split_early() {
let items = split_items("rename(serialize = \"a\", deserialize = \"b\"), default");
assert_eq!(items.len(), 2);
assert_eq!(items[1], "default");
}
#[test]
fn commas_inside_string_literals_do_not_split() {
let items = split_items("rename = \"a,b\", default");
assert_eq!(items, vec!["rename = \"a,b\"", "default"]);
}
#[test]
fn key_value_unquotes_and_trims() {
assert_eq!(
key_value("rename = \"qty\""),
("rename", Some("qty".into()))
);
assert_eq!(key_value("default"), ("default", None));
}
}