Skip to content

Commit ee1b4f2

Browse files
committed
Revisit posix short handling in Lexer
- Posix short style is expected to have only single dash and letters so use this style to come up better separation between options and arguments. - Backport #1077 - Fixes #1078
1 parent b67b301 commit ee1b4f2

File tree

3 files changed

+83
-5
lines changed

3 files changed

+83
-5
lines changed

spring-shell-core/src/main/java/org/springframework/shell/command/parser/Lexer.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023 the original author or authors.
2+
* Copyright 2023-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -201,12 +201,17 @@ public LexerResult tokenize(List<String> arguments) {
201201
}
202202
}
203203
else if (isLastTokenOfType(tokenList, TokenType.OPTION)) {
204-
if (argument.startsWith("-")) {
204+
// posix short style can only have one or more letters
205+
int decuceArgumentStyle = decuceArgumentStyle(argument);
206+
if (decuceArgumentStyle > 0) {
205207
tokenList.add(Token.of(argument, TokenType.OPTION, i2));
206208
}
207-
else {
209+
else if (decuceArgumentStyle < 0) {
208210
tokenList.add(Token.of(argument, TokenType.ARGUMENT, i2));
209211
}
212+
else {
213+
tokenList.add(Token.of(argument, TokenType.OPTION, i2));
214+
}
210215
}
211216
else if (isLastTokenOfType(tokenList, TokenType.COMMAND)) {
212217
if (argument.startsWith("-")) {
@@ -243,5 +248,28 @@ private static boolean isLastTokenOfType(List<Token> tokenList, TokenType type)
243248
}
244249
return false;
245250
}
251+
252+
private static int decuceArgumentStyle(String str) {
253+
// positive - looks like posix short
254+
// 0 - looks like long option
255+
// negative - looks like argument, not option
256+
if (str.length() < 2) {
257+
return -1;
258+
}
259+
if (str.charAt(0) != '-') {
260+
return -1;
261+
}
262+
if (str.length() > 1 && str.charAt(0) == '-' && str.charAt(1) == '-') {
263+
return 0;
264+
}
265+
int ret = 1;
266+
for (int i = 1; i < str.length(); i++) {
267+
if (!Character.isLetter(str.charAt(i))) {
268+
ret = -1;
269+
break;
270+
}
271+
}
272+
return ret;
273+
}
246274
}
247275
}

spring-shell-core/src/test/java/org/springframework/shell/command/parser/LexerTests.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023 the original author or authors.
2+
* Copyright 2023-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,8 @@
1919

2020
import org.junit.jupiter.api.Nested;
2121
import org.junit.jupiter.api.Test;
22+
import org.junit.jupiter.params.ParameterizedTest;
23+
import org.junit.jupiter.params.provider.ValueSource;
2224

2325
import org.springframework.shell.command.parser.Lexer.LexerResult;
2426
import org.springframework.shell.command.parser.ParserConfig.Feature;
@@ -431,6 +433,42 @@ void commandArgsWithoutOption() {
431433
TokenType.ARGUMENT);
432434
}
433435

436+
@Nested
437+
class ShortOptions {
438+
439+
@ParameterizedTest
440+
@ValueSource(strings = {
441+
"-1",
442+
"-1a",
443+
"-a1",
444+
"-ab1",
445+
"-ab1c"
446+
})
447+
void shouldNotBeOptionWhenDoesntLookLikeShortPosix(String arg) {
448+
register(ROOT6_OPTION_INT);
449+
List<Token> tokens = tokenize("root6", "--arg1", arg);
450+
451+
assertThat(tokens).extracting(Token::getType).containsExactly(TokenType.COMMAND, TokenType.OPTION,
452+
TokenType.ARGUMENT);
453+
}
454+
455+
@ParameterizedTest
456+
@ValueSource(strings = {
457+
"-a",
458+
"-ab",
459+
"-abc",
460+
"--abc"
461+
})
462+
void shouldBeOptionWhenLooksLikeShortPosix(String arg) {
463+
register(ROOT6_OPTION_INT);
464+
List<Token> tokens = tokenize("root6", "--arg1", arg);
465+
466+
assertThat(tokens).extracting(Token::getType).containsExactly(TokenType.COMMAND, TokenType.OPTION,
467+
TokenType.OPTION);
468+
}
469+
}
470+
471+
434472
@Nested
435473
class Directives {
436474

spring-shell-core/src/test/java/org/springframework/shell/command/parser/ParserTests.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023 the original author or authors.
2+
* Copyright 2023-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -116,6 +116,18 @@ void optionValueFailsFromStringToInteger() {
116116
assertThat(ms.getMessage()).contains("Failed to convert");
117117
});
118118
}
119+
120+
@Test
121+
void optionValueShouldBeNegativeInteger() {
122+
register(ROOT6_OPTION_INT);
123+
ParseResult result = parse("root6", "--arg1", "-1");
124+
assertThat(result).isNotNull();
125+
assertThat(result.commandRegistration()).isNotNull();
126+
assertThat(result.optionResults()).isNotEmpty();
127+
assertThat(result.optionResults().get(0).value()).isEqualTo(-1);
128+
assertThat(result.messageResults()).isEmpty();
129+
}
130+
119131
}
120132

121133
@Nested

0 commit comments

Comments
 (0)