recursive_file_loader/
lib.rs

1#![doc = include_str!("../README.md")]
2#[cfg(test)]
3extern crate indoc;
4#[cfg(test)]
5extern crate rstest;
6#[cfg(test)]
7extern crate temp_dir;
8extern crate thiserror;
9
10mod canonical_path;
11mod dependency_path;
12mod includes;
13mod loader;
14
15use loader::Loader;
16use std::path::{Path, PathBuf};
17
18/// Load the given file path and recursively follow references to other files
19/// inside it, inserting the text from references.
20///
21/// References are either `${include("<path>")}` or `${include_indent("<path>")}`,
22/// with the latter preserving local indentation for each new line in the referenced
23/// file. Paths can be relative or absolute.
24///
25/// The function will check references for cyclic dependencies and will return a [Error::CyclicDependency] should it detect one.
26///
27/// # Example
28///
29/// Given the following files...
30///
31/// start.txt:
32/// ```text
33/// START
34///   ${include_indent("mid.txt")}
35/// ```
36///
37/// mid.txt:
38/// ```text
39/// MIDDLE 1
40/// MIDDLE 2
41/// ${include("end.txt")}
42/// ```
43///
44/// end.txt:
45/// ```text
46/// END
47/// ```
48///
49/// Then after loading the `start.txt` file, you'll get all files combined.
50///
51/// ```
52/// use recursive_file_loader::load_file_recursively;
53/// use indoc::indoc;
54/// # use temp_dir::TempDir;
55/// # let dir = TempDir::new().unwrap();
56/// # let start = dir.child("start.txt");
57/// # let mid = dir.child("mid.txt");
58/// # let end = dir.child("end.txt");
59/// # std::fs::write(
60/// #     &start,
61/// #     "START\n  ${include_indent(\"mid.txt\")}".as_bytes(),
62/// # ).unwrap();
63/// # std::fs::write(
64/// #     &mid,
65/// #     "MIDDLE 1\nMIDDLE 2\n${include(\"end.txt\")}".as_bytes(),
66/// # ).unwrap();
67/// # std::fs::write(
68/// #     &end,
69/// #     "END".as_bytes(),
70/// # ).unwrap();
71///
72/// let path = "start.txt";
73/// # let path = &start;
74///
75/// let result = load_file_recursively(&path).unwrap();
76///
77/// assert_eq!(&result, indoc!("
78///     START
79///       MIDDLE 1
80///       MIDDLE 2
81///       END")
82/// );
83/// ```
84///
85/// Note that the indentation in `start.txt` has been applied to everything `start.txt` included.
86pub fn load_file_recursively<P: AsRef<Path>>(origin: P) -> Result<String, Error> {
87    Loader::new().load_file_recursively(origin)
88}
89
90#[derive(thiserror::Error, Debug)]
91pub enum Error {
92    #[error("file not found: '{0}'")]
93    FileNotFound(PathBuf),
94
95    #[error("cyclic dependency detected between '{0}' and '{1}'")]
96    CyclicDependency(PathBuf, PathBuf),
97
98    #[error("IO Error")]
99    IOError(#[from] std::io::Error),
100}