From d0b0c73ba37e41280a097129093df0a39f12278c Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Tue, 28 May 2024 10:07:38 +0100 Subject: [PATCH] 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 #1079 --- .../shell/command/parser/Lexer.java | 34 ++++++++++++++-- .../shell/command/parser/LexerTests.java | 40 ++++++++++++++++++- .../shell/command/parser/ParserTests.java | 14 ++++++- 3 files changed, 83 insertions(+), 5 deletions(-) diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/parser/Lexer.java b/spring-shell-core/src/main/java/org/springframework/shell/command/parser/Lexer.java index 8ffd35ca1..bb01ad9d2 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/command/parser/Lexer.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/command/parser/Lexer.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -201,12 +201,17 @@ public LexerResult tokenize(List arguments) { } } else if (isLastTokenOfType(tokenList, TokenType.OPTION)) { - if (argument.startsWith("-")) { + // posix short style can only have one or more letters + int decuceArgumentStyle = decuceArgumentStyle(argument); + if (decuceArgumentStyle > 0) { tokenList.add(Token.of(argument, TokenType.OPTION, i2)); } - else { + else if (decuceArgumentStyle < 0) { tokenList.add(Token.of(argument, TokenType.ARGUMENT, i2)); } + else { + tokenList.add(Token.of(argument, TokenType.OPTION, i2)); + } } else if (isLastTokenOfType(tokenList, TokenType.COMMAND)) { if (argument.startsWith("-")) { @@ -243,5 +248,28 @@ private static boolean isLastTokenOfType(List tokenList, TokenType type) } return false; } + + private static int decuceArgumentStyle(String str) { + // positive - looks like posix short + // 0 - looks like long option + // negative - looks like argument, not option + if (str.length() < 2) { + return -1; + } + if (str.charAt(0) != '-') { + return -1; + } + if (str.length() > 1 && str.charAt(0) == '-' && str.charAt(1) == '-') { + return 0; + } + int ret = 1; + for (int i = 1; i < str.length(); i++) { + if (!Character.isLetter(str.charAt(i))) { + ret = -1; + break; + } + } + return ret; + } } } diff --git a/spring-shell-core/src/test/java/org/springframework/shell/command/parser/LexerTests.java b/spring-shell-core/src/test/java/org/springframework/shell/command/parser/LexerTests.java index 94fb79e48..8cc82302a 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/command/parser/LexerTests.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/command/parser/LexerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,8 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.shell.command.parser.Lexer.LexerResult; import org.springframework.shell.command.parser.ParserConfig.Feature; @@ -431,6 +433,42 @@ void commandArgsWithoutOption() { TokenType.ARGUMENT); } + @Nested + class ShortOptions { + + @ParameterizedTest + @ValueSource(strings = { + "-1", + "-1a", + "-a1", + "-ab1", + "-ab1c" + }) + void shouldNotBeOptionWhenDoesntLookLikeShortPosix(String arg) { + register(ROOT6_OPTION_INT); + List tokens = tokenize("root6", "--arg1", arg); + + assertThat(tokens).extracting(Token::getType).containsExactly(TokenType.COMMAND, TokenType.OPTION, + TokenType.ARGUMENT); + } + + @ParameterizedTest + @ValueSource(strings = { + "-a", + "-ab", + "-abc", + "--abc" + }) + void shouldBeOptionWhenLooksLikeShortPosix(String arg) { + register(ROOT6_OPTION_INT); + List tokens = tokenize("root6", "--arg1", arg); + + assertThat(tokens).extracting(Token::getType).containsExactly(TokenType.COMMAND, TokenType.OPTION, + TokenType.OPTION); + } + } + + @Nested class Directives { diff --git a/spring-shell-core/src/test/java/org/springframework/shell/command/parser/ParserTests.java b/spring-shell-core/src/test/java/org/springframework/shell/command/parser/ParserTests.java index b36c35f63..511ba33cc 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/command/parser/ParserTests.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/command/parser/ParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -116,6 +116,18 @@ void optionValueFailsFromStringToInteger() { assertThat(ms.getMessage()).contains("Failed to convert"); }); } + + @Test + void optionValueShouldBeNegativeInteger() { + register(ROOT6_OPTION_INT); + ParseResult result = parse("root6", "--arg1", "-1"); + assertThat(result).isNotNull(); + assertThat(result.commandRegistration()).isNotNull(); + assertThat(result.optionResults()).isNotEmpty(); + assertThat(result.optionResults().get(0).value()).isEqualTo(-1); + assertThat(result.messageResults()).isEmpty(); + } + } @Nested