this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 288 lines 9.0 kB view raw
1//! Types which augment or modify the behavior of an underlying parser. 2 3use crate::error::ParseError; 4#[cfg(feature = "either")] 5use crate::label::Label; 6use crate::parse::Parser; 7use crate::storage::StorageLock; 8use crate::tag::KeyValueSep; 9use crate::tag::PathSep; 10#[cfg(feature = "either")] 11use crate::tag::Tag; 12#[cfg(feature = "convert_case")] 13pub use convert_case::Case; 14#[cfg(feature = "convert_case")] 15use convert_case::Casing as _; 16#[cfg(feature = "either")] 17use either::Either; 18#[cfg(feature = "regex")] 19use regex::Regex; 20#[cfg(feature = "regex")] 21use regex::Replacer; 22use std::hash::BuildHasher; 23use string_interner::backend::Backend as InternerBackend; 24#[cfg(feature = "either")] 25use string_interner::Symbol; 26 27// Helper macro to generate parser adapters. 28macro_rules! adapters { 29 ( 30 $( 31 $( #[$($attrss:meta)*] )* 32 $struct:ident $(< $($type_var:ident: $type_bound:ident),* >)? $(($($field_ty:ty),*))? => { $adapter:expr } 33 )* 34 ) => { 35 $( 36 adapters! { 37 @single 38 $( #[$($attrss)*] )* 39 $struct $(< $($type_var: $type_bound),* >)* $(($($field_ty),*))* => { $adapter } 40 } 41 )* 42 }; 43 44 ( 45 @single 46 $(#[$($attrss:meta)*] )* 47 $struct:ident $(< $($type_var:ident: $type_bound:ident),* >)? $(($($field_ty:ty),* ))? => { $adapter:expr } 48 ) => { 49 $( #[$($attrss)*] )* 50 #[derive(Debug, Clone)] 51 pub struct $struct<$($($type_var: $type_bound),*,)* P: Parser>($($(pub $field_ty),*,)* pub P); 52 53 impl<$($($type_var: $type_bound),*,)* P: Parser> $struct<$($($type_var),*,)* P> { 54 /// Parse a token with the given `interner` and `separator`. 55 #[allow(clippy::redundant_closure_call)] 56 fn parse<B, H>( 57 &self, 58 storage: &mut StorageLock<'_, <P::Tag as Tag>::Label, B, H>, 59 key_value_separator: KeyValueSep, 60 path_separator: PathSep, 61 raw: &str, 62 ) -> Result<P::Tag, ParseError> 63 where 64 B: InternerBackend<Symbol = <P::Tag as Tag>::Symbol>, 65 H: BuildHasher 66 { 67 ($adapter)(self, storage, key_value_separator, path_separator, raw) 68 } 69 70 } 71 72 impl<$($($type_var: $type_bound),*,)* P: Parser> Parser for $struct<$($($type_var),*,)* P> { 73 type Tag = P::Tag; 74 75 fn parse<B, H>( 76 &self, 77 storage: &mut StorageLock<'_, <Self::Tag as Tag>::Label, B, H>, 78 key_value_separator: KeyValueSep, 79 path_separator: PathSep, 80 raw: &str, 81 ) -> Result<Self::Tag, ParseError> 82 where 83 B: InternerBackend<Symbol = <Self::Tag as Tag>::Symbol>, 84 H: BuildHasher 85 { 86 self.parse(storage, key_value_separator, path_separator, raw) 87 } 88 } 89 }; 90} 91 92adapters! { 93 /// Trim whitespace from one or both sides of the tag. 94 Trim(TrimBounds) => { 95 |this: &Trim<P>, interner, kv_sep, path_sep, raw: &str| { 96 let Trim::<P>(bounds, sub_parser) = this; 97 let raw = match bounds { 98 TrimBounds::Both => raw.trim(), 99 TrimBounds::Start => raw.trim_start(), 100 TrimBounds::End => raw.trim_end(), 101 }; 102 sub_parser.parse(interner, kv_sep, path_sep, raw) 103 } 104 } 105 106 /// Filter out tags longer than a maximum number of characters. 107 MaxChar(usize) => { 108 |this: &MaxChar<P>, interner, kv_sep, path_sep, raw: &str| { 109 let MaxChar::<P>(limit, sub_parser) = this; 110 111 if raw.chars().count() > *limit { 112 return Err(ParseError::TagTooManyChars); 113 } 114 115 sub_parser.parse(interner, kv_sep, path_sep, raw) 116 } 117 } 118 119 /// Filter out tags longer than a maximum number of bytes. 120 MaxBytes(usize) => { 121 |this: &MaxBytes<P>, interner, kv_sep, path_sep, raw: &str| { 122 let MaxBytes::<P>(limit, sub_parser) = this; 123 124 if raw.len() > *limit { 125 return Err(ParseError::TagTooManyBytes); 126 } 127 128 sub_parser.parse(interner, kv_sep, path_sep, raw) 129 } 130 } 131} 132 133#[cfg(feature = "convert_case")] 134adapters! { 135 /// Change the case of the tag. 136 ChangeCase(Case) => { 137 |this: &ChangeCase<P>, interner, kv_sep, path_sep, raw: &str| { 138 let ChangeCase::<P>(case, sub_parser) = this; 139 let raw = raw.to_case(*case); 140 sub_parser.parse(interner, kv_sep, path_sep, &raw) 141 } 142 } 143} 144 145#[cfg(feature = "regex")] 146adapters! { 147 /// Filter tags by matching against a regex. 148 Match(Regex) => { 149 |this: &Match<P>, interner, kv_sep, path_sep, raw: &str| { 150 let Match::<P>(regex, sub_parser) = this; 151 152 let raw = regex 153 .is_match(raw) 154 .then_some(raw) 155 .ok_or(ParseError::TagDidntMatchRegex)?; 156 157 sub_parser.parse(interner, kv_sep, path_sep, raw) 158 } 159 } 160 161 /// Replace the content of a tag according to a regex. 162 Replace<R: CloneableReplacer>(Regex, R, ReplaceCount) => { 163 |this: &Replace<R, P>, interner, kv_sep, path_sep, raw: &str| { 164 let Replace::<R, P>(regex, replacer, count, sub_parser) = this; 165 166 let raw = match count { 167 ReplaceCount::First => regex.replace(raw, replacer.clone()), 168 ReplaceCount::N(count) => regex.replacen(raw, *count, replacer.clone()), 169 ReplaceCount::All => regex.replace_all(raw, replacer.clone()), 170 }; 171 172 sub_parser.parse(interner, kv_sep, path_sep, &raw) 173 } 174 } 175} 176 177/// A `regex::Replacer` that can be [`Clone`]d. 178/// 179/// This is automatically implemented for any type that implements both 180/// `regex::Replacer` and [`Clone`]. 181pub trait CloneableReplacer: Replacer + Clone {} 182 183impl<T: Replacer + Clone> CloneableReplacer for T {} 184 185// The `Or` adapter is implemented by hand, because making the adapter-generating 186// macro learn how to handle all of these bounds and everything isn't worth it. 187 188/// Apply one parser, and if it fails, apply the other one. 189/// 190/// Note that the tokens produced by the two parsers have to support the same underlying 191/// symbol type, as they're both being backed by the same interner for storage. 192#[cfg(feature = "either")] 193#[derive(Debug)] 194pub struct Or<L, S, T1, T2, P1, P2>(pub P1, pub P2) 195where 196 L: Label, 197 S: Symbol, 198 T1: Tag<Label = L, Symbol = S>, 199 T2: Tag<Label = L, Symbol = S>, 200 P1: Parser<Tag = T1>, 201 P2: Parser<Tag = T2>; 202 203#[cfg(feature = "either")] 204impl<L, S, T1, T2, P1, P2> Or<L, S, T1, T2, P1, P2> 205where 206 L: Label, 207 S: Symbol, 208 T1: Tag<Label = L, Symbol = S>, 209 T2: Tag<Label = L, Symbol = S>, 210 P1: Parser<Tag = T1>, 211 P2: Parser<Tag = T2>, 212{ 213 /// Parse a token with the given `interner` and `separator`. 214 fn parse<B, H>( 215 &self, 216 storage: &mut StorageLock<'_, L, B, H>, 217 key_value_separator: KeyValueSep, 218 path_separator: PathSep, 219 raw: &str, 220 ) -> Result<Either<T1, T2>, ParseError> 221 where 222 B: InternerBackend<Symbol = S>, 223 H: BuildHasher, 224 { 225 self.0 226 .parse(storage, key_value_separator, path_separator, raw) 227 .map(Either::Left) 228 .or_else(|err1| { 229 self.1 230 .parse(storage, key_value_separator, path_separator, raw) 231 .map(Either::Right) 232 .map_err(|err2| ParseError::FailedOr(Box::new(err1), Box::new(err2))) 233 }) 234 } 235} 236 237#[cfg(feature = "either")] 238impl<L, S, T1, T2, P1, P2> Parser for Or<L, S, T1, T2, P1, P2> 239where 240 L: Label, 241 S: Symbol, 242 T1: Tag<Label = L, Symbol = S>, 243 T2: Tag<Label = L, Symbol = S>, 244 P1: Parser<Tag = T1>, 245 P2: Parser<Tag = T2>, 246{ 247 type Tag = Either<T1, T2>; 248 249 fn parse<B, H>( 250 &self, 251 storage: &mut StorageLock<'_, L, B, H>, 252 key_value_separator: KeyValueSep, 253 path_separator: PathSep, 254 raw: &str, 255 ) -> Result<Self::Tag, ParseError> 256 where 257 B: InternerBackend<Symbol = S>, 258 H: BuildHasher, 259 { 260 self.parse(storage, key_value_separator, path_separator, raw) 261 } 262} 263 264/// Sets which side(s) of the raw tag should be trimmed of whitespace. 265#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)] 266pub enum TrimBounds { 267 /// Both sides should be trimmed. 268 Both, 269 270 /// Just the starting side should be trimmed. 271 Start, 272 273 /// Just the ending side should be trimmed. 274 End, 275} 276 277/// Sets how many replacements should be done when using the [`Replace`] adapter. 278#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)] 279pub enum ReplaceCount { 280 /// Replace just the first instance of the regex match. 281 First, 282 283 /// Replace the first `N` instances of the regex match. 284 N(usize), 285 286 /// Replace all instances of the regex match. 287 All, 288}