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}