Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Switch to dynamically discovering commands #1007

Merged
merged 1 commit into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 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 @@ -17,6 +17,7 @@ futures = "0.3"
gtmpl = "0.7"
human-errors = "0.1.3"
http = "0.2"
inventory = "0.3.13"
itertools = "0.12"
keyring = { version = "2.1", optional = true }
lazy_static = "1.4"
Expand Down
10 changes: 5 additions & 5 deletions src/commands/apps.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use super::*;

pub struct AppsCommand {}
pub struct AppsCommand;

impl Command for AppsCommand {
crate::command!(AppsCommand);

#[async_trait]
impl CommandRunnable for AppsCommand {
fn name(&self) -> String {
String::from("apps")
}
Expand All @@ -12,10 +15,7 @@ impl Command for AppsCommand {
.about("list applications which can be run through Git-Tool")
.long_about("Gets the list of applications that you have added to your configuration file. These applications can be run through the `open` and `scratch` commands.")
}
}

#[async_trait]
impl CommandRunnable for AppsCommand {
#[tracing::instrument(name = "gt apps", err, skip(self, core, _matches))]
async fn run(
&self,
Expand Down
10 changes: 5 additions & 5 deletions src/commands/auth.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use super::*;
use clap::Arg;

pub struct AuthCommand {}
pub struct AuthCommand;

impl Command for AuthCommand {
crate::command!(AuthCommand);

#[async_trait]
impl CommandRunnable for AuthCommand {
fn name(&self) -> String {
String::from("auth")
}
Expand All @@ -26,10 +29,7 @@
.help("specifies the token to be set (don't use this unless you have to)")
.action(clap::ArgAction::Set))
}
}

#[async_trait]
impl CommandRunnable for AuthCommand {
#[tracing::instrument(name = "gt auth", err, skip(self, core, matches))]
async fn run(
&self,
Expand Down Expand Up @@ -57,7 +57,7 @@
let token = match matches.get_one::<String>("token") {
Some(token) => token.to_string(),
None => {
if let Some(online_service) = crate::online::service::services()

Check warning on line 60 in src/commands/auth.rs

View workflow job for this annotation

GitHub Actions / Code Quality

unnecessarily eager cloning of iterator items
.iter()
.cloned()
.find(|s| s.handles(svc))
Expand Down
10 changes: 5 additions & 5 deletions src/commands/clone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ use crate::core::Target;
use crate::tasks::*;
use clap::{Arg, ArgMatches};

pub struct CloneCommand {}
pub struct CloneCommand;

impl Command for CloneCommand {
crate::command!(CloneCommand);

#[async_trait]
impl CommandRunnable for CloneCommand {
fn name(&self) -> String {
String::from("clone")
}
Expand All @@ -21,10 +24,7 @@ impl Command for CloneCommand {
.required(true)
.index(1))
}
}

#[async_trait]
impl CommandRunnable for CloneCommand {
#[tracing::instrument(name = "gt clone", err, skip(self, core, matches))]
async fn run(&self, core: &Core, matches: &ArgMatches) -> Result<i32, errors::Error> {
let repo_name = matches.get_one::<String>("repo").ok_or_else(|| errors::user(
Expand Down
58 changes: 18 additions & 40 deletions src/commands/complete.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use super::*;
use clap::Arg;

pub struct CompleteCommand {}
pub struct CompleteCommand;

impl Command for CompleteCommand {
crate::command!(CompleteCommand);

#[async_trait]
impl CommandRunnable for CompleteCommand {
fn name(&self) -> String {
String::from("complete")
}
Expand All @@ -21,10 +24,7 @@
.help("The parameters being passed to Git-Tool for auto-completion.")
.index(1))
}
}

#[async_trait]
impl CommandRunnable for CompleteCommand {
#[tracing::instrument(name = "gt complete", err, skip(self, core, matches))]
async fn run(
&self,
Expand All @@ -39,14 +39,12 @@
.map(|s| s.as_str())
.unwrap_or_default();

let commands = super::commands();
let (cmd, filter) = self
.extract_command_and_filter(args, position)
.unwrap_or_default();

let completer = Completer::new(core, &filter);
self.offer_completions(core, &commands, &cmd, &completer)
.await;
self.offer_completions(core, &cmd, &completer).await;

Ok(0)
}
Expand Down Expand Up @@ -91,13 +89,9 @@
Some((cmd, filter))
}

fn get_responsible_command(
&self,
commands: &[Arc<dyn CommandRunnable>],
args: &str,
) -> Option<(Arc<dyn CommandRunnable>, ArgMatches)> {
if let Ok(complete_matches) = self.get_completion_matches(commands, args) {
for cmd in commands.iter() {
fn get_responsible_command(&self, args: &str) -> Option<(Command, ArgMatches)> {
if let Ok(complete_matches) = self.get_completion_matches(args) {
for cmd in inventory::iter::<Command> {
if let Some(cmd_matches) = complete_matches.subcommand_matches(&cmd.name()) {
return Some((cmd.clone(), cmd_matches.clone()));
}
Expand All @@ -107,38 +101,28 @@
None
}

async fn offer_completions(
&self,
core: &Core,
commands: &[Arc<dyn CommandRunnable>],
args: &str,
completer: &Completer,
) {
match self.get_responsible_command(commands, args) {
async fn offer_completions(&self, core: &Core, args: &str, completer: &Completer) {
match self.get_responsible_command(args) {
Some((cmd, matches)) => {
cmd.complete(core, completer, &matches).await;
}
None => {
for cmd in commands.iter() {
for cmd in inventory::iter::<Command> {
completer.offer(&cmd.name());
}
}
}
}

fn get_completion_matches(
&self,
commands: &[Arc<dyn CommandRunnable>],
args: &str,
) -> Result<ArgMatches, errors::Error> {
fn get_completion_matches(&self, args: &str) -> Result<ArgMatches, errors::Error> {
let true_args = shell_words::split(args)
.map_err(|e| errors::user_with_internal(
"Could not parse the arguments you provided.",
"Please make sure that you are using auto-complete with a valid set of command line arguments.",
e))?;

let complete_app =
clap::Command::new("Git-Tool").subcommands(commands.iter().map(|x| x.app()));
let complete_app = clap::Command::new("Git-Tool")
.subcommands(inventory::iter::<Command>().map(|x| x.app()));

complete_app.try_get_matches_from(true_args).map_err(|err| {
errors::user_with_internal(
Expand All @@ -158,11 +142,8 @@

pub fn test_responsible_command(args: &str, expected: Option<&str>) {
let cmd = CompleteCommand {};
let cmds = default_commands();

let responsible = cmd
.get_responsible_command(&cmds, args)
.map(|(c, _)| c.name());
let responsible = cmd.get_responsible_command(args).map(|(c, _)| c.name());

assert_eq!(
responsible.clone(),
Expand All @@ -180,11 +161,10 @@
contains: Vec<&str>,
) {
let cmd = CompleteCommand {};
let cmds = default_commands();

let console = crate::console::mock();
let completer = Completer::new_for(filter, console.clone());
cmd.offer_completions(core, &cmds, args, &completer).await;
cmd.offer_completions(core, args, &completer).await;

let output = console.to_string();

Expand Down Expand Up @@ -222,7 +202,7 @@
pub async fn test_completions2(args: Vec<&str>, contains: Vec<&str>) {
let console = crate::console::mock();
let core = Core::builder()
.with_config_for_dev_directory(&get_dev_dir())

Check warning on line 205 in src/commands/complete.rs

View workflow job for this annotation

GitHub Actions / Code Quality

the borrowed expression implements the required traits
.with_console(console.clone())
.build();

Expand Down Expand Up @@ -303,10 +283,8 @@
fn get_completion_matches() {
let cmd = CompleteCommand {};

let cmds = default_commands();

assert_eq!(
cmd.get_completion_matches(&cmds, "git-tool new")
cmd.get_completion_matches("git-tool new")
.unwrap()
.subcommand_name(),
Some("new")
Expand Down
12 changes: 6 additions & 6 deletions src/commands/config.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
use crate::core::features;

use super::async_trait;
use super::Command;
use super::CommandRunnable;
use super::*;
use clap::{Arg, ArgMatches};
use online::registry::Registry;

pub struct ConfigCommand {}
pub struct ConfigCommand;

impl Command for ConfigCommand {
crate::command!(ConfigCommand);

#[async_trait]
impl CommandRunnable for ConfigCommand {
fn name(&self) -> String {
String::from("config")
}
Expand Down Expand Up @@ -84,10 +87,7 @@ impl Command for ConfigCommand {
.help("configure the scratchpads path instead of the repositories path")
.action(clap::ArgAction::SetTrue)))
}
}

#[async_trait]
impl CommandRunnable for ConfigCommand {
#[tracing::instrument(name = "gt config", err, skip(self, core, matches))]
async fn run(&self, core: &Core, matches: &ArgMatches) -> Result<i32, errors::Error> {
match matches.subcommand() {
Expand Down
9 changes: 4 additions & 5 deletions src/commands/doctor.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use super::*;

pub struct DoctorCommand {}
pub struct DoctorCommand;
crate::command!(DoctorCommand);

impl Command for DoctorCommand {
#[async_trait]
impl CommandRunnable for DoctorCommand {
fn name(&self) -> String {
String::from("doctor")
}
Expand All @@ -13,10 +15,7 @@ impl Command for DoctorCommand {
.about("checks that your environment is configured correctly for Git-Tool")
.long_about("Runs a series of checks to ensure that the environment is ready to run the application")
}
}

#[async_trait]
impl CommandRunnable for DoctorCommand {
#[tracing::instrument(name = "gt doctor", err, skip(self, core, _matches))]
async fn run(
&self,
Expand Down
9 changes: 4 additions & 5 deletions src/commands/fix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ use super::*;
use crate::{search, tasks::*};
use clap::{Arg, ArgMatches};

pub struct FixCommand {}
pub struct FixCommand;
crate::command!(FixCommand);

impl Command for FixCommand {
#[async_trait]
impl CommandRunnable for FixCommand {
fn name(&self) -> String {
String::from("fix")
}
Expand All @@ -29,10 +31,7 @@ impl Command for FixCommand {
.help("prevent the creation of a remote repository (on supported services)")
.action(clap::ArgAction::SetTrue))
}
}

#[async_trait]
impl CommandRunnable for FixCommand {
#[tracing::instrument(name = "gt fix", err, skip(self, core, matches))]
async fn run(&self, core: &Core, matches: &ArgMatches) -> Result<i32, errors::Error> {
let tasks = sequence![
Expand Down
11 changes: 5 additions & 6 deletions src/commands/ignore.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use super::async_trait;
use super::online::gitignore;
use super::Command;
use super::CommandRunnable;
use super::*;
use clap::{value_parser, Arg, ArgMatches};

pub struct IgnoreCommand {}
pub struct IgnoreCommand;
crate::command!(IgnoreCommand);

impl Command for IgnoreCommand {
#[async_trait]
impl CommandRunnable for IgnoreCommand {
fn name(&self) -> String {
String::from("ignore")
}
Expand All @@ -29,10 +31,7 @@ impl Command for IgnoreCommand {
.action(clap::ArgAction::Append)
.index(1))
}
}

#[async_trait]
impl CommandRunnable for IgnoreCommand {
#[tracing::instrument(name = "gt ignore", err, skip(self, core, matches))]
async fn run(&self, core: &Core, matches: &ArgMatches) -> Result<i32, errors::Error> {
match matches.get_many::<String>("language") {
Expand Down
11 changes: 6 additions & 5 deletions src/commands/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ use super::core::Target;
use super::*;
use clap::{Arg, ArgMatches};

pub struct InfoCommand {}
pub struct InfoCommand;
crate::command!(InfoCommand);

impl Command for InfoCommand {
#[async_trait]
impl CommandRunnable for InfoCommand {
fn name(&self) -> String {
String::from("info")
}
Expand All @@ -20,10 +22,7 @@ impl Command for InfoCommand {
.help("The name of the repository to get information about.")
.index(1))
}
}

#[async_trait]
impl CommandRunnable for InfoCommand {
#[tracing::instrument(name = "gt info", err, skip(self, core, matches))]
async fn run(&self, core: &Core, matches: &ArgMatches) -> Result<i32, errors::Error> {
let mut output = core.output();
Expand Down Expand Up @@ -75,6 +74,8 @@ impl CommandRunnable for InfoCommand {

#[cfg(test)]
mod tests {
use std::sync::Arc;

use mockall::predicate::eq;

use super::*;
Expand Down
Loading
Loading