1use config::builder::DefaultState;
8use config::{Config, ConfigBuilder, ConfigError, File, FileFormat, ValueKind};
9use lex_babel::formats::lex::formatting_rules::FormattingRules;
10use serde::Deserialize;
11use std::path::Path;
12
13const DEFAULT_TOML: &str = include_str!("../defaults/lex.default.toml");
14
15#[derive(Debug, Clone, Deserialize)]
17pub struct LexConfig {
18 pub formatting: FormattingConfig,
19 pub inspect: InspectConfig,
20 pub convert: ConvertConfig,
21}
22
23#[derive(Debug, Clone, Deserialize)]
25pub struct FormattingConfig {
26 pub rules: FormattingRulesConfig,
27}
28
29#[derive(Debug, Clone, Deserialize)]
31pub struct FormattingRulesConfig {
32 pub session_blank_lines_before: usize,
33 pub session_blank_lines_after: usize,
34 pub normalize_seq_markers: bool,
35 pub unordered_seq_marker: char,
36 pub max_blank_lines: usize,
37 pub indent_string: String,
38 pub preserve_trailing_blanks: bool,
39 pub normalize_verbatim_markers: bool,
40}
41
42impl From<FormattingRulesConfig> for FormattingRules {
43 fn from(config: FormattingRulesConfig) -> Self {
44 FormattingRules {
45 session_blank_lines_before: config.session_blank_lines_before,
46 session_blank_lines_after: config.session_blank_lines_after,
47 normalize_seq_markers: config.normalize_seq_markers,
48 unordered_seq_marker: config.unordered_seq_marker,
49 max_blank_lines: config.max_blank_lines,
50 indent_string: config.indent_string,
51 preserve_trailing_blanks: config.preserve_trailing_blanks,
52 normalize_verbatim_markers: config.normalize_verbatim_markers,
53 }
54 }
55}
56
57impl From<&FormattingRulesConfig> for FormattingRules {
58 fn from(config: &FormattingRulesConfig) -> Self {
59 FormattingRules {
60 session_blank_lines_before: config.session_blank_lines_before,
61 session_blank_lines_after: config.session_blank_lines_after,
62 normalize_seq_markers: config.normalize_seq_markers,
63 unordered_seq_marker: config.unordered_seq_marker,
64 max_blank_lines: config.max_blank_lines,
65 indent_string: config.indent_string.clone(),
66 preserve_trailing_blanks: config.preserve_trailing_blanks,
67 normalize_verbatim_markers: config.normalize_verbatim_markers,
68 }
69 }
70}
71
72#[derive(Debug, Clone, Deserialize)]
74pub struct InspectConfig {
75 pub ast: InspectAstConfig,
76 pub nodemap: NodemapConfig,
77}
78
79#[derive(Debug, Clone, Deserialize)]
80pub struct InspectAstConfig {
81 pub include_all_properties: bool,
82 pub show_line_numbers: bool,
83}
84
85#[derive(Debug, Clone, Deserialize)]
86pub struct NodemapConfig {
87 pub color_blocks: bool,
88 pub color_characters: bool,
89 pub show_summary: bool,
90}
91
92#[derive(Debug, Clone, Deserialize)]
94pub struct ConvertConfig {
95 pub pdf: PdfConfig,
96 pub html: HtmlConfig,
97}
98
99#[derive(Debug, Clone, Deserialize)]
100pub struct PdfConfig {
101 pub size: PdfPageSize,
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
105pub enum PdfPageSize {
106 #[serde(rename = "lexed")]
107 LexEd,
108 #[serde(rename = "mobile")]
109 Mobile,
110}
111
112#[derive(Debug, Clone, Deserialize)]
113pub struct HtmlConfig {
114 pub theme: String,
115 #[serde(default)]
117 pub custom_css: Option<String>,
118}
119
120#[derive(Debug, Clone)]
122pub struct Loader {
123 builder: ConfigBuilder<DefaultState>,
124}
125
126impl Loader {
127 pub fn new() -> Self {
129 let builder = Config::builder().add_source(File::from_str(DEFAULT_TOML, FileFormat::Toml));
130 Self { builder }
131 }
132
133 pub fn with_file(mut self, path: impl AsRef<Path>) -> Self {
135 let source = File::from(path.as_ref())
136 .format(FileFormat::Toml)
137 .required(true);
138 self.builder = self.builder.add_source(source);
139 self
140 }
141
142 pub fn with_optional_file(mut self, path: impl AsRef<Path>) -> Self {
144 let source = File::from(path.as_ref())
145 .format(FileFormat::Toml)
146 .required(false);
147 self.builder = self.builder.add_source(source);
148 self
149 }
150
151 pub fn set_override<I>(mut self, key: &str, value: I) -> Result<Self, ConfigError>
153 where
154 I: Into<ValueKind>,
155 {
156 self.builder = self.builder.set_override(key, value)?;
157 Ok(self)
158 }
159
160 pub fn build(self) -> Result<LexConfig, ConfigError> {
162 self.builder.build()?.try_deserialize()
163 }
164}
165
166impl Default for Loader {
167 fn default() -> Self {
168 Self::new()
169 }
170}
171
172pub fn load_defaults() -> Result<LexConfig, ConfigError> {
174 Loader::new().build()
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 #[test]
182 fn loads_default_config() {
183 let config = load_defaults().expect("defaults to deserialize");
184 assert_eq!(config.formatting.rules.session_blank_lines_before, 1);
185 assert!(config.inspect.ast.show_line_numbers);
186 assert_eq!(config.convert.pdf.size, PdfPageSize::LexEd);
187 }
188
189 #[test]
190 fn supports_overrides() {
191 let config = Loader::new()
192 .set_override("convert.pdf.size", "mobile")
193 .expect("override to apply")
194 .build()
195 .expect("config to build");
196 assert_eq!(config.convert.pdf.size, PdfPageSize::Mobile);
197 }
198
199 #[test]
200 fn formatting_rules_config_converts_to_formatting_rules() {
201 let config = load_defaults().expect("defaults to deserialize");
202 let rules: FormattingRules = config.formatting.rules.into();
203 assert_eq!(rules.session_blank_lines_before, 1);
204 assert_eq!(rules.session_blank_lines_after, 1);
205 assert!(rules.normalize_seq_markers);
206 assert_eq!(rules.unordered_seq_marker, '-');
207 assert_eq!(rules.max_blank_lines, 2);
208 assert_eq!(rules.indent_string, " ");
209 assert!(!rules.preserve_trailing_blanks);
210 assert!(rules.normalize_verbatim_markers);
211 }
212}