komichi/
lib.rs

1//! # 小道 Komichi
2//!
3//! `komichi` is a collection of tools to make working with file-system paths
4//! more convenient.
5//!
6//! ## Features
7//! Some notable features of `komichi`:
8//! * Uses [`camino`](module@camino) paths so that an application,
9//!   using this crate, can treat paths like normal Rust string-like types.
10//! * [`EnvVal`] provides the ability to retrieve environment variable
11//!   values and use a default value if the environment variable does NOT
12//!   exist or have a value.
13//! * [`ExpandPath`] provides a relatively-fast ability to expand unicode-paths
14//!   that:
15//!   * may contain BASH-like variables; and
16//!   * may start with a tilde; and
17//!   * may not be absolute.
18//! * [`ExpandText`] provides a relatively-fast ability to expand
19//!   given text or text from a file (using a callback function) that:
20//!   * may contain BASH-like curly-bracket variables (aka identifiers)
21//! * [`ExpandTextWith`] provides a relatively-fast ability to expand
22//!   given text or text from a file (using the [`Fetcher`](expand::text::Fetcher) trait) that:
23//!   * may contain BASH-like curly-bracket variables (aka identifiers)
24//! * [`LocalDirectories`] can provide application local (`$HOME`) path directory
25//!   locations for:
26//!   * cache files
27//!   * config files
28//!   * data files
29//!   * log files
30//!   * state files
31//! * [`SystemDirectories`] can provide application system path directories
32//!   locations for:
33//!   * system application cache files
34//!   * system application config files
35//!   * system application data files
36//!   * system application log files
37//!   * system application state files
38//!   * system application install files
39//!
40//!
41
42#![doc = include_str!("../doc/releases.md")]
43#![doc = include_str!("../doc/glossary.md")]
44
45#[doc(hidden)]
46pub mod environ;
47pub mod error;
48#[doc(hidden)]
49pub mod expand;
50#[doc(hidden)]
51pub mod local;
52#[doc(hidden)]
53pub mod system;
54#[doc(hidden)]
55pub mod util;
56
57use camino::{Utf8Component, Utf8Path, Utf8PathBuf};
58use dirs_sys::home_dir;
59use std::ffi::OsStr;
60
61#[doc(inline)]
62pub use environ::EnvVal;
63#[doc(inline)]
64pub use expand::path::ExpandPath;
65#[doc(inline)]
66pub use expand::text::ExpandText;
67#[doc(inline)]
68pub use expand::text::ExpandTextWith;
69#[doc(inline)]
70pub use expand::text::Fetcher;
71#[doc(inline)]
72pub use local::LocalDirectories;
73#[doc(inline)]
74pub use util::Scrub;
75
76use error::KomichiError;
77
78#[doc(inline)]
79pub use system::SystemDirectories;
80#[doc(inline)]
81pub use util::scrub_path;
82
83/// Return the current user's home directory as a
84/// [`Utf8PathBuf`](struct@camino::Utf8PathBuf).
85///
86/// # Errors
87///
88/// * [`KomichiError`] - will be returned if:
89///   * unable to locate the user's home directory; or
90///   * the retrieved home directory contains non UTF-8 encoded characters; or
91///   * the retrieved home directory is not an absolute path.
92///
93/// # Example
94///
95/// ```
96/// use komichi::get_home;
97/// let value = get_home().unwrap();
98/// assert!(!value.to_string().is_empty());
99/// ```
100///
101///
102pub fn get_home() -> Result<Utf8PathBuf, KomichiError> {
103    Ok(util::get_home(home_dir)?)
104}
105
106/// Return the given home directory or the current user's home directory
107/// as a [`Utf8PathBuf`](struct@camino::Utf8PathBuf).
108///
109/// # Arguments
110///
111/// * `path` - A string-like object that can be referenced as a
112///   [`Utf8Path`](struct@camino::Utf8PathBuf).
113///
114/// # Errors
115///
116/// * [`KomichiError`] - will be returned if:
117///   * unable to locate the user's home directory; or
118///   * the retrieved home directory contains non UTF-8 encoded characters; or
119///   * the retrieved, or given, home directory is not an absolute path.
120///
121/// # Example
122///
123/// ```
124/// use camino::Utf8PathBuf;
125/// use komichi::use_home;
126/// let value = use_home(Some("/a/b/c")).unwrap();
127/// let expect = Utf8PathBuf::from("/a/b/c");
128/// assert_eq!(expect, value);
129/// ```
130///
131pub fn use_home<T>(
132    path: Option<&T>
133) -> Result<Utf8PathBuf, KomichiError>
134where
135    T: AsRef<Utf8Path> + ?Sized,
136{
137    Ok(util::use_home(path, home_dir)?)
138}
139
140/// Return the current user's current working directory ("CWD") as a
141/// [`Utf8PathBuf`](struct@camino::Utf8PathBuf).
142///
143/// # Errors
144///
145/// * [`KomichiError`] - will be returned if:
146///   * unable to locate the current user's CWD; or
147///   * the retrieved CWD has a file-error e.g. Does not exist, permissions,
148///     etc., or
149///   * the retrieved CWD contains non UTF-8 encoded characters.
150///
151/// # Example
152///
153/// ```
154/// use komichi::get_cwd;
155/// let value = get_cwd().unwrap();
156/// assert!(!value.to_string().is_empty());
157/// ```
158pub fn get_cwd() -> Result<Utf8PathBuf, KomichiError> {
159    Ok(util::get_cwd(std::env::current_dir)?)
160}
161
162/// Return the given path or the current user's current working directory
163/// ("CWD") as a [`Utf8PathBuf`](struct@camino::Utf8PathBuf).
164///
165/// # Arguments
166///
167/// * `path` - A string-like object that can be referenced as a
168///   [`Utf8Path`](struct@camino::Utf8Path).
169///
170/// # Errors
171///
172/// * [`KomichiError`] - will be returned if:
173///   * unable to locate the current user's CWD; or
174///   * the retrieved CWD has a file-error e.g. Does not exist, permissions,
175///     etc., or
176///   * the retrieved CWD contains non UTF-8 encoded characters; or
177///   * the retrieved, or given, CWD is not an absolute path.
178///
179/// # Example
180///
181/// ```
182/// use camino::Utf8PathBuf;
183/// use komichi::use_cwd;
184/// let value = use_cwd(Some("/a/b/c")).unwrap();
185/// let expect = Utf8PathBuf::from("/a/b/c");
186/// assert_eq!(expect, value);
187/// ```
188pub fn use_cwd<T>(
189    path: Option<&T>
190) -> Result<Utf8PathBuf, KomichiError>
191where
192    T: AsRef<Utf8Path> + ?Sized,
193{
194    Ok(util::use_cwd(path, std::env::current_dir)?)
195}
196
197/// Convert the given path into a [`Utf8PathBuf`](struct@camino::Utf8PathBuf).
198///
199/// # Arguments
200///
201/// * `path` - Any string-like object that can be referenced as a
202///   [`std::ffi::OsStr`].
203///
204/// # Notes
205/// * This is a convenience function that wraps
206///   [`Utf8PathBuf::try_from`](struct@camino::Utf8PathBuf)
207///
208/// # Errors
209///
210/// * [`KomichiError`] - will be returned if unable to convert the given
211///   path. The given path must safely convert to UTF-8.
212///
213/// # Example
214///
215/// ```
216/// use camino::Utf8PathBuf;
217/// use komichi::utf8_path_buf;
218/// let value = utf8_path_buf("a/b/c").unwrap();
219/// let expect = Utf8PathBuf::from("a/b/c");
220/// assert_eq!(expect, value);
221/// ```
222pub fn utf8_path_buf<T>(
223    path: &T
224) -> Result<Utf8PathBuf, KomichiError>
225where
226    T: AsRef<OsStr> + ?Sized,
227{
228    util::utf8_path_buf(&path).map_err(|e| {
229        KomichiError::PathConvertError {
230            path: path.as_ref().to_string_lossy().to_string(),
231            source: e,
232        }
233    })
234}
235
236/// Expand the given `path` with the given function/callback.
237///
238/// # Notes
239/// * Any identifiers contained in the given path which cannot be expanded.
240/// * This function will NOT expand a tilde or prepend the current working
241///   directory if the given `path` is not absolute.
242///
243/// # Arguments
244///
245/// * `path` - Any string-like object that can be referenced as a
246///   [`str`].
247/// * `fetch` - The function/callback that will retrieve a value for a
248///   given identifier key.
249///
250/// # Errors
251/// [`KomichiError`] will be returned if:
252/// * if the given `path` is empty; or,
253/// * the given `path` contains an open curly bracket `{` with no preceding
254///   dollar sign `$`; or,
255/// * the given `path` contains an identifier that has an invalid start
256///   character; or,
257/// * the given `path` contains an identifier that is missing a closing
258///   curly bracket `}`; or,
259/// * the given `path` contains an invalid character after a starting
260///   tilde `~`.
261///
262/// # Example
263/// ```
264/// use komichi::expand_path_with;
265/// use camino::Utf8PathBuf;
266///
267/// fn fetcher(key: &str) -> Option<String> {
268///     match key {
269///         "ONE" => Some(String::from("1")),
270///         "TWO" => Some(String::from("2")),
271///         _ => None
272///     }
273/// }
274///
275/// let path = "/1${ONE}/${TWO}/${THREE}";
276/// let result = expand_path_with(&path, fetcher).unwrap();
277/// let expect = Utf8PathBuf::from("/11/2/${THREE}");
278/// assert_eq!(expect, result);
279/// ```
280///
281pub fn expand_path_with<T>(
282    path: &T,
283    fetch: fn(&str) -> Option<String>,
284) -> Result<Utf8PathBuf, KomichiError>
285where
286    T: AsRef<str> + ?Sized,
287{
288    Ok(ExpandPath::new().set_fetch(fetch).path(&path)?)
289}
290
291/// Strictly expand the given `path` with the given function/callback.
292///
293/// # Notes
294/// * This differs from [`expand_path_with`] in that an
295///   [`KomichiError`] will be returned on any identifiers that cannot be expanded
296///   by the given function/callback.
297/// * This function will NOT expand a tilde or prepend the current working
298///   directory if the given `path` is not absolute.
299///
300/// # Arguments
301///
302/// * `path` - Any string-like object that can be referenced as a
303///   [`str`].
304/// * `fetch` - The function/callback that will retrieve a value for a
305///   given identifier key.
306///
307/// # Errors
308/// [`KomichiError`] will be returned if:
309/// * if the given `path` is empty; or,
310/// * the given `path` contains an open curly bracket `{` with no preceding
311///   dollar sign `$`; or,
312/// * the given `path` contains an identifier that has an invalid start
313///   character; or,
314/// * the given `path` contains an identifier that is missing a closing
315///   curly bracket `}`; or,
316/// * the given `path` contains an invalid character after a starting
317///   tilde `~`; or,
318/// * if the given `path` contains an identifier which cannot be
319///   expanded by the given `fetch` callback/function.
320///
321/// # Example
322/// ```
323/// use komichi::expand_path_strict_with;
324/// use camino::Utf8PathBuf;
325///
326/// fn fetcher(key: &str) -> Option<String> {
327///     match key {
328///         "ONE" => Some(String::from("1")),
329///         "TWO" => Some(String::from("2")),
330///         _ => None
331///     }
332/// }
333///
334/// let path = "/1${ONE}/${TWO}";
335/// let result = expand_path_strict_with(&path, fetcher).unwrap();
336/// let expect = Utf8PathBuf::from("/11/2");
337/// assert_eq!(expect, result);
338/// ```
339///
340pub fn expand_path_strict_with<T>(
341    path: &T,
342    fetch: fn(&str) -> Option<String>,
343) -> Result<Utf8PathBuf, KomichiError>
344where
345    T: AsRef<str> + ?Sized,
346{
347    Ok(ExpandPath::new().set_fetch(fetch).path_strict(&path)?)
348}
349
350/// Expand a starting tilde in the given `path` to the home directory.
351///
352/// If the given `path` does not contain a starting tilde, then it will
353/// be returned.
354/// .
355/// Arguments:
356/// * `path` - a string-like reference to a [`str`] containing the
357///   path that will be expanded.
358/// * `home` - a string-like reference to a
359///   [`Utf8Path`](struct@camino::Utf8Path) containing the desired path
360///   to the home directory. If `None` is given
361///   then the current user's home directory will be used.
362///
363/// # Errors
364///
365/// [`KomichiError`] will be returned if:
366///
367/// * the given `home` is not an absolute path; or,
368/// * the current user's home directory could not be found; or,
369/// * if the given `path` is empty; or,
370/// * the given `path` contains an open curly bracket `{` with no preceding
371///   dollar sign `$`; or,
372/// * the given `path` contains an identifier that has an invalid start
373///   character; or,
374/// * the given `path` contains an identifier that is missing a closing
375///   curly bracket `}`; or,
376/// * the given `path` contains an invalid character after a starting
377///   tilde `~`.
378///
379pub fn expand_path_tilde<P, H>(
380    path: &P,
381    home: Option<&H>,
382) -> Result<Utf8PathBuf, KomichiError>
383where
384    P: AsRef<str> + ?Sized,
385    H: AsRef<Utf8Path> + ?Sized,
386{
387    let home = use_home(home)?;
388    Ok(ExpandPath::new().set_home(&home)?.path(path)?)
389}
390
391/// Prepend the current working directory ("CWD") to the given `path`.
392///
393/// If the given `path` is absolute the it will be returned.
394///
395/// Arguments:
396/// * `path` - a string-like reference to a [`str`] containing the
397///   path that will be expanded.
398/// * `cwd` - a string-like reference to a
399///   [`Utf8Path`](struct@camino::Utf8Path) containing the desired CWD.
400///   If `None` is given then the current
401///   user's current-working-directory will be used.
402///
403/// # Errors
404///
405/// [`KomichiError`] will be returned if:
406///
407/// * the given `cwd` is not an absolute path; or,
408/// * the current user's CWD could not be found; or,
409/// * the current user's CWD has permission issues; or,
410/// * if the given `path` is empty; or,
411/// * the given `path` contains an open curly bracket `{` with no preceding
412///   dollar sign `$`; or,
413/// * the given `path` contains an identifier that has an invalid start
414///   character; or,
415/// * the given `path` contains an identifier that is missing a closing
416///   curly bracket `}`; or,
417/// * the given `path` contains an invalid character after a starting
418///   tilde `~`.
419///
420pub fn expand_path_cwd<P, C>(
421    path: &P,
422    cwd: Option<&C>,
423) -> Result<Utf8PathBuf, KomichiError>
424where
425    P: AsRef<str> + ?Sized,
426    C: AsRef<Utf8Path> + ?Sized,
427{
428    let cwd = use_cwd(cwd)?;
429    Ok(ExpandPath::new().set_cwd(&cwd)?.path(path)?)
430}
431
432/// Return the given path with all its intermediate components normalized,
433/// without performing I/O.
434///
435/// This is modified, to use [`camino`], from the crate
436/// [`normalize_path`](https://docs.rs/normalize-path/latest/normalize_path/)
437///
438/// # Arguments
439///
440/// * `path` - A string-like reference to a [`str`] containing the path
441///   to be normalized
442///
443/// # Example
444///
445/// ```
446/// use komichi::normalize_path;
447/// use camino::Utf8PathBuf;
448/// let exp = Utf8PathBuf::from("/a/b/c/d");
449/// let val = normalize_path("/a//b///c/d");
450/// assert_eq!(exp, val);
451/// ```
452///
453pub fn normalize_path<T>(path: &T) -> Utf8PathBuf
454where
455    T: AsRef<str> + ?Sized,
456{
457    let path = Utf8PathBuf::from(path.as_ref());
458    let mut components = path.components().peekable();
459    let mut out = if let Some(c @ Utf8Component::Prefix(..)) =
460        components.peek()
461    {
462        let buf = Utf8PathBuf::from(c.as_str());
463        components.next();
464        buf
465    } else {
466        Utf8PathBuf::new()
467    };
468    for component in components {
469        match component {
470            Utf8Component::Prefix(..) => unreachable!(),
471            Utf8Component::RootDir => {
472                out.push(component.as_str());
473            },
474            Utf8Component::CurDir => {},
475            Utf8Component::ParentDir => {
476                out.pop();
477            },
478            Utf8Component::Normal(c) => {
479                out.push(c);
480            },
481        }
482    }
483    out
484}
485
486/// Expand any environment variables in the given `path`
487///
488/// # Arguments
489///
490/// * `path` - A string-like reference to a [`str`] containing the path
491///   to be expanded.
492///
493/// # Errors
494/// [`KomichiError`] will be returned if:
495/// * if the given `path` is empty; or,
496/// * the given `path` contains an open curly bracket `{` with no preceding
497///   dollar sign `$`; or,
498/// * the given `path` contains an identifier that has an invalid start
499///   character; or,
500/// * the given `path` contains an identifier that is missing a closing
501///   curly bracket `}`; or,
502/// * the given `path` contains an invalid character after a starting
503///   tilde `~`; or,
504/// * if the given `path` contains an identifier which cannot be
505///   expanded by the given `fetch` callback/function.
506///
507/// # Example
508///
509/// ```
510/// use komichi::expand_path_with_environ;
511/// use std::env::set_var;
512///
513/// // Only using unsafe for the example. Most likely the environment-variables
514/// // will be set via the shell.
515/// unsafe {
516///     set_var("AVAR", "test");
517/// }
518/// let path = "/a/${AVAR}/b";
519/// let result = expand_path_with_environ(&path).unwrap();
520/// assert!(result.as_str().len() > 0);
521/// ```
522///
523pub fn expand_path_with_environ<T>(
524    path: &T
525) -> Result<Utf8PathBuf, KomichiError>
526where
527    T: AsRef<str> + ?Sized,
528{
529    Ok(ExpandPath::new()
530        .set_fetch(|key| std::env::var(key).ok())
531        .path_strict(path)?)
532}
533
534/// Return a [`LocalDirectories`] struct that has functions
535/// to get various local application paths for the given application name.
536///
537/// # Arguments
538/// * `app_name` - The name of the application used when creating the paths.
539///   It is recommended that this value contain no spaces.
540/// * `home` - An [`Option`] containing string-like reference to a
541///   [`Utf8Path`](struct@camino::Utf8Path). This will be the path to the
542///   user's home directory. This function will search for, and use, the
543///   current user's home directory when given a value of `None`.
544///
545/// # Errors
546///
547/// * [`KomichiError`] - will be returned if:
548///   * unable to locate the user's home directory; or
549///   * the retrieved home directory contains non UTF-8 encoded characters; or
550///   * the retrieved, or given, home directory is not an absolute path.
551///
552/// # Example
553///
554/// ```
555/// use komichi::get_local_application_paths;
556///
557/// match get_local_application_paths::<&str>("myapp", None) {
558///     Ok(local) => {
559///         println!("{}", local.get_cache_home());
560///         println!("{}", local.get_config_home());
561///         println!("{}", local.get_data_home());
562///         println!("{}", local.get_log_home());
563///         println!("{}", local.get_state_home());
564///     },
565///     Err(_) => {}
566/// }
567///
568/// ```
569pub fn get_local_application_paths<T>(
570    app_name: &str,
571    home: Option<&T>,
572) -> Result<LocalDirectories, KomichiError>
573where
574    T: AsRef<Utf8Path> + ?Sized,
575{
576    let home = use_home(home)?;
577    Ok(LocalDirectories::new(app_name, &home))
578}
579
580/// Return a [`SystemDirectories`](struct@crate::system::SystemDirectories)
581/// struct which can be used to get system directories for a given
582/// application-name as if the application has been installed on the system
583/// but not installed locally (in the user's home directory)
584///
585/// # Arguments
586/// * `app_name` - The name of the application used when generating the path
587///   locations. It is recommended that this value contain no spaces.
588///
589/// # Example
590///
591/// ```
592/// use komichi::get_system_application_paths;
593/// let application_paths = get_system_application_paths("myapp");
594/// println!("cache home: {}", application_paths.get_cache_home());
595/// println!("config home: {}", application_paths.get_config_home());
596/// println!("data home: {}", application_paths.get_data_home());
597/// println!("log home: {}", application_paths.get_log_home());
598/// println!("state home: {}", application_paths.get_state_home());
599/// println!("opt home: {}", application_paths.get_opt_home());
600///
601/// ````
602pub fn get_system_application_paths(
603    app_name: &str
604) -> SystemDirectories {
605    SystemDirectories::new(app_name)
606}
607
608/// Return the given text, containing bash-like-curly variables that are
609/// expanded with values from the given callback function.
610///
611/// The text can contain BASH-like variables with each variable using
612/// the curly syntax (e.g. `A dog named ${DOG_NAME}`). BASH-like variables
613/// that do not contain curly brackets (e.g. `$DOG_NAME`) will become
614/// identifier errors.
615///
616/// The `$` character can be escaped by using two `$` characters e.g. `$$`.
617///
618/// Any identifier that does not have a value, as given by the callback
619/// function will be skipped. Meaning that the BASH-like variable will be
620/// put back into the output.
621///
622/// # Arguments
623///
624/// * `fetch` - The function/callback that will retrieve a value for a
625///   given identifier key.
626/// * `text` - A string-slice containing the text-to-be-expanded
627///
628/// # Errors
629/// A [`KomichiError`] - will be returned if:
630///   * there are any syntactical errors with any BASH-like variables:
631///     * Non-ASCII characters between the curly-brackets,
632///     * Non-alpha-numeric-underscore characters between the curly-brackets,
633///     * No characters/empty between the curly-brackets,
634///     * Missing an opening curly-bracket, after the `$`,
635///     * Missing a closing curly-bracket, after the identifier,
636///     * Identifier starts with a number. Can only start with an underscore
637///       or an ASCII-letter,
638///     * Invalid identifier name when the identifier name contains spaces
639///       between parts of the identifier name (e.g. `${dog name}`).
640///
641/// # Example
642/// ```
643/// use komichi::expand_text_with;
644///
645/// fn fetcher(key: &str) -> Option<String> {
646///     match key {
647///         "speed" => Some(String::from("quick")),
648///         "color" => Some(String::from("brown")),
649///         "animal" => Some(String::from("dog")),
650///         _ => None
651///     }
652/// }
653/// let expect = "“The quick brown fox jumps over the lazy dog”";
654/// let arg = "“The ${speed} ${color} fox jumps over the lazy ${animal}”";
655/// let result = expand_text_with(arg, fetcher).unwrap();
656/// assert_eq!(result, expect);
657/// ```
658///
659pub fn expand_text_with(
660    text: &str,
661    fetch: fn(&str) -> Option<String>,
662) -> Result<String, KomichiError> {
663    Ok(ExpandText::new(fetch).text(text)?)
664}
665
666/// Return the given text, containing bash-like-curly variables that are
667/// expanded with values from the given callback-function. And return
668/// an [`KomichiError`] when a value cannot be found by the given
669/// callback-function
670///
671/// The text can contain BASH-like variables with each variable using
672/// the curly syntax (e.g. `A dog named ${DOG_NAME}`). BASH-like variables
673/// that do not contain curly brackets (e.g. `$DOG_NAME`) will become
674/// identifier errors.
675///
676/// The `$` character can be escaped by using two `$` characters e.g. `$$`.
677///
678/// # Arguments
679///
680/// * `fetch` - The function/callback that will retrieve a value for a
681///   given identifier key.
682/// * `text` - A string-slice containing the text-to-be-expanded
683///
684/// # Errors
685/// A [`KomichiError`] - will be returned if:
686///   * there are any syntactical errors with any BASH-like variables:
687///     * Non-ASCII characters between the curly-brackets,
688///     * Non-alpha-numeric-underscore characters between the curly-brackets,
689///     * No characters/empty between the curly-brackets,
690///     * Missing an opening curly-bracket, after the `$`,
691///     * Missing a closing curly-bracket, after the identifier,
692///     * Identifier starts with a number. Can only start with an underscore
693///       or an ASCII-letter,
694///     * Invalid identifier name when the identifier name contains spaces
695///       between parts of the identifier name (e.g. `${dog name}`),
696///   * if the value for any identifier cannot be returned by the given
697///     callback-function.
698///
699/// # Example
700/// ```
701/// use komichi::expand_text_strict_with;
702///
703/// fn fetcher(key: &str) -> Option<String> {
704///     match key {
705///         "speed" => Some(String::from("quick")),
706///         "color" => Some(String::from("brown")),
707///         "animal" => Some(String::from("dog")),
708///         _ => None
709///     }
710/// }
711/// let expect = "“The quick brown fox jumps over the lazy dog”";
712/// let arg = "“The ${speed} ${color} fox jumps over the lazy ${animal}”";
713/// let result = expand_text_strict_with(arg, fetcher).unwrap();
714/// assert_eq!(result, expect);
715/// ```
716///
717pub fn expand_text_strict_with(
718    text: &str,
719    fetch: fn(&str) -> Option<String>,
720) -> Result<String, KomichiError> {
721    Ok(ExpandText::new(fetch).text_strict(text)?)
722}