1#![doc = include_str!("../README.md")]
26
27use crate::peekable2::{Peekable2, Peekable2Ext};
28use std::collections::HashMap;
29use std::fmt;
30use std::fmt::{Debug, Display, Formatter};
31use std::str::CharIndices;
32
33mod peekable2;
34
35pub type Key = String;
37
38pub type Value = String;
40
41pub type Params = HashMap<Key, Value>;
44
45pub struct ConfStr {
49 service: String,
50 params: Params,
51}
52
53impl Debug for ConfStr {
54 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
55 write!(f, "ConfStr {{ service: {:?}, .. }}", self.service)
57 }
58}
59
60impl ConfStr {
61 pub fn new(service: String, params: Params) -> Self {
63 ConfStr { service, params }
64 }
65
66 pub fn service(&self) -> &str {
68 &self.service
69 }
70
71 pub fn params(&self) -> &Params {
73 &self.params
74 }
75
76 pub fn get(&self, key: &str) -> Option<&str> {
79 self.params.get(key).map(|s| s.as_str())
80 }
81}
82
83pub type Position = usize;
85
86#[derive(Debug, PartialEq, Eq, Clone)]
88pub enum ErrorKind {
89 ExpectedIdentifierNot(char),
90 MustBeAlphanumeric(char),
91 ExpectedIdentifierNotEmpty,
92 BadSeparator((char, char)),
93 IncompleteKeyValue,
94 InvalidCharInValue(char),
95 MissingTrailingSemicolon,
96 DuplicateKey(String),
97}
98
99impl<'a> PartialEq<&'a ErrorKind> for ErrorKind {
100 fn eq(&self, other: &&'a ErrorKind) -> bool {
101 self == *other
102 }
103}
104
105impl<'a> PartialEq<ErrorKind> for &'a ErrorKind {
106 fn eq(&self, other: &ErrorKind) -> bool {
107 *self == other
108 }
109}
110
111impl Display for ErrorKind {
112 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
113 match self {
115 ErrorKind::ExpectedIdentifierNot(c) => {
116 write!(
117 f,
118 "expected identifier to start with ascii letter, not {:?}",
119 c
120 )
121 }
122 ErrorKind::MustBeAlphanumeric(c) => write!(f, "must be alphanumeric, not {:?}", c),
123 ErrorKind::ExpectedIdentifierNotEmpty => {
124 write!(f, "expected identifier, not an empty string")
125 }
126 ErrorKind::BadSeparator((e, c)) => {
127 write!(f, "bad separator, expected {:?} got {:?}", e, c)
128 }
129 ErrorKind::IncompleteKeyValue => {
130 write!(f, "incomplete key-value pair before end of input")
131 }
132 ErrorKind::InvalidCharInValue(c) => write!(f, "invalid char {:?} in value", c),
133 ErrorKind::MissingTrailingSemicolon => write!(f, "missing trailing semicolon"),
134 ErrorKind::DuplicateKey(s) => write!(f, "duplicate key {:?}", s),
135 }
136 }
137}
138
139#[derive(Debug)]
141pub struct ParsingError {
142 kind: ErrorKind,
143 position: usize,
144}
145
146impl ParsingError {
147 pub fn position(&self) -> usize {
149 self.position
150 }
151
152 pub fn kind(&self) -> &ErrorKind {
154 &self.kind
155 }
156}
157
158fn parse_err(kind: ErrorKind, position: Position) -> ParsingError {
159 ParsingError { kind, position }
160}
161
162impl Display for ParsingError {
163 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
164 write!(f, "{} at position {}", self.kind, self.position)
165 }
166}
167
168impl std::error::Error for ParsingError {}
169
170fn parse_ident(
171 iter: &mut Peekable2<CharIndices>,
172 next_pos: &mut Position,
173) -> Result<Key, ParsingError> {
174 let mut token = String::new();
175 while let Some((pos, c)) = iter.peek0() {
176 *next_pos = *pos;
177 if c.is_ascii_alphanumeric() || *c == '_' {
178 token.push(*c);
179 iter.next();
180 } else {
181 if token.is_empty() {
182 return Err(parse_err(ErrorKind::ExpectedIdentifierNot(*c), *next_pos));
183 } else if !c.is_ascii() || matches!(c, '\0'..=' ') {
184 return Err(parse_err(ErrorKind::MustBeAlphanumeric(*c), *next_pos));
185 }
186 break;
187 }
188 }
189
190 if token.is_empty() {
191 return Err(parse_err(ErrorKind::ExpectedIdentifierNotEmpty, *next_pos));
192 }
193
194 Ok(token)
195}
196
197fn parse_value(
198 iter: &mut Peekable2<CharIndices>,
199 next_pos: &mut Position,
200 input_len: usize,
201) -> Result<Value, ParsingError> {
202 let mut value = String::new();
203 loop {
204 let c1 = iter.peek0().cloned();
205 let c2 = iter.peek1().cloned();
206 if let Some((p, _)) = c1 {
207 *next_pos = p;
208 }
209 match (c1, c2) {
210 (Some((_, ';')), Some((_, ';'))) => {
211 let _ = iter.next();
212 let _ = iter.next();
213 value.push(';');
214 }
215 (Some((_, ';')), _) => break,
216 (Some((p, c)), _) => {
217 if matches!(c, '\u{0}'..='\u{1f}' | '\u{7f}'..='\u{9f}') {
218 return Err(parse_err(ErrorKind::InvalidCharInValue(c), p));
219 }
220 value.push(c);
221 let _ = iter.next();
222 }
223 (None, _) => return Err(parse_err(ErrorKind::MissingTrailingSemicolon, input_len)),
224 }
225 }
226 Ok(value)
227}
228
229fn parse_double_colon(
230 iter: &mut Peekable2<CharIndices>,
231 next_pos: &mut Position,
232) -> Result<bool, ParsingError> {
233 let c1 = iter.next();
234 let c2 = iter.next();
235 match (c1, c2) {
236 (Some((_, ':')), Some((_, ':'))) => {
237 *next_pos += 2;
238 Ok(true)
239 }
240 (None, None) => Ok(false),
241 (Some((_, ':')), Some((p, c))) => Err(parse_err(ErrorKind::BadSeparator((':', c)), p)),
242 (Some((p, c)), _) => Err(parse_err(ErrorKind::BadSeparator((':', c)), p)),
243 (None, _) => unreachable!("peekable2 guarantees that the second item is always None"),
244 }
245}
246
247fn parse_params(
248 iter: &mut Peekable2<CharIndices>,
249 next_pos: &mut Position,
250 input_len: usize,
251) -> Result<Params, ParsingError> {
252 let mut params = Params::new();
253 while let Some((p, _)) = iter.peek0() {
254 *next_pos = *p;
255 let key_pos = *next_pos;
256 let key = parse_ident(iter, next_pos)?;
257 if params.contains_key(&key) {
258 return Err(parse_err(ErrorKind::DuplicateKey(key.clone()), key_pos));
259 }
260 match iter.next() {
261 Some((p, '=')) => *next_pos = p + 1,
262 Some((p, c)) => return Err(parse_err(ErrorKind::BadSeparator(('=', c)), p)),
263 None => return Err(parse_err(ErrorKind::IncompleteKeyValue, input_len)),
264 }
265 let value = parse_value(iter, next_pos, input_len)?;
266 iter.next().unwrap(); params.insert(key, value);
268 }
269 Ok(params)
270}
271
272pub fn parse_conf_str(input: &str) -> Result<ConfStr, ParsingError> {
284 let mut iter = input.char_indices().peekable2();
285 let mut next_pos = 0;
286 let service = parse_ident(&mut iter, &mut next_pos)?;
287 let has_separator = parse_double_colon(&mut iter, &mut next_pos)?;
288 if !has_separator {
289 return Ok(ConfStr::new(service, Params::new()));
290 }
291 let params = parse_params(&mut iter, &mut next_pos, input.len())?;
292 Ok(ConfStr::new(service, params))
293}