feat: harmony-cli v0.1 #8 #9

Merged
taha merged 23 commits from harmony-cli into master 2025-04-19 01:13:40 +00:00
7 changed files with 337 additions and 4 deletions
Showing only changes of commit e845544035 - Show all commits

View File

@@ -14,13 +14,14 @@ This is the harmony CLI, a minimal implementation
The current help text: The current help text:
``` ````
Usage: example-cli [OPTIONS] Usage: harmony_cli [OPTIONS]
Options: Options:
-r, --run <RUN> Name of score to run -r, --run Run score(s) or not
-i, --interactive Run interactive or not -f, --filter <FILTER> Filter query
-a, --all Run all or nth -i, --interactive Run interactive TUI or not
-a, --all Run all or nth, defaults to all
-n, --number <NUMBER> Run nth matching, zero indexed [default: 0] -n, --number <NUMBER> Run nth matching, zero indexed [default: 0]
-l, --list list scores, will also be affected by run filter -l, --list list scores, will also be affected by run filter
-h, --help Print help -h, --help Print help
@@ -30,3 +31,4 @@ Options:
## Core architecture ## Core architecture
![Harmony Core Architecture](docs/diagrams/Harmony_Core_Architecture.drawio.svg) ![Harmony Core Architecture](docs/diagrams/Harmony_Core_Architecture.drawio.svg)
````

View File

@@ -1,3 +1,5 @@
use std::error::Error;
use clap::builder::ArgPredicate; use clap::builder::ArgPredicate;
use clap::{Arg, CommandFactory, Parser}; use clap::{Arg, CommandFactory, Parser};
use harmony; use harmony;
@@ -9,26 +11,20 @@ use harmony_tui;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(version, about, long_about = None)] #[command(version, about, long_about = None)]
struct Args { struct Args {
/// Name of score to run /// Run score(s) or not
#[cfg(feature = "tui")] #[arg(short, long, default_value_t = false)]
#[arg( run: bool,
short,
long,
default_value_if("interactive", ArgPredicate::IsPresent, Some(""))
)]
run: Option<String>,
/// Name of score to run /// Filter query
#[cfg(not(feature = "tui"))]
#[arg(short, long)] #[arg(short, long)]
run: String, filter: Option<String>,
/// Run interactive or not /// Run interactive TUI or not
#[arg(short, long, default_value_t = false)] #[arg(short, long, default_value_t = false)]
interactive: bool, interactive: bool,
/// Run all or nth /// Run all or nth, defaults to all
#[arg(short, long, default_value_t = false)] #[arg(short, long, default_value_t = true)]
all: bool, all: bool,
/// Run nth matching, zero indexed /// Run nth matching, zero indexed
@@ -56,41 +52,46 @@ pub async fn init<T: Topology + std::fmt::Debug + Send + Sync + 'static>(
} }
Review

So much better. I think there are more Rust idiomatic ways to handle that with Slices or something but it's good enough :

  • Clear, easy to read and understand
  • Relatively flat, not too much nested ifs and fors
  • Leveraging functional Rust, usually makes for more robust code easily

Now though you should at least reverse the map and filter calls so scores don't all get cloned for nothing.

In general you always filter your ensemble before processing.

So much better. I think there are more Rust idiomatic ways to handle that with Slices or something but it's good enough : - Clear, easy to read and understand - Relatively flat, not too much nested ifs and fors - Leveraging functional Rust, usually makes for more robust code easily Now though you should at least reverse the map and filter calls so scores don't all get cloned for nothing. In general you always filter your ensemble before processing.
// if no score to run provided, and list isn't specified, print help // if no score to run provided, and list isn't specified, print help
if args.run.is_none() && !args.list { if !args.run && !args.list {
println!("Please include at least --run or --list");
Args::command().print_help(); Args::command().print_help();
return Ok(()); return Ok(());
} }
let scores = maestro.scores(); let scores = maestro.scores();
let scores_read = scores.read().expect("Should be able to read scores"); let scores_read = scores.read().expect("Should be able to read scores");
let scores_vec: Vec<Box<dyn Score<T>>> = match args.run.clone() { let scores_vec: Vec<Box<dyn Score<T>>> = match args.filter.clone() {
Some(filter) => scores_read Some(f) => scores_read
.iter() .iter()
.map(|s| s.clone_box()) .map(|s| s.clone_box())
.filter(|s| s.name().contains(&filter)) .filter(|s| s.name().contains(&f))
.collect(), .collect(),
None => scores_read.iter().map(|s| s.clone_box()).collect(), None => scores_read.iter().map(|s| s.clone_box()).collect(),
}; };
if scores_vec.len() == 0 {
return Err("No score found".into());
}
// if list option is specified, print filtered list and exit // if list option is specified, print filtered list and exit
if args.list { if args.list {
println!("Available scores: {:#?}", scores_vec); println!("Available scores: {:#?}", scores_vec);
return Ok(()); return Ok(());
} }
if scores_vec.len() == 0 {
return Err("No score containing query found".into());
}
// if all is specified, run all. Otherwise, run the first match or specified index // if all is specified, run all. Otherwise, run the first match or specified index
if args.all { if args.all {
for s in scores_vec { for s in scores_vec {
println!("Running: {}", s.clone_box().name()); println!("Running: {}", s.name());
maestro.interpret(s).await?;
} }
} else { } else {
let score = scores_vec.get(args.number as usize); let score = scores_vec.get(args.number as usize);
match score { match score {
Some(s) => println!("Running: {}", s.clone_box().name()), Some(s) => {
println!("Running: {}", s.name());
maestro.interpret(s.clone_box()).await?;
}
None => return Err("Invalid index given".into()), None => return Err("Invalid index given".into()),
} }
} }