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}
116
117#[derive(Debug, Clone)]
119pub struct Loader {
120 builder: ConfigBuilder<DefaultState>,
121}
122
123impl Loader {
124 pub fn new() -> Self {
126 let builder = Config::builder().add_source(File::from_str(DEFAULT_TOML, FileFormat::Toml));
127 Self { builder }
128 }
129
130 pub fn with_file(mut self, path: impl AsRef<Path>) -> Self {
132 let source = File::from(path.as_ref())
133 .format(FileFormat::Toml)
134 .required(true);
135 self.builder = self.builder.add_source(source);
136 self
137 }
138
139 pub fn with_optional_file(mut self, path: impl AsRef<Path>) -> Self {
141 let source = File::from(path.as_ref())
142 .format(FileFormat::Toml)
143 .required(false);
144 self.builder = self.builder.add_source(source);
145 self
146 }
147
148 pub fn set_override<I>(mut self, key: &str, value: I) -> Result<Self, ConfigError>
150 where
151 I: Into<ValueKind>,
152 {
153 self.builder = self.builder.set_override(key, value)?;
154 Ok(self)
155 }
156
157 pub fn build(self) -> Result<LexConfig, ConfigError> {
159 self.builder.build()?.try_deserialize()
160 }
161}
162
163impl Default for Loader {
164 fn default() -> Self {
165 Self::new()
166 }
167}
168
169pub fn load_defaults() -> Result<LexConfig, ConfigError> {
171 Loader::new().build()
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177
178 #[test]
179 fn loads_default_config() {
180 let config = load_defaults().expect("defaults to deserialize");
181 assert_eq!(config.formatting.rules.session_blank_lines_before, 1);
182 assert!(config.inspect.ast.show_line_numbers);
183 assert_eq!(config.convert.pdf.size, PdfPageSize::LexEd);
184 }
185
186 #[test]
187 fn supports_overrides() {
188 let config = Loader::new()
189 .set_override("convert.pdf.size", "mobile")
190 .expect("override to apply")
191 .build()
192 .expect("config to build");
193 assert_eq!(config.convert.pdf.size, PdfPageSize::Mobile);
194 }
195
196 #[test]
197 fn formatting_rules_config_converts_to_formatting_rules() {
198 let config = load_defaults().expect("defaults to deserialize");
199 let rules: FormattingRules = config.formatting.rules.into();
200 assert_eq!(rules.session_blank_lines_before, 1);
201 assert_eq!(rules.session_blank_lines_after, 1);
202 assert!(rules.normalize_seq_markers);
203 assert_eq!(rules.unordered_seq_marker, '-');
204 assert_eq!(rules.max_blank_lines, 2);
205 assert_eq!(rules.indent_string, " ");
206 assert!(!rules.preserve_trailing_blanks);
207 assert!(rules.normalize_verbatim_markers);
208 }
209}