diff --git a/src/cli/mod.rs b/src/cli/mod.rs index f7bd0f5e..96534b76 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -104,6 +104,8 @@ pub struct CargoMsrvOpts { #[derive(Debug, Subcommand)] #[command(propagate_version = true)] pub enum SubCommand { + /// Find & fix MSRV related issues + Doctor(DoctorOpts), /// Find the MSRV Find(FindOpts), /// Display the MSRV's of dependencies @@ -118,6 +120,14 @@ pub enum SubCommand { Verify(VerifyOpts), } +#[derive(Debug, Args)] +#[command(next_help_heading = "Doctor options")] +pub struct DoctorOpts { + /// Try to fix the reported issues + #[arg(long)] + pub fix: bool, +} + // Cli Options for top-level cargo-msrv (find) command #[derive(Debug, Args)] #[command(next_help_heading = "Find MSRV options")] diff --git a/src/context/doctor.rs b/src/context/doctor.rs new file mode 100644 index 00000000..bd89621b --- /dev/null +++ b/src/context/doctor.rs @@ -0,0 +1,34 @@ +use crate::cli::{CargoMsrvOpts, SubCommand}; +use crate::context::EnvironmentContext; +use crate::error::CargoMSRVError; + +#[derive(Debug)] +pub struct DoctorContext { + /// Try and fix the issues found! + pub fix: bool, + + /// Resolved environment options + pub environment: EnvironmentContext, +} + +impl TryFrom for DoctorContext { + type Error = CargoMSRVError; + + fn try_from(opts: CargoMsrvOpts) -> Result { + let CargoMsrvOpts { + shared_opts, + subcommand, + .. + } = opts; + + let doctor_opts = match subcommand { + SubCommand::Doctor(opts) => opts, + _ => unreachable!("This should never happen. The subcommand is not `doctor`!"), + }; + + Ok(Self { + fix: doctor_opts.fix, + environment: (&shared_opts).try_into()?, + }) + } +} diff --git a/src/context/mod.rs b/src/context/mod.rs index a3c81fb3..7ae273e9 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -19,24 +19,27 @@ use std::path::Path; use std::str::FromStr; use std::{env, fmt}; -pub mod find; -pub mod list; -pub mod set; -pub mod show; -pub mod verify; - use crate::cli::custom_check_opts::CustomCheckOpts; use crate::cli::rust_releases_opts::Edition; use crate::cli::{CargoMsrvOpts, SubCommand}; use crate::log_level::LogLevel; use crate::reporter::event::SelectedPackage; use crate::rust::default_target::default_target; + +pub use doctor::DoctorContext; pub use find::FindContext; pub use list::ListContext; pub use set::SetContext; pub use show::ShowContext; pub use verify::VerifyContext; +pub mod doctor; +pub mod find; +pub mod list; +pub mod set; +pub mod show; +pub mod verify; + /// A `context` in `cargo-msrv`, is a definitive and flattened set of options, /// required for the program (and its selected sub-command) to function. /// @@ -57,6 +60,7 @@ pub use verify::VerifyContext; /// data. #[derive(Debug)] pub enum Context { + Doctor(DoctorContext), Find(FindContext), List(ListContext), Set(SetContext), @@ -67,6 +71,7 @@ pub enum Context { impl Context { pub fn reporting_name(&self) -> &'static str { match self { + Context::Doctor(_) => "Doctor", Context::Find(_) => "find", Context::List(_) => "list", Context::Set(_) => "set", @@ -77,6 +82,7 @@ impl Context { pub fn environment_context(&self) -> &EnvironmentContext { match self { + Context::Doctor(ctx) => &ctx.environment, Context::Find(ctx) => &ctx.environment, Context::List(ctx) => &ctx.environment, Context::Set(ctx) => &ctx.environment, @@ -85,22 +91,26 @@ impl Context { } } - /// Returns the inner find context, if it was present. - pub fn to_find_context(self) -> Option { - if let Self::Find(ctx) = self { - Some(ctx) - } else { - None - } + /// Returns the inner find context + /// + /// **Panics** + /// + /// Panics if the context does not match with `Find` + pub fn to_find_context(self) -> FindContext { + let Self::Find(ctx) = self else { + unreachable!("Find Context does not exist") + }; + + ctx } - /// Returns the inner find context, if it was present. - pub fn to_verify_context(self) -> Option { - if let Self::Verify(ctx) = self { - Some(ctx) - } else { - None - } + /// Returns the inner verify context + pub fn to_verify_context(self) -> VerifyContext { + let Self::Verify(ctx) = self else { + unreachable!("Find Context does not exist") + }; + + ctx } } @@ -109,6 +119,7 @@ impl TryFrom for Context { fn try_from(opts: CargoMsrvOpts) -> Result { let ctx = match opts.subcommand { + SubCommand::Doctor(_) => Self::Doctor(DoctorContext::try_from(opts)?), SubCommand::Find(_) => Self::Find(FindContext::try_from(opts)?), SubCommand::List(_) => Self::List(ListContext::try_from(opts)?), SubCommand::Set(_) => Self::Set(SetContext::try_from(opts)?), diff --git a/src/lib.rs b/src/lib.rs index c0d78ba4..4caa3763 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,7 @@ extern crate tracing; pub use crate::context::{Context, OutputFormat, TracingOptions, TracingTargetOption}; pub use crate::outcome::Compatibility; -pub use crate::sub_command::{Find, List, Set, Show, SubCommand, Verify}; +pub use crate::sub_command::{Doctor, Find, List, Set, Show, SubCommand, Verify}; use crate::compatibility::RustupToolchainCheck; use crate::context::ReleaseSource; @@ -61,6 +61,9 @@ pub fn run_app(ctx: &Context, reporter: &impl Reporter) -> TResult<()> { reporter.report_event(SubcommandInit::new(ctx.reporting_name()))?; match ctx { + Context::Doctor(ctx) => { + Doctor.run(ctx, reporter)?; + } Context::Find(ctx) => { let index = release_index::fetch_index(reporter, ctx.rust_releases.release_source)?; diff --git a/src/sub_command/doctor.rs b/src/sub_command/doctor.rs new file mode 100644 index 00000000..e2cfe2f5 --- /dev/null +++ b/src/sub_command/doctor.rs @@ -0,0 +1,29 @@ +use crate::cli::rust_releases_opts::Edition; +use crate::context::DoctorContext; +use crate::error::TResult; +use crate::manifest::bare_version::BareVersion; +use crate::reporter::Reporter; +use crate::sub_command::verify::RustVersion; +use crate::SubCommand; + +#[derive(Default)] +pub struct Doctor; + +impl SubCommand for Doctor { + type Context = DoctorContext; + type Output = (); + + fn run(&self, _ctx: &Self::Context, _reporter: &impl Reporter) -> TResult { + todo!("Implement cargo msrv doctor!") + } +} + +struct IssueAnalyzer { + msrv_cargo_toml: BareVersion, + edition: Edition, +} + +struct MsrvSource { + cargo_toml: Option, + clippy_toml: Option, +} diff --git a/src/sub_command/mod.rs b/src/sub_command/mod.rs index 2acf6f16..b1d6cdb2 100644 --- a/src/sub_command/mod.rs +++ b/src/sub_command/mod.rs @@ -1,8 +1,18 @@ +use crate::reporter::Reporter; +use crate::TResult; + +/// Find MSRV related issues +/// +/// # Example (CLI) +/// +/// `cargo msrv doctor` +pub use doctor::Doctor; + /// Find the MSRV of a Rust package. /// /// # Example (CLI) /// -/// `cargo msrv` +/// `cargo msrv find` pub use find::Find; /// List the MSRV's of libraries you depend on. @@ -38,9 +48,7 @@ pub use set::Set; /// `cargo msrv show` pub use show::Show; -use crate::reporter::Reporter; -use crate::TResult; - +pub mod doctor; pub mod find; pub mod list; pub mod set; diff --git a/tests/common/sub_cmd_find.rs b/tests/common/sub_cmd_find.rs index 500fd3a8..3c72e395 100644 --- a/tests/common/sub_cmd_find.rs +++ b/tests/common/sub_cmd_find.rs @@ -58,7 +58,7 @@ pub fn find_msrv_with_releases< let matches = CargoCli::parse_args(with_args); let opts = matches.to_cargo_msrv_cli().to_opts(); let ctx = Context::try_from(opts)?; - let find_ctx = ctx.to_find_context().unwrap(); + let find_ctx = ctx.to_find_context(); // Limit the available versions: this ensures we don't need to incrementally install more toolchains // as more Rust toolchains become available. diff --git a/tests/common/sub_cmd_verify.rs b/tests/common/sub_cmd_verify.rs index 5b6ae3cf..803b626a 100644 --- a/tests/common/sub_cmd_verify.rs +++ b/tests/common/sub_cmd_verify.rs @@ -17,7 +17,7 @@ where let matches = CargoCli::parse_args(with_args); let opts = matches.to_cargo_msrv_cli().to_opts(); let ctx = Context::try_from(opts)?; - let verify_ctx = ctx.to_verify_context().unwrap(); + let verify_ctx = ctx.to_verify_context(); // Limit the available versions: this ensures we don't need to incrementally install more toolchains // as more Rust toolchains become available.