1use std::collections::HashMap;
2
3pub use panache_parser::Dialect;
4pub use panache_parser::Extensions;
5pub use panache_parser::Extensions as ParserExtensions;
6pub use panache_parser::Flavor;
7pub use panache_parser::PandocCompat;
8pub use panache_parser::ParserOptions;
9
10fn default_external_max_parallel() -> usize {
11 std::thread::available_parallelism()
12 .map(|n| n.get())
13 .unwrap_or(1)
14 .clamp(1, 8)
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
18pub enum MathDelimiterStyle {
19 #[default]
21 Preserve,
22 Dollars,
24 Backslash,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
29pub enum TabStopMode {
30 #[default]
32 Normalize,
33 Preserve,
35}
36
37#[derive(Debug, Clone, PartialEq)]
38pub struct FormatterConfig {
39 pub cmd: String,
40 pub args: Vec<String>,
41 pub enabled: bool,
42 pub stdin: bool,
43}
44
45#[derive(Debug, Clone, PartialEq)]
46pub enum WrapMode {
47 Preserve,
48 Reflow,
49 Sentence,
50}
51
52#[derive(Debug, Clone, PartialEq)]
53pub enum LineEnding {
54 Auto,
55 Lf,
56 Crlf,
57}
58
59#[derive(Debug, Clone, PartialEq)]
60pub enum BlankLines {
61 Preserve,
63 Collapse,
65}
66
67#[derive(Debug, Clone, PartialEq)]
68pub struct FormatterExtensions {
69 pub blank_before_header: bool,
70 pub bookdown_references: bool,
71 pub escaped_line_breaks: bool,
72 pub gfm_auto_identifiers: bool,
73 pub quarto_crossrefs: bool,
74 pub smart: bool,
75 pub smart_quotes: bool,
76}
77
78impl Default for FormatterExtensions {
79 fn default() -> Self {
80 Self::for_flavor(Flavor::default())
81 }
82}
83
84impl FormatterExtensions {
85 pub fn for_flavor(flavor: Flavor) -> Self {
86 let parser_defaults = ParserExtensions::for_flavor(flavor);
87 let smart_default = matches!(flavor, Flavor::Pandoc | Flavor::Quarto | Flavor::RMarkdown);
88
89 Self {
90 blank_before_header: parser_defaults.blank_before_header,
91 bookdown_references: parser_defaults.bookdown_references,
92 escaped_line_breaks: parser_defaults.escaped_line_breaks,
93 gfm_auto_identifiers: parser_defaults.gfm_auto_identifiers,
94 quarto_crossrefs: parser_defaults.quarto_crossrefs,
95 smart: smart_default,
96 smart_quotes: false,
97 }
98 }
99
100 pub fn merge_with_flavor(overrides: HashMap<String, bool>, flavor: Flavor) -> Self {
101 let mut base = Self::for_flavor(flavor);
102 for (key, value) in overrides {
103 match key.replace('_', "-").to_ascii_lowercase().as_str() {
104 "blank-before-header" => base.blank_before_header = value,
105 "bookdown-references" => base.bookdown_references = value,
106 "escaped-line-breaks" => base.escaped_line_breaks = value,
107 "gfm-auto-identifiers" => base.gfm_auto_identifiers = value,
108 "quarto-crossrefs" => base.quarto_crossrefs = value,
109 "smart" => base.smart = value,
110 "smart-quotes" => base.smart_quotes = value,
111 _ => {}
112 }
113 }
114 base
115 }
116}
117
118#[derive(Debug, Clone)]
119pub struct Config {
120 pub flavor: Flavor,
121 pub parser_extensions: ParserExtensions,
122 pub formatter_extensions: FormatterExtensions,
123 pub line_ending: Option<LineEnding>,
124 pub line_width: usize,
125 pub math_indent: usize,
126 pub math_delimiter_style: MathDelimiterStyle,
127 pub tab_stops: TabStopMode,
128 pub tab_width: usize,
129 pub wrap: Option<WrapMode>,
130 pub blank_lines: BlankLines,
131 pub formatters: HashMap<String, Vec<FormatterConfig>>,
133 pub external_max_parallel: usize,
135 pub parser: PandocCompat,
137}
138
139impl Default for Config {
140 fn default() -> Self {
141 let flavor = Flavor::default();
142 Self {
143 flavor,
144 parser_extensions: ParserExtensions::for_flavor(flavor),
145 formatter_extensions: FormatterExtensions::for_flavor(flavor),
146 line_ending: Some(LineEnding::Auto),
147 line_width: 80,
148 math_indent: 0,
149 math_delimiter_style: MathDelimiterStyle::default(),
150 tab_stops: TabStopMode::Normalize,
151 tab_width: 4,
152 wrap: Some(WrapMode::Reflow),
153 blank_lines: BlankLines::Collapse,
154 formatters: HashMap::new(), external_max_parallel: default_external_max_parallel(),
156 parser: PandocCompat::default(),
157 }
158 }
159}
160
161impl Config {
162 pub fn parser_options(&self) -> ParserOptions {
163 ParserOptions {
164 flavor: self.flavor,
165 dialect: Dialect::for_flavor(self.flavor),
166 extensions: self.parser_extensions.clone(),
167 pandoc_compat: self.parser,
168 refdef_labels: None,
169 }
170 }
171}
172
173#[derive(Default, Clone)]
174pub struct ConfigBuilder {
175 config: Config,
176}
177
178impl ConfigBuilder {
179 pub fn math_indent(mut self, indent: usize) -> Self {
180 self.config.math_indent = indent;
181 self
182 }
183
184 pub fn tab_stops(mut self, mode: TabStopMode) -> Self {
185 self.config.tab_stops = mode;
186 self
187 }
188
189 pub fn tab_width(mut self, width: usize) -> Self {
190 self.config.tab_width = width;
191 self
192 }
193
194 pub fn line_width(mut self, width: usize) -> Self {
195 self.config.line_width = width;
196 self
197 }
198
199 pub fn line_ending(mut self, ending: LineEnding) -> Self {
200 self.config.line_ending = Some(ending);
201 self
202 }
203
204 pub fn blank_lines(mut self, mode: BlankLines) -> Self {
205 self.config.blank_lines = mode;
206 self
207 }
208
209 pub fn build(self) -> Config {
210 self.config
211 }
212}