1use percent_encoding::percent_decode_str;
5
6use crate::{ScopeParseError, ScopeWarning};
7
8pub(crate) fn split_kind(atom: &str) -> Option<(&str, &str)> {
15 match (atom.find(':'), atom.find('?')) {
16 (Some(colon), query) if query.is_none_or(|q| colon < q) => {
17 Some((&atom[..colon], &atom[colon + 1..]))
18 }
19 (_, Some(query)) => Some(atom.split_at(query)),
20 _ => None,
21 }
22}
23
24pub(crate) struct Syntax<'a> {
31 positional: Option<String>,
32 params: Vec<(&'a str, String)>,
33 warnings: Vec<ScopeWarning>,
34}
35
36impl<'a> Syntax<'a> {
37 pub(crate) fn split(atom: &'a str, kind: &str) -> Result<Self, ScopeParseError> {
39 let after = &atom[kind.len()..];
40 let (positional, query) = match after.strip_prefix(':') {
41 Some(rest) => match rest.split_once('?') {
42 Some((positional, query)) => (positional, query),
43 None => (rest, ""),
44 },
45 None => ("", after.strip_prefix('?').unwrap_or_default()),
46 };
47 let params = query
48 .split('&')
49 .filter(|pair| !pair.is_empty())
50 .map(|pair| {
51 pair.split_once('=')
52 .map(|(name, value)| (name, decode(value)))
53 .ok_or_else(|| {
54 ScopeParseError::MalformedQuery(pair.to_owned(), atom.to_owned())
55 })
56 })
57 .collect::<Result<_, _>>()?;
58 Ok(Self {
59 positional: (!positional.is_empty()).then(|| decode(positional)),
60 params,
61 warnings: Vec::new(),
62 })
63 }
64
65 pub(crate) fn list(&mut self, name: &str, positional: bool) -> Vec<String> {
68 let mut values: Vec<String> = match positional {
69 true => self.positional.take().into_iter().collect(),
70 false => Vec::new(),
71 };
72 let named = self.take(name);
73 if !values.is_empty() && !named.is_empty() {
74 self.warn(ScopeWarning::RepeatedList(name.to_owned()));
75 }
76 values.extend(named);
77 values
78 }
79
80 pub(crate) fn single(&mut self, name: &str, positional: bool) -> Option<String> {
83 let mut values = match positional {
84 true => self.positional.take().into_iter().collect(),
85 false => Vec::new(),
86 };
87 values.extend(self.take(name));
88 if values.len() > 1 {
89 self.warn(ScopeWarning::RepeatedValue(name.to_owned()));
90 }
91 values.into_iter().next()
92 }
93
94 pub(crate) fn warn(&mut self, warning: ScopeWarning) {
96 if !self.warnings.contains(&warning) {
97 self.warnings.push(warning);
98 }
99 }
100
101 pub(crate) fn finish(mut self) -> Vec<ScopeWarning> {
104 for (name, _) in std::mem::take(&mut self.params) {
105 self.warn(ScopeWarning::UnknownParameter(name.to_owned()));
106 }
107 self.warnings
108 }
109
110 fn take(&mut self, name: &str) -> Vec<String> {
112 let (taken, kept) = std::mem::take(&mut self.params)
113 .into_iter()
114 .partition(|(key, _)| *key == name);
115 self.params = kept;
116 taken.into_iter().map(|(_, value)| value).collect()
117 }
118}
119
120fn decode(value: &str) -> String {
123 percent_decode_str(value).decode_utf8_lossy().into_owned()
124}