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 DuplicateKey(String),
96}
97
98impl<'a> PartialEq<&'a ErrorKind> for ErrorKind {
99 fn eq(&self, other: &&'a ErrorKind) -> bool {
100 self == *other
101 }
102}
103
104impl PartialEq<ErrorKind> for &ErrorKind {
105 fn eq(&self, other: &ErrorKind) -> bool {
106 *self == other
107 }
108}
109
110impl Display for ErrorKind {
111 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
112 match self {
114 ErrorKind::ExpectedIdentifierNot(c) => {
115 write!(
116 f,
117 "expected identifier to start with ascii letter, not {:?}",
118 c
119 )
120 }
121 ErrorKind::MustBeAlphanumeric(c) => write!(f, "must be alphanumeric, not {:?}", c),
122 ErrorKind::ExpectedIdentifierNotEmpty => {
123 write!(f, "expected identifier, not an empty string")
124 }
125 ErrorKind::BadSeparator((e, c)) => {
126 write!(f, "bad separator, expected {:?} got {:?}", e, c)
127 }
128 ErrorKind::IncompleteKeyValue => {
129 write!(f, "incomplete key-value pair before end of input")
130 }
131 ErrorKind::InvalidCharInValue(c) => write!(f, "invalid char {:?} in value", c),
132 ErrorKind::DuplicateKey(s) => write!(f, "duplicate key {:?}", s),
133 }
134 }
135}
136
137#[derive(Debug)]
139pub struct ParsingError {
140 kind: ErrorKind,
141 position: usize,
142}
143
144impl ParsingError {
145 pub fn position(&self) -> usize {
147 self.position
148 }
149
150 pub fn kind(&self) -> &ErrorKind {
152 &self.kind
153 }
154}
155
156fn parse_err(kind: ErrorKind, position: Position) -> ParsingError {
157 ParsingError { kind, position }
158}
159
160impl Display for ParsingError {
161 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
162 write!(f, "{} at position {}", self.kind, self.position)
163 }
164}
165
166impl std::error::Error for ParsingError {}
167
168fn parse_ident(
169 iter: &mut Peekable2<CharIndices>,
170 next_pos: &mut Position,
171) -> Result<Key, ParsingError> {
172 let mut token = String::new();
173 while let Some((pos, c)) = iter.peek0() {
174 *next_pos = *pos;
175 if c.is_ascii_alphanumeric() || *c == '_' {
176 token.push(*c);
177 iter.next();
178 } else {
179 if token.is_empty() {
180 return Err(parse_err(ErrorKind::ExpectedIdentifierNot(*c), *next_pos));
181 } else if !c.is_ascii() || matches!(c, '\0'..=' ') {
182 return Err(parse_err(ErrorKind::MustBeAlphanumeric(*c), *next_pos));
183 }
184 break;
185 }
186 }
187
188 if token.is_empty() {
189 return Err(parse_err(ErrorKind::ExpectedIdentifierNotEmpty, *next_pos));
190 }
191
192 Ok(token)
193}
194
195fn parse_value(
196 iter: &mut Peekable2<CharIndices>,
197 next_pos: &mut Position,
198) -> Result<Value, ParsingError> {
199 let mut value = String::new();
200 loop {
201 let c1 = iter.peek0().cloned();
202 let c2 = iter.peek1().cloned();
203 if let Some((p, _)) = c1 {
204 *next_pos = p;
205 }
206 match (c1, c2) {
207 (Some((_, ';')), Some((_, ';'))) => {
208 let _ = iter.next();
209 let _ = iter.next();
210 value.push(';');
211 }
212 (Some((_, ';')), _) => break,
213 (Some((p, c)), _) => {
214 if matches!(c, '\u{0}'..='\u{1f}' | '\u{7f}'..='\u{9f}') {
215 return Err(parse_err(ErrorKind::InvalidCharInValue(c), p));
216 }
217 value.push(c);
218 let _ = iter.next();
219 }
220 (None, _) => break,
221 }
222 }
223 Ok(value)
224}
225
226fn parse_double_colon(
227 iter: &mut Peekable2<CharIndices>,
228 next_pos: &mut Position,
229) -> Result<bool, ParsingError> {
230 let c1 = iter.next();
231 let c2 = iter.next();
232 match (c1, c2) {
233 (Some((_, ':')), Some((_, ':'))) => {
234 *next_pos += 2;
235 Ok(true)
236 }
237 (None, None) => Ok(false),
238 (Some((_, ':')), Some((p, c))) => Err(parse_err(ErrorKind::BadSeparator((':', c)), p)),
239 (Some((p, c)), _) => Err(parse_err(ErrorKind::BadSeparator((':', c)), p)),
240 (None, _) => unreachable!("peekable2 guarantees that the second item is always None"),
241 }
242}
243
244fn parse_params(
245 iter: &mut Peekable2<CharIndices>,
246 next_pos: &mut Position,
247 input_len: usize,
248) -> Result<Params, ParsingError> {
249 let mut params = Params::new();
250 while let Some((p, _)) = iter.peek0() {
251 *next_pos = *p;
252 let key_pos = *next_pos;
253 let key = parse_ident(iter, next_pos)?;
254 if params.contains_key(&key) {
255 return Err(parse_err(ErrorKind::DuplicateKey(key.clone()), key_pos));
256 }
257 match iter.next() {
258 Some((p, '=')) => *next_pos = p + 1,
259 Some((p, c)) => return Err(parse_err(ErrorKind::BadSeparator(('=', c)), p)),
260 None => return Err(parse_err(ErrorKind::IncompleteKeyValue, input_len)),
261 }
262 let value = parse_value(iter, next_pos)?;
263 iter.next(); params.insert(key, value);
265 }
266 Ok(params)
267}
268
269pub fn parse_conf_str(input: &str) -> Result<ConfStr, ParsingError> {
281 let mut iter = input.char_indices().peekable2();
282 let mut next_pos = 0;
283 let service = parse_ident(&mut iter, &mut next_pos)?;
284 let has_separator = parse_double_colon(&mut iter, &mut next_pos)?;
285 if !has_separator {
286 return Ok(ConfStr::new(service, Params::new()));
287 }
288 let params = parse_params(&mut iter, &mut next_pos, input.len())?;
289 Ok(ConfStr::new(service, params))
290}