mirror of
https://github.com/storytold/soundcraft.git
synced 2026-11-09 20:04:53 +00:00
Merge pull request #1372 from ZIDAN44/fix/744-commands-json
feat(cli): commands --json lists every command's details (#744)
This commit is contained in:
6 files changed
+56
-14
No files matched your search
@@ -228,7 +228,8 @@ soundcraft-cli mcp --automation-read-root DIR --automation-write-root DIR
|
||||
|
||||
See [`docs/cli.md`](docs/cli.md) for the command-line tool (subcommands, exit codes, environment
|
||||
variables, man pages and completions), [`docs/control-protocol.md`](docs/control-protocol.md) and
|
||||
[`docs/mcp.md`](docs/mcp.md). `soundcraft-cli commands` lists every command.
|
||||
[`docs/mcp.md`](docs/mcp.md). `soundcraft-cli commands` lists every command (`--json` for each
|
||||
one's parameters and details, as JSON).
|
||||
|
||||
Open **SoundCraft › Session Audio Health** to check loaded audio availability, sample-rate
|
||||
mismatches and clip source bounds across all playlists, including alternate takes. The report
|
||||
|
||||
@@ -168,6 +168,12 @@ pub(crate) struct CommandsArgs {
|
||||
/// Only the commands whose id, or label in any case, contains this text
|
||||
#[arg(value_parser = NonEmptyStringValueParser::new())]
|
||||
pub(crate) filter: Option<String>,
|
||||
/// Print a JSON array of each command's details, as describe prints them
|
||||
#[arg(long)]
|
||||
pub(crate) json: bool,
|
||||
/// One line of JSON instead of pretty-printed
|
||||
#[arg(long, requires = "json")]
|
||||
pub(crate) compact: bool,
|
||||
}
|
||||
|
||||
/// Print one engine command's details as JSON
|
||||
|
||||
@@ -7,11 +7,15 @@ use std::io::Write;
|
||||
pub(crate) fn commands(args: &CommandsArgs, out: &mut dyn Write) {
|
||||
let filter = args.filter.clone().unwrap_or_default();
|
||||
let e = Engine::default();
|
||||
for c in soundcraft_engine::command_specs() {
|
||||
if !filter.is_empty() && !c.id.contains(&filter) && !c.label.to_ascii_lowercase().contains(&filter.to_ascii_lowercase()) {
|
||||
continue;
|
||||
}
|
||||
let i = c.info(&e);
|
||||
let infos = soundcraft_engine::command_specs()
|
||||
.iter()
|
||||
.filter(|c| filter.is_empty() || c.id.contains(&filter) || c.label.to_ascii_lowercase().contains(&filter.to_ascii_lowercase()))
|
||||
.map(|c| c.info(&e));
|
||||
if args.json {
|
||||
print_json(out, &infos.collect::<Vec<_>>(), args.compact);
|
||||
return;
|
||||
}
|
||||
for i in infos {
|
||||
outln!(out, "{:<36} {:<34} {}", i.id, i.label, i.menu.join(" > "));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,3 +253,31 @@ fn script_bounce_without_range_covers_the_session() {
|
||||
assert!((secs - 19.2).abs() < 0.01, "bounced {secs} s: {stdout}");
|
||||
let _ = std::fs::remove_dir_all(base);
|
||||
}
|
||||
|
||||
/// `commands --json` lists every command with the details `describe` prints for one (#744).
|
||||
#[test]
|
||||
fn commands_json_lists_what_describe_prints() {
|
||||
let json = |o: &Output| -> Value {
|
||||
assert!(o.status.success(), "{}", String::from_utf8_lossy(&o.stderr));
|
||||
serde_json::from_slice(&o.stdout).unwrap()
|
||||
};
|
||||
let all = json(&run(&["commands", "--json"]));
|
||||
let all = all.as_array().unwrap();
|
||||
let table = run(&["commands"]);
|
||||
assert_eq!(all.len(), String::from_utf8_lossy(&table.stdout).lines().count());
|
||||
let bounce = json(&run(&["describe", "file.bounce_mix"]));
|
||||
assert!(all.contains(&bounce), "{bounce}");
|
||||
assert!(all.iter().all(|c| c["id"].is_string() && c["params"].is_string() && c["menu"].is_array()));
|
||||
// The filter applies, and --compact prints one line.
|
||||
let compact = run(&["commands", "bounce", "--json", "--compact"]);
|
||||
assert_eq!(String::from_utf8_lossy(&compact.stdout).lines().count(), 1);
|
||||
let some = json(&compact);
|
||||
let some = some.as_array().unwrap();
|
||||
assert!(some.contains(&bounce) && some.len() < all.len(), "{some:?}");
|
||||
// No match is an empty list, still JSON.
|
||||
assert_eq!(json(&run(&["commands", "zzzzqq", "--json"])), Value::Array(Vec::new()));
|
||||
// --compact shapes JSON output only.
|
||||
let bad = run(&["commands", "--compact"]);
|
||||
assert_eq!(bad.status.code(), Some(2));
|
||||
assert!(String::from_utf8_lossy(&bad.stderr).contains("--json"), "{}", String::from_utf8_lossy(&bad.stderr));
|
||||
}
|
||||
@@ -151,7 +151,7 @@ fn every_subcommand_has_help_listing_its_arguments() {
|
||||
),
|
||||
("run", "run [OPTIONS]", &["--cmd <ID[=JSON]>"]),
|
||||
("script", "script [OPTIONS] <FILE>", &["<FILE>", "--keep-going"]),
|
||||
("commands", "commands [FILTER]", &["[FILTER]"]),
|
||||
("commands", "commands [OPTIONS] [FILTER]", &["[FILTER]", "--json", "--compact"]),
|
||||
("describe", "describe [OPTIONS] <ID>", &["<ID>", "--compact"]),
|
||||
(
|
||||
"app",
|
||||
|
||||
+10
-7
@@ -14,7 +14,7 @@ flags, values and defaults. This page does not repeat them.
|
||||
| `convert` | Converts an audio file: resample, change the bit depth, normalize |
|
||||
| `run` | Runs engine commands on a new, demo or saved session; can bounce, save and inspect |
|
||||
| `script` | Runs a file of engine commands, one `ID JSON` per line |
|
||||
| `commands` | Lists the engine commands |
|
||||
| `commands` | Lists the engine commands; with `--json`, each one's details as `describe` prints them |
|
||||
| `describe` | Prints one engine command's details as JSON |
|
||||
| `app` | Calls a running app's control channel (see [the control protocol](control-protocol.md)) |
|
||||
| `mcp` | Serves MCP on stdin and stdout (see [the MCP guide](mcp.md)) |
|
||||
@@ -29,16 +29,19 @@ soundcraft-cli convert in.wav out.flac --bit-depth 16 --rate 44100
|
||||
soundcraft-cli run --demo --cmd 'mix.volume={"track":"Kick","db":-6}' --bounce mix.wav
|
||||
soundcraft-cli script steps.txt --demo --keep-going --save out.scraft
|
||||
soundcraft-cli commands bounce
|
||||
soundcraft-cli commands --json --compact
|
||||
soundcraft-cli describe file.bounce_mix
|
||||
```
|
||||
|
||||
## Output and exit codes
|
||||
|
||||
Results go to standard output, as JSON where a program is meant to read them. `info` (on an audio
|
||||
file), `describe`, `app` and the `--inspect` output of `run` and `script` pretty-print it; `--compact`
|
||||
prints the same JSON on one line. The result lines of `run` and `script` (`ID: JSON`) and the
|
||||
`--bounce` and `--save` results are always one line each. `commands`, `plugins`, `parity` and the
|
||||
`.scraft` summary of `info` are text. Errors go to standard error as `error: <message>`, and so do the warnings and errors SoundCraft and the plugin hosts log (`warning: <message>`, `error: <message>`), such as a plugin that fails to load. `mcp` writes only the protocol
|
||||
file), `describe`, `commands --json`, `app` and the `--inspect` output of `run` and `script`
|
||||
pretty-print it; `--compact` prints the same JSON on one line. The result lines of `run` and
|
||||
`script` (`ID: JSON`) and the `--bounce` and `--save` results are always one line each. `commands`
|
||||
(without `--json`), `plugins`, `parity` and the `.scraft` summary of `info` are text. The
|
||||
`enabled` and `disabled_reason` fields of `describe` and `commands --json` are worked out against
|
||||
an empty session, not a session you have open. Errors go to standard error as `error: <message>`, and so do the warnings and errors SoundCraft and the plugin hosts log (`warning: <message>`, `error: <message>`), such as a plugin that fails to load. `mcp` writes only the protocol
|
||||
to standard output.
|
||||
|
||||
| Exit code | Meaning |
|
||||
@@ -138,8 +141,8 @@ output replacing an input without `--in-place`, and the ast-grep rules in `lint/
|
||||
|
||||
Rules not followed yet:
|
||||
|
||||
- **JSON flag (rule 1.3).** `commands`, `plugins`, `parity` and the `info` summary of a `.scraft`
|
||||
session print text and have no `--json` flag. Whether such listings need one is an open question
|
||||
- **JSON flag (rule 1.3).** `plugins`, `parity` and the `info` summary of a `.scraft` session
|
||||
print text and have no `--json` flag (`commands` has one). Whether such listings need one is an open question
|
||||
in the RFC itself.
|
||||
- **`string-error` rule (section 3).** Its ast-grep rule is on for the CLI code and the shared flags,
|
||||
not yet for the rest of the workspace.
|
||||
Reference in new issue
Block a user