Skip to content

Commit

Permalink
feat: add & separator (#3)
Browse files Browse the repository at this point in the history
* feat: add & separator

* feat: add more help information and version information

* release: 0.1.3
  • Loading branch information
guuzaa authored Jan 19, 2025
1 parent 17878e6 commit 8804d2f
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 32 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cargo-q"
version = "0.1.2"
version = "0.1.3"
edition = "2021"
description = "A cargo subcommand for running multiple cargo commands in a time"
keywords = ["cargo", "subcommand", "plugin"]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Cargo subcommand to run multiple Cargo commands in a time.

- ✅ Add sequential execution
- ✅ Add ; as command separator
- Add & as command separator
- Add & as command separator
- ❌ Add > as command separator
- ❌ Add parallel execution

Expand Down
17 changes: 16 additions & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,30 @@ use clap::Parser;

#[derive(Parser, Debug)]
#[command(name = "cargo-q")]
#[command(version)]
#[command(about = "A cargo subcommand for running multiple cargo commands in a time")]
#[command(author)]
pub struct Cli {
/// Commands to execute
///
/// Supports multiple separators:
///
/// space: Independent commands (e.g., "check test")
///
/// ; : Independent commands with args (e.g., "test --features f1 ; run")
///
/// & : Dependent commands (e.g., "check & test & run")
pub command_string: String,

/// Run in verbose mode
/// Run commands in verbose mode
///
/// Shows the output of each command as it runs
#[arg(short, long)]
pub verbose: bool,

/// Run commands in parallel
///
/// Only works with independent commands (space or ; separator)
#[arg(short, long)]
pub parallel: bool,
}
Expand Down
115 changes: 88 additions & 27 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,23 @@ impl Parser {
}

pub fn parse(&self, input: &str, parallel: bool, verbose: bool) -> Executor {
let routines = if input.contains(';') {
self.parse_semicolon_separated(input)
// Check for & first as it's the most restrictive
if input.contains('&') {
let routines = self.parse_ampersand_separated(input);
Executor::new(parallel, verbose, routines, Strategy::Dependent)
} else if input.contains(';') {
let routines = self.parse_semicolon_separated(input);
Executor::new(parallel, verbose, routines, Strategy::Independent)
} else {
self.parse_space_separated(input)
};

Executor::new(parallel, verbose, routines, Strategy::Independent)
let routines = self.parse_space_separated(input);
Executor::new(parallel, verbose, routines, Strategy::Independent)
}
}

fn parse_space_separated(&self, input: &str) -> Vec<Routine> {
input
.split_whitespace()
.map(|cmd| {
let parts: Vec<&str> = cmd.split_whitespace().collect();
Routine {
name: parts[0].to_string(),
args: parts[1..].iter().map(|s| s.to_string()).collect(),
}
})
.map(|cmd| self.create_routine(cmd))
.collect()
}

Expand All @@ -47,22 +45,31 @@ impl Parser {
.split(';')
.map(str::trim)
.filter(|s| !s.is_empty())
.map(|cmd| {
let parts: Vec<&str> = cmd.split_whitespace().collect();
if parts.is_empty() {
return Routine {
name: String::new(),
args: Vec::new(),
};
}
Routine {
name: parts[0].to_string(),
args: parts[1..].iter().map(|s| s.to_string()).collect(),
}
})
.filter(|routine| !routine.name.is_empty())
.map(|cmd| self.create_routine(cmd))
.filter(|routine| !routine.is_empty())
.collect()
}

fn parse_ampersand_separated(&self, input: &str) -> Vec<Routine> {
input
.split('&')
.map(str::trim)
.filter(|s| !s.is_empty())
.map(|cmd| self.create_routine(cmd))
.filter(|routine| !routine.is_empty())
.collect()
}

fn create_routine(&self, cmd: &str) -> Routine {
let parts: Vec<&str> = cmd.split_whitespace().collect();
if parts.is_empty() {
return Routine::default();
}
Routine {
name: parts[0].to_string(),
args: parts[1..].iter().map(|s| s.to_string()).collect(),
}
}
}

#[cfg(test)]
Expand Down Expand Up @@ -101,6 +108,60 @@ mod tests {
assert!(executor.routines[1].args.is_empty());
}

#[test]
fn test_parse_ampersand_separated() {
let parser = Parser::new();
let input = "check & test & run";
let executor = parser.parse(input, false, false);

assert_eq!(executor.strategy, Strategy::Dependent);
assert_eq!(executor.routines.len(), 3);

assert_eq!(executor.routines[0].name, "check");
assert!(executor.routines[0].args.is_empty());

assert_eq!(executor.routines[1].name, "test");
assert!(executor.routines[1].args.is_empty());

assert_eq!(executor.routines[2].name, "run");
assert!(executor.routines[2].args.is_empty());
}

#[test]
fn test_parse_ampersand_with_args() {
let parser = Parser::new();
let input = "test --features feature1 & run --release";
let executor = parser.parse(input, false, false);

assert_eq!(executor.strategy, Strategy::Dependent);
assert_eq!(executor.routines.len(), 2);

assert_eq!(executor.routines[0].name, "test");
assert_eq!(executor.routines[0].args, vec!["--features", "feature1"]);

assert_eq!(executor.routines[1].name, "run");
assert_eq!(executor.routines[1].args, vec!["--release"]);
}

#[test]
fn test_parse_ampersand_without_spaces() {
let parser = Parser::new();
let input = "check&test&run";
let executor = parser.parse(input, false, false);

assert_eq!(executor.strategy, Strategy::Dependent);
assert_eq!(executor.routines.len(), 3);

assert_eq!(executor.routines[0].name, "check");
assert!(executor.routines[0].args.is_empty());

assert_eq!(executor.routines[1].name, "test");
assert!(executor.routines[1].args.is_empty());

assert_eq!(executor.routines[2].name, "run");
assert!(executor.routines[2].args.is_empty());
}

#[test]
fn test_parse_semicolon_with_empty() {
let parser = Parser::new();
Expand Down
6 changes: 5 additions & 1 deletion src/routine.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::io;
use std::process::{Command, Output, Stdio};

#[derive(Debug)]
#[derive(Debug, Default)]
pub(crate) struct Routine {
pub(crate) name: String,
pub(crate) args: Vec<String>,
Expand All @@ -28,4 +28,8 @@ impl Routine {
Ok((output.status.success(), output))
}
}

pub fn is_empty(&self) -> bool {
self.name.is_empty()
}
}

0 comments on commit 8804d2f

Please sign in to comment.