From c6b2a73b27b22c99d62a67f22e24bdb30e34e9ad Mon Sep 17 00:00:00 2001 From: Wim Looman Date: Fri, 22 Sep 2023 14:06:30 +0200 Subject: [PATCH 1/3] Add support for global config file --- src/cli.rs | 4 +++ src/cli/config.rs | 76 +++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 14 +++++++++ 3 files changed, 94 insertions(+) create mode 100644 src/cli/config.rs diff --git a/src/cli.rs b/src/cli.rs index de6a4925..a663a326 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -5,6 +5,10 @@ use tracing::level_filters::LevelFilter; use crate::format::{CriteriaName, ImportName, PackageName, VersionReq, VetVersion}; +pub use self::config::Config; + +mod config; + #[derive(Parser)] #[clap(version, about, long_about = None)] #[clap(propagate_version = true)] diff --git a/src/cli/config.rs b/src/cli/config.rs new file mode 100644 index 00000000..4c284751 --- /dev/null +++ b/src/cli/config.rs @@ -0,0 +1,76 @@ +use crate::errors::{LoadTomlError, SourceFile, TomlParseError}; +use miette::SourceOffset; + +use std::path::PathBuf; + +#[derive(serde::Deserialize, Default)] +#[serde(rename_all = "kebab-case")] +pub struct Config {} + +// Can't use types from `errors` because this may error before `miette` is +// configured. We need to collect the data here then transform it into a report +// once the logger is configured. +pub enum LoadConfigError { + TomlParse { + path: PathBuf, + content: String, + line: usize, + col: usize, + error: toml::de::Error, + }, + + IoError { + path: PathBuf, + error: std::io::Error, + }, +} + +impl From for miette::Report { + fn from(err: LoadConfigError) -> Self { + match err { + LoadConfigError::TomlParse { + path, + content, + line, + col, + error, + } => TomlParseError { + span: SourceOffset::from_location(&content, line + 1, col + 1), + source_code: SourceFile::new(&path.display().to_string(), content), + error, + } + .into(), + + LoadConfigError::IoError { path, error } => { + miette::Report::from(LoadTomlError::from(error)) + .context(format!("reading '{}'", path.display())) + } + } + } +} + +impl Config { + pub fn load() -> Result { + let Some(config_dir) = dirs::config_dir() else { + return Ok(Self::default()); + }; + let path = config_dir.join("cargo-vet").join("config.toml"); + match std::fs::read_to_string(&path) { + Ok(content) => { + let config = toml::de::from_str(&content).map_err(|error| { + let (line, col) = error.line_col().unwrap_or((0, 0)); + LoadConfigError::TomlParse { + path, + content, + line, + col, + error, + } + })?; + Ok(config) + } + Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(Self::default()), + Err(error) => Err(LoadConfigError::IoError { path, error }), + } + } +} diff --git a/src/main.rs b/src/main.rs index 606f4b3c..996ffa5e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -76,6 +76,8 @@ pub struct Config { pub struct PartialConfig { /// Details of the CLI invocation (args) pub cli: Cli, + /// Global config file + pub config: cli::Config, /// The date and time to use as the current time. pub now: chrono::DateTime, /// Path to the cache directory we're using @@ -230,6 +232,13 @@ fn main() -> Result<(), ()> { fn real_main() -> Result<(), miette::Report> { use cli::Commands::*; + let config = cli::Config::load(); + // We can't return if this errored here as error logging isn't configured + // yet, instead use a default and then check the error later + let (config, config_err) = match config { + Ok(config) => (config, None), + Err(err) => (cli::Config::default(), Some(err)), + }; let fake_cli = cli::FakeCli::parse(); let cli::FakeCli::Vet(cli) = fake_cli; @@ -327,6 +336,10 @@ fn real_main() -> Result<(), miette::Report> { set_report_errors_as_json(out.clone()); } + if let Some(err) = config_err { + return Err(miette::Report::from(err).context("failed to load global config")); + } + //////////////////////////////////////////////////// // Potentially handle freestanding commands //////////////////////////////////////////////////// @@ -341,6 +354,7 @@ fn real_main() -> Result<(), miette::Report> { .unwrap_or_else(|| chrono::DateTime::from(SystemTime::now())); let partial_cfg = PartialConfig { cli, + config, now, cache_dir, mock_cache: false, From b9b6f4a4e9dd53ea2470c0710d9b53e6b3f9a3b9 Mon Sep 17 00:00:00 2001 From: Wim Looman Date: Fri, 22 Sep 2023 14:07:04 +0200 Subject: [PATCH 2/3] Add `inspect.mode` to global config --- Cargo.lock | 20 +++++++++++++------- src/cli.rs | 32 ++++++++++++++++++++++++++++++-- src/cli/config.rs | 12 +++++++++++- src/main.rs | 4 ++-- 4 files changed, 56 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a3aa1c5..213a90d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,7 +159,7 @@ dependencies = [ "similar", "tar", "tempfile", - "textwrap", + "textwrap 0.15.0", "thiserror", "tokio", "toml", @@ -212,9 +212,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.6" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1fe12880bae935d142c8702d500c63a4e8634b6c3c57ad72bf978fc7b6249a" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "atty", "bitflags", @@ -224,7 +224,7 @@ dependencies = [ "once_cell", "strsim", "termcolor", - "textwrap", + "textwrap 0.16.0", ] [[package]] @@ -239,9 +239,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.2.6" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6db9e867166a43a53f7199b5e4d1f522a1e5bd626654be263c999ce59df39a" +checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" dependencies = [ "heck", "proc-macro-error", @@ -877,7 +877,7 @@ dependencies = [ "supports-hyperlinks", "supports-unicode", "terminal_size", - "textwrap", + "textwrap 0.15.0", "thiserror", "unicode-width", ] @@ -1513,6 +1513,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" + [[package]] name = "thiserror" version = "1.0.40" diff --git a/src/cli.rs b/src/cli.rs index a663a326..4fe4d2d8 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,6 +1,6 @@ use std::{path::PathBuf, str::FromStr}; -use clap::{Parser, Subcommand, ValueEnum}; +use clap::{CommandFactory, FromArgMatches, Parser, Subcommand, ValueEnum}; use tracing::level_filters::LevelFilter; use crate::format::{CriteriaName, ImportName, PackageName, VersionReq, VetVersion}; @@ -17,6 +17,33 @@ pub enum FakeCli { Vet(Cli), } +impl FakeCli { + pub fn parse_with_config(config: &Config) -> Self { + let mut command = Self::command_for_update().mut_subcommand("vet", |vet| { + vet.mut_subcommand("inspect", |inspect| { + inspect.mut_arg("mode", |mode| { + if let Some(default_mode) = config.inspect.mode { + // TODO: clap v4 doesn't require leaking here + mode.default_value(String::leak( + default_mode + .to_possible_value() + .unwrap() + .get_name() + .to_owned(), + )) + } else { + mode + } + }) + }) + }); + match Self::from_arg_matches_mut(&mut command.get_matches_mut()) { + Ok(cli) => cli, + Err(err) => err.format(&mut command).exit(), + } + } +} + #[derive(clap::Args)] #[clap(version)] #[clap(bin_name = "cargo vet")] @@ -793,7 +820,8 @@ impl FromStr for DependencyCriteriaArg { } } -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, serde::Deserialize)] +#[serde(rename_all = "kebab-case")] pub enum FetchMode { Local, Sourcegraph, diff --git a/src/cli/config.rs b/src/cli/config.rs index 4c284751..f0aefa35 100644 --- a/src/cli/config.rs +++ b/src/cli/config.rs @@ -5,7 +5,10 @@ use std::path::PathBuf; #[derive(serde::Deserialize, Default)] #[serde(rename_all = "kebab-case")] -pub struct Config {} +pub struct Config { + #[serde(default)] + pub inspect: Inspect, +} // Can't use types from `errors` because this may error before `miette` is // configured. We need to collect the data here then transform it into a report @@ -74,3 +77,10 @@ impl Config { } } } + +#[derive(serde::Deserialize, Default)] +#[serde(rename_all = "kebab-case")] +pub struct Inspect { + #[serde(default)] + pub mode: Option, +} diff --git a/src/main.rs b/src/main.rs index 996ffa5e..15e776e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use std::time::{Duration, SystemTime}; use std::{fs::File, io, panic, path::PathBuf}; use cargo_metadata::{Metadata, Package}; -use clap::{CommandFactory, Parser}; +use clap::CommandFactory; use console::Term; use errors::{ AggregateCriteriaDescription, AggregateCriteriaDescriptionMismatchError, @@ -239,7 +239,7 @@ fn real_main() -> Result<(), miette::Report> { Ok(config) => (config, None), Err(err) => (cli::Config::default(), Some(err)), }; - let fake_cli = cli::FakeCli::parse(); + let fake_cli = cli::FakeCli::parse_with_config(&config); let cli::FakeCli::Vet(cli) = fake_cli; ////////////////////////////////////////////////////// From 60513b0657a1c453d3d034d64f64dd04dfe34132 Mon Sep 17 00:00:00 2001 From: Wim Looman Date: Fri, 22 Sep 2023 14:28:37 +0200 Subject: [PATCH 3/3] Add global user config --- src/cli/config.rs | 2 ++ src/main.rs | 26 ++++++++++++++++---------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/cli/config.rs b/src/cli/config.rs index f0aefa35..141e8115 100644 --- a/src/cli/config.rs +++ b/src/cli/config.rs @@ -8,6 +8,8 @@ use std::path::PathBuf; pub struct Config { #[serde(default)] pub inspect: Inspect, + #[serde(default)] + pub(crate) user: Option, } // Can't use types from `errors` because this may error before `miette` is diff --git a/src/main.rs b/src/main.rs index 15e776e7..95969666 100644 --- a/src/main.rs +++ b/src/main.rs @@ -785,9 +785,9 @@ fn do_cmd_certify( }; let (username, who) = if sub_args.who.is_empty() { - let user_info = get_user_info()?; - let who = format!("{} <{}>", user_info.username, user_info.email); - (user_info.username, vec![Spanned::from(who)]) + let user_info = get_user_info(&cfg)?; + let who = format!("{} <{}>", user_info.name, user_info.email); + (user_info.name, vec![Spanned::from(who)]) } else { ( sub_args.who.join(", "), @@ -1533,9 +1533,9 @@ fn cmd_record_violation( }; let (_username, who) = if sub_args.who.is_empty() { - let user_info = get_user_info()?; - let who = format!("{} <{}>", user_info.username, user_info.email); - (user_info.username, vec![Spanned::from(who)]) + let user_info = get_user_info(&cfg)?; + let who = format!("{} <{}>", user_info.name, user_info.email); + (user_info.name, vec![Spanned::from(who)]) } else { ( sub_args.who.join(", "), @@ -2676,12 +2676,14 @@ fn cmd_gc( // Utils +#[derive(Clone, serde::Deserialize)] +#[serde(rename_all = "kebab-case")] struct UserInfo { - username: String, + name: String, email: String, } -fn get_user_info() -> Result { +fn get_user_info(cfg: &Config) -> Result { fn get_git_config(value_name: &str) -> Result { let out = std::process::Command::new("git") .arg("config") @@ -2698,10 +2700,14 @@ fn get_user_info() -> Result { .map_err(CommandError::BadOutput) } - let username = get_git_config("user.name").map_err(UserInfoError::UserCommandFailed)?; + if let Some(user_info) = &cfg.config.user { + return Ok(user_info.clone()); + } + + let name = get_git_config("user.name").map_err(UserInfoError::UserCommandFailed)?; let email = get_git_config("user.email").map_err(UserInfoError::EmailCommandFailed)?; - Ok(UserInfo { username, email }) + Ok(UserInfo { name, email }) } async fn eula_for_criteria(