use anyhow::{bail, Result};
use crev_data::{Level, Version};
use std::{ffi::OsString, io::Write, path::PathBuf};
use structopt::StructOpt;
use term::color;
use crate::term::Term;
#[derive(Debug, StructOpt, Clone, Default)]
pub struct CrateSelector {
#[structopt(long = "unrelated", short = "u")]
pub unrelated: bool,
#[structopt(long = "vers", short = "v")]
version: Option<Version>,
pub name: Option<String>,
version_positional: Option<Version>,
}
impl CrateSelector {
pub fn new(name: Option<String>, version: Option<Version>, unrelated: bool) -> Self {
Self {
unrelated,
name,
version,
version_positional: None,
}
}
pub fn version(&self) -> Result<Option<&Version>> {
match (self.version_positional.as_ref(), self.version.as_ref()) {
(Some(p), Some(np)) => bail!(
"Can't use both positional (`{}`) and non-positional (`{}`) version argument",
p,
np
),
(Some(p), None) => Ok(Some(p)),
(None, Some(np)) => Ok(Some(np)),
(None, None) => Ok(None),
}
}
pub fn auto_unrelated(self) -> Result<Self> {
let repo = crate::Repo::auto_open_cwd_default()?;
Ok(match repo.get_manifest_path() {
Ok(_) => self,
Err(e) => {
Term::new().eprintln(
format_args!("Unable to find manifest: {e}. Assuming `-u` option."),
Some(color::YELLOW),
)?;
CrateSelector {
unrelated: true,
..self
}
}
})
}
pub fn is_empty(&self) -> bool {
self.name.is_none() && self.version.is_none()
}
pub fn ensure_name_given(&self) -> Result<()> {
if self.name.is_none() {
bail!("Crate name argument required!")
}
Ok(())
}
}
#[derive(Debug, StructOpt, Clone, Default)]
pub struct CargoOpts {
#[structopt(long = "features", value_name = "FEATURES")]
pub features: Option<String>,
#[structopt(long = "all-features")]
pub all_features: bool,
#[structopt(long = "no-default-features")]
pub no_default_features: bool,
#[structopt(long = "dev-dependencies")]
dev_dependencies: bool,
#[structopt(long = "no-dev-dependencies")]
no_dev_dependencies: bool,
#[structopt(long = "manifest-path", value_name = "PATH", parse(from_os_str))]
pub manifest_path: Option<PathBuf>,
#[structopt(short = "Z", value_name = "FLAG")]
#[structopt(long = "unstable-flags")]
pub unstable_flags: Vec<String>,
#[structopt(long = "target")]
pub target: Option<Option<String>>,
}
impl CargoOpts {
pub fn dev_dependencies(&self) -> Result<bool> {
if self.dev_dependencies && self.no_dev_dependencies {
bail!("`--no-dev-dependencies` and `--dev-dependencies` can't be used together");
}
if self.no_dev_dependencies {
writeln!(std::io::stderr(), "`--no-dev-dependencies` is the default, is now ignored and will be removed in the future")?;
}
Ok(self.dev_dependencies)
}
}
#[derive(Debug, StructOpt, Clone)]
pub struct IdNew {
#[structopt(long = "url")]
pub url: Option<String>,
#[structopt(long = "github-username")]
pub github_username: Option<String>,
#[structopt(long = "https-push")]
pub use_https_push: bool,
}
#[derive(Debug, StructOpt, Clone)]
pub struct IdSwitch {
pub id: String,
}
#[derive(Debug, StructOpt, Clone, Default)]
pub struct TrustDistanceParams {
#[structopt(long = "depth", default_value = "20")]
pub depth: u64,
#[structopt(long = "high-cost", default_value = "0")]
pub high_cost: u64,
#[structopt(long = "medium-cost", default_value = "1")]
pub medium_cost: u64,
#[structopt(long = "low-cost", default_value = "5")]
pub low_cost: u64,
#[structopt(long = "none-cost", default_value = "21")]
pub none_cost: u64,
#[structopt(long = "distrust-cost", default_value = "21")]
pub distrust_cost: u64,
}
impl From<TrustDistanceParams> for crev_lib::TrustDistanceParams {
fn from(params: TrustDistanceParams) -> Self {
crev_lib::TrustDistanceParams {
max_distance: params.depth,
high_trust_distance: params.high_cost,
medium_trust_distance: params.medium_cost,
low_trust_distance: params.low_cost,
none_trust_distance: params.none_cost,
distrust_distance: params.distrust_cost,
}
}
}
#[derive(Debug, StructOpt, Clone)]
pub struct Diff {
#[structopt(long = "src")]
pub src: Option<Version>,
#[structopt(long = "dst")]
pub dst: Option<Version>,
#[structopt(long = "unrelated", short = "u")]
pub unrelated: bool,
#[structopt(flatten)]
pub requirements: VerificationRequirements,
#[structopt(flatten)]
pub trust_params: TrustDistanceParams,
pub name: String,
#[structopt(parse(from_os_str))]
pub args: Vec<OsString>,
}
#[derive(Debug, StructOpt, Clone, Default)]
pub struct TrustLevelRequirements {
#[structopt(long = "trust", default_value = "low")]
pub trust_level: crev_data::Level,
}
#[derive(Debug, StructOpt, Clone, Default)]
pub struct VerificationRequirements {
#[structopt(flatten)]
pub trust_level: TrustLevelRequirements,
#[structopt(long = "redundancy", default_value = "1")]
pub redundancy: u64,
#[structopt(long = "understanding", default_value = "none")]
pub understanding_level: Level,
#[structopt(long = "thoroughness", default_value = "none")]
pub thoroughness_level: Level,
}
impl From<VerificationRequirements> for crev_lib::VerificationRequirements {
fn from(req: VerificationRequirements) -> Self {
crev_lib::VerificationRequirements {
trust_level: req.trust_level.trust_level,
redundancy: req.redundancy,
understanding: req.understanding_level,
thoroughness: req.thoroughness_level,
}
}
}
#[derive(Debug, StructOpt, Clone, Default)]
pub struct Update {
#[structopt(flatten)]
pub cargo_opts: CargoOpts,
}
#[derive(Debug, StructOpt, Clone, Default)]
pub struct WotOpts {
#[structopt(flatten)]
pub trust_params: TrustDistanceParams,
#[structopt(long = "for-id")]
pub for_id: Option<String>,
}
#[derive(Debug, StructOpt, Clone, Default)]
pub struct CrateVerifyCommon {
#[structopt(flatten)]
pub requirements: VerificationRequirements,
#[structopt(flatten)]
pub cargo_opts: CargoOpts,
}
#[derive(Debug, StructOpt, Clone, Copy, Default)]
pub struct CrateVerifyColumns {
#[structopt(long = "show-digest")]
pub show_digest: Option<Option<bool>>,
#[structopt(long = "show-leftpad-index")]
pub show_leftpad_index: Option<Option<bool>>,
#[structopt(long = "show-downloads")]
pub show_downloads: Option<Option<bool>>,
#[structopt(long = "show-owners")]
pub show_owners: Option<Option<bool>>,
#[structopt(long = "show-latest-trusted")]
pub show_latest_trusted: Option<Option<bool>>,
#[structopt(long = "show-reviews")]
pub show_reviews: Option<Option<bool>>,
#[structopt(long = "show-loc")]
pub show_loc: Option<Option<bool>>,
#[structopt(long = "show-issues")]
pub show_issues: Option<Option<bool>>,
#[structopt(long = "show-geiger")]
pub show_geiger: Option<Option<bool>>,
#[structopt(long = "show-flags")]
pub show_flags: Option<Option<bool>>,
#[structopt(long = "show-all")]
pub show_all: bool,
}
macro_rules! show_x {
($name:ident, $default:expr) => {
pub fn $name(self) -> bool {
self.$name
.unwrap_or(Some(self.show_all))
.unwrap_or($default)
}
};
}
impl CrateVerifyColumns {
pub fn any_selected(self) -> bool {
self.show_digest.is_some()
|| self.show_leftpad_index.is_some()
|| self.show_downloads.is_some()
|| self.show_owners.is_some()
|| self.show_reviews.is_some()
|| self.show_latest_trusted.is_some()
|| self.show_flags.is_some()
|| self.show_issues.is_some()
|| self.show_loc.is_some()
|| self.show_geiger.is_some()
|| self.show_all
}
pub fn show_digest(self) -> bool {
self.show_digest.flatten().unwrap_or(false)
}
show_x!(show_reviews, false);
show_x!(show_leftpad_index, false);
show_x!(show_downloads, false);
show_x!(show_latest_trusted, true);
show_x!(show_flags, true);
show_x!(show_owners, false);
show_x!(show_issues, true);
show_x!(show_loc, false);
show_x!(show_geiger, false);
}
#[derive(Debug, StructOpt, Clone, Default)]
#[structopt(
after_help = r#"Recursive mode will calculate most metrics for the crate together with all its transitive dependencies.
Column description:
- status - Trust check result: `pass` for trusted, `none` for lacking reviews, `flagged` or `dangerous` for crates with problem reports. `N/A` when crev is not configured yet.
- reviews - Number of reviews for the specific version and for all available versions (total)
- issues - Number of issues repored (from trusted sources/all)
- owner
- In non-recursive mode: Owner counts from crates.io (known/all)
- In recursive mode:
- Total number of owners from crates.io
- Total number of owner groups ignoring subsets
- downloads - Download counts from crates.io for the specific version and all versions
- loc - Lines of Rust code
- lpidx - "left-pad" index (ratio of downloads to lines of code)
- geiger - Geiger score: number of `unsafe` lines
- flgs - Flags for specific types of packages
- CB - Custom Build (runs arbitrary code at build time)
- UM - Unmaintained crate
- name - Crate name
- version - Crate version
- latest_t - Latest trusted version
"#
)]
pub struct CrateVerify {
#[structopt(flatten)]
pub common: CrateVerifyCommon,
#[structopt(flatten)]
pub wot: WotOpts,
#[structopt(flatten)]
pub columns: CrateVerifyColumns,
#[structopt(long = "interactive", short = "i")]
pub interactive: bool,
#[structopt(long = "skip-verified")]
pub skip_verified: bool,
#[structopt(long = "skip-known-owners")]
pub skip_known_owners: bool,
#[structopt(long = "skip-indirect")]
pub skip_indirect: bool,
#[structopt(long = "recursive")]
pub recursive: bool,
}
#[derive(Debug, StructOpt, Clone)]
pub struct IdTrust {
#[structopt(long = "overrides")]
pub overrides: bool,
pub public_ids: Vec<String>,
#[structopt(long = "level")]
pub level: Option<crev_data::TrustLevel>,
#[structopt(flatten)]
pub common_proof_create: CommonProofCreate,
}
#[derive(Debug, StructOpt, Clone)]
pub struct TrustUrls {
#[structopt(long = "overrides")]
pub overrides: bool,
pub public_ids_or_urls: Vec<String>,
#[structopt(long = "level")]
pub level: Option<crev_data::TrustLevel>,
#[structopt(flatten)]
pub common_proof_create: CommonProofCreate,
}
#[derive(Debug, StructOpt, Clone)]
pub struct RepoFetchUrl {
pub url: String,
}
#[derive(Debug, StructOpt, Clone)]
pub enum RepoFetch {
#[structopt(name = "trusted")]
Trusted {
#[structopt(flatten)]
distance_params: TrustDistanceParams,
#[structopt(long = "for-id")]
for_id: Option<String>,
},
#[structopt(name = "url")]
Url(RepoFetchUrl),
#[structopt(name = "all")]
All,
}
#[derive(Debug, StructOpt, Clone)]
pub enum IdQuery {
#[structopt(name = "current")]
Current {
#[structopt(flatten)]
trust_params: TrustDistanceParams,
},
#[structopt(name = "all")]
All {
#[structopt(flatten)]
trust_params: TrustDistanceParams,
#[structopt(long = "for-id")]
for_id: Option<String>,
},
#[structopt(name = "own")]
Own {
#[structopt(flatten)]
trust_params: TrustDistanceParams,
},
#[structopt(name = "trusted")]
Trusted {
#[structopt(flatten)]
trust_params: TrustDistanceParams,
#[structopt(long = "for-id")]
for_id: Option<String>,
#[structopt(flatten)]
trust_level: TrustLevelRequirements,
},
}
#[derive(Debug, StructOpt, Clone)]
pub struct RepoQueryReview {
#[structopt(flatten)]
pub crate_: CrateSelector,
}
#[derive(Debug, StructOpt, Clone)]
pub struct RepoQueryAdvisory {
#[structopt(flatten)]
pub crate_: CrateSelector,
}
#[derive(Debug, StructOpt, Clone)]
pub struct RepoQueryIssue {
#[structopt(flatten)]
pub crate_: CrateSelector,
#[structopt(flatten)]
pub trust_params: TrustDistanceParams,
#[structopt(long = "trust", default_value = "none")]
pub trust_level: crev_data::Level,
}
#[derive(Debug, StructOpt, Clone)]
pub struct CrateDir {
#[structopt(flatten)]
pub common: ReviewOrGotoCommon,
}
#[derive(Debug, StructOpt, Clone)]
pub enum RepoQuery {
#[structopt(name = "review")]
Review(RepoQueryReview),
#[structopt(name = "advisory")]
Advisory(RepoQueryAdvisory),
#[structopt(name = "issue")]
Issue(RepoQueryIssue),
}
#[derive(Debug, StructOpt, Clone)]
pub enum RepoEdit {
#[structopt(name = "readme")]
Readme,
#[structopt(name = "known")]
Known,
}
#[derive(Debug, StructOpt, Clone)]
pub struct RepoGit {
#[structopt(parse(from_os_str))]
pub args: Vec<OsString>,
}
#[derive(Debug, StructOpt, Clone)]
pub struct ReviewOrGotoCommon {
#[structopt(flatten)]
pub crate_: CrateSelector,
}
#[derive(Debug, StructOpt, Clone)]
pub struct CrateOpen {
#[structopt(long = "cmd")]
pub cmd: Option<String>,
#[structopt(long = "cmd-save")]
pub cmd_save: bool,
#[structopt(flatten)]
pub common: ReviewOrGotoCommon,
}
#[derive(Debug, StructOpt, Clone)]
pub struct CommonProofCreate {
#[structopt(long = "no-commit")]
pub no_commit: bool,
#[structopt(long = "print-unsigned")]
pub print_unsigned: bool,
#[structopt(long = "print-signed")]
pub print_signed: bool,
#[structopt(long = "no-store")]
pub no_store: bool,
}
#[derive(Debug, StructOpt, Clone)]
pub struct CrateReview {
#[structopt(flatten)]
pub common: ReviewOrGotoCommon,
#[structopt(flatten)]
pub common_proof_create: CommonProofCreate,
#[structopt(long = "advisory")]
pub advisory: bool,
#[structopt(long = "affected")]
pub affected: Option<crev_data::proof::review::package::VersionRange>,
#[structopt(long = "severity")]
pub severity: Option<Level>,
#[structopt(long = "issue")]
pub issue: bool,
#[structopt(long = "skip-activity-check")]
pub skip_activity_check: bool,
#[structopt(long = "overrides")]
pub overrides: bool,
#[structopt(long = "diff", name = "base-version")]
#[allow(clippy::option_option)]
pub diff: Option<Option<Version>>,
#[structopt(flatten)]
pub cargo_opts: CargoOpts,
}
#[derive(Debug, Clone, Default)]
pub struct AdviseCommon {
pub affected: crev_data::proof::review::package::VersionRange,
pub severity: Level,
}
#[derive(Debug, StructOpt, Clone)]
pub struct CrateSearch {
#[structopt(long = "count", default_value = "10")]
pub count: usize,
pub query: String,
}
#[derive(Debug, StructOpt, Clone)]
pub struct IdExport {
pub id: Option<String>,
}
#[derive(Debug, StructOpt, Clone)]
pub struct IdSetUrl {
#[structopt(long = "https-push")]
pub use_https_push: bool,
pub url: String,
}
#[derive(Debug, StructOpt, Clone)]
pub struct RepoImport {
#[structopt(long = "reset-date")]
pub reset_date: bool,
#[structopt(flatten)]
pub common: CommonProofCreate,
}
#[derive(Debug, StructOpt, Clone)]
pub enum Id {
#[structopt(name = "new")]
New(IdNew),
#[structopt(name = "export")]
Export(IdExport),
#[structopt(name = "import")]
Import,
#[structopt(name = "current")]
Current,
#[structopt(name = "switch")]
Switch(IdSwitch),
#[structopt(name = "passwd")]
Passwd,
#[structopt(name = "set-url")]
SetUrl(IdSetUrl),
#[structopt(name = "trust")]
Trust(IdTrust),
#[structopt(name = "untrust")]
Untrust(IdTrust),
#[structopt(name = "distrust")]
Distrust(IdTrust),
#[structopt(name = "query")]
Query(IdQuery),
}
#[derive(Debug, StructOpt, Clone)]
pub struct CrateVerifyFull {
#[structopt(flatten)]
pub opts: CrateVerify,
#[structopt(flatten)]
pub crate_: CrateSelector,
}
#[derive(Debug, StructOpt, Clone)]
pub enum Crate {
#[structopt(name = "goto")]
Goto(ReviewOrGotoCommon),
#[structopt(name = "open")]
Open(CrateOpen),
#[structopt(name = "expand")]
Expand(ReviewOrGotoCommon),
#[structopt(name = "clean")]
Clean(ReviewOrGotoCommon),
#[structopt(name = "diff")]
#[structopt(setting = structopt::clap::AppSettings::TrailingVarArg)]
#[structopt(setting = structopt::clap::AppSettings::AllowLeadingHyphen)]
Diff(Diff),
#[structopt(name = "dir")]
Dir(CrateDir),
#[structopt(name = "verify")]
Verify(CrateVerifyFull),
#[structopt(name = "mvp")]
Mvp {
#[structopt(flatten)]
opts: CrateVerifyCommon,
#[structopt(flatten)]
wot: WotOpts,
#[structopt(flatten)]
crate_: CrateSelector,
},
#[structopt(name = "review")]
Review(CrateReview),
#[structopt(name = "unreview")]
Unreview(CrateReview),
#[structopt(name = "search")]
Search(CrateSearch),
#[structopt(name = "info")]
Info {
#[structopt(flatten)]
opts: CrateVerifyCommon,
#[structopt(flatten)]
wot: WotOpts,
#[structopt(flatten)]
crate_: CrateSelector,
},
}
#[derive(Debug, StructOpt, Clone)]
pub enum Config {
#[structopt(name = "edit")]
Edit,
#[structopt(name = "completions")]
Completions {
#[structopt(long = "shell")]
shell: Option<String>,
},
#[structopt(name = "dir")]
Dir,
#[structopt(name = "data-dir")]
DataDir,
#[structopt(name = "cache-dir")]
CacheDir,
}
#[derive(Debug, StructOpt, Clone)]
pub struct ProofFind {
#[structopt(name = "crate", long = "crate")]
pub crate_: Option<String>,
#[structopt(name = "vers", long = "vers")]
pub version: Option<Version>,
#[structopt(name = "author", long = "author")]
pub author: Option<String>,
}
#[derive(Debug, StructOpt, Clone)]
pub enum Repo {
#[structopt(name = "publish")]
Publish,
#[structopt(name = "update")]
Update(Update),
#[structopt(name = "git")]
#[structopt(setting = structopt::clap::AppSettings::TrailingVarArg)]
#[structopt(setting = structopt::clap::AppSettings::AllowLeadingHyphen)]
Git(RepoGit),
#[structopt(name = "edit")]
Edit(RepoEdit),
#[structopt(name = "import")]
Import(RepoImport),
#[structopt(name = "query")]
Query(RepoQuery),
#[structopt(name = "fetch")]
Fetch(RepoFetch),
#[structopt(name = "dir")]
Dir,
}
#[derive(Debug, StructOpt, Clone)]
pub enum Proof {
#[structopt(name = "find")]
Find(ProofFind),
}
#[derive(Debug, StructOpt, Clone)]
pub enum Wot {
#[structopt(name = "log")]
Log {
#[structopt(flatten)]
wot: WotOpts,
},
}
#[derive(Debug, StructOpt, Clone)]
#[structopt(setting = structopt::clap::AppSettings::DeriveDisplayOrder)]
#[structopt(setting = structopt::clap::AppSettings::DisableHelpSubcommand)]
#[allow(clippy::large_enum_variant)]
pub enum Command {
#[structopt(name = "config")]
Config(Config),
#[structopt(name = "crate")]
Crate(Crate),
#[structopt(name = "id")]
Id(Id),
#[structopt(name = "proof")]
Proof(Proof),
#[structopt(name = "repo")]
Repo(Repo),
Trust(TrustUrls),
#[structopt(name = "wot")]
Wot(Wot),
#[structopt(name = "goto")]
Goto(ReviewOrGotoCommon),
#[structopt(name = "open")]
Open(CrateOpen),
#[structopt(name = "publish")]
Publish,
#[structopt(name = "review")]
Review(CrateReview),
#[structopt(name = "update")]
Update(Update),
#[structopt(name = "verify")]
Verify(CrateVerifyFull),
}
#[derive(Debug, StructOpt, Clone)]
pub enum MainCommand {
#[structopt(name = "crev")]
#[structopt(after_help = r#"All commands can be abbreviated.
Help and feedback: https://github.com/crev-dev/cargo-crev/discussions/
User documentation: https://docs.rs/crate/cargo-crev
"#)]
Crev(Command),
}
#[derive(Debug, StructOpt, Clone)]
#[structopt(about = "Distributed code review system")]
#[structopt(bin_name = "cargo")]
#[structopt(global_setting = structopt::clap::AppSettings::ColoredHelp)]
#[structopt(global_setting = structopt::clap::AppSettings::InferSubcommands)]
pub struct Opts {
#[structopt(subcommand)]
pub command: MainCommand,
}