1use std::borrow::Cow;
2
3use itertools::Itertools;
4use winnow::combinator::{alt, preceded, repeat, terminated};
5use winnow::prelude::*;
6use winnow::token::{one_of, take_while};
7use winnow::Result;
8
9#[derive(Debug)]
10pub struct QuoteError;
11
12impl std::fmt::Display for QuoteError {
13 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
14 write!(f, "invalid quoting")
15 }
16}
17
18impl std::error::Error for QuoteError {}
19
20pub fn sq_dequote_step<'i>(input: &mut &'i str) -> Result<Cow<'i, str>, QuoteError> {
21 sq_dequote.parse_next(input).map_err(|_e| QuoteError)
22}
23
24#[allow(clippy::result_unit_err)]
25pub fn sq_dequote<'i>(input: &mut &'i str) -> Result<Cow<'i, str>, ()> {
26 alt((sq_dequote_escaped, sq_dequote_no_escaped)).parse_next(input)
28}
29
30fn sq_dequote_escaped<'i>(input: &mut &'i str) -> Result<Cow<'i, str>, ()> {
31 (
32 sq_dequote_section,
33 sq_dequote_trail,
34 repeat(0.., sq_dequote_trail),
35 )
36 .map(|(start, trail, mut trails): (_, _, Vec<_>)| {
37 trails.insert(0, trail);
38 trails.insert(0, [start, ""]);
39 let value = trails.into_iter().flatten().join("");
40 Cow::Owned(value)
41 })
42 .parse_next(input)
43}
44
45fn sq_dequote_no_escaped<'i>(input: &mut &'i str) -> Result<Cow<'i, str>, ()> {
46 sq_dequote_section.map(Cow::Borrowed).parse_next(input)
47}
48
49fn sq_dequote_section<'i>(input: &mut &'i str) -> Result<&'i str, ()> {
50 terminated(preceded('\'', take_while(0.., |c| c != '\'')), '\'').parse_next(input)
51}
52
53fn sq_dequote_trail<'i>(input: &mut &'i str) -> Result<[&'i str; 2], ()> {
54 (escaped, sq_dequote_section)
55 .map(|(e, s)| [e, s])
56 .parse_next(input)
57}
58
59fn escaped<'i>(input: &mut &'i str) -> Result<&'i str, ()> {
60 preceded('\\', one_of(['\'', '!']).take()).parse_next(input)
61}
62
63#[cfg(test)]
64mod test_sq_dequote_step {
65 use super::*;
66
67 #[test]
68 fn word() {
69 let fixture = "'name'";
70 let expected = Cow::Borrowed("name");
71 let (_, actual) = sq_dequote.parse_peek(fixture).unwrap();
72 assert_eq!(actual, expected);
73 }
74
75 #[test]
76 fn space() {
77 let fixture = "'a b'";
78 let expected = Cow::Borrowed("a b");
79 let (_, actual) = sq_dequote.parse_peek(fixture).unwrap();
80 assert_eq!(actual, expected);
81 }
82
83 #[test]
84 fn sq_escaped() {
85 let fixture = "'a'\\''b'";
86 let expected: Cow<'_, str> = Cow::Owned("a'b".into());
87 let (_, actual) = sq_dequote.parse_peek(fixture).unwrap();
88 assert_eq!(actual, expected);
89 }
90
91 #[test]
92 fn exclamation_escaped() {
93 let fixture = "'a'\\!'b'";
94 let expected: Cow<'_, str> = Cow::Owned("a!b".into());
95 let (_, actual) = sq_dequote.parse_peek(fixture).unwrap();
96 assert_eq!(actual, expected);
97 }
98}