Skip to content

Commit b856b96

Browse files
authored
feat(config): Support for pyproject.toml files (#790)
This PR adds support for parsing `pyproject.toml` config files. The convention for these files is to put any tooling related configuration into the `tool.NAME` section, so in this case, `tool.typos`. I have verified that the changes are pulled correctly, even if the `tool.typos` section is not present. Closes #361
1 parent 65d2fb6 commit b856b96

File tree

2 files changed

+56
-22
lines changed

2 files changed

+56
-22
lines changed

crates/typos-cli/src/bin/typos-cli/main.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ fn run_dump_config(args: &args::Args, output_path: &std::path::Path) -> proc_exi
6868
if let Some(path) = args.custom_config.as_ref() {
6969
let custom = typos_cli::config::Config::from_file(path)
7070
.with_code(proc_exit::sysexits::CONFIG_ERR)?;
71-
overrides.update(&custom);
71+
if let Some(custom) = custom {
72+
overrides.update(&custom);
73+
}
7274
}
7375
overrides.update(&args.config.to_config());
7476
engine.set_overrides(overrides);
@@ -119,7 +121,9 @@ fn run_type_list(args: &args::Args) -> proc_exit::ExitResult {
119121
if let Some(path) = args.custom_config.as_ref() {
120122
let custom = typos_cli::config::Config::from_file(path)
121123
.with_code(proc_exit::sysexits::CONFIG_ERR)?;
122-
overrides.update(&custom);
124+
if let Some(custom) = custom {
125+
overrides.update(&custom);
126+
}
123127
}
124128
overrides.update(&args.config.to_config());
125129
engine.set_overrides(overrides);
@@ -154,7 +158,9 @@ fn run_checks(args: &args::Args) -> proc_exit::ExitResult {
154158
if let Some(path) = args.custom_config.as_ref() {
155159
let custom = typos_cli::config::Config::from_file(path)
156160
.with_code(proc_exit::sysexits::CONFIG_ERR)?;
157-
overrides.update(&custom);
161+
if let Some(custom) = custom {
162+
overrides.update(&custom);
163+
}
158164
}
159165
overrides.update(&args.config.to_config());
160166
engine.set_overrides(overrides);

crates/typos-cli/src/config.rs

Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ use kstring::KString;
44

55
use crate::file_type_specifics;
66

7-
pub const SUPPORTED_FILE_NAMES: &[&str] = &["typos.toml", "_typos.toml", ".typos.toml"];
7+
pub const SUPPORTED_FILE_NAMES: &[&str] =
8+
&["typos.toml", "_typos.toml", ".typos.toml", "pyproject.toml"];
89

910
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1011
#[serde(deny_unknown_fields)]
@@ -19,26 +20,54 @@ pub struct Config {
1920
pub overrides: EngineConfig,
2021
}
2122

23+
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
24+
#[serde(default)]
25+
#[serde(rename_all = "kebab-case")]
26+
pub struct PyprojectTomlConfig {
27+
pub tool: PyprojectTomlTool,
28+
}
29+
30+
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
31+
#[serde(default)]
32+
#[serde(rename_all = "kebab-case")]
33+
pub struct PyprojectTomlTool {
34+
pub typos: Option<Config>,
35+
}
36+
2237
impl Config {
2338
pub fn from_dir(cwd: &std::path::Path) -> Result<Option<Self>, anyhow::Error> {
24-
let config = if let Some(path) = find_project_file(cwd, SUPPORTED_FILE_NAMES) {
25-
log::debug!("Loading {}", path.display());
26-
Some(Self::from_file(&path)?)
27-
} else {
28-
None
29-
};
30-
Ok(config)
39+
for file in find_project_files(cwd, SUPPORTED_FILE_NAMES) {
40+
log::debug!("Loading {}", file.display());
41+
if let Some(config) = Self::from_file(&file)? {
42+
return Ok(Some(config));
43+
}
44+
}
45+
46+
Ok(None)
3147
}
3248

33-
pub fn from_file(path: &std::path::Path) -> Result<Self, anyhow::Error> {
49+
pub fn from_file(path: &std::path::Path) -> Result<Option<Self>, anyhow::Error> {
3450
let s = std::fs::read_to_string(path).map_err(|err| {
3551
let kind = err.kind();
3652
std::io::Error::new(
3753
kind,
3854
format!("could not read config at `{}`", path.display()),
3955
)
4056
})?;
41-
Self::from_toml(&s)
57+
58+
if path.file_name().unwrap() == "pyproject.toml" {
59+
let config = toml::from_str::<PyprojectTomlConfig>(&s)?;
60+
61+
if config.tool.typos.is_none() {
62+
log::debug!("No `tool.typos` section found in `pyproject.toml`, skipping");
63+
64+
Ok(None)
65+
} else {
66+
Ok(config.tool.typos)
67+
}
68+
} else {
69+
Self::from_toml(&s).map(Some)
70+
}
4271
}
4372

4473
pub fn from_toml(data: &str) -> Result<Self, anyhow::Error> {
@@ -455,15 +484,14 @@ impl DictConfig {
455484
}
456485
}
457486

458-
fn find_project_file(dir: &std::path::Path, names: &[&str]) -> Option<std::path::PathBuf> {
459-
let mut file_path = dir.join("placeholder");
460-
for name in names {
461-
file_path.set_file_name(name);
462-
if file_path.exists() {
463-
return Some(file_path);
464-
}
465-
}
466-
None
487+
fn find_project_files<'a>(
488+
dir: &'a std::path::Path,
489+
names: &'a [&'a str],
490+
) -> impl Iterator<Item = std::path::PathBuf> + 'a {
491+
names
492+
.iter()
493+
.map(|name| dir.join(name))
494+
.filter(|path| path.exists())
467495
}
468496

469497
impl PartialEq for DictConfig {

0 commit comments

Comments
 (0)