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#[derive(Debug)]
16pub struct HyperlitConfig {
17 pub config_path: String,
19 pub src_directory: String,
22 pub src_globs: Vec<String>,
25 pub docs_directory: String,
28 pub doc_globs: Vec<String>,
31 pub build_directory: String,
34 pub output_directory: String,
37 pub doc_markers: Vec<String>,
40 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
78fn 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
88fn 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
103fn 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
122fn 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}