Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions src/main/java/io/jawk/Cli.java
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,13 @@ public void parse(String[] args) {
if (arg.length() == 0) {
throw new IllegalArgumentException("zero-length argument at position " + (argIdx + 1));
}
if (isAttachedOptionArgument(arg)) {
// POSIX attached option-argument, e.g. -fprog.awk: split it into
// the option and its value so the option branches below see the
// same shape as the separate form -f prog.awk
args = splitAttachedOptionArgument(args, argIdx);
arg = args[argIdx];
}
if (arg.charAt(0) != '-') {
// end of options: remaining args are part of the script execution
break;
Expand Down Expand Up @@ -364,6 +371,40 @@ public void parse(String[] args) {
}
}

/** Single-letter options that take a value and accept it attached. */
private static final String VALUE_OPTION_LETTERS = "fvFLKl";

/**
* Tells whether an argument is a value-taking short option with its value
* attached, as in {@code -fprog.awk} for {@code -f prog.awk}.
*
* @param arg the raw command-line argument
* @return {@code true} when the argument must be split before dispatch
*/
private static boolean isAttachedOptionArgument(String arg) {
return arg.length() > 2
&& arg.charAt(0) == '-'
&& VALUE_OPTION_LETTERS.indexOf(arg.charAt(1)) >= 0;
}

/**
* Splits an attached option-argument in two, so that {@code -fprog.awk}
* becomes {@code -f prog.awk} in the argument array.
*
* @param args full array of arguments
* @param argIdx index of the attached option-argument to split
* @return a copy of the array where the argument at {@code argIdx} is
* replaced by the option and its value as separate elements
*/
private static String[] splitAttachedOptionArgument(String[] args, int argIdx) {
String[] split = new String[args.length + 1];
System.arraycopy(args, 0, split, 0, argIdx);
split[argIdx] = args[argIdx].substring(0, 2);
split[argIdx + 1] = args[argIdx].substring(2);
System.arraycopy(args, argIdx + 1, split, argIdx + 2, args.length - argIdx - 1);
return split;
}

/**
* Ensures that the current command-line option is followed by a value.
*
Expand Down
5 changes: 5 additions & 0 deletions src/site/markdown/behavior-changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ released version automatically via .github/scripts/stamp-behavior-changes.sh.

## Unreleased

- The CLI now accepts POSIX attached option-arguments for the value-taking short options:
`-fprog.awk`, `-vx=1`, `-F:` (and the Jawk-specific `-L`, `-K`, `-l`) are equivalent to
`-f prog.awk`, `-v x=1`, `-F :`, as in gawk, mawk, BWK awk, and goawk. Previously the glued
form was rejected with `Unknown parameter`
([#574](https://github.com/jawkio/jawk/issues/574)).
- An input-derived value whose text is a number surrounded by blanks — a record like `" 12 "`,
a `getline var` result read from padded input, a `split()` piece under a non-default
separator — is now recognized as a POSIX numeric string, so `$0 == 12` is true for the record
Expand Down
1 change: 1 addition & 0 deletions src/site/markdown/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ java -jar jawk-${project.version}-standalone.jar --list-ext
> - An operand containing `=` is treated as an AWK-style file-list assignment that applies before the next input file is consumed.
> - Use `-v name=value` instead when the variable must exist before `BEGIN`.
> - As in gawk, once the program text has been supplied (with `-f` or `-L`), an unknown option ends option processing and is passed on to the AWK program through `ARGV`, which is useful for `#!` interpreter scripts.
> - The value of a value-taking short option may be attached to the option letter (POSIX/getopt style): `-fprog.awk`, `-vx=1`, and `-F:` are equivalent to `-f prog.awk`, `-v x=1`, and `-F :`. This applies to `-f`, `-v`, `-F`, `-L`, `-K`, and `-l`.
>
> - Variables and formatting
>
Expand Down
66 changes: 66 additions & 0 deletions src/test/java/io/jawk/CliOptionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,72 @@ public void profileOptionWithEmptyFilenameIsRejected() throws Exception {
assertTrue(result.thrownException().getMessage().contains("Need output filename for --profile"));
}

@Test
public void attachedProgramFileOptionLoadsScript() throws Exception {
AwkTestSupport
.cliTest("CLI -fprog.awk loads the script like -f prog.awk")
.file("prog.awk", "BEGIN { print \"attached\" }")
.argument("-f{{prog.awk}}")
.expectLines("attached")
.runAndAssert();
}

@Test
public void attachedProgramFileOptionCombinesWithSeparateForm() throws Exception {
AwkTestSupport
.cliTest("CLI mixes -fone.awk with -f two.awk")
.file("one.awk", "BEGIN { print \"one\" }")
.file("two.awk", "BEGIN { print \"two\" }")
.argument("-f{{one.awk}}", "-f", "{{two.awk}}")
.expectLines("one", "two")
.runAndAssert();
}

@Test
public void attachedVariableAssignmentIsApplied() throws Exception {
AwkTestSupport
.cliTest("CLI -vx=42 assigns the variable like -v x=42")
.argument("-vx=42")
.script("BEGIN { print x }")
.expectLines("42")
.runAndAssert();
}

@Test
public void attachedFieldSeparatorIsApplied() throws Exception {
AwkTestSupport
.cliTest("CLI -F: sets the field separator like -F :")
.argument("-F:")
.script("{ print $2 }")
.stdin("a:b:c\n")
.expectLines("b")
.runAndAssert();
}

@Test
public void attachedOptionArgumentAfterDoubleDashStaysInArgv() throws Exception {
AwkTestSupport
.cliTest("CLI attached option-argument after -- is an operand")
.file("argv.awk", "BEGIN { for (i = 1; i < ARGC; i++) print i \"=\" ARGV[i] }")
.argument("-f", "{{argv.awk}}", "--")
.operand("-fnot-an-option.awk")
.expectLines("1=-fnot-an-option.awk")
.runAndAssert();
}

@Test
public void attachedUnknownOptionWithoutScriptIsStillRejected() throws Exception {
AwkTestSupport.TestResult result = AwkTestSupport
.cliTest("CLI unknown glued option without script is rejected")
.argument("-q2")
.script("{ print }")
.expectThrow(IllegalArgumentException.class)
.run();

result.assertExpected();
assertTrue(result.thrownException().getMessage().contains("Unknown parameter: -q2"));
}

@Test
public void doubleDashEndsOptionProcessing() throws Exception {
// After "--", the dash-leading argument is no longer an option: it is
Expand Down
Loading