Skip to content

Commit

Permalink
read config from pyproject.toml
Browse files Browse the repository at this point in the history
  • Loading branch information
brownben committed Nov 9, 2024
1 parent 0e07e10 commit 106cec2
Show file tree
Hide file tree
Showing 5 changed files with 255 additions and 71 deletions.
75 changes: 75 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ rayon = "1.10.0"
ruff_python_parser = { git = "https://github.com/astral-sh/ruff", "rev" = "d3f1c8e" }
serde = { version = "1.0.210", features = ["derive"] }
serde_json = "1.0.128"
toml = "0.8.19"
widestring = "1.1.0"

[dev-dependencies]
Expand Down
164 changes: 164 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
use clap::Parser;

#[derive(Parser, Debug, Default)]
#[command(version, about, long_about = None)]
pub(crate) struct Settings {
/// List of files or directories to test
#[clap(default_value = ".")]
pub paths: Vec<std::path::PathBuf>,

/// List of files or directories to exclude from testing
#[clap(long, value_name = "FILE_PATTERN")]
pub exclude: Vec<std::path::PathBuf>,

#[clap(flatten)]
pub coverage: CoverageSettings,

/// Don't stop executing tests after one has failed
#[clap(long, default_value_t = false)]
pub no_fail_fast: bool,

/// How test results should be reported
#[clap(long, value_enum, default_value_t = OutputFormat::Standard)]
pub output: OutputFormat,
}

#[derive(clap::Args, Debug, Default)]
pub(crate) struct CoverageSettings {
/// Enable line coverage gathering and reporting
#[clap(long = "coverage", default_value_t = false)]
pub enabled: bool,

/// List of paths, used to determine files to report coverage for
#[clap(
name = "coverage-include",
long = "coverage-include",
value_name = "FILE_PATTERN",
help_heading = "Coverage"
)]
pub include: Vec<std::path::PathBuf>,

/// List of paths, used to omit files and/or directories from coverage reporting
#[clap(
name = "coverage-exclude",
long = "coverage-exclude",
value_name = "FILE_PATTERN",
help_heading = "Coverage"
)]
pub exclude: Vec<std::path::PathBuf>,
}

#[derive(Copy, Clone, Default, Debug, clap::ValueEnum)]
pub(crate) enum OutputFormat {
/// The standard output format to the terminal
#[default]
Standard,
/// Output each test as a JSON object on a new line
Json,
}

/// Reads settings from command line arguments and `pyproject.toml`
pub fn read_settings() -> Settings {
let mut settings = Settings::parse();

if let Some(pyproject_toml) = pyproject_toml::find() {
if let Some(xc_config) = pyproject_toml::load(&pyproject_toml) {
pyproject_toml::update_settings(&mut settings, xc_config);
}
}

settings
}

mod pyproject_toml {
use serde::Deserialize;
use std::{
env, fs, mem,
path::{Path, PathBuf},
};

#[derive(Deserialize, Default)]
struct PyprojectToml {
tool: Option<PyprojectTomlTool>,
}

#[derive(Deserialize, Default)]
struct PyprojectTomlTool {
xc: Option<XCSettings>,
}

#[derive(Deserialize, Default)]
pub struct XCSettings {
include: Option<Vec<PathBuf>>,
exclude: Option<Vec<PathBuf>>,
no_fail_fast: Option<bool>,
coverage: Option<bool>,
coverage_include: Option<Vec<PathBuf>>,
coverage_exclude: Option<Vec<PathBuf>>,
}

/// Get the path to a `pyproject.toml` file, if one exists in the current tree
pub fn find() -> Option<PathBuf> {
let mut path = env::current_dir().unwrap();

while path.parent().is_some() {
path.push("./pyproject.toml");
if path.exists() {
return Some(path);
}

// Remove the `pyproject.toml` file
path.pop();

// Go up to the next folder
path.pop();
}

None
}

pub fn load(path: &Path) -> Option<XCSettings> {
let pyproject_file = fs::read_to_string(path).ok()?;
let pyproject = toml::from_str::<PyprojectToml>(&pyproject_file).ok()?;

pyproject.tool?.xc
}

pub fn update_settings(settings: &mut super::Settings, mut toml_config: XCSettings) {
if let Some(include) = &mut toml_config.include {
if settings.paths.is_empty() {
settings.paths = mem::take(include);
}
}

if let Some(exclude) = &mut toml_config.exclude {
if settings.exclude.is_empty() {
settings.exclude = mem::take(exclude);
}
}

if let Some(no_fail_fast) = toml_config.no_fail_fast {
if !settings.no_fail_fast {
settings.no_fail_fast = no_fail_fast;
}
}

if let Some(coverage) = toml_config.coverage {
if !settings.coverage.enabled {
settings.coverage.enabled = coverage;
}
}

if let Some(coverage_include) = &mut toml_config.coverage_include {
if settings.coverage.include.is_empty() {
settings.coverage.include = mem::take(coverage_include);
}
}

if let Some(coverage_exclude) = &mut toml_config.coverage_exclude {
if settings.coverage.exclude.is_empty() {
settings.coverage.exclude = mem::take(coverage_exclude);
}
}
}
}
Loading

0 comments on commit 106cec2

Please sign in to comment.