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 334 additions and 4 deletions
Showing only changes of commit 0a5d3b531a - Show all commits

View File

@@ -15,13 +15,14 @@ This is the harmony CLI, a minimal implementation
The current help text:
```
Usage: example-cli [OPTIONS] --run <RUN>
Usage: example-cli [OPTIONS]
Options:
-r, --run <RUN> Name of score to run
-i, --interactive Run interactive or not
-a, --all Run all or nth
-n, --number <NUMBER> Run nth matching, zero indexed [default: 0]
-l, --list list scores, will also be affected by run filter
-h, --help Print help

Je viens de l'essayer, semble pas pire, mais je pense que ca prend quelques trucs pour que le UX soit correct :

  • Une option --list qui liste les scores
  • La feature tui devrait etre la par defaut
  • Le comportement de nth me semble etrange. Pas sur qu'on veut prendre le nth apres le filtrage.
  • Lorsqu'on ne passe aucun argument on devrait voir soit le help ou la liste des scores. Probablement les deux en fait.
Je viens de l'essayer, semble pas pire, mais je pense que ca prend quelques trucs pour que le UX soit correct : - Une option --list qui liste les scores - La feature tui devrait etre la par defaut - Le comportement de nth me semble etrange. Pas sur qu'on veut prendre le nth apres le filtrage. - Lorsqu'on ne passe aucun argument on devrait voir soit le help ou la liste des scores. Probablement les deux en fait.
-V, --version Print version
```

View File

@@ -12,4 +12,5 @@ harmony_tui = { path = "../harmony_tui", optional = true }
[features]
default = ["tui"]
tui = ["dep:harmony_tui"]

View File

@@ -1,5 +1,5 @@
use clap::Parser;
use clap::builder::ArgPredicate;
use clap::{Arg, CommandFactory, Parser};
use harmony;
use harmony::{score::Score, topology::Topology};
@@ -14,9 +14,9 @@ struct Args {
#[arg(
short,
long,
default_value_if("interactive", ArgPredicate::IsPresent, Some("unused"))
default_value_if("interactive", ArgPredicate::IsPresent, Some(""))
)]
run: String,
run: Option<String>,
/// Name of score to run
#[cfg(not(feature = "tui"))]
@@ -34,6 +34,10 @@ struct Args {
/// Run nth matching, zero indexed
#[arg(short, long, default_value_t = 0)]
number: u8,
/// list scores, will also be affected by run filter
#[arg(short, long, default_value_t = false)]
list: bool,
}
pub async fn init<T: Topology + std::fmt::Debug + Send + Sync + 'static>(
@@ -51,24 +55,40 @@ pub async fn init<T: Topology + std::fmt::Debug + Send + Sync + 'static>(
return Err("Not compiled with interactive support".into());
}
// if no score to run provided, and list isn't specified, print help
if args.run.is_none() && !args.list {
Args::command().print_help();
return Ok(());
}
let scores = maestro.scores();
let scores_read = scores.read().expect("Should be able to read scores");
let filtered: Vec<Box<dyn Score<T>>> = scores_read
.iter()
.map(|s| s.clone_box())
.filter(|s| s.name().contains(&args.run))
.collect();
let scores_vec: Vec<Box<dyn Score<T>>> = match args.run.clone() {
Some(filter) => scores_read
.iter()
taha marked this conversation as resolved Outdated

Calling a function here that uses .iter().filter() and returns an option is more idiomatic rust (and more robust) than the mut bool found and mut count Just filter the array and them you can match filtered.len()

let filtered = scores_read_vec.iter().filter(...).collect();
match filtered.len() { // Not sure about .len, could be length or size
  0 => todo!("Handle zero"),
  1 => todo!("Handle one"),
  _ => todo!("Handle more than one"),
}
Calling a function here that uses .iter().filter() and returns an option is more idiomatic rust (and more robust) than the `mut bool found` and `mut count` Just filter the array and them you can match `filtered.len()` ```rust let filtered = scores_read_vec.iter().filter(...).collect(); match filtered.len() { // Not sure about .len, could be length or size 0 => todo!("Handle zero"), 1 => todo!("Handle one"), _ => todo!("Handle more than one"), } ```
.map(|s| s.clone_box())
.filter(|s| s.name().contains(&filter))
.collect(),
None => scores_read.iter().map(|s| s.clone_box()).collect(),
};

This function, though pretty simple, deserves a test. Could be a good place for a doctest. Not necessary though, just spilling my thoughts.

This function, though pretty simple, deserves a test. Could be a good place for a doctest. Not necessary though, just spilling my thoughts.
if filtered.len() == 0 {
// if list option is specified, print filtered list and exit
if args.list {
println!("Available scores: {:#?}", scores_vec);
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 args.all {
for s in filtered {
for s in scores_vec {
println!("Running: {}", s.clone_box().name());
}
} else {
let score = filtered.get(args.number as usize);
let score = scores_vec.get(args.number as usize);
match score {
Some(s) => println!("Running: {}", s.clone_box().name()),
None => return Err("Invalid index given".into()),