use crate::types::LineLength;
use indexmap::IndexMap;
use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::marker::PhantomData;
use super::flavor::{ConfigLoaded, MarkdownFlavor};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConfigSource {
Default,
EditorConfig,
UserConfig,
PyprojectToml,
ProjectConfig,
Cli,
}
fn source_precedence(src: ConfigSource) -> u8 {
match src {
ConfigSource::Default => 0,
ConfigSource::EditorConfig => 1,
ConfigSource::UserConfig => 2,
ConfigSource::PyprojectToml => 3,
ConfigSource::ProjectConfig => 4,
ConfigSource::Cli => 5,
}
}
#[derive(Debug, Clone)]
pub struct SourcedValue<T> {
pub value: T,
pub source: ConfigSource,
pub origin: Option<String>,
}
impl<T: Clone> SourcedValue<T> {
pub fn new(value: T, source: ConfigSource) -> Self {
Self {
value,
source,
origin: None,
}
}
pub fn merge_override(&mut self, new_value: T, new_source: ConfigSource, new_origin: Option<String>) -> bool {
if source_precedence(new_source) >= source_precedence(self.source) {
self.value = new_value;
self.source = new_source;
self.origin = new_origin;
true
} else {
false
}
}
pub fn merge_from(&mut self, other: SourcedValue<T>) -> bool {
self.merge_override(other.value, other.source, other.origin)
}
pub fn push_override(&mut self, value: T, source: ConfigSource, origin: Option<String>) {
self.value = value;
self.source = source;
self.origin = origin;
}
}
impl<T: Clone + Eq + std::hash::Hash> SourcedValue<Vec<T>> {
pub fn merge_union(&mut self, new_value: Vec<T>, new_source: ConfigSource, new_origin: Option<String>) {
if source_precedence(new_source) >= source_precedence(self.source) {
for item in new_value {
if !self.value.contains(&item) {
self.value.push(item);
}
}
self.source = new_source;
self.origin = new_origin;
}
}
pub fn merge_union_from(&mut self, other: SourcedValue<Vec<T>>) {
self.merge_union(other.value, other.source, other.origin);
}
}
#[derive(Debug, Clone)]
pub struct SourcedGlobalConfig {
pub enable: SourcedValue<Vec<String>>,
pub disable: SourcedValue<Vec<String>>,
pub exclude: SourcedValue<Vec<String>>,
pub include: SourcedValue<Vec<String>>,
pub include_withheld: Option<String>,
pub respect_gitignore: SourcedValue<bool>,
pub line_length: SourcedValue<LineLength>,
pub output_format: Option<SourcedValue<String>>,
pub fixable: SourcedValue<Vec<String>>,
pub unfixable: SourcedValue<Vec<String>>,
pub flavor: SourcedValue<MarkdownFlavor>,
pub force_exclude: SourcedValue<bool>,
pub cache_dir: Option<SourcedValue<String>>,
pub cache: SourcedValue<bool>,
pub extend_enable: SourcedValue<Vec<String>>,
pub extend_disable: SourcedValue<Vec<String>>,
pub editorconfig: SourcedValue<bool>,
}
impl Default for SourcedGlobalConfig {
fn default() -> Self {
SourcedGlobalConfig {
enable: SourcedValue::new(Vec::new(), ConfigSource::Default),
disable: SourcedValue::new(Vec::new(), ConfigSource::Default),
exclude: SourcedValue::new(Vec::new(), ConfigSource::Default),
include: SourcedValue::new(Vec::new(), ConfigSource::Default),
include_withheld: None,
respect_gitignore: SourcedValue::new(true, ConfigSource::Default),
line_length: SourcedValue::new(LineLength::default(), ConfigSource::Default),
output_format: None,
fixable: SourcedValue::new(Vec::new(), ConfigSource::Default),
unfixable: SourcedValue::new(Vec::new(), ConfigSource::Default),
flavor: SourcedValue::new(MarkdownFlavor::default(), ConfigSource::Default),
force_exclude: SourcedValue::new(false, ConfigSource::Default),
cache_dir: None,
cache: SourcedValue::new(true, ConfigSource::Default),
extend_enable: SourcedValue::new(Vec::new(), ConfigSource::Default),
extend_disable: SourcedValue::new(Vec::new(), ConfigSource::Default),
editorconfig: SourcedValue::new(false, ConfigSource::Default),
}
}
}
#[derive(Debug, Default, Clone)]
pub struct SourcedRuleConfig {
pub severity: Option<SourcedValue<crate::rule::Severity>>,
pub values: BTreeMap<String, SourcedValue<toml::Value>>,
pub withheld_keys: BTreeSet<String>,
}
#[derive(Debug, Clone)]
pub struct SourcedConfigFragment {
pub extends: Option<String>,
pub global: SourcedGlobalConfig,
pub per_file_ignores: SourcedValue<BTreeMap<String, Vec<String>>>,
pub per_file_flavor: SourcedValue<IndexMap<String, MarkdownFlavor>>,
pub code_block_tools: SourcedValue<crate::code_block_tools::CodeBlockToolsConfig>,
pub rules: BTreeMap<String, SourcedRuleConfig>,
pub rule_display_names: HashMap<String, String>,
pub unknown_keys: Vec<(String, String, Option<String>)>,
pub load_warnings: Vec<String>,
}
impl Default for SourcedConfigFragment {
fn default() -> Self {
Self {
extends: None,
global: SourcedGlobalConfig::default(),
per_file_ignores: SourcedValue::new(BTreeMap::new(), ConfigSource::Default),
per_file_flavor: SourcedValue::new(IndexMap::new(), ConfigSource::Default),
code_block_tools: SourcedValue::new(
crate::code_block_tools::CodeBlockToolsConfig::default(),
ConfigSource::Default,
),
rules: BTreeMap::new(),
rule_display_names: HashMap::new(),
unknown_keys: Vec::new(),
load_warnings: Vec::new(),
}
}
}
impl SourcedGlobalConfig {
pub fn is_empty(&self) -> bool {
let Self {
enable,
disable,
exclude,
include,
include_withheld: _,
respect_gitignore,
line_length,
output_format,
fixable,
unfixable,
flavor,
force_exclude,
cache_dir,
cache,
extend_enable,
extend_disable,
editorconfig,
} = self;
output_format.is_none()
&& cache_dir.is_none()
&& [
enable.source,
disable.source,
exclude.source,
include.source,
fixable.source,
unfixable.source,
extend_enable.source,
extend_disable.source,
]
.iter()
.all(|source| *source == ConfigSource::Default)
&& respect_gitignore.source == ConfigSource::Default
&& line_length.source == ConfigSource::Default
&& flavor.source == ConfigSource::Default
&& force_exclude.source == ConfigSource::Default
&& cache.source == ConfigSource::Default
&& editorconfig.source == ConfigSource::Default
}
}
impl SourcedConfigFragment {
pub fn is_empty(&self) -> bool {
let Self {
extends,
global,
per_file_ignores,
per_file_flavor,
code_block_tools,
rules,
rule_display_names,
unknown_keys,
load_warnings,
} = self;
extends.is_none()
&& global.is_empty()
&& per_file_ignores.source == ConfigSource::Default
&& per_file_flavor.source == ConfigSource::Default
&& code_block_tools.source == ConfigSource::Default
&& rules.is_empty()
&& rule_display_names.is_empty()
&& unknown_keys.is_empty()
&& load_warnings.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct ConfigValidationWarning {
pub message: String,
pub rule: Option<String>,
pub key: Option<String>,
}
#[derive(Debug, Clone)]
pub struct SourcedConfig<State = ConfigLoaded> {
pub global: SourcedGlobalConfig,
pub per_file_ignores: SourcedValue<BTreeMap<String, Vec<String>>>,
pub per_file_flavor: SourcedValue<IndexMap<String, MarkdownFlavor>>,
pub code_block_tools: SourcedValue<crate::code_block_tools::CodeBlockToolsConfig>,
pub rules: BTreeMap<String, SourcedRuleConfig>,
pub loaded_files: Vec<String>,
pub unknown_keys: Vec<(String, String, Option<String>)>,
pub project_root: Option<std::path::PathBuf>,
pub discovery_warnings: Vec<String>,
pub validation_warnings: Vec<ConfigValidationWarning>,
pub(super) _state: PhantomData<State>,
}
impl Default for SourcedConfig<ConfigLoaded> {
fn default() -> Self {
Self {
global: SourcedGlobalConfig::default(),
per_file_ignores: SourcedValue::new(BTreeMap::new(), ConfigSource::Default),
per_file_flavor: SourcedValue::new(IndexMap::new(), ConfigSource::Default),
code_block_tools: SourcedValue::new(
crate::code_block_tools::CodeBlockToolsConfig::default(),
ConfigSource::Default,
),
rules: BTreeMap::new(),
loaded_files: Vec::new(),
unknown_keys: Vec::new(),
project_root: None,
discovery_warnings: Vec::new(),
validation_warnings: Vec::new(),
_state: PhantomData,
}
}
}