source_text/
lib.rs

1//! Enable API's that consume `&str` to retrieve the text string from any `LoadSource` source while tracking the origin.
2//!
3//! # Terminology
4//!
5//! This crate uses some terminology to help in disambiguation:
6//!
7//! - "text" - an `&str` to be "processed" by application code; in contrast to `&str` used for
8//!   other purposes such as naming the origin of a "text" or used in error messages.
9//! - "source" - a [Source] value which tracks both a `text` and the `name` of its origin.
10//!
11//! # Example: [Parsable]
12//!
13//! Suppose we have a config file parser which parses a configuration into a `Config` struct. We
14//! want to be able to either parse strings in memory or load and parse config files from disk,
15//! with error messages indicating the source:
16//!
17//! ```
18//! use indoc::indoc; // For cleanly formatting test assertions.
19//! use source_text::Parsable;
20//!
21//! #[derive(Debug)]
22//! pub struct Config {
23//!     // ...
24//! }
25//!
26//! impl Parsable for Config {
27//!     fn parse_text(text: &str) -> anyhow::Result<Self> {
28//!         if text.is_empty() {
29//!             Err(anyhow::Error::msg("empty input\n"))
30//!         } else {
31//!             todo!("implement an `&str` parser...");
32//!         }
33//!     }
34//! }
35//!
36//! let err1 = Config::parse_source("").err().unwrap();
37//!
38//! assert_eq!(
39//!     format!("{:?}", err1).trim_end(),
40//!     indoc! {
41//!     r#"
42//!        Error in <input>:
43//!
44//!        Caused by:
45//!            empty input
46//!     "#
47//!     }.trim_end()
48//! );
49//!
50//! let configpath = std::path::Path::new("/__this_path_should_not_exist__");
51//! let err2 = Config::parse_source(configpath).err().unwrap();
52//!
53//! assert_eq!(
54//!     format!("{:?}", err2).trim_end(),
55//!     indoc! { r#"
56//!         Error in "/__this_path_should_not_exist__":
57//!
58//!         Caused by:
59//!             No such file or directory (os error 2)
60//!     "# }.trim_end(),
61//! );
62//! ```
63//!
64//! # Example: `process_text`
65//!
66//! If [Parsable] does not fit your usage, you can wrap any `fn(&str) -> anyhow::Result<T>` with
67//! [process_text]:
68//!
69//! ```
70//! use indoc::indoc; // For cleanly formatting test assertions.
71//!
72//! fn count_lines<S>(source: S) -> anyhow::Result<usize>
73//!     where S: source_text::LoadSource,
74//! {
75//!     source_text::process_text(source, |text: &str| {
76//!         Ok(text.lines().count())
77//!     })
78//! }
79//!
80//! fn main() {
81//!     let linecount = count_lines("Hello\nWorld!").unwrap();
82//!     assert_eq!(2, linecount);
83//!
84//!     let path = std::path::Path::new("/__this_path_should_not_exist__");
85//!     let err = count_lines(path).err().unwrap();
86//!
87//!     assert_eq!(
88//!         format!("{:?}", err).trim_end(),
89//!         indoc! { r#"
90//!             Error in "/__this_path_should_not_exist__":
91//!
92//!             Caused by:
93//!                 No such file or directory (os error 2)
94//!         "# }.trim_end(),
95//!     );
96//! }
97//! ```
98
99mod load;
100mod ownedsource;
101mod parsable;
102mod process;
103mod source;
104
105pub use self::load::LoadSource;
106pub use self::ownedsource::OwnedSource;
107pub use self::parsable::Parsable;
108pub use self::process::{process_source, process_text};
109pub use self::source::Source;
110
111/// Describe optional names consistently between [Source] and [OwnedSource].
112fn optname_to_str(x: Option<&str>) -> &str {
113    x.unwrap_or("<input>")
114}