Skip to content

Commit 11ff287

Browse files
committed
Warn user about unused config keys
This is useful especially if you typo a configuration setting.
1 parent 1845483 commit 11ff287

File tree

4 files changed

+132
-50
lines changed

4 files changed

+132
-50
lines changed

src/cli.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ impl Plugin {
248248
dir,
249249
uses,
250250
apply,
251+
rest: None,
251252
}),
252253
)
253254
}

src/config.rs

Lines changed: 99 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::{
99
str::FromStr,
1010
};
1111

12-
use anyhow::{bail, Context as ResultExt, Result};
12+
use anyhow::{anyhow, bail, Context as ResultExt, Error, Result};
1313
use indexmap::IndexMap;
1414
use lazy_static::lazy_static;
1515
use regex::Regex;
@@ -125,6 +125,9 @@ pub struct RawPlugin {
125125
/// What templates to apply to each matched file. If this is `None` then the
126126
/// default templates will be applied.
127127
pub apply: Option<Vec<String>>,
128+
/// Any extra keys,
129+
#[serde(flatten, deserialize_with = "deserialize_rest_toml_value")]
130+
pub rest: Option<toml::Value>,
128131
}
129132

130133
/// An external configured plugin.
@@ -171,6 +174,9 @@ pub struct RawConfig {
171174
templates: IndexMap<String, Template>,
172175
/// A map of name to plugin.
173176
pub plugins: IndexMap<String, RawPlugin>,
177+
/// Any extra keys,
178+
#[serde(flatten, deserialize_with = "deserialize_rest_toml_value")]
179+
pub rest: Option<toml::Value>,
174180
}
175181

176182
/// The user configuration.
@@ -459,10 +465,46 @@ impl_deserialize_from_str!(git_protocol, GitProtocol, "a Git protocol type");
459465
impl_deserialize_from_str!(gist_repository, GistRepository, "a Gist identifier");
460466
impl_deserialize_from_str!(github_repository, GitHubRepository, "a GitHub repository");
461467

468+
/// Deserialize the remaining keys into a `Option<toml::Value>`. Empty tables
469+
/// are coerced to `None`.
470+
fn deserialize_rest_toml_value<'de, D>(deserializer: D) -> Result<Option<toml::Value>, D::Error>
471+
where
472+
D: de::Deserializer<'de>,
473+
{
474+
let value: toml::Value = de::Deserialize::deserialize(deserializer)?;
475+
Ok(match value {
476+
toml::Value::Table(table) => {
477+
if table.is_empty() {
478+
None
479+
} else {
480+
Some(toml::Value::Table(table))
481+
}
482+
}
483+
value => Some(value),
484+
})
485+
}
486+
462487
/////////////////////////////////////////////////////////////////////////
463488
// Normalization implementations
464489
/////////////////////////////////////////////////////////////////////////
465490

491+
/// Check for extra TOML keys, and if they exist then call the given function on
492+
/// the key.
493+
fn check_extra_toml<F>(rest: Option<toml::Value>, mut f: F)
494+
where
495+
F: FnMut(&str) -> (),
496+
{
497+
match rest {
498+
Some(toml::Value::Table(table)) => {
499+
for key in table.keys() {
500+
f(key)
501+
}
502+
}
503+
Some(_) => unreachable!(), // unreachable because we are using #[serde(flatten)]
504+
None => {}
505+
}
506+
}
507+
466508
/// Check whether the specifed templates actually exist.
467509
fn validate_template_names(
468510
apply: &Option<Vec<String>>,
@@ -517,7 +559,12 @@ enum TempSource {
517559
impl RawPlugin {
518560
/// Normalize a `RawPlugin` into a `Plugin` which is simpler and easier
519561
/// to handle.
520-
pub fn normalize(self, name: String, templates: &IndexMap<String, Template>) -> Result<Plugin> {
562+
pub fn normalize(
563+
self,
564+
name: String,
565+
templates: &IndexMap<String, Template>,
566+
warnings: &mut Vec<Error>,
567+
) -> Result<Plugin> {
521568
let Self {
522569
git,
523570
gist,
@@ -530,6 +577,7 @@ impl RawPlugin {
530577
dir,
531578
uses,
532579
apply,
580+
rest,
533581
} = self;
534582

535583
let is_reference_some = reference.is_some();
@@ -582,6 +630,10 @@ impl RawPlugin {
582630
}
583631
};
584632

633+
check_extra_toml(rest, |key| {
634+
warnings.push(anyhow!("unused config key: `plugins.{}.{}`", name, key))
635+
});
636+
585637
match raw_source {
586638
TempSource::External(source) => {
587639
if !source.is_git() && is_reference_some {
@@ -639,12 +691,13 @@ impl RawConfig {
639691
}
640692

641693
/// Normalize a `RawConfig` into a `Config`.
642-
fn normalize(self) -> Result<Config> {
694+
fn normalize(self, mut warnings: &mut Vec<Error>) -> Result<Config> {
643695
let Self {
644696
matches,
645697
apply,
646698
templates,
647699
plugins,
700+
rest,
648701
} = self;
649702

650703
// Check that the templates can be compiled.
@@ -666,11 +719,15 @@ impl RawConfig {
666719
for (name, plugin) in plugins {
667720
normalized_plugins.push(
668721
plugin
669-
.normalize(name.clone(), &templates)
722+
.normalize(name.clone(), &templates, &mut warnings)
670723
.with_context(s!("failed to normalize plugin `{}`", name))?,
671724
);
672725
}
673726

727+
check_extra_toml(rest, |key| {
728+
warnings.push(anyhow!("unused config key: `{}`", key))
729+
});
730+
674731
Ok(Config {
675732
matches,
676733
apply,
@@ -682,11 +739,11 @@ impl RawConfig {
682739

683740
impl Config {
684741
/// Read a `Config` from the given path.
685-
pub fn from_path<P>(path: P) -> Result<Self>
742+
pub fn from_path<P>(path: P, mut warnings: &mut Vec<Error>) -> Result<Self>
686743
where
687744
P: AsRef<Path>,
688745
{
689-
Ok(RawConfig::from_path(path)?.normalize()?)
746+
Ok(RawConfig::from_path(path)?.normalize(&mut warnings)?)
690747
}
691748
}
692749

@@ -896,7 +953,7 @@ mod tests {
896953
let text = format!("{} = '{}'\n{} = '{}'", a, example_a, b, example_b);
897954
let e = toml::from_str::<RawPlugin>(&text)
898955
.unwrap()
899-
.normalize("test".to_string(), &IndexMap::new())
956+
.normalize("test".to_string(), &IndexMap::new(), &mut Vec::new())
900957
.unwrap_err();
901958
assert_eq!(e.to_string(), "plugin `test` has multiple source fields")
902959
}
@@ -922,7 +979,9 @@ mod tests {
922979
..Default::default()
923980
};
924981
assert_eq!(
925-
raw_plugin.normalize(name, &IndexMap::new()).unwrap(),
982+
raw_plugin
983+
.normalize(name, &IndexMap::new(), &mut Vec::new())
984+
.unwrap(),
926985
expected
927986
);
928987
}
@@ -950,7 +1009,9 @@ mod tests {
9501009
..Default::default()
9511010
};
9521011
assert_eq!(
953-
raw_plugin.normalize(name, &IndexMap::new()).unwrap(),
1012+
raw_plugin
1013+
.normalize(name, &IndexMap::new(), &mut Vec::new())
1014+
.unwrap(),
9541015
expected
9551016
);
9561017
}
@@ -974,7 +1035,9 @@ mod tests {
9741035
..Default::default()
9751036
};
9761037
assert_eq!(
977-
raw_plugin.normalize(name, &IndexMap::new()).unwrap(),
1038+
raw_plugin
1039+
.normalize(name, &IndexMap::new(), &mut Vec::new())
1040+
.unwrap(),
9781041
expected
9791042
);
9801043
}
@@ -1003,7 +1066,9 @@ mod tests {
10031066
..Default::default()
10041067
};
10051068
assert_eq!(
1006-
raw_plugin.normalize(name, &IndexMap::new()).unwrap(),
1069+
raw_plugin
1070+
.normalize(name, &IndexMap::new(), &mut Vec::new())
1071+
.unwrap(),
10071072
expected
10081073
);
10091074
}
@@ -1030,7 +1095,9 @@ mod tests {
10301095
..Default::default()
10311096
};
10321097
assert_eq!(
1033-
raw_plugin.normalize(name, &IndexMap::new()).unwrap(),
1098+
raw_plugin
1099+
.normalize(name, &IndexMap::new(), &mut Vec::new())
1100+
.unwrap(),
10341101
expected
10351102
);
10361103
}
@@ -1056,7 +1123,9 @@ mod tests {
10561123
..Default::default()
10571124
};
10581125
assert_eq!(
1059-
raw_plugin.normalize(name, &IndexMap::new()).unwrap(),
1126+
raw_plugin
1127+
.normalize(name, &IndexMap::new(), &mut Vec::new())
1128+
.unwrap(),
10601129
expected
10611130
);
10621131
}
@@ -1083,7 +1152,9 @@ mod tests {
10831152
..Default::default()
10841153
};
10851154
assert_eq!(
1086-
raw_plugin.normalize(name, &IndexMap::new()).unwrap(),
1155+
raw_plugin
1156+
.normalize(name, &IndexMap::new(), &mut Vec::new())
1157+
.unwrap(),
10871158
expected
10881159
);
10891160
}
@@ -1106,7 +1177,9 @@ mod tests {
11061177
..Default::default()
11071178
};
11081179
assert_eq!(
1109-
raw_plugin.normalize(name, &IndexMap::new()).unwrap(),
1180+
raw_plugin
1181+
.normalize(name, &IndexMap::new(), &mut Vec::new())
1182+
.unwrap(),
11101183
expected
11111184
);
11121185
}
@@ -1124,7 +1197,7 @@ mod tests {
11241197
..Default::default()
11251198
};
11261199
let error = raw_plugin
1127-
.normalize("test".to_string(), &IndexMap::new())
1200+
.normalize("test".to_string(), &IndexMap::new(), &mut Vec::new())
11281201
.unwrap_err();
11291202
assert_eq!(
11301203
error.to_string(),
@@ -1145,7 +1218,7 @@ mod tests {
11451218
..Default::default()
11461219
};
11471220
let error = raw_plugin
1148-
.normalize("test".to_string(), &IndexMap::new())
1221+
.normalize("test".to_string(), &IndexMap::new(), &mut Vec::new())
11491222
.unwrap_err();
11501223
assert_eq!(
11511224
error.to_string(),
@@ -1170,7 +1243,9 @@ mod tests {
11701243
..Default::default()
11711244
};
11721245
assert_eq!(
1173-
raw_plugin.normalize(name, &IndexMap::new()).unwrap(),
1246+
raw_plugin
1247+
.normalize(name, &IndexMap::new(), &mut Vec::new())
1248+
.unwrap(),
11741249
expected
11751250
);
11761251
}
@@ -1187,7 +1262,9 @@ mod tests {
11871262
..Default::default()
11881263
};
11891264
assert_eq!(
1190-
raw_plugin.normalize(name, &IndexMap::new()).unwrap(),
1265+
raw_plugin
1266+
.normalize(name, &IndexMap::new(), &mut Vec::new())
1267+
.unwrap(),
11911268
expected
11921269
);
11931270
}
@@ -1200,7 +1277,7 @@ mod tests {
12001277
..Default::default()
12011278
};
12021279
let error = raw_plugin
1203-
.normalize("test".to_string(), &IndexMap::new())
1280+
.normalize("test".to_string(), &IndexMap::new(), &mut Vec::new())
12041281
.unwrap_err();
12051282
assert_eq!(
12061283
error.to_string(),
@@ -1219,7 +1296,7 @@ mod tests {
12191296
..Default::default()
12201297
};
12211298
let error = raw_plugin
1222-
.normalize("test".to_string(), &IndexMap::new())
1299+
.normalize("test".to_string(), &IndexMap::new(), &mut Vec::new())
12231300
.unwrap_err();
12241301
assert_eq!(error.to_string(), "unknown template `test`");
12251302
}
@@ -1228,6 +1305,6 @@ mod tests {
12281305
fn config_from_path_example() {
12291306
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
12301307
path.push("docs/plugins.example.toml");
1231-
Config::from_path(path).unwrap();
1308+
Config::from_path(path, &mut Vec::new()).unwrap();
12321309
}
12331310
}

0 commit comments

Comments
 (0)