Skip to main content

tor_basic_utils/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3// @@ begin lint list maintained by maint/add_warning @@
4#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
5#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
6#![warn(missing_docs)]
7#![warn(noop_method_call)]
8#![warn(unreachable_pub)]
9#![warn(clippy::all)]
10#![deny(clippy::await_holding_lock)]
11#![deny(clippy::cargo_common_metadata)]
12#![deny(clippy::cast_lossless)]
13#![deny(clippy::checked_conversions)]
14#![warn(clippy::cognitive_complexity)]
15#![deny(clippy::debug_assert_with_mut_call)]
16#![deny(clippy::exhaustive_enums)]
17#![deny(clippy::exhaustive_structs)]
18#![deny(clippy::expl_impl_clone_on_copy)]
19#![deny(clippy::fallible_impl_from)]
20#![deny(clippy::implicit_clone)]
21#![deny(clippy::large_stack_arrays)]
22#![warn(clippy::manual_ok_or)]
23#![deny(clippy::missing_docs_in_private_items)]
24#![warn(clippy::needless_borrow)]
25#![warn(clippy::needless_pass_by_value)]
26#![warn(clippy::option_option)]
27#![deny(clippy::print_stderr)]
28#![deny(clippy::print_stdout)]
29#![warn(clippy::rc_buffer)]
30#![deny(clippy::ref_option_ref)]
31#![warn(clippy::semicolon_if_nothing_returned)]
32#![warn(clippy::trait_duplication_in_bounds)]
33#![deny(clippy::unchecked_time_subtraction)]
34#![deny(clippy::unnecessary_wraps)]
35#![warn(clippy::unseparated_literal_suffix)]
36#![deny(clippy::unwrap_used)]
37#![deny(clippy::mod_module_files)]
38#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
39#![allow(clippy::uninlined_format_args)]
40#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
41#![allow(clippy::result_large_err)] // temporary workaround for arti#587
42#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
43#![allow(clippy::needless_lifetimes)] // See arti#1765
44#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
45#![deny(clippy::unused_async)]
46//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
47
48use std::collections::BinaryHeap;
49use std::fmt;
50use std::mem;
51use std::ops::{RangeInclusive, RangeToInclusive};
52use std::path::Path;
53use std::time::Duration;
54
55pub mod intern;
56pub mod iter;
57pub mod n_key_list;
58pub mod n_key_set;
59pub mod rand_hostname;
60pub mod rangebounds;
61pub mod retry;
62pub mod test_rng;
63
64mod byte_qty;
65pub use byte_qty::ByteQty;
66
67pub use paste::paste;
68
69use rand::Rng;
70
71/// Sealed
72mod sealed {
73    /// Sealed
74    pub trait Sealed {}
75}
76use sealed::Sealed;
77
78// ----------------------------------------------------------------------
79
80/// Function with the signature of `Debug::fmt` that just prints `".."`
81///
82/// ```
83/// use educe::Educe;
84/// use tor_basic_utils::skip_fmt;
85///
86/// #[derive(Educe, Default)]
87/// #[educe(Debug)]
88/// struct Wombat {
89///     visible: usize,
90///
91///     #[educe(Debug(method = "skip_fmt"))]
92///     invisible: [u8; 2],
93/// }
94///
95/// assert_eq!( format!("{:?}", &Wombat::default()),
96///             "Wombat { visible: 0, invisible: .. }" );
97/// ```
98pub fn skip_fmt<T>(_: &T, f: &mut fmt::Formatter) -> fmt::Result {
99    /// Inner function avoids code bloat due to generics
100    fn inner(f: &mut fmt::Formatter) -> fmt::Result {
101        write!(f, "..")
102    }
103    inner(f)
104}
105
106// ----------------------------------------------------------------------
107
108/// Extension trait to provide `.strip_suffix_ignore_ascii_case()` etc.
109// Using `.as_ref()` as a supertrait lets us make the method a provided one.
110pub trait StrExt: AsRef<str> {
111    /// Like `str.strip_suffix()` but ASCII-case-insensitive
112    fn strip_suffix_ignore_ascii_case(&self, suffix: &str) -> Option<&str> {
113        let whole = self.as_ref();
114        let suffix_start = whole.len().checked_sub(suffix.len())?;
115        whole[suffix_start..]
116            .eq_ignore_ascii_case(suffix)
117            .then(|| &whole[..suffix_start])
118    }
119
120    /// Like `str.ends_with()` but ASCII-case-insensitive
121    fn ends_with_ignore_ascii_case(&self, suffix: &str) -> bool {
122        self.strip_suffix_ignore_ascii_case(suffix).is_some()
123    }
124}
125impl StrExt for str {}
126
127// ----------------------------------------------------------------------
128
129/// Extension trait to provide `.gen_range_checked()`
130pub trait RngExt: Rng {
131    /// Generate a random value in the given range.
132    ///
133    /// This function is optimised for the case that only a single sample is made from the given range. See also the [`Uniform`](rand::distr::uniform::Uniform)  distribution type which may be faster if sampling from the same range repeatedly.
134    ///
135    /// If the supplied range is empty, returns `None`.
136    ///
137    /// (This is a non-panicking version of [`Rng::gen_range`].)
138    ///
139    /// ### Example
140    ///
141    /// ```
142    /// use tor_basic_utils::RngExt as _;
143    //
144    // Fake plastic imitation tor_error, since that's actually higher up the stack
145    /// # #[macro_use]
146    /// # mod tor_error {
147    /// #     #[derive(Debug)]
148    /// #     pub struct Bug;
149    /// #     pub fn internal() {} // makes `use` work
150    /// # }
151    /// # macro_rules! internal { { $x:expr } => { Bug } }
152    //
153    /// use tor_error::{Bug, internal};
154    ///
155    /// fn choose(slice: &[i32]) -> Result<i32, Bug> {
156    ///     let index = rand::rng()
157    ///         .gen_range_checked(0..slice.len())
158    ///         .ok_or_else(|| internal!("empty slice"))?;
159    ///     Ok(slice[index])
160    /// }
161    ///
162    /// assert_eq!(choose(&[42]).unwrap(), 42);
163    /// let _: Bug = choose(&[]).unwrap_err();
164    /// ```
165    //
166    // TODO: We may someday wish to rename this function to random_range_checked,
167    // since gen_range was renamed to random_range in rand 0.9.
168    // Or we might decide to leave it alone.
169    fn gen_range_checked<T, R>(&mut self, range: R) -> Option<T>
170    where
171        T: rand::distr::uniform::SampleUniform,
172        R: rand::distr::uniform::SampleRange<T>,
173    {
174        if range.is_empty() {
175            None
176        } else {
177            #[allow(clippy::disallowed_methods)]
178            Some(Rng::random_range(self, range))
179        }
180    }
181
182    /// Generate a random value in the given upper-bounded-only range.
183    ///
184    /// For use with an inclusive upper-bounded-only range,
185    /// with types that implement `GenRangeInfallible`
186    /// (that necessarily then implement the appropriate `rand` traits).
187    ///
188    /// This function is optimised for the case that only a single sample is made from the given range. See also the [`Uniform`](rand::distr::uniform::Uniform)  distribution type which may be faster if sampling from the same range repeatedly.
189    ///
190    /// ### Example
191    ///
192    /// ```
193    /// use std::time::Duration;
194    /// use tor_basic_utils::RngExt as _;
195    ///
196    /// fn stochastic_sleep(max: Duration) {
197    ///     let chosen_delay = rand::rng()
198    ///         .gen_range_infallible(..=max);
199    ///     std::thread::sleep(chosen_delay);
200    /// }
201    /// ```
202    fn gen_range_infallible<T>(&mut self, range: RangeToInclusive<T>) -> T
203    where
204        T: GenRangeInfallible,
205    {
206        self.gen_range_checked(T::lower_bound()..=range.end)
207            .expect("GenRangeInfallible type with an empty lower_bound()..=T range")
208    }
209}
210impl<T: Rng> RngExt for T {}
211
212/// Types that can be infallibly sampled using `gen_range_infallible`
213///
214/// In addition to the supertraits, the implementor of this trait must guarantee that:
215///
216/// `<Self as GenRangeInfallible>::lower_bound() ..= UPPER`
217/// is a nonempty range for every value of `UPPER`.
218//
219// One might think that this trait is wrong because we might want to be able to
220// implement gen_range_infallible for arguments other than RangeToInclusive<T>.
221// However, double-ended ranges are inherently fallible because the actual values
222// might be in the wrong order.  Non-inclusive ranges are fallible because the
223// upper bound might be zero, unless a NonZero type is used, which seems like a further
224// complication that we probably don't want to introduce here.  That leaves lower-bounded
225// ranges, but those are very rare.
226pub trait GenRangeInfallible: rand::distr::uniform::SampleUniform + Ord
227where
228    RangeInclusive<Self>: rand::distr::uniform::SampleRange<Self>,
229{
230    /// The usual lower bound, for converting a `RangeToInclusive` to a `RangeInclusive`
231    ///
232    /// Only makes sense with types with a sensible lower bound, such as zero.
233    fn lower_bound() -> Self;
234}
235
236impl GenRangeInfallible for Duration {
237    fn lower_bound() -> Self {
238        Duration::ZERO
239    }
240}
241
242// ----------------------------------------------------------------------
243
244/// Implementation of `ErrorKind::NotADirectory` that doesn't require Nightly
245pub trait IoErrorExt: Sealed {
246    /// Is this `io::ErrorKind::NotADirectory` ?
247    fn is_not_a_directory(&self) -> bool;
248}
249impl Sealed for std::io::Error {}
250impl IoErrorExt for std::io::Error {
251    fn is_not_a_directory(&self) -> bool {
252        self.raw_os_error()
253            == Some(
254                #[cfg(target_family = "unix")]
255                libc::ENOTDIR,
256                #[cfg(target_family = "windows")]
257                {
258                    /// Obtained from Rust stdlib source code
259                    /// See also:
260                    ///   <https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499->
261                    /// (although the documentation is anaemic) and
262                    /// <https://github.com/rust-lang/rust/pull/79965>
263                    const ERROR_DIRECTORY: i32 = 267;
264                    ERROR_DIRECTORY
265                },
266            )
267    }
268}
269
270// ----------------------------------------------------------------------
271
272/// Implementation of `BinaryHeap::retain` that doesn't require Nightly
273pub trait BinaryHeapExt<T> {
274    /// Remove all elements for which `f` returns `false`
275    ///
276    /// Performance is not great right now - the algorithm is `O(n*log(n))`
277    /// where `n` is the number of elements in the heap (not the number removed).
278    ///
279    /// The name is `retain_ext` to avoid a name collision with the unstable function,
280    /// which would require the use of UFCS and make this unergonomic.
281    fn retain_ext<F: FnMut(&T) -> bool>(&mut self, f: F);
282}
283impl<T: Ord> BinaryHeapExt<T> for BinaryHeap<T> {
284    fn retain_ext<F: FnMut(&T) -> bool>(&mut self, f: F) {
285        let items = mem::take(self).into_iter();
286        *self = items.filter(f).collect();
287    }
288}
289
290// ----------------------------------------------------------------------
291
292/// Renaming of `Path::display` as `display_lossy`
293pub trait PathExt: Sealed {
294    /// Display this `Path` as an approximate string, for human consumption in messages
295    ///
296    /// Operating system paths cannot always be faithfully represented as Rust strings,
297    /// because they might not be valid Unicode.
298    ///
299    /// This helper method provides a way to display a string for human users.
300    /// **This may lose information** so should only be used for error messages etc.
301    ///
302    /// This method is exactly the same as [`std::path::Path::display`],
303    /// but with a different and more discouraging name.
304    fn display_lossy(&self) -> std::path::Display<'_>;
305}
306impl Sealed for Path {}
307impl PathExt for Path {
308    #[allow(clippy::disallowed_methods)]
309    fn display_lossy(&self) -> std::path::Display<'_> {
310        self.display()
311    }
312}
313
314// ----------------------------------------------------------------------
315
316/// Define an "accessor trait", which describes structs that have fields of certain types
317///
318/// This can be useful if a large struct, living high up in the dependency graph,
319/// contains fields that lower-lever crates want to be able to use without having
320/// to copy the data about etc.
321///
322/// ```
323/// // imagine this in the lower-level module
324/// pub trait Supertrait {}
325/// use tor_basic_utils::define_accessor_trait;
326/// define_accessor_trait! {
327///     pub trait View: Supertrait {
328///         lorem: String,
329///         ipsum: usize,
330///         +
331///         fn other_accessor(&self) -> bool;
332///         // any other trait items can go here
333///    }
334/// }
335///
336/// fn test_view<V: View>(v: &V) {
337///     assert_eq!(v.lorem(), "sit");
338///     assert_eq!(v.ipsum(), &42);
339/// }
340///
341/// // imagine this in the higher-level module
342/// use derive_more::AsRef;
343/// #[derive(AsRef)]
344/// struct Everything {
345///     #[as_ref] lorem: String,
346///     #[as_ref] ipsum: usize,
347///     dolor: Vec<()>,
348/// }
349/// impl Supertrait for Everything { }
350/// impl View for Everything {
351///     fn other_accessor(&self) -> bool { false }
352/// }
353///
354/// let everything = Everything {
355///     lorem: "sit".into(),
356///     ipsum: 42,
357///     dolor: vec![()],
358/// };
359///
360/// test_view(&everything);
361/// ```
362///
363/// ### Generated code
364///
365/// ```
366/// # pub trait Supertrait { }
367/// pub trait View: AsRef<String> + AsRef<usize> + Supertrait {
368///     fn lorem(&self) -> &String { self.as_ref() }
369///     fn ipsum(&self) -> &usize { self.as_ref() }
370/// }
371/// ```
372#[macro_export]
373macro_rules! define_accessor_trait {
374    {
375        $( #[ $attr:meta ])*
376        $vis:vis trait $Trait:ident $( : $( $Super:path )* )? {
377            $( $accessor:ident: $type:ty, )*
378            $( + $( $rest:tt )* )?
379        }
380    } => {
381        $( #[ $attr ])*
382        $vis trait $Trait: $( core::convert::AsRef<$type> + )* $( $( $Super + )* )?
383        {
384            $(
385                /// Access the field
386                fn $accessor(&self) -> &$type { core::convert::AsRef::as_ref(self) }
387            )*
388            $(
389                $( $rest )*
390            )?
391        }
392    }
393}
394
395// ----------------------------------------------------------------------
396
397/// Helper for assisting with macro "argument" defaulting
398///
399/// ```ignore
400/// macro_coalesce_args!{ [ something ]  ... }  // =>   something
401/// macro_coalesce_args!{ [ ], [ other ] ... }  // =>   other
402/// // etc.
403/// ```
404///
405/// ### Usage note
406///
407/// It is generally possible to avoid use of `macro_coalesce_args`, at the cost of
408/// providing many alternative matcher patterns.  Using `macro_coalesce_args` can make
409/// it possible to provide a single pattern with the optional items in `$( )?`.
410///
411/// This is valuable because a single pattern with some optional items
412/// makes much better documentation than several patterns which the reader must compare
413/// by eye - and it also simplifies the implementation.
414///
415/// `macro_coalesce_args` takes each of its possible expansions in `[ ]` and returns
416/// the first nonempty one.
417#[macro_export]
418macro_rules! macro_first_nonempty {
419    { [ $($yes:tt)+ ] $($rhs:tt)* } => { $($yes)* };
420    { [ ]$(,)? [ $($otherwise:tt)* ] $($rhs:tt)* } => {
421        $crate::macro_first_nonempty!{ [ $($otherwise)* ] $($rhs)* }
422    };
423}
424
425// ----------------------------------------------------------------------
426
427/// Define `Debug` to print as hex
428///
429/// # Usage
430///
431/// ```ignore
432/// impl_debug_hex! { $type }
433/// impl_debug_hex! { $type . $field_accessor }
434/// impl_debug_hex! { $type , $accessor_fn }
435/// ```
436///
437/// By default, this expects `$type` to implement `AsRef<[u8]>`.
438///
439/// Or, you can supply a series of tokens `$field_accessor`,
440/// which will be used like this: `self.$field_accessor.as_ref()`
441/// to get a `&[u8]`.
442///
443/// Or, you can supply `$accessor: fn(&$type) -> &[u8]`.
444///
445/// # Examples
446///
447/// ```
448/// use tor_basic_utils::impl_debug_hex;
449/// #[derive(Default)]
450/// struct FourBytes([u8; 4]);
451/// impl AsRef<[u8]> for FourBytes { fn as_ref(&self) -> &[u8] { &self.0 } }
452/// impl_debug_hex! { FourBytes }
453///
454/// assert_eq!(
455///     format!("{:?}", FourBytes::default()),
456///     "FourBytes(00000000)",
457/// );
458/// ```
459///
460/// ```
461/// use tor_basic_utils::impl_debug_hex;
462/// #[derive(Default)]
463/// struct FourBytes([u8; 4]);
464/// impl_debug_hex! { FourBytes .0 }
465///
466/// assert_eq!(
467///     format!("{:?}", FourBytes::default()),
468///     "FourBytes(00000000)",
469/// );
470/// ```
471///
472/// ```
473/// use tor_basic_utils::impl_debug_hex;
474/// struct FourBytes([u8; 4]);
475/// impl_debug_hex! { FourBytes, |self_| &self_.0 }
476///
477/// assert_eq!(
478///     format!("{:?}", FourBytes([1,2,3,4])),
479///     "FourBytes(01020304)",
480/// )
481/// ```
482#[macro_export]
483macro_rules! impl_debug_hex {
484    { $type:ty $(,)? } => {
485        $crate::impl_debug_hex! { $type, |self_| <$type as AsRef<[u8]>>::as_ref(&self_) }
486    };
487    { $type:ident . $($accessor:tt)+ } => {
488        $crate::impl_debug_hex! { $type, |self_| self_ . $($accessor)* .as_ref() }
489    };
490    { $type:ty, $obtain:expr $(,)? } => {
491        impl std::fmt::Debug for $type {
492            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
493                use std::fmt::Write;
494                let obtain: fn(&$type) -> &[u8] = $obtain;
495                let bytes: &[u8] = obtain(self);
496                write!(f, "{}(", stringify!($type))?;
497                for b in bytes {
498                    write!(f, "{:02x}", b)?;
499                }
500                write!(f, ")")?;
501                Ok(())
502            }
503        }
504    };
505}
506
507// ----------------------------------------------------------------------
508
509/// Helper for defining a struct which can be (de)serialized several ways, including "natively"
510///
511/// Ideally we would have
512/// ```rust ignore
513/// #[derive(Deserialize)]
514/// #[serde(try_from=Possibilities)]
515/// struct Main { /* principal definition */ }
516///
517/// #[derive(Deserialize)]
518/// #[serde(untagged)]
519/// enum Possibilities { Main(Main), Other(OtherRepr) }
520///
521/// #[derive(Deserialize)]
522/// struct OtherRepr { /* other representation we still want to read */ }
523///
524/// impl TryFrom<Possibilities> for Main { /* ... */ }
525/// ```
526///
527/// But the impl for `Possibilities` ends up honouring the `try_from` on `Main`
528/// so is recursive.
529///
530/// We solve that (ab)using serde's remote feature,
531/// on a second copy of the struct definition.
532///
533/// See the Example for instructions.
534/// It is important to **add test cases**
535/// for all the representations you expect to parse and serialise,
536/// since there are easy-to-write bugs,
537/// for example omitting some of the necessary attributes.
538///
539/// # Generated output:
540///
541///  * The original struct definition, unmodified
542///  * `#[derive(Serialize, Deserialize)] struct $main_Raw { }`
543///
544/// The `$main_Raw` struct ought not normally be to constructed anywhere,
545/// and *isn't* convertible to or from the near-identical `$main` struct.
546/// It exists only as a thing to feed to the serde remove derive,
547/// and name in `with=`.
548///
549/// # Example
550///
551/// ```
552/// use serde::{Deserialize, Serialize};
553/// use tor_basic_utils::derive_serde_raw;
554///
555/// derive_serde_raw! {
556///     #[derive(Deserialize, Serialize, Default, Clone, Debug)]
557///     #[serde(try_from="BridgeConfigBuilderSerde", into="BridgeConfigBuilderSerde")]
558///     pub struct BridgeConfigBuilder = "BridgeConfigBuilder" {
559///         transport: Option<String>,
560///         //...
561///     }
562/// }
563///
564/// #[derive(Serialize,Deserialize)]
565/// #[serde(untagged)]
566/// enum BridgeConfigBuilderSerde {
567///     BridgeLine(String),
568///     Dict(#[serde(with="BridgeConfigBuilder_Raw")] BridgeConfigBuilder),
569/// }
570///
571/// impl TryFrom<BridgeConfigBuilderSerde> for BridgeConfigBuilder { //...
572/// #    type Error = std::io::Error;
573/// #    fn try_from(_: BridgeConfigBuilderSerde) -> Result<Self, Self::Error> { todo!() } }
574/// impl From<BridgeConfigBuilder> for BridgeConfigBuilderSerde { //...
575/// #    fn from(_: BridgeConfigBuilder) -> BridgeConfigBuilderSerde { todo!() } }
576/// ```
577#[macro_export]
578macro_rules! derive_serde_raw { {
579    $( #[ $($attrs:meta)* ] )*
580    $vis:vis struct $main:ident=$main_s:literal
581    $($body:tt)*
582} => {
583    $(#[ $($attrs)* ])*
584    $vis struct $main
585    $($body)*
586
587    $crate::paste! {
588        #[allow(non_camel_case_types)]
589        #[derive(Serialize, Deserialize)]
590        #[serde(remote=$main_s)]
591        struct [< $main _Raw >]
592        $($body)*
593    }
594} }
595
596// ----------------------------------------------------------------------
597
598/// Flatten a `Result<Result<T, E>, E>` into a `Result<T, E>`.
599///
600/// See [`Result::flatten`], which is not available
601/// at our current MSRV.
602// TODO MSRV 1.89: When our MSRV is at least 1.89,
603// remove this function and replace uses with `Result::flatten`.
604pub fn flatten<T, E>(x: Result<Result<T, E>, E>) -> Result<T, E> {
605    match x {
606        Ok(Ok(x)) => Ok(x),
607        Err(e) | Ok(Err(e)) => Err(e),
608    }
609}
610
611// ----------------------------------------------------------------------
612
613/// Asserts that the type of the expression implements the given trait.
614///
615/// Example:
616///
617/// ```
618/// # use tor_basic_utils::assert_val_impl_trait;
619/// let x: u32 = 0;
620/// assert_val_impl_trait!(x, Clone);
621/// ```
622#[macro_export]
623macro_rules! assert_val_impl_trait {
624    ($check:expr, $trait:path $(,)?) => {{
625        fn ensure_trait<T: $trait>(_s: &T) {}
626        ensure_trait(&$check);
627    }};
628}
629
630// ----------------------------------------------------------------------
631
632#[cfg(test)]
633mod test {
634    // @@ begin test lint list maintained by maint/add_warning @@
635    #![allow(clippy::bool_assert_comparison)]
636    #![allow(clippy::clone_on_copy)]
637    #![allow(clippy::dbg_macro)]
638    #![allow(clippy::mixed_attributes_style)]
639    #![allow(clippy::print_stderr)]
640    #![allow(clippy::print_stdout)]
641    #![allow(clippy::single_char_pattern)]
642    #![allow(clippy::unwrap_used)]
643    #![allow(clippy::unchecked_time_subtraction)]
644    #![allow(clippy::useless_vec)]
645    #![allow(clippy::needless_pass_by_value)]
646    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
647    use super::*;
648
649    #[test]
650    fn test_strip_suffix_ignore_ascii_case() {
651        assert_eq!(
652            "hi there".strip_suffix_ignore_ascii_case("THERE"),
653            Some("hi ")
654        );
655        assert_eq!("hi here".strip_suffix_ignore_ascii_case("THERE"), None);
656        assert_eq!("THERE".strip_suffix_ignore_ascii_case("there"), Some(""));
657        assert_eq!("hi".strip_suffix_ignore_ascii_case("THERE"), None);
658    }
659}