1use std::{
2 collections::HashMap,
3 path::{Path, PathBuf},
4 process,
5};
6
7use serde::{Deserialize, Serialize};
8
9use crate::violation::Severity;
10
11#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq)]
12pub struct Config {
13 #[serde(default)]
14 pub general: GeneralConfig,
15
16 #[serde(default)]
17 pub rules: HashMap<String, RuleSeverity>,
18
19 #[serde(default)]
20 pub style: StyleConfig,
21
22 #[serde(default)]
23 pub exclude: ExcludeConfig,
24
25 #[serde(default)]
26 pub fix: FixConfig,
27}
28
29#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
30pub struct GeneralConfig {
31 #[serde(default = "GeneralConfig::default_min_severity")]
32 pub min_severity: RuleSeverity,
33}
34
35impl Default for GeneralConfig {
36 fn default() -> Self {
37 Self {
38 min_severity: Self::default_min_severity(),
39 }
40 }
41}
42
43impl GeneralConfig {
44 const fn default_min_severity() -> RuleSeverity {
45 RuleSeverity::Warning }
47}
48
49#[derive(Debug, Clone, Copy, Deserialize, Serialize, Default, PartialEq, PartialOrd, Ord, Eq)]
50#[serde(rename_all = "lowercase")]
51pub enum RuleSeverity {
52 Off,
53 Info,
54 Warning,
55 #[default]
56 Error,
57}
58
59impl From<RuleSeverity> for Option<Severity> {
60 fn from(rule_sev: RuleSeverity) -> Self {
61 match rule_sev {
62 RuleSeverity::Error => Some(Severity::Error),
63 RuleSeverity::Warning => Some(Severity::Warning),
64 RuleSeverity::Info => Some(Severity::Info),
65 RuleSeverity::Off => None,
66 }
67 }
68}
69
70impl From<Severity> for RuleSeverity {
71 fn from(severity: Severity) -> Self {
72 match severity {
73 Severity::Error => Self::Error,
74 Severity::Warning => Self::Warning,
75 Severity::Info => Self::Info,
76 }
77 }
78}
79
80#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq, Eq)]
81pub struct StyleConfig {
82 #[serde(default = "StyleConfig::default_line_length")]
83 pub line_length: usize,
84
85 #[serde(default = "StyleConfig::default_indent_spaces")]
86 pub indent_spaces: usize,
87}
88
89impl StyleConfig {
90 const fn default_line_length() -> usize {
91 100
92 }
93
94 const fn default_indent_spaces() -> usize {
95 4
96 }
97}
98
99#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq, Eq)]
100pub struct ExcludeConfig {
101 #[serde(default)]
102 pub patterns: Vec<String>,
103}
104
105#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq, Eq)]
106pub struct FixConfig {
107 pub enabled: bool,
108
109 pub safe_only: bool,
110}
111
112impl Config {
113 pub fn load_from_file(path: &Path) -> Result<Self, crate::LintError> {
120 let content = std::fs::read_to_string(path)?;
121 Ok(toml::from_str(&content)?)
122 }
123
124 pub fn rule_severity(&self, rule_id: &str) -> Option<Severity> {
125 self.rules.get(rule_id).copied().and_then(Into::into)
126 }
127}
128
129#[must_use]
131pub fn find_config_file() -> Option<PathBuf> {
132 let mut current_dir = std::env::current_dir().ok()?;
133
134 loop {
135 let config_path = current_dir.join(".nu-lint.toml");
136 if config_path.exists() && config_path.is_file() {
137 return Some(config_path);
138 }
139
140 if !current_dir.pop() {
142 break;
143 }
144 }
145
146 None
147}
148
149#[must_use]
151pub fn load_config(config_path: Option<&PathBuf>) -> Config {
152 config_path
153 .cloned()
154 .or_else(find_config_file)
155 .map_or_else(Config::default, |path| {
156 Config::load_from_file(&path).unwrap_or_else(|e| {
157 eprintln!("Error loading config from {}: {e}", path.display());
158 process::exit(2);
159 })
160 })
161}