diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 897258d4b..cbe5856c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - name: Validate Gradle Wrapper uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 @@ -135,7 +135,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - name: Validate Gradle Wrapper uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 @@ -148,7 +148,7 @@ jobs: cache: gradle - name: Build - uses: gradle/gradle-build-action@v2.11.1 # v2.11.1 + uses: gradle/gradle-build-action@v3.1.0 # v2.11.1 with: gradle-version: 1.12 build-root-directory: picocli-tests-java567/ diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index f43afdfd1..68afa830a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -54,11 +54,11 @@ jobs: egress-policy: audit - name: Checkout repository - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v2 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@b374143c1149a9115d881581d29b8390bbcbb59c # v1 + uses: github/codeql-action/init@05963f47d870e2cb19a537396c1f668a348c7d8f # v1 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -84,4 +84,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@b374143c1149a9115d881581d29b8390bbcbb59c # v1 + uses: github/codeql-action/analyze@05963f47d870e2cb19a537396c1f668a348c7d8f # v1 diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml index b066678bf..847a01d2a 100644 --- a/.github/workflows/scorecards-analysis.yml +++ b/.github/workflows/scorecards-analysis.yml @@ -31,7 +31,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v2.4.0 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v2.4.0 with: persist-credentials: false @@ -51,7 +51,7 @@ jobs: # Upload the results as artifacts (optional). - name: "Upload artifact" - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v2.3.1 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v2.3.1 with: name: SARIF file path: results.sarif @@ -59,6 +59,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@b374143c1149a9115d881581d29b8390bbcbb59c # v1.0.26 + uses: github/codeql-action/upload-sarif@05963f47d870e2cb19a537396c1f668a348c7d8f # v1.0.26 with: sarif_file: results.sarif diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index b7113be7f..d7f7906c5 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -28,6 +28,7 @@ Artifacts in this release are signed by Remko Popma (6601 E5C0 8DCC BB96). * [#2102][#2107] Enhancement: `PropertiesDefaultProvider` should try to load properties from classpath (last). Thanks to [Lumír Návrat](https://github.com/rimuln) for the pull request. * [#2058] Bugfix: `defaultValue` should not be applied in addition to user-specified value for options with a custom `IParameterConsumer`. Thanks to [Staffan Arvidsson McShane](https://github.com/StaffanArvidsson) for raising this. * [#2148] Bugfix: Fix NPE in jline3 `Example.jar` as `ConfigurationPath` cannot be `null` anymore. Thanks to [llzen44](https://github.com/llzen44) for the pull request. +* [#2232] Bugfix: fix bug for `Optional` arguments with initial value. Thanks to [hq6](https://github.com/hq6) for raising this. * [#2171] Fix a few typos in CommandLine's JavaDoc. Thanks to [Michael Vorburger](https://github.com/vorburger) for the pull request. * [#2172] Fix broken build. Thanks to [Michael Vorburger](https://github.com/vorburger) for the pull request. * [#2174] Fix .gitattributes related CR/LF problems. Thanks to [Michael Vorburger](https://github.com/vorburger) for the pull request. diff --git a/build.gradle b/build.gradle index 3bbcba9ef..1bd9b85ec 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,7 @@ buildscript { dependencies { classpath "org.asciidoctor:asciidoctor-gradle-jvm:$asciidoctorGradlePluginVersion" - classpath 'org.asciidoctor:asciidoctorj-pdf:2.3.9' + classpath 'org.asciidoctor:asciidoctorj-pdf:2.3.15' classpath "org.beryx:badass-jar:2.0.0" classpath 'biz.aQute.bnd:biz.aQute.bnd.gradle:6.4.0' classpath "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.30.0" @@ -68,7 +68,7 @@ allprojects { apply plugin: "net.ltgt.errorprone" dependencies { - errorprone("com.google.errorprone:error_prone_core:2.23.0") + errorprone("com.google.errorprone:error_prone_core:2.26.1") } tasks.withType(JavaCompile).configureEach { diff --git a/dependencies.gradle b/dependencies.gradle index 8545d630f..d7c596ec1 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -58,6 +58,6 @@ ext { hibernate_validator_annproc: "org.hibernate.validator:hibernate-validator-annotation-processor:8.0.1.Final", hibernate_validator : "org.hibernate.validator:hibernate-validator:8.0.1.Final", - kotlin_script_runtime : "org.jetbrains.kotlin:kotlin-script-runtime:1.9.10", + kotlin_script_runtime : "org.jetbrains.kotlin:kotlin-script-runtime:1.9.23", ] } diff --git a/docs/index.adoc b/docs/index.adoc index e28761c65..064752516 100644 --- a/docs/index.adoc +++ b/docs/index.adoc @@ -1040,6 +1040,8 @@ assert(mixed.options == Arrays.asList("AAA", "BBB")) ---- === Double dash (`--`) +Picocli offers built-in support for the End-of-Options delimiter (`--`), as defined in Guideline 10 of the link:https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02[POSIX Utility Syntax Guidelines]. + When one of the command line arguments is just two dashes without any characters attached (`--`), picocli interprets all following arguments as positional parameters, even arguments that match an option name. diff --git a/picocli-examples/build.gradle b/picocli-examples/build.gradle index 7c6234964..0264c587a 100644 --- a/picocli-examples/build.gradle +++ b/picocli-examples/build.gradle @@ -80,6 +80,12 @@ normalization { } tasks.withType(Javadoc).all { enabled = false } +//tasks.named('compileKotlin', org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask.class) { +tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { + compilerOptions { + freeCompilerArgs.add('-Xskip-metadata-version-check') + } +} //tasks.withType(Javadoc) { // options.addBooleanOption('Xdoclint:none', true) //} diff --git a/picocli-examples/src/main/java/picocli/examples/stdin/PrintFirstLine.java b/picocli-examples/src/main/java/picocli/examples/stdin/PrintFirstLine.java new file mode 100644 index 000000000..39ecaeeec --- /dev/null +++ b/picocli-examples/src/main/java/picocli/examples/stdin/PrintFirstLine.java @@ -0,0 +1,61 @@ +package picocli.examples.stdin; + +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.util.List; +import java.util.concurrent.Callable; + +/** + * This example reads a file, or from standard input, and prints the first line. + * + * It follows the UNIX convention that tools should read from STDIN (standard input) + * when the end user specifies the `-` character instead of a file name. + * + * See POSIX Utility Syntax Guidelines, Guideline 13: + * https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02 + */ +@Command(name = "firstline") +public class PrintFirstLine implements Callable { + + @Parameters(arity = "1..*") + List files; + + @Option(names = "--charset", description = "Charset of the file (or STDIN) to read. Default: ${DEFAULT_VALUE}") + Charset charset = Charset.defaultCharset(); + + public Integer call() throws Exception { + for (File file : files) { + printFirstLine(file); + } + return 0; + } + + private void printFirstLine(File file) throws IOException { + try (BufferedReader reader = createReader(file)) { + String line = reader.readLine(); + System.out.println(line); + } + } + + private BufferedReader createReader(File file) throws IOException { + InputStream in = "-".equals(file.toString()) + ? System.in + : Files.newInputStream(file.toPath()); + return new BufferedReader(new InputStreamReader(in, charset)); + } + + public static void main(String... args) { + int exitCode = new CommandLine(new PrintFirstLine()).execute(args); + //System.exit(exitCode); // prevents this method from being testable... + } +} diff --git a/picocli-examples/src/test/java/picocli/examples/stdin/PrintFirstLineTest.java b/picocli-examples/src/test/java/picocli/examples/stdin/PrintFirstLineTest.java new file mode 100644 index 000000000..681478f12 --- /dev/null +++ b/picocli-examples/src/test/java/picocli/examples/stdin/PrintFirstLineTest.java @@ -0,0 +1,34 @@ +package picocli.examples.stdin; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.contrib.java.lang.system.SystemOutRule; +import org.junit.contrib.java.lang.system.TextFromStandardInputStream; + +import java.net.URL; + +import static org.junit.Assert.*; + +public class PrintFirstLineTest { + + @Rule + public final SystemOutRule outRule = new SystemOutRule().enableLog().muteForSuccessfulTests(); + + @Rule + public final TextFromStandardInputStream systemInMock = TextFromStandardInputStream.emptyStandardInputStream(); + + @Test + public void testMain() { + URL resource = PrintFirstLineTest.class.getResource("/PrintFirstLineTest.txt"); + String path = resource.getPath(); + PrintFirstLine.main(path); + assertEquals("file line 1", outRule.getLog().trim()); + } + + @Test + public void testStandardInput() { + systemInMock.provideLines("stdin line1", "stdin line2", "stdin line3"); + PrintFirstLine.main("-"); + assertEquals("stdin line1", outRule.getLog().trim()); + } +} diff --git a/picocli-examples/src/test/resources/PrintFirstLineTest.txt b/picocli-examples/src/test/resources/PrintFirstLineTest.txt new file mode 100644 index 000000000..dc9503958 --- /dev/null +++ b/picocli-examples/src/test/resources/PrintFirstLineTest.txt @@ -0,0 +1,3 @@ +file line 1 +file line 2 +file line 3 diff --git a/picocli-tests-java8/README.md b/picocli-tests-java8/README.md new file mode 100644 index 000000000..aea972e58 --- /dev/null +++ b/picocli-tests-java8/README.md @@ -0,0 +1,10 @@ +# Picocli Java 8 Tests + +This subproject contains tests that use Java 8, and the JUnit 5 and System Lambda test frameworks. + +This module does not publish any artifacts. + +NOTE: only put tests here that require Java 8 and cannot be run in earlier versions of Java. + +Tests that require Java 9 or later should be located in the `picocli-tests-java9plus` subproject. + diff --git a/picocli-tests-java8/build.gradle b/picocli-tests-java8/build.gradle new file mode 100644 index 000000000..123e5d83c --- /dev/null +++ b/picocli-tests-java8/build.gradle @@ -0,0 +1,34 @@ +plugins { + id 'java' +} + +group 'info.picocli' +description 'Picocli Tests Requiring Java 8' +version "$projectVersion" + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +test { + useJUnitPlatform() +} + + +dependencies { + api rootProject + testImplementation supportDependencies.junit5Api + testRuntimeOnly supportDependencies.junit5Engine + testImplementation supportDependencies.systemLambda +} + +jar { + manifest { + attributes 'Specification-Title': 'Picocli Tests Requiring Java 8', + 'Specification-Vendor' : 'Remko Popma', + 'Specification-Version' : archiveVersion.get(), + 'Implementation-Title' : 'Picocli Tests Requiring Java 8', + 'Implementation-Vendor' : 'Remko Popma', + 'Implementation-Version': archiveVersion.get(), + 'Automatic-Module-Name' : 'info.picocli.tests.java8' + } +} diff --git a/picocli-tests-java8/src/test/java/picocli/Issue2232.java b/picocli-tests-java8/src/test/java/picocli/Issue2232.java new file mode 100644 index 000000000..b651138ba --- /dev/null +++ b/picocli-tests-java8/src/test/java/picocli/Issue2232.java @@ -0,0 +1,30 @@ +package picocli; + +import org.junit.Test; +import picocli.CommandLine; +import picocli.CommandLine.*; + +import java.io.File; +import java.util.Optional; + +import static org.junit.Assert.*; + +public class Issue2232 { + static class Tar { + @Option(names = { "-f", "--file" }, paramLabel = "ARCHIVE", description = "the archive file") + Optional archive; + + public Tar() { + archive = Optional.of(new File("helloworld")); + } + } + + @Test + public void testDefault() { + Tar tar = new Tar(); + System.out.println(tar.archive); + assertEquals(Optional.of(new File("helloworld")), tar.archive); + new CommandLine(tar).parseArgs(); + assertEquals(Optional.of(new File("helloworld")), tar.archive); + } +} diff --git a/picocli-examples/src/test/java/picocli/MapOptionsOptionalTest.java b/picocli-tests-java8/src/test/java/picocli/MapOptionsOptionalTest.java similarity index 100% rename from picocli-examples/src/test/java/picocli/MapOptionsOptionalTest.java rename to picocli-tests-java8/src/test/java/picocli/MapOptionsOptionalTest.java diff --git a/picocli-examples/src/test/java/picocli/OptionalTest.java b/picocli-tests-java8/src/test/java/picocli/OptionalTest.java similarity index 100% rename from picocli-examples/src/test/java/picocli/OptionalTest.java rename to picocli-tests-java8/src/test/java/picocli/OptionalTest.java diff --git a/settings.gradle b/settings.gradle index 019077c51..cca309a64 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,6 +3,7 @@ include 'picocli-groovy' include 'picocli-examples' include 'picocli-shell-jline2' include 'picocli-codegen' +include 'picocli-tests-java8' if (org.gradle.api.JavaVersion.current().isJava8Compatible()) { include 'picocli-spring-boot-starter' diff --git a/src/main/java/picocli/CommandLine.java b/src/main/java/picocli/CommandLine.java index e4b730511..4b814b64f 100644 --- a/src/main/java/picocli/CommandLine.java +++ b/src/main/java/picocli/CommandLine.java @@ -13719,9 +13719,14 @@ private boolean applyDefault(IDefaultValueProvider defaultValueProvider, ArgSpec arg.valueIsDefaultValue = true; } else { if (arg.typeInfo().isOptional()) { - if (tracer.isDebug()) { - tracer.debug("Applying Optional.empty() to %s on %s", arg, arg.scopeString());} - arg.setValue(getOptionalEmpty()); + if (arg.hasInitialValue() && arg.initialValue() != null) { + if (tracer.isDebug()) { + tracer.debug("Leaving initial value %s for %s on %s", arg.initialValue(), arg, arg.scopeString());} + } else { + if (tracer.isDebug()) { + tracer.debug("Applying Optional.empty() to %s on %s", arg, arg.scopeString());} + arg.setValue(getOptionalEmpty()); + } arg.valueIsDefaultValue = true; } else if (ArgSpec.UNSPECIFIED.equals(arg.originalDefaultValue)) { tracer.debug("defaultValue not defined for %s", arg);