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}