1use crate::types::LineLength;
2use indexmap::IndexMap;
3use std::collections::BTreeMap;
4use std::collections::HashMap;
5use std::marker::PhantomData;
6
7use super::flavor::{ConfigLoaded, MarkdownFlavor};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum ConfigSource {
19 Default,
21 UserConfig,
23 PyprojectToml,
25 ProjectConfig,
27 Cli,
29}
30
31#[derive(Debug, Clone)]
32pub struct ConfigOverride<T> {
33 pub value: T,
34 pub source: ConfigSource,
35 pub file: Option<String>,
36 pub line: Option<usize>,
37}
38
39#[derive(Debug, Clone)]
40pub struct SourcedValue<T> {
41 pub value: T,
42 pub source: ConfigSource,
43 pub overrides: Vec<ConfigOverride<T>>,
44}
45
46impl<T: Clone> SourcedValue<T> {
47 pub fn new(value: T, source: ConfigSource) -> Self {
48 Self {
49 value: value.clone(),
50 source,
51 overrides: vec![ConfigOverride {
52 value,
53 source,
54 file: None,
55 line: None,
56 }],
57 }
58 }
59
60 pub fn merge_override(
64 &mut self,
65 new_value: T,
66 new_source: ConfigSource,
67 new_file: Option<String>,
68 new_line: Option<usize>,
69 ) {
70 fn source_precedence(src: ConfigSource) -> u8 {
72 match src {
73 ConfigSource::Default => 0,
74 ConfigSource::UserConfig => 1,
75 ConfigSource::PyprojectToml => 2,
76 ConfigSource::ProjectConfig => 3,
77 ConfigSource::Cli => 4,
78 }
79 }
80
81 if source_precedence(new_source) >= source_precedence(self.source) {
82 self.value = new_value.clone();
83 self.source = new_source;
84 self.overrides.push(ConfigOverride {
85 value: new_value,
86 source: new_source,
87 file: new_file,
88 line: new_line,
89 });
90 }
91 }
92
93 pub fn push_override(&mut self, value: T, source: ConfigSource, file: Option<String>, line: Option<usize>) {
94 self.value = value.clone();
97 self.source = source;
98 self.overrides.push(ConfigOverride {
99 value,
100 source,
101 file,
102 line,
103 });
104 }
105}
106
107impl<T: Clone + Eq + std::hash::Hash> SourcedValue<Vec<T>> {
108 pub fn merge_union(
111 &mut self,
112 new_value: Vec<T>,
113 new_source: ConfigSource,
114 new_file: Option<String>,
115 new_line: Option<usize>,
116 ) {
117 fn source_precedence(src: ConfigSource) -> u8 {
118 match src {
119 ConfigSource::Default => 0,
120 ConfigSource::UserConfig => 1,
121 ConfigSource::PyprojectToml => 2,
122 ConfigSource::ProjectConfig => 3,
123 ConfigSource::Cli => 4,
124 }
125 }
126
127 if source_precedence(new_source) >= source_precedence(self.source) {
128 let mut combined = self.value.clone();
130 for item in new_value.iter() {
131 if !combined.contains(item) {
132 combined.push(item.clone());
133 }
134 }
135
136 self.value = combined;
137 self.source = new_source;
138 self.overrides.push(ConfigOverride {
139 value: new_value,
140 source: new_source,
141 file: new_file,
142 line: new_line,
143 });
144 }
145 }
146}
147
148#[derive(Debug, Clone)]
149pub struct SourcedGlobalConfig {
150 pub enable: SourcedValue<Vec<String>>,
151 pub disable: SourcedValue<Vec<String>>,
152 pub exclude: SourcedValue<Vec<String>>,
153 pub include: SourcedValue<Vec<String>>,
154 pub respect_gitignore: SourcedValue<bool>,
155 pub line_length: SourcedValue<LineLength>,
156 pub output_format: Option<SourcedValue<String>>,
157 pub fixable: SourcedValue<Vec<String>>,
158 pub unfixable: SourcedValue<Vec<String>>,
159 pub flavor: SourcedValue<MarkdownFlavor>,
160 pub force_exclude: SourcedValue<bool>,
161 pub cache_dir: Option<SourcedValue<String>>,
162 pub cache: SourcedValue<bool>,
163 pub extend_enable: SourcedValue<Vec<String>>,
164 pub extend_disable: SourcedValue<Vec<String>>,
165}
166
167impl Default for SourcedGlobalConfig {
168 fn default() -> Self {
169 SourcedGlobalConfig {
170 enable: SourcedValue::new(Vec::new(), ConfigSource::Default),
171 disable: SourcedValue::new(Vec::new(), ConfigSource::Default),
172 exclude: SourcedValue::new(Vec::new(), ConfigSource::Default),
173 include: SourcedValue::new(Vec::new(), ConfigSource::Default),
174 respect_gitignore: SourcedValue::new(true, ConfigSource::Default),
175 line_length: SourcedValue::new(LineLength::default(), ConfigSource::Default),
176 output_format: None,
177 fixable: SourcedValue::new(Vec::new(), ConfigSource::Default),
178 unfixable: SourcedValue::new(Vec::new(), ConfigSource::Default),
179 flavor: SourcedValue::new(MarkdownFlavor::default(), ConfigSource::Default),
180 force_exclude: SourcedValue::new(false, ConfigSource::Default),
181 cache_dir: None,
182 cache: SourcedValue::new(true, ConfigSource::Default),
183 extend_enable: SourcedValue::new(Vec::new(), ConfigSource::Default),
184 extend_disable: SourcedValue::new(Vec::new(), ConfigSource::Default),
185 }
186 }
187}
188
189#[derive(Debug, Default, Clone)]
190pub struct SourcedRuleConfig {
191 pub severity: Option<SourcedValue<crate::rule::Severity>>,
192 pub values: BTreeMap<String, SourcedValue<toml::Value>>,
193}
194
195#[derive(Debug, Clone)]
198pub struct SourcedConfigFragment {
199 pub extends: Option<String>,
201 pub global: SourcedGlobalConfig,
202 pub per_file_ignores: SourcedValue<HashMap<String, Vec<String>>>,
203 pub per_file_flavor: SourcedValue<IndexMap<String, MarkdownFlavor>>,
204 pub code_block_tools: SourcedValue<crate::code_block_tools::CodeBlockToolsConfig>,
205 pub rules: BTreeMap<String, SourcedRuleConfig>,
206 pub unknown_keys: Vec<(String, String, Option<String>)>, }
209
210impl Default for SourcedConfigFragment {
211 fn default() -> Self {
212 Self {
213 extends: None,
214 global: SourcedGlobalConfig::default(),
215 per_file_ignores: SourcedValue::new(HashMap::new(), ConfigSource::Default),
216 per_file_flavor: SourcedValue::new(IndexMap::new(), ConfigSource::Default),
217 code_block_tools: SourcedValue::new(
218 crate::code_block_tools::CodeBlockToolsConfig::default(),
219 ConfigSource::Default,
220 ),
221 rules: BTreeMap::new(),
222 unknown_keys: Vec::new(),
223 }
224 }
225}
226
227#[derive(Debug, Clone)]
229pub struct ConfigValidationWarning {
230 pub message: String,
231 pub rule: Option<String>,
232 pub key: Option<String>,
233}
234
235#[derive(Debug, Clone)]
253pub struct SourcedConfig<State = ConfigLoaded> {
254 pub global: SourcedGlobalConfig,
255 pub per_file_ignores: SourcedValue<HashMap<String, Vec<String>>>,
256 pub per_file_flavor: SourcedValue<IndexMap<String, MarkdownFlavor>>,
257 pub code_block_tools: SourcedValue<crate::code_block_tools::CodeBlockToolsConfig>,
258 pub rules: BTreeMap<String, SourcedRuleConfig>,
259 pub loaded_files: Vec<String>,
260 pub unknown_keys: Vec<(String, String, Option<String>)>, pub project_root: Option<std::path::PathBuf>,
263 pub validation_warnings: Vec<ConfigValidationWarning>,
265 pub(super) _state: PhantomData<State>,
267}
268
269impl Default for SourcedConfig<ConfigLoaded> {
270 fn default() -> Self {
271 Self {
272 global: SourcedGlobalConfig::default(),
273 per_file_ignores: SourcedValue::new(HashMap::new(), ConfigSource::Default),
274 per_file_flavor: SourcedValue::new(IndexMap::new(), ConfigSource::Default),
275 code_block_tools: SourcedValue::new(
276 crate::code_block_tools::CodeBlockToolsConfig::default(),
277 ConfigSource::Default,
278 ),
279 rules: BTreeMap::new(),
280 loaded_files: Vec::new(),
281 unknown_keys: Vec::new(),
282 project_root: None,
283 validation_warnings: Vec::new(),
284 _state: PhantomData,
285 }
286 }
287}