mirror of
https://github.com/apple/pkl.git
synced 2026-04-24 09:18:35 +02:00
Command flag behavior improvements (#1432)
* Forbid overlap of built-in and command-defined flag names * Allow interleaving built-in and command-defined flags on the command line * List abbreviated flag names first, matching the behavior of built-in flags
This commit is contained in:
@@ -100,7 +100,7 @@ public record CommandSpec(
|
||||
public String[] getNames() {
|
||||
return shortName == null
|
||||
? new String[] {"--" + name}
|
||||
: new String[] {"--" + name, "-" + shortName};
|
||||
: new String[] {"-" + shortName, "--" + name};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ public record CommandSpec(
|
||||
public String[] getNames() {
|
||||
return shortName == null
|
||||
? new String[] {"--" + name}
|
||||
: new String[] {"--" + name, "-" + shortName};
|
||||
: new String[] {"-" + shortName, "--" + name};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ public record CommandSpec(
|
||||
public String[] getNames() {
|
||||
return shortName == null
|
||||
? new String[] {"--" + name}
|
||||
: new String[] {"--" + name, "-" + shortName};
|
||||
: new String[] {"-" + shortName, "--" + name};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.pkl.core;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import org.pkl.core.runtime.VmEvalException;
|
||||
|
||||
@@ -237,7 +238,11 @@ public interface Evaluator extends AutoCloseable {
|
||||
* @throws PklException if an error occurs during evaluation
|
||||
* @throws IllegalStateException if this evaluator has already been closed
|
||||
*/
|
||||
void evaluateCommand(ModuleSource moduleSource, Consumer<CommandSpec> run);
|
||||
void evaluateCommand(
|
||||
ModuleSource moduleSource,
|
||||
Set<String> reservedFlagNames,
|
||||
Set<String> reservedFlagShortNames,
|
||||
Consumer<CommandSpec> run);
|
||||
|
||||
/**
|
||||
* Releases all resources held by this evaluator. If an {@code evaluate} method is currently
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -277,7 +278,11 @@ public final class EvaluatorImpl implements Evaluator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evaluateCommand(ModuleSource moduleSource, Consumer<CommandSpec> run) {
|
||||
public void evaluateCommand(
|
||||
ModuleSource moduleSource,
|
||||
Set<String> reservedFlagNames,
|
||||
Set<String> reservedFlagShortNames,
|
||||
Consumer<CommandSpec> run) {
|
||||
doEvaluate(
|
||||
moduleSource,
|
||||
(module) -> {
|
||||
@@ -287,6 +292,8 @@ public final class EvaluatorImpl implements Evaluator {
|
||||
securityManager,
|
||||
frameTransformer,
|
||||
color,
|
||||
reservedFlagNames,
|
||||
reservedFlagShortNames,
|
||||
(fileOutput) -> new FileOutputImpl(this, fileOutput));
|
||||
run.accept(commandRunner.parse(module));
|
||||
return null;
|
||||
|
||||
@@ -75,6 +75,8 @@ public final class CommandSpecParser {
|
||||
private final SecurityManager securityManager;
|
||||
private final StackFrameTransformer frameTransformer;
|
||||
private final boolean color;
|
||||
private final Set<String> reservedFlagNames;
|
||||
private final Set<String> reservedFlagShortNames;
|
||||
private final Function<VmTyped, FileOutput> makeFileOutput;
|
||||
|
||||
public CommandSpecParser(
|
||||
@@ -82,11 +84,15 @@ public final class CommandSpecParser {
|
||||
SecurityManager securityManager,
|
||||
StackFrameTransformer frameTransformer,
|
||||
boolean color,
|
||||
Set<String> reservedFlagNames,
|
||||
Set<String> reservedFlagShortNames,
|
||||
Function<VmTyped, FileOutput> makeFileOutput) {
|
||||
this.moduleResolver = moduleResolver;
|
||||
this.securityManager = securityManager;
|
||||
this.frameTransformer = frameTransformer;
|
||||
this.color = color;
|
||||
this.reservedFlagNames = reservedFlagNames;
|
||||
this.reservedFlagShortNames = reservedFlagShortNames;
|
||||
this.makeFileOutput = makeFileOutput;
|
||||
}
|
||||
|
||||
@@ -249,11 +255,22 @@ public final class CommandSpecParser {
|
||||
}
|
||||
|
||||
private void checkFlagNames(ClassProperty prop, String name, @Nullable String shortName) {
|
||||
if ("help".equals(name) || "h".equals(shortName)) {
|
||||
throw exceptionBuilder()
|
||||
.withSourceSection(prop.getHeaderSection())
|
||||
.evalError("commandFlagHelpCollision", prop.getName())
|
||||
.build();
|
||||
for (var reserved : reservedFlagNames) {
|
||||
if (reserved.equals(name)) {
|
||||
throw exceptionBuilder()
|
||||
.withSourceSection(prop.getHeaderSection())
|
||||
.evalError("commandFlagNameCollision", prop.getName(), "name", "")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
for (var reserved : reservedFlagShortNames) {
|
||||
if (reserved.equals(shortName)) {
|
||||
throw exceptionBuilder()
|
||||
.withSourceSection(prop.getHeaderSection())
|
||||
.evalError(
|
||||
"commandFlagNameCollision", prop.getName(), "short name", "`" + shortName + "` ")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1121,8 +1121,10 @@ More than one repeated option annotated with `@Argument` found: `{0}` and `{1}`.
|
||||
\n\
|
||||
Only one repeated argument is permitted per command.
|
||||
|
||||
commandFlagHelpCollision=\
|
||||
Flag option `{0}` may not have name "help" or short name "h".
|
||||
commandFlagNameCollision=\
|
||||
Flag option `{0}` {1} {2}collides with a reserved flag {1}.\n\
|
||||
\n\
|
||||
Option {1}s must not overlap with built-in options.
|
||||
|
||||
commandFlagInvalidType=\
|
||||
Option `{0}` with annotation `@{1}` has invalid type `{2}`.\n\
|
||||
|
||||
@@ -57,7 +57,7 @@ class CommandSpecParserTest {
|
||||
|
||||
private fun parse(moduleUri: URI): CommandSpec {
|
||||
var spec: CommandSpec? = null
|
||||
evaluator.evaluateCommand(uri(moduleUri)) { spec = it }
|
||||
evaluator.evaluateCommand(uri(moduleUri), setOf("help", "root-dir"), setOf("h")) { spec = it }
|
||||
return spec!!
|
||||
}
|
||||
|
||||
@@ -440,8 +440,7 @@ class CommandSpecParserTest {
|
||||
|
||||
val exc = assertThrows<PklException> { parse(moduleUri) }
|
||||
assertThat(exc.message).contains("help: Boolean")
|
||||
assertThat(exc.message)
|
||||
.contains("Flag option `help` may not have name \"help\" or short name \"h\".")
|
||||
assertThat(exc.message).contains("Flag option `help` name collides with a reserved flag name.")
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -462,7 +461,27 @@ class CommandSpecParserTest {
|
||||
val exc = assertThrows<PklException> { parse(moduleUri) }
|
||||
assertThat(exc.message).contains("showHelp: Boolean")
|
||||
assertThat(exc.message)
|
||||
.contains("Flag option `showHelp` may not have name \"help\" or short name \"h\".")
|
||||
.contains("Flag option `showHelp` short name `h` collides with a reserved flag short name.")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flag with collision on reserved option name`() {
|
||||
val moduleUri =
|
||||
writePklFile(
|
||||
"cmd.pkl",
|
||||
renderOptions +
|
||||
"""
|
||||
class Options {
|
||||
`root-dir`: String
|
||||
}
|
||||
"""
|
||||
.trimIndent(),
|
||||
)
|
||||
|
||||
val exc = assertThrows<PklException> { parse(moduleUri) }
|
||||
assertThat(exc.message).contains("`root-dir`: String")
|
||||
assertThat(exc.message)
|
||||
.contains("Flag option `root-dir` name collides with a reserved flag name.")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user