Skip to content

Commit

Permalink
Merge pull request #1007 from SierraSoftworks/refactor/inventory
Browse files Browse the repository at this point in the history
refactor: Switch to dynamically discovering commands
  • Loading branch information
notheotherben authored Dec 6, 2023
2 parents d9d5c7a + 48ac833 commit bb6252b
Show file tree
Hide file tree
Showing 24 changed files with 171 additions and 196 deletions.
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 @@ impl Command for AuthCommand {
.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
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 @@ impl Command for CompleteCommand {
.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 @@ where {
.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 @@ impl CompleteCommand {
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 @@ impl CompleteCommand {
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 mod helpers {

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 @@ pub mod helpers {
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 @@ -303,10 +283,8 @@ mod tests {
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

0 comments on commit bb6252b

Please sign in to comment.