Skip to main content

hyperlit_core/
config.rs

1use hyperlit_base::result::HyperlitResult;
2use hyperlit_base::{bail, context, err};
3use path_absolutize::Absolutize;
4use std::path::Path;
5use toml_span::parse;
6
7/* 📖 hyperlit.toml configuration file #config #howto
8
9The hyperlit.toml configuration file is used to configure the document generation process.
10It contains the following fields:
11
12 */
13
14/// Hyperlit configuration used to configure the document generation process
15#[derive(Debug)]
16pub struct HyperlitConfig {
17    /// path to the hyperlit.toml file
18    pub config_path: String,
19    // 📖 ... - `src_directory`: root path to source code. This may be the repository root (i.e., ".") to scan all directories in the repository
20    /// Root path to source code. This may be the repository root to collect all files
21    pub src_directory: String,
22    // 📖 ... - `src_globs`: globs to use when searching for source files, these may be prefixed with "!" to exclude files or directories
23    /// Globs to use when searching for source files, these may be prefixed with "!" to exclude files or directories
24    pub src_globs: Vec<String>,
25    // 📖 ... - `docs_directory`: path to the docs directory, this should contain the documentation files
26    /// Path to the docs directory
27    pub docs_directory: String,
28    // 📖 ... - `doc_globs`: globs to use when searching for documentation files, may be "*" to include all files
29    /// Globs to use when searching for documentation files, may be "*" to include all files
30    pub doc_globs: Vec<String>,
31    // 📖 ... - `build_directory`: path to a build directory used for temporary files
32    /// Path to a build directory used for temporary files
33    pub build_directory: String,
34    // 📖 ... - `output_directory`: directory to write the complete documentation output to
35    /// Directory to write the complete documentation output to
36    pub output_directory: String,
37    // 📖 ... - `doc_markers`: list of marker strings used to identify documentation segments to extract from the source code, defaults to `["📖", "DOC"]`
38    /// List of marker strings used to identify documentation segments to extract from the source code
39    pub doc_markers: Vec<String>,
40    // 📖 ... - `source_link_template`: Template used to generate links to source code (e.g. on github, etc.), placeholders `${path}` and `${line}` will be replaced
41    /// Template used to generate links to source code (e.g. on github, etc.)
42    pub source_link_template: Option<String>,
43}
44
45impl HyperlitConfig {
46    pub fn from_path(path: impl AsRef<Path>) -> HyperlitResult<Self> {
47        let absolute_path = path.as_ref().absolutize()?.to_path_buf();
48        let mut config = context!("read config from file: {:?}", absolute_path => {
49            let string = std::fs::read_to_string(path.as_ref())?;
50            Self::from_string(&string)
51        })?;
52        config.config_path = absolute_path.to_str().unwrap().to_owned();
53        Ok(config)
54    }
55
56    pub fn from_string(string: &str) -> HyperlitResult<Self> {
57        let toml = parse(string)?;
58        let table = toml.as_table().ok_or_else(|| err!("not a table"))?;
59
60        Ok(Self {
61            config_path: table
62                .get("config_path")
63                .and_then(|x| x.as_str())
64                .unwrap_or("hyperlit.toml")
65                .to_string(),
66            src_directory: get_string(table, "src_directory")?,
67            docs_directory: get_string(table, "docs_directory")?,
68            build_directory: get_string_or(table, "build_directory", "build")?,
69            output_directory: get_string_or(table, "output_directory", "output")?,
70            doc_globs: get_string_array(table, "doc_globs")?,
71            src_globs: get_string_array(table, "src_globs")?,
72            doc_markers: get_string_array_or(table, "doc_markers", &["📖", "DOC"])?,
73            source_link_template: get_string(table, "source_link_template").ok(),
74        })
75    }
76}
77
78/// Helper method to get a string value from a TOML table
79fn get_string(table: &toml_span::value::Table, key: &str) -> HyperlitResult<String> {
80    table
81        .get(key)
82        .ok_or_else(|| err!("{} not found", key))?
83        .as_str()
84        .ok_or_else(|| err!("{} is not a string", key))
85        .map(|s| s.to_string())
86}
87
88/// Helper method to get a string value with a default
89fn get_string_or(
90    table: &toml_span::value::Table,
91    key: &str,
92    default: &str,
93) -> HyperlitResult<String> {
94    match table.get(key) {
95        None => Ok(default.to_string()),
96        Some(value) => value
97            .as_str()
98            .ok_or_else(|| err!("{} is not a string", key))
99            .map(|s| s.to_string()),
100    }
101}
102
103/// Helper method to get a string array
104fn get_string_array(table: &toml_span::value::Table, key: &str) -> HyperlitResult<Vec<String>> {
105    match table.get(key) {
106        None => {
107            bail!("{} not found", key);
108        }
109        Some(value) => value
110            .as_array()
111            .ok_or_else(|| err!("{} is not an array", key))?
112            .iter()
113            .map(|v| {
114                v.as_str()
115                    .ok_or_else(|| err!("{} is not a string", key))
116                    .map(|s| s.to_string())
117            })
118            .collect(),
119    }
120}
121
122/// Helper method to get a string array or default
123fn get_string_array_or(
124    table: &toml_span::value::Table,
125    key: &str,
126    default: &[&str],
127) -> HyperlitResult<Vec<String>> {
128    match table.get(key) {
129        None => Ok(Vec::from_iter(default.iter().map(|s| s.to_string()))),
130        Some(value) => value
131            .as_array()
132            .ok_or_else(|| err!("{} is not an array", key))?
133            .iter()
134            .map(|v| {
135                v.as_str()
136                    .ok_or_else(|| err!("{} is not a string", key))
137                    .map(|s| s.to_string())
138            })
139            .collect(),
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use crate::config::HyperlitConfig;
146    use expect_test::expect;
147    use hyperlit_base::result::HyperlitResult;
148
149    #[test]
150    fn read_config_with_defaults() -> HyperlitResult<()> {
151        let config = HyperlitConfig::from_string(
152            r#"
153
154            src_directory = "the_source"
155            src_globs = ["*.rs"]
156            docs_directory = "the_docs"
157            doc_globs = ["*.md", "*.mdx"]
158        "#,
159        )?;
160
161        expect![[r#"
162            HyperlitConfig {
163                config_path: "hyperlit.toml",
164                src_directory: "the_source",
165                src_globs: [
166                    "*.rs",
167                ],
168                docs_directory: "the_docs",
169                doc_globs: [
170                    "*.md",
171                    "*.mdx",
172                ],
173                build_directory: "build",
174                output_directory: "output",
175                doc_markers: [
176                    "📖",
177                    "DOC",
178                ],
179                source_link_template: None,
180            }
181        "#]]
182        .assert_debug_eq(&config);
183        Ok(())
184    }
185
186    #[test]
187    fn read_config_with_no_defaults() -> HyperlitResult<()> {
188        let config = HyperlitConfig::from_string(
189            r#"
190            src_directory = "the_source"
191            src_globs = ["*.rs"]
192            docs_directory = "the_docs"
193            doc_globs = ["*.md", "*.mdx"]
194            build_directory = "the_build"
195            output_directory = "the_output"
196            doc_markers = ["foo", "bar"]
197        "#,
198        )?;
199
200        expect![[r#"
201            HyperlitConfig {
202                config_path: "hyperlit.toml",
203                src_directory: "the_source",
204                src_globs: [
205                    "*.rs",
206                ],
207                docs_directory: "the_docs",
208                doc_globs: [
209                    "*.md",
210                    "*.mdx",
211                ],
212                build_directory: "the_build",
213                output_directory: "the_output",
214                doc_markers: [
215                    "foo",
216                    "bar",
217                ],
218                source_link_template: None,
219            }
220        "#]]
221        .assert_debug_eq(&config);
222        Ok(())
223    }
224
225    #[test]
226    fn read_config_with_no_defaults_from_file() -> HyperlitResult<()> {
227        let mut config = HyperlitConfig::from_path("test/hyperlit-test.toml")?;
228        config.config_path = "test/hyperlit-test.toml".to_string();
229        expect![[r#"
230            HyperlitConfig {
231                config_path: "test/hyperlit-test.toml",
232                src_directory: "the_source",
233                src_globs: [
234                    "*.rs",
235                ],
236                docs_directory: "the_docs",
237                doc_globs: [
238                    "*.md",
239                    "*.mdx",
240                ],
241                build_directory: "the_build",
242                output_directory: "the_output",
243                doc_markers: [
244                    "📖",
245                    "DOC",
246                ],
247                source_link_template: None,
248            }
249        "#]]
250        .assert_debug_eq(&config);
251        Ok(())
252    }
253}