Skip to content

Commit 28de7b2

Browse files
committed
v2: Restructure CLI into validate/run-tests/strictness-help subcommands, keeping flat flags as deprecated aliases
1 parent 91e6f42 commit 28de7b2

12 files changed

Lines changed: 351 additions & 50 deletions

.github/workflows/build.yaml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ jobs:
104104
run: |
105105
pwd
106106
ls -ahl
107-
${{ matrix.test-binary }} --run-tests
107+
${{ matrix.test-binary }} run-tests
108108
109109
- name: Cache JUCE example plugin binaries
110110
id: cache-plugins
@@ -127,9 +127,9 @@ jobs:
127127
- name: Validate JUCE Plugin examples (VST3)
128128
shell: bash
129129
run: |
130-
# Paths must be single quoted for bash not to escape the Windows backslash character \ used in absolute paths
131-
${{ env.APP_DIR }}/${{ matrix.test-binary }} --strictness-level 10 --validate '${{ env.PLUGIN_CACHE_PATH }}/DSPModulePluginDemo_artefacts/Release/VST3/DSPModulePluginDemo.vst3'
132-
${{ env.APP_DIR }}/${{ matrix.test-binary }} --strictness-level 10 --validate '${{ env.PLUGIN_CACHE_PATH }}/MultiOutSynthPlugin_artefacts/Release/VST3/MultiOutSynthPlugin.vst3'
130+
# Paths must be single quoted for bash not to escape the Windows backslash character \ used in absolute paths
131+
${{ env.APP_DIR }}/${{ matrix.test-binary }} validate --strictness-level 10 '${{ env.PLUGIN_CACHE_PATH }}/DSPModulePluginDemo_artefacts/Release/VST3/DSPModulePluginDemo.vst3'
132+
${{ env.APP_DIR }}/${{ matrix.test-binary }} validate --strictness-level 10 '${{ env.PLUGIN_CACHE_PATH }}/MultiOutSynthPlugin_artefacts/Release/VST3/MultiOutSynthPlugin.vst3'
133133
134134
- name: Validate JUCE Plugin examples (AU)
135135
shell: bash
@@ -141,6 +141,8 @@ jobs:
141141
mkdir -p ~/Library/Audio/Plug-Ins/Components/
142142
cp -R ${{ env.PLUGIN_CACHE_PATH }}/DSPModulePluginDemo_artefacts/Release/AU/DSPModulePluginDemo.component ~/Library/Audio/Plug-Ins/Components/
143143
killall -9 AudioComponentRegistrar # kick the AU registrar
144+
# Intentionally uses the deprecated "--validate" flat flag to keep the
145+
# backwards-compatible alias covered (this step is continue-on-error).
144146
${{ env.APP_DIR }}/${{ matrix.test-binary }} --strictness-level 10 --validate ~/Library/Audio/Plug-Ins/Components/DSPModulePluginDemo.component
145147
146148
- name: Codesign (macOS)

CHANGELIST.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# pluginval Change List
22

33
### 2.0.0
4+
- Restructured the command line into subcommands: `pluginval validate [options] <plugin>` (the default), `pluginval run-tests` and `pluginval strictness-help [level]`. The plugin path is now a positional argument of `validate`
5+
- **Deprecated:** the flat flags `--validate <plugin>`, `--run-tests` and `--strictness-help [level]` still work as aliases (with a one-line notice) but will be removed in a future version. `pluginval <plugin>` remains a silent shorthand for `validate`
46
- Replaced the hand-rolled command-line parser with CLI11 and a single JSON-based settings pipeline
57
- Added `--config <file.json>` to load settings from JSON (repeatable; later files win per key)
68
- Settings precedence is now (lowest to highest): defaults, environment variables, `--config`, command-line options

CLAUDE.md

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,38 @@ VST2_SDK_DIR=/path/to/vst2sdk cmake -B Builds/Debug .
193193

194194
### CLI Settings Pipeline
195195

196+
#### Subcommand dispatch layer
197+
198+
The command line is structured into subcommands, peeled off by a thin verb
199+
dispatcher in front of the settings pipeline (it does **not** use CLI11-native
200+
subcommands, so the env/config/CLI layering below is untouched):
201+
202+
- `pluginval validate [options] <plugin>` — the default; `<plugin>` is a
203+
positional argument. `pluginval <plugin>` and `pluginval [options] <plugin>`
204+
(no verb) also resolve to validate.
205+
- `pluginval run-tests` — runs the internal unit tests.
206+
- `pluginval strictness-help [level]` — lists tests at a strictness level.
207+
208+
`settings_parser::dispatch()` (in `SettingsParser.cpp`) takes the `tokenise()`d
209+
command line and returns a `DispatchResult { command, validateTokens,
210+
deprecatedAlias, strictnessLevel }`. For `validate` it strips the verb and hands
211+
`validateTokens` to `parseTokens` unchanged. `CommandLine.cpp`'s
212+
`performCommandLine()` switches on `command` and emits a one-line stderr notice
213+
when `deprecatedAlias` is set.
214+
215+
The old flat flags (`--validate <plugin>`, `--run-tests`, `--strictness-help`)
216+
are kept as **deprecated aliases** that route to the same commands with
217+
`deprecatedAlias = true` (the bare-path shorthand and the internal child handoff
218+
stay silent). `preprocess()` is now `tokenise()` + `insertImplicitValidate()`.
219+
The child process is launched with the explicit verb:
220+
`validate --config-base64 <b64> <path>`.
221+
222+
Note: `--config` is parsed manually (it is stripped from the tokens fed to the
223+
CLI11 pass) so its greedy CLI11 vector parsing can't swallow the positional
224+
plugin path; it stays registered only so it appears in `--help`.
225+
226+
#### Settings layering
227+
196228
Command-line parsing centres on one plain settings struct (`PluginvalSettings`)
197229
that CLI11 binds to directly. A single instance is filled by successive layers,
198230
**lowest to highest precedence: defaults → environment → `--config` → CLI**
@@ -360,11 +392,17 @@ ut.logVerboseMessage("Detail message"); // Only with --verbose flag
360392

361393
Basic usage:
362394
```bash
363-
./pluginval --strictness-level 5 /path/to/plugin.vst3
395+
./pluginval validate --strictness-level 5 /path/to/plugin.vst3
364396
```
365397

366-
Key options:
367-
- `--validate [path]` - Validate plugin at path
398+
Commands:
399+
- `validate [options] <plugin>` - Validate the plugin at the given path/AU id (the default; `./pluginval <plugin>` also works)
400+
- `run-tests` - Run the internal unit tests
401+
- `strictness-help [level]` - List the tests that run at a strictness level
402+
403+
The flat flags `--validate <plugin>`, `--run-tests` and `--strictness-help [level]` are deprecated aliases.
404+
405+
Key options (for `validate`):
368406
- `--config [file.json]` - Load a full settings set from JSON (overridden by env vars and CLI options)
369407
- `--strictness-level [1-10]` - Test thoroughness (default: 5)
370408
- `--skip-gui-tests` - Skip GUI tests (for headless CI)
@@ -434,7 +472,7 @@ Debug unit tests run automatically in debug builds:
434472

435473
Run internal tests via CLI:
436474
```bash
437-
./pluginval --run-tests
475+
./pluginval run-tests
438476
```
439477

440478
## Release Process

CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,9 @@ else()
250250
# TODO: This doesn't populate the executable in clion
251251
add_custom_target(${CMAKE_PROJECT_NAME}_pluginval_cli
252252
COMMAND $<TARGET_FILE:pluginval>
253-
--validate ${artefact}
253+
validate
254254
--strictness-level 10
255+
${artefact}
255256
DEPENDS pluginval ${PLUGINVAL_TARGET}
256257
COMMENT "Run pluginval CLI with strict validation")
257258
endif()

Source/CommandLine.cpp

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -203,38 +203,47 @@ juce::StringArray createCommandLine (juce::String fileOrID, PluginTests::Options
203203
}
204204

205205
//==============================================================================
206+
static void warnDeprecated (const juce::String& oldForm, const juce::String& newForm)
207+
{
208+
std::cerr << "!!! WARNING: " << oldForm << " is deprecated; use '" << newForm
209+
<< "' instead. It will be removed in a future version." << std::endl;
210+
}
211+
206212
void performCommandLine (CommandLineValidator& validator, const juce::String& commandLine)
207213
{
208214
hideDockIcon();
209215

210216
auto& app = *juce::JUCEApplication::getInstance();
211-
const auto tokens = settings_parser::preprocess (commandLine);
217+
const auto routed = settings_parser::dispatch (settings_parser::tokenise (commandLine));
212218

213-
if (tokens.contains ("--run-tests"))
219+
if (routed.command == settings_parser::Command::runTests)
214220
{
221+
if (routed.deprecatedAlias)
222+
warnDeprecated ("--run-tests", "pluginval run-tests");
223+
215224
runUnitTests();
216225
app.quit();
217226
return;
218227
}
219228

220-
if (tokens.contains ("--strictness-help"))
229+
if (routed.command == settings_parser::Command::strictnessHelp)
221230
{
222-
int level = 5;
223-
224-
if (const auto idx = tokens.indexOf ("--strictness-help"); idx >= 0 && idx + 1 < tokens.size())
225-
if (const auto next = tokens[idx + 1]; ! next.startsWith ("-"))
226-
level = next.getIntValue();
231+
if (routed.deprecatedAlias)
232+
warnDeprecated ("--strictness-help", "pluginval strictness-help");
227233

228-
printStrictnessHelp (level);
234+
printStrictnessHelp (routed.strictnessLevel);
229235
app.quit();
230236
return;
231237
}
232238

233-
// Otherwise this is a validation run (explicit or implicit --validate).
234-
// CLI11 handles --help/--version and parse errors.
239+
// Otherwise this is a validation run (positional plugin, or explicit/implicit
240+
// --validate). CLI11 handles --help/--version and parse errors.
241+
if (routed.deprecatedAlias)
242+
warnDeprecated ("--validate", "pluginval validate <plugin>");
243+
235244
try
236245
{
237-
const auto result = settings_parser::parseTokens (tokens);
246+
const auto result = settings_parser::parseTokens (routed.validateTokens);
238247

239248
if (result.handled)
240249
{

Source/CommandLineTests.cpp

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,112 @@ struct CommandLineTests : public juce::UnitTest
331331
expectEquals (r.exitCode, 1);
332332
}
333333
}
334+
335+
beginTest ("Subcommand: validate with a positional plugin path");
336+
{
337+
const auto currentDir = juce::File::getCurrentWorkingDirectory();
338+
339+
// Plugin path as a positional after the verb.
340+
expectEquals (juce::String (parse ("validate MyPlugin.vst3").validatePath),
341+
currentDir.getChildFile ("MyPlugin.vst3").getFullPathName());
342+
343+
// Options before the positional plugin path.
344+
const auto s = parse ("validate --strictness-level 8 MyPlugin.vst3");
345+
expectEquals (s.strictnessLevel, 8);
346+
expectEquals (juce::String (s.validatePath), currentDir.getChildFile ("MyPlugin.vst3").getFullPathName());
347+
348+
// A bare AU component id positional is left untouched.
349+
expectEquals (juce::String (parse ("validate MyPluginID").validatePath), juce::String ("MyPluginID"));
350+
}
351+
352+
beginTest ("Subcommand: validate with --config before the positional plugin");
353+
{
354+
juce::TemporaryFile configFile (".json");
355+
configFile.getFile().replaceWithText (R"({ "strictnessLevel": 2 })");
356+
357+
const auto currentDir = juce::File::getCurrentWorkingDirectory();
358+
const auto cmd = "validate --config " + configFile.getFile().getFullPathName().quoted() + " MyPlugin.vst3";
359+
360+
const auto s = parse (cmd);
361+
expectEquals (s.strictnessLevel, 2); // --config didn't swallow the plugin path
362+
expectEquals (juce::String (s.validatePath), currentDir.getChildFile ("MyPlugin.vst3").getFullPathName());
363+
}
364+
365+
beginTest ("Subcommand dispatch routing (new verbs do not warn)");
366+
{
367+
using settings_parser::Command;
368+
369+
{
370+
const auto d = settings_parser::dispatch (settings_parser::tokenise ("run-tests"));
371+
expect (d.command == Command::runTests);
372+
expect (! d.deprecatedAlias);
373+
}
374+
{
375+
const auto d = settings_parser::dispatch (settings_parser::tokenise ("strictness-help 7"));
376+
expect (d.command == Command::strictnessHelp);
377+
expectEquals (d.strictnessLevel, 7);
378+
expect (! d.deprecatedAlias);
379+
}
380+
{
381+
const auto d = settings_parser::dispatch (settings_parser::tokenise ("strictness-help"));
382+
expect (d.command == Command::strictnessHelp);
383+
expectEquals (d.strictnessLevel, 5); // default level
384+
}
385+
{
386+
const auto d = settings_parser::dispatch (settings_parser::tokenise ("validate MyPlugin.vst3"));
387+
expect (d.command == Command::validate);
388+
expect (! d.deprecatedAlias);
389+
}
390+
}
391+
392+
beginTest ("Deprecated flat flags still route, flagged as deprecated");
393+
{
394+
using settings_parser::Command;
395+
396+
{
397+
const auto d = settings_parser::dispatch (settings_parser::tokenise ("--run-tests"));
398+
expect (d.command == Command::runTests);
399+
expect (d.deprecatedAlias);
400+
}
401+
{
402+
const auto d = settings_parser::dispatch (settings_parser::tokenise ("--strictness-help 9"));
403+
expect (d.command == Command::strictnessHelp);
404+
expectEquals (d.strictnessLevel, 9);
405+
expect (d.deprecatedAlias);
406+
}
407+
{
408+
const auto d = settings_parser::dispatch (settings_parser::tokenise ("--validate x"));
409+
expect (d.command == Command::validate);
410+
expect (d.deprecatedAlias);
411+
}
412+
}
413+
414+
beginTest ("Bare-path shorthand and child handoff do not warn");
415+
{
416+
juce::TemporaryFile temp ("path_to_file.vst3");
417+
expect (temp.getFile().create());
418+
419+
// Bare plugin path -> validate, no deprecation warning.
420+
const auto bare = settings_parser::dispatch (settings_parser::tokenise (temp.getFile().getFullPathName()));
421+
expect (bare.command == settings_parser::Command::validate);
422+
expect (! bare.deprecatedAlias);
423+
424+
// The internal child handoff uses the explicit verb -> no warning.
425+
PluginTests::Options opts;
426+
juce::StringArray childArgs (createCommandLine ("/some/MyPlugin.vst3", opts));
427+
childArgs.remove (0); // drop the executable path
428+
const auto child = settings_parser::dispatch (childArgs);
429+
expect (child.command == settings_parser::Command::validate);
430+
expect (! child.deprecatedAlias);
431+
}
432+
433+
beginTest ("Should perform command line recognises subcommands");
434+
{
435+
expect (shouldPerformCommandLine ("run-tests"));
436+
expect (shouldPerformCommandLine ("strictness-help"));
437+
expect (shouldPerformCommandLine ("validate MyPlugin.vst3"));
438+
expect (shouldPerformCommandLine ("validate MyPluginID"));
439+
}
334440
}
335441
};
336442

0 commit comments

Comments
 (0)