diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..9be205f --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +# Dependabot configuration: +# https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + # Maintain dependencies for Gradle dependencies + - package-ecosystem: "gradle" + directory: "/" + target-branch: "next" + schedule: + interval: "daily" + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + target-branch: "next" + schedule: + interval: "daily" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..a0b6859 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,158 @@ +# GitHub Actions Workflow created for testing and preparing the plugin release in following steps: +# - validate Gradle Wrapper, +# - run 'test' and 'verifyPlugin' tasks, +# - run Qodana inspections, +# - run 'buildPlugin' task and prepare artifact for the further tests, +# - run 'runPluginVerifier' task, +# - create a draft release. +# +# Workflow is triggered on push and pull_request events. +# +# GitHub Actions reference: https://help.github.com/en/actions +# +## JBIJPPTPL + +name: Build +on: + # Trigger the workflow on pushes to only the 'main' branch (this avoids duplicate checks being run e.g. for dependabot pull requests) + push: + branches: [main] + # Trigger the workflow on any pull request + pull_request: + +jobs: + + # Run Gradle Wrapper Validation Action to verify the wrapper's checksum + # Run verifyPlugin, IntelliJ Plugin Verifier, and test Gradle tasks + # Build plugin and provide the artifact for the next workflow jobs + build: + name: Build + runs-on: ubuntu-latest + outputs: + version: ${{ steps.properties.outputs.version }} + changelog: ${{ steps.properties.outputs.changelog }} + steps: + + # Check out current repository + - name: Fetch Sources + uses: actions/checkout@v2.4.0 + + # Validate wrapper + - name: Gradle Wrapper Validation + uses: gradle/wrapper-validation-action@v1.0.4 + + # Setup Java 11 environment for the next steps + - name: Setup Java + uses: actions/setup-java@v2 + with: + distribution: zulu + java-version: 11 + cache: gradle + + # Set environment variables + - name: Export Properties + id: properties + shell: bash + run: | + PROPERTIES="$(./gradlew properties --console=plain -q)" + VERSION="$(echo "$PROPERTIES" | grep "^version:" | cut -f2- -d ' ')" + NAME="$(echo "$PROPERTIES" | grep "^pluginName:" | cut -f2- -d ' ')" + CHANGELOG="$(./gradlew getChangelog --unreleased --no-header --console=plain -q)" + CHANGELOG="${CHANGELOG//'%'/'%25'}" + CHANGELOG="${CHANGELOG//$'\n'/'%0A'}" + CHANGELOG="${CHANGELOG//$'\r'/'%0D'}" + + echo "::set-output name=version::$VERSION" + echo "::set-output name=name::$NAME" + echo "::set-output name=changelog::$CHANGELOG" + echo "::set-output name=pluginVerifierHomeDir::~/.pluginVerifier" + + ./gradlew listProductsReleases # prepare list of IDEs for Plugin Verifier + + # Run tests + - name: Run Tests + run: ./gradlew test + + # Collect Tests Result of failed tests + - name: Collect Tests Result + if: ${{ failure() }} + uses: actions/upload-artifact@v2 + with: + name: tests-result + path: ${{ github.workspace }}/build/reports/tests + + # Cache Plugin Verifier IDEs + - name: Setup Plugin Verifier IDEs Cache + uses: actions/cache@v2.1.7 + with: + path: ${{ steps.properties.outputs.pluginVerifierHomeDir }}/ides + key: plugin-verifier-${{ hashFiles('build/listProductsReleases.txt') }} + + # Run Verify Plugin task and IntelliJ Plugin Verifier tool + - name: Run Plugin Verification tasks + run: ./gradlew runPluginVerifier -Pplugin.verifier.home.dir=${{ steps.properties.outputs.pluginVerifierHomeDir }} + + # Collect Plugin Verifier Result + - name: Collect Plugin Verifier Result + if: ${{ always() }} + uses: actions/upload-artifact@v2 + with: + name: pluginVerifier-result + path: ${{ github.workspace }}/build/reports/pluginVerifier + + # Run Qodana inspections + - name: Qodana - Code Inspection + uses: JetBrains/qodana-action@v4.2.5 + + # Prepare plugin archive content for creating artifact + - name: Prepare Plugin Artifact + id: artifact + shell: bash + run: | + cd ${{ github.workspace }}/build/distributions + FILENAME=`ls *.zip` + unzip "$FILENAME" -d content + + echo "::set-output name=filename::${FILENAME:0:-4}" + + # Store already-built plugin as an artifact for downloading + - name: Upload artifact + uses: actions/upload-artifact@v2.2.4 + with: + name: ${{ steps.artifact.outputs.filename }} + path: ./build/distributions/content/*/* + + # Prepare a draft release for GitHub Releases page for the manual verification + # If accepted and published, release workflow would be triggered + releaseDraft: + name: Release Draft + if: github.event_name != 'pull_request' + needs: build + runs-on: ubuntu-latest + steps: + + # Check out current repository + - name: Fetch Sources + uses: actions/checkout@v2.4.0 + + # Remove old release drafts by using the curl request for the available releases with draft flag + - name: Remove Old Release Drafts + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh api repos/{owner}/{repo}/releases \ + --jq '.[] | select(.draft == true) | .id' \ + | xargs -I '{}' gh api -X DELETE repos/{owner}/{repo}/releases/{} + + # Create new release draft - which is not publicly visible and requires manual acceptance + - name: Create Release Draft + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create v${{ needs.build.outputs.version }} \ + --draft \ + --title "v${{ needs.build.outputs.version }}" \ + --notes "$(cat << 'EOM' + ${{ needs.build.outputs.changelog }} + EOM + )" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..0bcf2d5 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,87 @@ +# GitHub Actions Workflow created for handling the release process based on the draft release prepared +# with the Build workflow. Running the publishPlugin task requires the PUBLISH_TOKEN secret provided. + +name: Release +on: + release: + types: [prereleased, released] + +jobs: + + # Prepare and publish the plugin to the Marketplace repository + release: + name: Publish Plugin + runs-on: ubuntu-latest + steps: + + # Check out current repository + - name: Fetch Sources + uses: actions/checkout@v2.4.0 + with: + ref: ${{ github.event.release.tag_name }} + + # Setup Java 11 environment for the next steps + - name: Setup Java + uses: actions/setup-java@v2 + with: + distribution: zulu + java-version: 11 + cache: gradle + + # Set environment variables + - name: Export Properties + id: properties + shell: bash + run: | + CHANGELOG="$(cat << 'EOM' | sed -e 's/^[[:space:]]*$//g' -e '/./,$!d' + ${{ github.event.release.body }} + EOM + )" + + CHANGELOG="${CHANGELOG//'%'/'%25'}" + CHANGELOG="${CHANGELOG//$'\n'/'%0A'}" + CHANGELOG="${CHANGELOG//$'\r'/'%0D'}" + + echo "::set-output name=changelog::$CHANGELOG" + + # Update Unreleased section with the current release note + - name: Patch Changelog + if: ${{ steps.properties.outputs.changelog != '' }} + env: + CHANGELOG: ${{ steps.properties.outputs.changelog }} + run: | + ./gradlew patchChangelog --release-note="$CHANGELOG" + + # Publish the plugin to the Marketplace + - name: Publish Plugin + env: + PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} + run: ./gradlew publishPlugin + + # Upload artifact as a release asset + - name: Upload Release Asset + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh release upload ${{ github.event.release.tag_name }} ./build/distributions/* + + # Create pull request + - name: Create Pull Request + if: ${{ steps.properties.outputs.changelog != '' }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + VERSION="${{ github.event.release.tag_name }}" + BRANCH="changelog-update-$VERSION" + + git config user.email "action@github.com" + git config user.name "GitHub Action" + + git checkout -b $BRANCH + git commit -am "Changelog update - $VERSION" + git push --set-upstream origin $BRANCH + + gh pr create \ + --title "Changelog update - \`$VERSION\`" \ + --body "Current pull request contains patched \`CHANGELOG.md\` file for the \`$VERSION\` version." \ + --base main \ + --head $BRANCH diff --git a/.github/workflows/run-ui-tests.yml b/.github/workflows/run-ui-tests.yml new file mode 100644 index 0000000..3108cf1 --- /dev/null +++ b/.github/workflows/run-ui-tests.yml @@ -0,0 +1,60 @@ +# GitHub Actions Workflow for launching UI tests on Linux, Windows, and Mac in the following steps: +# - prepare and launch IDE with your plugin and robot-server plugin, which is needed to interact with UI +# - wait for IDE to start +# - run UI tests with separate Gradle task +# +# Please check https://github.com/JetBrains/intellij-ui-test-robot for information about UI tests with IntelliJ Platform +# +# Workflow is triggered manually. + +name: Run UI Tests +on: + workflow_dispatch + +jobs: + + testUI: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + runIde: | + export DISPLAY=:99.0 + Xvfb -ac :99 -screen 0 1920x1080x16 & + gradle runIdeForUiTests & + - os: windows-latest + runIde: start gradlew.bat runIdeForUiTests + - os: macos-latest + runIde: ./gradlew runIdeForUiTests & + + steps: + + # Check out current repository + - name: Fetch Sources + uses: actions/checkout@v2.4.0 + + # Setup Java 11 environment for the next steps + - name: Setup Java + uses: actions/setup-java@v2 + with: + distribution: zulu + java-version: 11 + cache: gradle + + # Run IDEA prepared for UI testing + - name: Run IDE + run: ${{ matrix.runIde }} + + # Wait for IDEA to be started + - name: Health Check + uses: jtalk/url-health-check-action@v2 + with: + url: http://127.0.0.1:8082 + max-attempts: 15 + retry-delay: 30s + + # Run tests + - name: Tests + run: ./gradlew test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e2e5d94 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.gradle +.idea +.qodana +build diff --git a/.run/Run IDE for UI Tests.run.xml b/.run/Run IDE for UI Tests.run.xml new file mode 100644 index 0000000..9b028c3 --- /dev/null +++ b/.run/Run IDE for UI Tests.run.xml @@ -0,0 +1,22 @@ + + + + + + + + true + true + false + + + \ No newline at end of file diff --git a/.run/Run Plugin Tests.run.xml b/.run/Run Plugin Tests.run.xml new file mode 100644 index 0000000..ae9ae13 --- /dev/null +++ b/.run/Run Plugin Tests.run.xml @@ -0,0 +1,24 @@ + + + + + + + + true + true + false + + + diff --git a/.run/Run Plugin Verification.run.xml b/.run/Run Plugin Verification.run.xml new file mode 100644 index 0000000..b3b4eb8 --- /dev/null +++ b/.run/Run Plugin Verification.run.xml @@ -0,0 +1,24 @@ + + + + + + + + true + true + false + + + \ No newline at end of file diff --git a/.run/Run Qodana.run.xml b/.run/Run Qodana.run.xml new file mode 100644 index 0000000..9603583 --- /dev/null +++ b/.run/Run Qodana.run.xml @@ -0,0 +1,26 @@ + + + + + + + + true + true + false + + + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b03e9a4 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ + + +# mosn-intellij Changelog + +## [Unreleased] +### Added +- support create mosn plugin project for goland diff --git a/README.md b/README.md index 2df8021..a1e5fde 100644 --- a/README.md +++ b/README.md @@ -1 +1,33 @@ -# mesh-intellij +# mosn-intellij + +![Build](https://github.com/zonghaishang/mosn-intellij/workflows/Build/badge.svg) +[![Version](https://img.shields.io/jetbrains/plugin/v/PLUGIN_ID.svg)](https://plugins.jetbrains.com/plugin/PLUGIN_ID) +[![Downloads](https://img.shields.io/jetbrains/plugin/d/PLUGIN_ID.svg)](https://plugins.jetbrains.com/plugin/PLUGIN_ID) + + + + +

Support for developing mosn plugin applications. Mosn mecha gives developers an easy and productive +way to build and deploy cross-platform.

Adds Mosn Plugin to File | New | Project... +

Some example plugin programs are provided:

+
  • Demo program source code:plugin samples
  • + + + +## Installation + +- Using IDE built-in plugin system: + + Settings/Preferences > Plugins > Marketplace > Search for "mosn mecha" > + Install Plugin + +- Manually: + + Download the [latest release](https://github.com/zonghaishang/mosn-intellij/releases/latest) and install it manually + using + Settings/Preferences > Plugins > ⚙️ > Install plugin from disk... + +--- +Plugin based on the [IntelliJ Platform Plugin Template][template]. + +[template]: https://github.com/JetBrains/intellij-platform-plugin-template diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..f035077 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,269 @@ +import org.jetbrains.changelog.markdownToHTML + +fun properties(key: String) = project.findProperty(key).toString() + +plugins { + // Java support + id("java") + // Gradle IntelliJ Plugin + id("org.jetbrains.intellij") version "1.5.2" + id("org.jetbrains.gradle.plugin.idea-ext") version "1.1.6" + + // Gradle Changelog Plugin + id("org.jetbrains.changelog") version "1.3.1" + // Gradle Qodana Plugin + id("org.jetbrains.qodana") version "0.1.13" + + + // build for cli + id("application") + id("com.github.johnrengelman.shadow") version "7.1.2" +} + +group = properties("pluginGroup") +version = properties("pluginVersion") + +// Configure project's dependencies +repositories { + mavenLocal() + maven(url = "https://www.jetbrains.com/intellij-repository/releases") + maven(url = "https://cache-redirector.jetbrains.com/intellij-dependencies") + maven(url = "https://mvn.cloud.alipay.com/nexus/content/groups/open") + mavenCentral() +} + +val goland: Configuration by configurations.creating +val terminal: Configuration by configurations.creating { + extendsFrom(goland) +} + +dependencies { + // https://mavenlibs.com/maven/dependency/com.google.code.gson/gson + goland("com.google.code.gson:gson:2.10") + + // https://mvnrepository.com/artifact/com.alibaba/fastjson + goland("com.alibaba:fastjson:1.2.83") + + // https://mvnrepository.com/artifact/io.fabric8/kubernetes-client + goland("io.fabric8:kubernetes-client:6.8.1") { + exclude(group = "org.slf4j") + } + + goland("com.mysql:mysql-connector-j:8.0.33"){ + exclude(group = "com.google.protobuf") + } + + // sofa registry for fetch mesh server console address + + goland("javax.xml.bind:jaxb-api:2.3.1") + goland("com.alipay.sofa.cloud:sdk-core:0.1") + + goland("commons-lang:commons-lang:2.6") + goland("commons-logging:commons-logging:1.2") + goland("commons-pool:commons-pool:1.6") + goland("commons-io:commons-io:2.7") + + goland("com.alipay.sofa:hessian:3.3.10") + goland("com.alipay.sofa:bolt:1.6.5") { + exclude(group = "org.slf4j") + } + goland("com.alipay.sofa.common:sofa-common-tools:1.0.22") { + exclude(group = "org.slf4j") + } + goland("com.alipay.sofa:sofa-common-configs:1.1.6") { + exclude(group = "org.slf4j") + exclude(group = "com.alipay.bkmi") + } + goland("com.alipay.sofa.lookout:lookout-api:1.6.1") + goland("com.alipay.sofa:registry-client-all:5.3.1.cloud.20220531") { + exclude(group = "org.slf4j") + } + + goland("com.antcloud.antvip:antcloud-antvip-common:1.1.5") + goland("com.alibaba.toolkit.common:toolkit-common-lang:1.1.5") + goland("com.alibaba.toolkit.common:toolkit-common-logging:1.0") + goland("com.antcloud.antvip:antcloud-antvip-client:1.1.5") + + goland("com.alipay.sofa:registry-client-enterprise-all:5.5.1.RELEASE") + // end of : sofa registry for fetch mesh server console address + + goland("com.intellij:forms_rt:7.0.3") + + // local dependencies + goland(fileTree(mapOf("dir" to "src/main/libs", "include" to listOf("*.jar")))) + + // only local start SubscribeConsoleAddress.main +// goland("org.slf4j:slf4j-api:1.7.21") + // goland 出包,禁用一下包 +// terminal("org.slf4j:slf4j-api:1.7.21") +// terminal("ch.qos.logback:logback-classic:1.2.9") +} + +configurations { + compileClasspath.extendsFrom(goland) + runtimeClasspath.extendsFrom(goland) +} + + +// Configure Gradle IntelliJ Plugin - read more: https://github.com/JetBrains/gradle-intellij-plugin +intellij { + pluginName.set(properties("pluginName")) + version.set(properties("platformVersion")) + type.set(properties("platformType")) + + // Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file. + plugins.set(properties("platformPlugins").split(',').map(String::trim).filter(String::isNotEmpty)) +} + +// Configure Gradle Changelog Plugin - read more: https://github.com/JetBrains/gradle-changelog-plugin +changelog { + version.set(properties("pluginVersion")) + groups.set(emptyList()) +} + +// Configure Gradle Qodana Plugin - read more: https://github.com/JetBrains/gradle-qodana-plugin +qodana { + cachePath.set(projectDir.resolve(".qodana").canonicalPath) + reportPath.set(projectDir.resolve("build/reports/inspections").canonicalPath) + saveReport.set(true) + showReport.set(System.getenv("QODANA_SHOW_REPORT")?.toBoolean() ?: false) +} + +// build for cli +application { + mainClass.set("io.mosn.coder.cli.Cli") +} + +tasks.runIde { + jvmArgs("--add-exports", "java.base/jdk.internal.vm=ALL-UNNAMED", "-Xmx2048m") +} + +tasks { + + // Set the JVM compatibility versions + properties("javaVersion").let { + withType { + sourceCompatibility = it + targetCompatibility = it + } + } + + wrapper { + gradleVersion = properties("gradleVersion") + } + + patchPluginXml { + version.set(properties("pluginVersion")) + sinceBuild.set(properties("pluginSinceBuild")) + untilBuild.set(properties("pluginUntilBuild")) + + // Extract the section from README.md and provide for the plugin's manifest + pluginDescription.set( + projectDir.resolve("README.md").readText().lines().run { + val start = "" + val end = "" + + if (!containsAll(listOf(start, end))) { + throw GradleException("Plugin description section not found in README.md:\n$start ... $end") + } + subList(indexOf(start) + 1, indexOf(end)) + }.joinToString("\n").run { markdownToHTML(this) } + ) + + // Get the latest available change notes from the changelog file + changeNotes.set(provider { + changelog.run { + getOrNull(properties("pluginVersion")) ?: getLatest() + }.toHTML() + }) + } + + // Configure UI tests plugin + // Read more: https://github.com/JetBrains/intellij-ui-test-robot + runIdeForUiTests { + systemProperty("robot-server.port", "8082") + systemProperty("ide.mac.message.dialogs.as.sheets", "false") + systemProperty("jb.privacy.policy.text", "") + systemProperty("jb.consents.confirmation.enabled", "false") + } + + signPlugin { + certificateChain.set(System.getenv("CERTIFICATE_CHAIN")) + privateKey.set(System.getenv("PRIVATE_KEY")) + password.set(System.getenv("PRIVATE_KEY_PASSWORD")) + } + + publishPlugin { + dependsOn("patchChangelog") + token.set(System.getenv("PUBLISH_TOKEN")) + // pluginVersion is based on the SemVer (https://semver.org) and supports pre-release labels, like 2.1.7-alpha.3 + // Specify pre-release label to publish the plugin in a custom Release Channel automatically. Read more: + // https://plugins.jetbrains.com/docs/intellij/deployment.html#specifying-a-release-channel + channels.set(listOf(properties("pluginVersion").split('-').getOrElse(1) { "default" }.split('.').first())) + } +} + +/** + * Generate goland extension for running plugin. + */ +tasks.register("goland") { + + configurations { + compileClasspath.extendsFrom(goland) + runtimeClasspath.extendsFrom(goland) + } + + project.delete { + delete(fileTree("build/distributions")) + delete(fileTree(mapOf("dir" to "build/plugins", "include" to listOf("mosn-intellij-*.zip")))) + } + + // clean build and package dist zip first + dependsOn(tasks["distZip"]) + + doLast { + + /** + * copy to release directory + */ + copy { + into("build/plugins/goland") + from("build/distributions") { + exclude("mosn-intellij-shadow*.zip") + include("mosn-intellij-*.zip") + } + } + } +} + +/** + * Generate terminal fat jar for running plugin command. + */ +tasks.register("terminal") { + + configurations { + compileClasspath.extendsFrom(terminal) + runtimeClasspath.extendsFrom(terminal) + } + + project.delete { + delete(fileTree("build/libs")) + delete(fileTree(mapOf("dir" to "build/plugins", "include" to listOf("*-all.jar")))) + } + + // clean build and package dist zip first + dependsOn(tasks["build"]) + + doLast { + + /** + * copy to release directory + */ + copy { + into("build/plugins/terminal") + from("build/libs") { + include("*-all.jar") + } + } + } +} \ No newline at end of file diff --git "a/doc/pdf/mosn mecha \344\275\277\347\224\250\346\214\207\345\215\227.pdf" "b/doc/pdf/mosn mecha \344\275\277\347\224\250\346\214\207\345\215\227.pdf" new file mode 100644 index 0000000..5d08b50 Binary files /dev/null and "b/doc/pdf/mosn mecha \344\275\277\347\224\250\346\214\207\345\215\227.pdf" differ diff --git "a/doc/pdf/\346\217\222\344\273\266\351\203\250\347\275\262&\345\215\207\347\272\247.pdf" "b/doc/pdf/\346\217\222\344\273\266\351\203\250\347\275\262&\345\215\207\347\272\247.pdf" new file mode 100644 index 0000000..e7440e8 Binary files /dev/null and "b/doc/pdf/\346\217\222\344\273\266\351\203\250\347\275\262&\345\215\207\347\272\247.pdf" differ diff --git a/generate.sh b/generate.sh new file mode 100755 index 0000000..a5c54fc --- /dev/null +++ b/generate.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# generate goland zip package +bash ./gradlew goland + +# generate terminal cli fat jar +# uncomment // terminal("org.slf4j:slf4j-api:1.7.21") first +bash ./gradlew terminal + +tree build/plugins \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..f931fa5 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,31 @@ +# IntelliJ Platform Artifacts Repositories +# -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html + +pluginGroup = io.mosn.coder.intellij +pluginName = mosn-intellij +# SemVer format -> https://semver.org +pluginVersion = 1.0.16 + +# See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html +# for insight into build numbers and IntelliJ Platform versions. +pluginSinceBuild = 203 +pluginUntilBuild = 291.* + +# IntelliJ Platform Properties -> https://github.com/JetBrains/gradle-intellij-plugin#intellij-platform-properties +platformType = GO +platformVersion = 2021.2 + +# Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html +# Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22 +platformPlugins = org.jetbrains.plugins.go:212.4746.52 + +# Java language level used to compile sources and to generate the files for - Java 11 is required since 2020.3 +javaVersion = 11 + +# Gradle Releases -> https://github.com/gradle/gradle/releases +gradleVersion = 7.4 + +# Opt-out flag for bundling Kotlin standard library. +# See https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library for details. +# suppress inspection "UnusedProperty" +kotlin.stdlib.default.dependency = false diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..41d9927 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..41dfb87 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..1b6c787 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/qodana.yml b/qodana.yml new file mode 100644 index 0000000..dac95d3 --- /dev/null +++ b/qodana.yml @@ -0,0 +1,6 @@ +# Qodana configuration: +# https://www.jetbrains.com/help/qodana/qodana-yaml.html + +version: 1.0 +profile: + name: qodana.recommended diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..957bd2b --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "mesh-intellij" \ No newline at end of file diff --git a/src/main/java/io/mosn/coder/cli/BaseCli.java b/src/main/java/io/mosn/coder/cli/BaseCli.java new file mode 100644 index 0000000..88a1afd --- /dev/null +++ b/src/main/java/io/mosn/coder/cli/BaseCli.java @@ -0,0 +1,85 @@ +package io.mosn.coder.cli; + +import io.mosn.coder.upgrade.ProjectMod; + +import java.io.File; + +/** + * @author yiji@apache.org + */ +public abstract class BaseCli { + + @CommandLine.ParentCommand + Cli parent; + + @CommandLine.Option(required = true, names = {"--plugin", "-p"}, description = "Set the plugin name") + String plugin; + + @CommandLine.Option(names = {"--api-version"}, description = "Set api version, default read from go.mod") + String api; + + @CommandLine.Option(names = {"--pkg-version"}, description = "Set pkg version, default read from go.mod") + String pkg; + + @CommandLine.Option(required = true, names = {"--project-dir", "-d"}, paramLabel = "", description = "Set the path to the project including project name") + String path; + + @CommandLine.Option(names = {"--organization", "-o"}, paramLabel = "", description = "New project module prefix name, if the project already exists, this value does not need to be provided. example: github.com/zonghaishang") + String organization; + + void check() { + if (parent != null) { + + File file = new File(/*parent.*/path); + if (file == null || !file.exists()) { + if (/*parent.*/organization == null || /*parent.*/organization.length() <= 0) { + System.err.println("--organization is required, example: github.com/zonghaishang"); + System.exit(0); + } + } + + /** + * update api and pkg + */ + + File upgradeMod = new File(path, "build/upgrade/remote.mod"); + if (upgradeMod.exists()) { + ProjectMod current = new ProjectMod(path, "build/upgrade/remote.mod"); + current.readFile(); + + if (current.getApi() != null) { + api = current.getApi(); + } + + if (current.getPkg() != null) { + pkg = current.getPkg(); + } + + return; + } + + File mod = new File(path, "go.mod"); + if (mod.exists() + /** + * go.mod is required and user not reset api or pkg + */ + && ((api == null || api.length() == 0) || (pkg == null || pkg.length() == 0)) + ) { + /** + * read default go.mod + */ + ProjectMod current = new ProjectMod(path, "go.mod"); + current.readFile(); + + if (current.getApi() != null) { + api = current.getApi(); + } + + if (current.getPkg() != null) { + pkg = current.getPkg(); + } + } + + } + } +} diff --git a/src/main/java/io/mosn/coder/cli/BaseDeploy.java b/src/main/java/io/mosn/coder/cli/BaseDeploy.java new file mode 100644 index 0000000..a158d3d --- /dev/null +++ b/src/main/java/io/mosn/coder/cli/BaseDeploy.java @@ -0,0 +1,1504 @@ +package io.mosn.coder.cli; + +import com.alibaba.fastjson.JSON; +import io.mosn.coder.common.NetUtils; +import io.mosn.coder.common.StringUtils; +import io.mosn.coder.common.TimerHolder; +import io.mosn.coder.common.URL; +import io.mosn.coder.compiler.Command; +import io.mosn.coder.compiler.CommandBuilder; +import io.mosn.coder.compiler.TerminalCompiler; +import io.mosn.coder.console.CustomCommandUtil; +import io.mosn.coder.intellij.template.VersionTemplate; +import io.mosn.coder.intellij.view.SidecarInjectRuleModel; +import io.mosn.coder.plugin.Protocol; +import io.mosn.coder.plugin.RequestBuilder; +import io.mosn.coder.plugin.RpcClient; +import io.mosn.coder.plugin.handler.CommitTaskFactory; +import io.mosn.coder.plugin.handler.InitTaskFactory; +import io.mosn.coder.plugin.handler.UploadFileFactory; +import io.mosn.coder.plugin.model.PluginBundle; +import io.mosn.coder.plugin.model.PluginBundleRule; +import io.mosn.coder.plugin.model.PluginMetadata; +import io.mosn.coder.plugin.model.PluginStatus; +import io.mosn.coder.registry.SubscribeConsoleAddress; +import io.mosn.coder.upgrade.ProjectMod; +import io.netty.util.Timeout; +import org.jetbrains.annotations.Nullable; + +import java.io.*; +import java.net.URLEncoder; +import java.nio.charset.Charset; +import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +public abstract class BaseDeploy { + + protected String project; + + protected DeployMode mode; + + protected String oldVersion; + + protected String upgradeVersion; + + protected volatile String address; + + protected AtomicBoolean addrNotified = new AtomicBoolean(); + + protected boolean skipCompile; + + protected List rules = new ArrayList<>(); + + protected List images = new ArrayList<>(); + + protected SidecarInjectRuleModel.InjectRule selectedRule = null; + + protected SidecarInjectRuleModel.UpgradeImage selectedImage = null; + + protected CountDownLatch signal = new CountDownLatch(1); + + protected LinkedBlockingDeque queue = new LinkedBlockingDeque(); + + protected Runnable quit = () -> { + }; + + public enum DeployMode { + Deploy, Upgrade; + } + + protected void registerCallBack(String project) { + + + /** + * initialize mesh server address + */ + registerPluginRegistryCallback(project); + + + if (this.mode == DeployMode.Upgrade) { + + /** + * upgrade model, should fetch sidecar rule and sidecar image first + */ + + String address = this.address; + if (address != null && address.length() > 0) { + querySidecarRule(project, address, null); + querySidecarVersion(project, address); + } + + } + + } + + private void registerPluginRegistryCallback(String project) { + SubscribeConsoleAddress.DefaultNotify defaultNotify = SubscribeConsoleAddress.getProjectNotify(project); + if (defaultNotify != null) { + List urls = defaultNotify.getUrls(); + if (!urls.isEmpty()) { + + /** + * prefer local address for debug + */ + + String host = NetUtils.getLocalHost(); + String addr = urls.get(0).getAddress(); + for (URL u : urls) { + if (u.getHost().equals(host)) { + addr = u.getAddress(); + break; + } + } + + this.address = addr; + + System.out.println("mesh server address:" + this.address); + + if (this.mode == DeployMode.Deploy) { + this.signal.countDown(); + } + + } else { + + System.out.println("fetching address, please wait..."); + + /** + * register registry callback + */ + + defaultNotify.setCallback(() -> { + + String address = this.address; + + boolean alreadyExist = false; + if (address != null && address.length() > 0) { + for (URL url : defaultNotify.getUrls()) { + if (url.getAddress().equals(address)) { + alreadyExist = true; + break; + } + } + } + + if (alreadyExist) return; + + List notifyUrls = defaultNotify.getUrls(); + + String host = NetUtils.getLocalHost(); + address = notifyUrls.get(0).getAddress(); + for (URL u : notifyUrls) { + if (u.getHost().equals(host)) { + address = u.getAddress(); + break; + } + } + + this.address = address; + + if (addrNotified.compareAndSet(false, true)) { + + System.out.println("mesh server address:" + this.address); + + if (this.mode == DeployMode.Upgrade) { + + /** + * registry callback trigger fetch api first time. + */ + querySidecarRule(project, address, null); + + /** + * query sidecar version + */ + querySidecarVersion(project, address); + } else { + /** + * notify client thread start deploy + */ + this.signal.countDown(); + } + + } + + }); + } + } + } + + private void querySidecarRule(String project, String address, Long id) { + RpcClient rpc = RpcClient.getClient(address); + + Protocol.Request request = RequestBuilder.newSidecarRuleRequest(project); + + if (id != null) { + /** + * query sidecar rule detail with id + */ + request.appendHead(Protocol.WrapCommand.RULE_ID, id.toString()); + } + + Protocol.Response response = rpc.request(request); + if (response.isSuccess()) { + try { + PluginBundleRule bundle = JSON.parseObject(new ByteArrayInputStream(response.getContents()), PluginBundleRule.class); + + if (id == null) { + + /** + * query sidecar rule information + */ + if (bundle != null && bundle.getPluginRules() != null + && !bundle.getPluginRules().isEmpty()) { + + this.rules.clear(); + + int selected = -1; + int order = 1; + + System.out.println("Available sidecar inject rules:"); + for (PluginBundleRule.PluginRule rule : bundle.getPluginRules()) { + this.rules.add(new SidecarInjectRuleModel.InjectRule(rule)); + System.out.println(" " + order + ") " + this.rules.get(order - 1)); + order++; + } + + System.out.print("Please select sidecar rule number:"); + + Scanner scan = new Scanner(System.in); + if (scan.hasNextInt()) { + selected = scan.nextInt(); + } + + if (selected > 0 && selected < order) { + this.selectedRule = this.rules.get(selected - 1); + if (this.selectedRule != null && this.selectedRule.getId() != null) { + querySidecarRule(project, address, this.selectedRule.getId()); + } + + } else { + System.out.println("Bad sidecar rule number."); + } + + } else { + System.out.println("sidecar rule is empty"); + } + + return; + + } + + /** + * query sidecar rule detail information + */ + if (bundle != null && bundle.getBindingTask() != null) { + + for (int i = 0; i < this.rules.size(); i++) { + PluginBundleRule.PluginRule rule = this.rules.get(i); + if (rule.getId() == id) { + rule.setPluginTask(bundle.getBindingTask()); + break; + } + } + + /** + * render plugin bundle + */ + PluginBundleRule.PluginRule rule = this.selectedRule; + if (rule != null && rule.getPluginTask() != null + && rule.getPluginTask().getPluginBundle() != null) { + PluginBundle bd = new PluginBundle(); + bd.setBundles(rule.getPluginTask().getPluginBundle()); + + for (PluginBundle.Plugin plugin : rule.getPluginTask().getPluginBundle()) { + File file = getPluginFile(project, plugin); + plugin.setOwner(file != null && file.exists()); + } + } + + } else { + System.out.println("fetch sidecar rule detail failed"); + } + + } catch (IOException e) { + + System.out.println("fetch sidecar rule failed"); + + /** + * clear text first. + */ + // this.pluginInfo.setText(""); + + System.out.println("fetch sidecar rule failed:\n"); + System.out.println(e.getMessage()); + + } + + return; + } + + /** + * rpc request failed. + */ + + displayErrorStack(response); + } + + protected File getPluginFile(String project, PluginBundle.Plugin plugin) { + File file = null; + switch (plugin.getKind()) { + case PluginBundle.KIND_FILTER: { + file = new File(project, "configs/stream_filters/" + plugin.getName() + "/metadata.json"); + break; + } + case PluginBundle.KIND_TRANSCODER: { + file = new File(project, "configs/transcoders/" + plugin.getName() + "/metadata.json"); + break; + } + case PluginBundle.KIND_TRACE: { + file = new File(project, "configs/traces/" + plugin.getName() + "/metadata.json"); + break; + } + case PluginBundle.KIND_PROTOCOL: { + file = new File(project, "configs/codecs/" + plugin.getName() + "/metadata.json"); + break; + } + } + return file; + } + + private void querySidecarVersion(String project, String address) { + RpcClient rpc = RpcClient.getClient(address); + + Protocol.Request request = RequestBuilder.newSidecarVersionRequest(project); + + Protocol.Response response = rpc.request(request); + boolean quit = false; + if (response.isSuccess()) { + try { + PluginBundleRule bundle = JSON.parseObject(new ByteArrayInputStream(response.getContents()), PluginBundleRule.class); + + /** + * query sidecar version information + */ + if (bundle != null && bundle.getPluginRules() != null + && !bundle.getPluginRules().isEmpty()) { + this.images.clear(); + + int selected = -1; + int order = 1; + System.out.println("Available upgrade sidecar images:"); + for (PluginBundleRule.PluginRule rule : bundle.getPluginRules()) { + this.images.add(new SidecarInjectRuleModel.UpgradeImage(rule)); + System.out.println(" " + order + ") " + this.images.get(order - 1)); + order++; + } + + System.out.print("Please select upgrade sidecar version number:"); + + Scanner scan = new Scanner(System.in); + if (scan.hasNextInt()) { + selected = scan.nextInt(); + } + + if (selected > 0 && selected < order) { + this.selectedImage = this.images.get(selected - 1); + + /** + * after select image, render bundle + */ + + this.queue.offerLast(() -> { + PluginBundle bd = new PluginBundle(); + + bd.setSelectedRule(this.selectedRule); + bd.setSelectedImage(this.selectedImage); + bd.setDeployVersion(this.upgradeVersion); + bd.setMeshServer(this.address); + bd.setTerminal(true); + + bd.setBundles(this.selectedRule.getPluginTask().getPluginBundle()); + + clearConsole(); + + System.out.println(bd.renderAllTasks(false)); + }); + + } else { + System.out.println("Bad sidecar version number."); + quit = true; + } + + } else { + System.out.println("sidecar version is empty"); + quit = true; + } + } catch (IOException e) { + + System.out.println("fetch sidecar version failed"); + + /** + * clear text first. + */ + + System.out.println("fetch sidecar version failed:\n"); + System.out.println(e.getMessage()); + + quit = true; + } + } else { + quit = true; + displayErrorStack(response); + } + + /** + * notify main thread to running + */ + this.signal.countDown(); + if (quit) { + this.notifyQuit(); + } + } + + public String deployOrUpgradePlugins(PluginBundle deployBundle) { + + String version = this.upgradeVersion; + if (version == null) { + return "upgrade plugin version is required"; + } + + version = version.trim(); + + String error = StringUtils.checkPluginVersion(version); + if (error != null) return error; + + try { + signal.await(); + } catch (Exception ignored) { + return ignored.getMessage(); + } + + final String updateVersion = version; + + /** + * must update the version first because the packaging reads the version number locally + */ + updateLocalPluginVersion(); + + if (this.mode == DeployMode.Upgrade) { + return upgradePlugins(updateVersion, null); + } + + deployBundle.setMeshServer(this.address); + deployBundle.setDeployVersion(updateVersion); + + deployPlugins(deployBundle); + + return null; + } + + protected void updateLocalPluginVersion() { + /** + * update plugin version + */ + File version = new File(project, VersionTemplate.Name); + if (version.exists()) { + try (FileOutputStream out = new FileOutputStream(version)) { + if (this.upgradeVersion != null && this.upgradeVersion.length() > 0) { + out.write(this.upgradeVersion.getBytes()); + out.flush(); + } + } catch (Exception ignored) { + } + } + } + + protected String upgradePlugins(String updateVersion, PluginBundle localBundle) { + PluginBundle bundle = new PluginBundle(); + bundle.setTerminal(true); + + /** + * build current upgrade bundle + */ + + PluginBundleRule.PluginRule rule = this.selectedRule; + if (rule == null + || rule.getPluginTask() == null) { + return "sidecar rule not found"; + } + + if (rule.getPluginTask().getPluginBundle() == null + || rule.getPluginTask().getPluginBundle().isEmpty()) { + return "no plugin need to update"; + } + + PluginBundleRule.PluginRule image = this.selectedImage; + if (image == null || image.getImage() == null) { + return "please update image from mesh console first"; + } + + + /** + * pretty terminal banner + */ + bundle.setSelectedRule(new SidecarInjectRuleModel.InjectRule(rule)); + bundle.setSelectedImage(new SidecarInjectRuleModel.UpgradeImage(image)); + + + bundle.setMeshServer(this.address); + bundle.setDeployVersion(updateVersion); + + /** + * build bundle task + */ + PluginBundleRule.PluginTask task = rule.getPluginTask(); + List plugins = task.getPluginBundle(); + + bundle.setRuleId(rule.getId().toString()); + bundle.setUpgradeId(image.getId().toString()); + + bundle.setBundles(plugins); + + bundle.resetTaskStatus(PluginStatus.COMMAND_RUNNING); + + bundle.setOldImage(rule.getImage()); + bundle.setRuleName(rule.getName()); + + bundle.setUpgradeImage(image.getImage()); + + String errorVersion = checkUpgradeVersion(updateVersion, plugins, localBundle); + if (errorVersion != null) return errorVersion; + + executePluginUpload(bundle, localBundle); + + return null; + } + + protected void executePluginUpload(PluginBundle bundle, PluginBundle localBundle) { + + PluginBundleRule.PluginRule image = this.selectedImage; + + /** + * update plugin dependencies first + */ + + long start = System.currentTimeMillis(); + AtomicReference runningTimeout = new AtomicReference<>(); + Timeout timeout = TimerHolder.getTimer().newTimeout(tt -> { + if (tt.isCancelled()) { + return; + } + + this.queue.offerLast(() -> { + + + /** + * clear screen + */ + clearConsole(); + + System.out.println(bundle.renderWithCommand(false)); + System.out.println("\n\nWait until the remote image is pulled for the first time."); + System.out.println("\nPlease wait copy upgrade dependency... "); + System.out.println(String.valueOf(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - start))); + System.out.println("s"); + }); + + /** + * schedule next time + * + */ + runningTimeout.set(tt.timer().newTimeout(tt.task(), 1, TimeUnit.SECONDS)); + + }, 1, TimeUnit.SECONDS); + runningTimeout.set(timeout); + + Command command = CommandBuilder.createCopyUpgradeCommand(this.project, image); + command.callback = status -> { + runningTimeout.get().cancel(); + + if (status != 0) { + System.out.println("\n\nInit container failed, exit code '" + status + "', maybe image is old, please check goland console message"); + return; + } + + /** + * read container id + */ + String last, containerId = null; + File container = new File(project, "build/upgrade/container.id"); + if (!container.exists()) { + System.out.println("\n\nbuild/upgrade/container.id file is missing, please try again."); + return; + } + + try (BufferedReader reader = new BufferedReader(new FileReader(container))) { + containerId = last = reader.readLine(); + while (last != null) { + last = reader.readLine(); + if (last != null && last.length() > 0) { + containerId = last; + } + } + } catch (Exception e) { + System.out.println("\n\nbuild/upgrade/container.id is valid: " + e.getMessage()); + } + + if (containerId != null && containerId.length() > 0) { + + /** + * copy sidecar mod file + */ + Command cp = new Command(); + + File remote = new File(project, "build/upgrade/remote.mod"); + if (remote.exists()) remote.delete(); + + ArrayList exec = new ArrayList<>(); + exec.add("docker"); + exec.add("cp"); + exec.add(containerId + ":/home/admin/mosn/base_conf/mosn.gmd"); + exec.add(remote.getAbsolutePath()); + + cp.exec = exec; + cp.title = CustomCommandUtil.CUSTOM_CONSOLE; + + String finalContainerId = containerId; + cp.callback = s -> { + File updateMod = new File(project, "build/upgrade/remote.mod"); + try { + if (s != 0 || !updateMod.exists()) { + /** + * copy file failed. + */ + + System.out.println("\ncopy upgrade dependency failed, please try again."); + this.notifyQuit(); + + return; + } + + + System.out.println("\ncopy upgrade dependency complete"); + + /** + * ready to upgrade + */ + + try { + bundle.resetTaskStatus(PluginStatus.COMMAND_RUNNING); + readyDeployPlugin(bundle); + } catch (Exception e) { + System.out.println("exception caught: " + e.getMessage()); + + this.notifyQuit(); + } + + } finally { + removeContainer(exec, finalContainerId); + } + }; + + + TerminalCompiler.compile(project, cp); + + } + }; + TerminalCompiler.compile(project, command); + } + + @Nullable + protected String checkUpgradeVersion(String updateVersion, List plugins, PluginBundle deployBundle) { + /** + * check upgrade version + */ + for (PluginBundle.Plugin plugin : plugins) { + if (plugin.getRevision() != null + && plugin.getRevision().equals(updateVersion)) { + return "The upgraded version '" + updateVersion + "' must be different from the older version '" + plugin.getRevision() + "'"; + } + } + return null; + } + + protected void deployPlugins(PluginBundle deployBundle) { + try { + deployBundle.setTerminal(true); + deployBundle.resetTaskStatus(PluginStatus.COMMAND_RUNNING); + readyDeployPlugin(deployBundle); + } catch (Exception e) { + System.err.println("exception caught: " + e.getMessage()); + this.retryPluginAction(); + } + } + + protected void readyDeployPlugin(PluginBundle bundle) { + + /** + * + * current running in the awt thread. + * + * update dependencies first. + */ + + ProjectMod current; + + if (this.mode == DeployMode.Upgrade) { + File upgradeMod = new File(project, "build/upgrade/remote.mod"); + if (!upgradeMod.exists()) { + System.err.println("missing upgrade file 'build/upgrade/remote.mod'"); + notifyQuit(); + return; + } + + File currentMod = new File(project, "go.mod"); + if (!currentMod.exists()) { + notifyQuit(); + return; + } + + current = new ProjectMod(project, "go.mod"); + ProjectMod upgrade = new ProjectMod(project, "build/upgrade/remote.mod"); + /** + * refresh project dependencies + */ + try { + current.merge(upgrade); + + /** + * flush mod dependency + */ + current.flush(); + } catch (Exception e) { + System.err.println("\nfailed update project go.mod"); + notifyQuit(); + return; + } + } else { + current = new ProjectMod(project, "go.mod"); + current.readFile(); + } + + int ownerPlugins = 0; + + for (PluginBundle.Plugin plugin : bundle.getBundles()) { + + if (plugin.getStatus() == null + || PluginStatus.INIT.equals(plugin.getStatus())) { + plugin.setStatus(PluginStatus.COMMAND_RUNNING); + } + + plugin.setVersion(this.upgradeVersion.trim()); + plugin.setFullName(plugin.getName() + ( + plugin.getVersion() == null + ? ".zip" : ("-" + plugin.getVersion())) + ".zip"); + + File file = null; + switch (plugin.getKind()) { + case PluginBundle.KIND_FILTER: { + file = new File(project, "configs/stream_filters/" + plugin.getName() + "/metadata.json"); + break; + } + case PluginBundle.KIND_TRANSCODER: { + file = new File(project, "configs/transcoders/" + plugin.getName() + "/metadata.json"); + break; + } + case PluginBundle.KIND_TRACE: { + file = new File(project, "configs/traces/" + plugin.getName() + "/metadata.json"); + break; + } + case PluginBundle.KIND_PROTOCOL: { + file = new File(project, "configs/codecs/" + plugin.getName() + "/metadata.json"); + break; + } + } + + plugin.setOwner(file != null && file.exists()); + + if (file != null) { + + if (!plugin.getOwner()) continue; + + ownerPlugins++; + boolean shouldUpdate = false; + + /** + * update dependencies( api and pkg ) + */ + try (FileInputStream in = new FileInputStream(file)) { + byte[] bytes = in.readAllBytes(); + if (bytes != null) { + PluginMetadata metadata = JSON.parseObject(new ByteArrayInputStream(bytes), PluginMetadata.class); + plugin.setMetadata(metadata); + + String mApi = metadata.getDependencies().get("mosn_api"); + String mPkg = metadata.getDependencies().get("mosn_pkg"); + + /** + * api or pkg changed, update metadata.json + */ + if ((mApi != null && current.getApi() != null && !current.getApi().equals(mApi)) + || (mPkg != null && current.getPkg() != null && !current.getPkg().equals(mPkg))) { + metadata.getDependencies().put("mosn_api", current.getApi()); + metadata.getDependencies().put("mosn_pkg", current.getPkg()); + + shouldUpdate = true; + } + + if (plugin.getDependency() == null) { + plugin.setDependency(new HashMap<>()); + plugin.getDependency().putAll(metadata.getDependencies()); + } + + /** + * insert protocol port + */ + if (PluginBundle.KIND_PROTOCOL.equals(plugin.getKind())) { + + if (metadata.getExtension() == null) { + metadata.setExtension(new HashMap<>()); + } + + File proto = null; + if ("X".equals(metadata.getFramework())) { + proto = new File(project, "configs/codecs/" + plugin.getName() + "/egress_" + plugin.getName() + ".json"); + } else if ("HTTP1".equals(metadata.getFramework())) { + proto = new File(project, "configs/codecs/" + plugin.getName() + "/egress_" + plugin.getName() + ".json"); + + /** + * failover egress_http format + */ + if (!proto.exists()) { + proto = new File(project, "configs/codecs/" + plugin.getName() + "/egress_http.json"); + } + } + + if (proto != null && proto.exists()) { + + try (FileInputStream fin = new FileInputStream(proto)) { + Object object = JSON.parseObject(fin, Map.class); + if (object instanceof Map && ((Map) object).containsKey("address")) { + Object address = ((Map) object).get("address"); + String ingressPort = String.valueOf(address); + int index = ingressPort.indexOf(":"); + if (index > 0) { + ingressPort = ingressPort.substring(index + 1); + } + + metadata.getExtension().put("ingressPort", ingressPort); + } + } + } + + if ("X".equals(metadata.getFramework())) { + proto = new File(project, "configs/codecs/" + plugin.getName() + "/ingress_" + plugin.getName() + ".json"); + } else if ("HTTP1".equals(metadata.getFramework())) { + proto = new File(project, "configs/codecs/" + plugin.getName() + "/ingress_" + plugin.getName() + ".json"); + + if (!proto.exists()) { + proto = new File(project, "configs/codecs/" + plugin.getName() + "/ingress_http.json"); + } + } + + if (proto != null && proto.exists()) { + + try (FileInputStream fin = new FileInputStream(proto)) { + Object object = JSON.parseObject(fin, Map.class); + if (object instanceof Map && ((Map) object).containsKey("address")) { + Object address = ((Map) object).get("address"); + String egressPort = String.valueOf(address); + int index = egressPort.indexOf(":"); + if (index > 0) { + egressPort = egressPort.substring(index + 1); + } + + metadata.getExtension().put("egressPort", egressPort); + } + } + } + + } + } + } catch (Exception e) { + // dump console ? + e.printStackTrace(); + throw new RuntimeException("failed read plugin '" + plugin.getName() + "' metadata", e); + } + + if (shouldUpdate) { + try (FileOutputStream out = new FileOutputStream(file)) { + byte[] bytes = JSON.toJSONBytes(plugin.getMetadata()); + if (bytes != null) { + out.write(bytes); + } + } catch (Exception e) { + // dump console ? + e.printStackTrace(); + + throw new RuntimeException("failed flush plugin '" + plugin.getName() + "' metadata", e); + } + } + } + } + + /** + * now we prepare create compile or package plugin command + */ + AtomicInteger waitingCommands = new AtomicInteger(); + for (PluginBundle.Plugin plugin : bundle.getBundles()) { + if (plugin.getOwner()) { + + /** + * create compile or package command + */ + + if (plugin.getCommands() == null) { + plugin.setCommands(new ArrayList<>()); + + String os = System.getProperty("os.name"); + if (os != null && !os.toLowerCase().contains("mac")) { + plugin.getCommands().add(CommandBuilder.createCompileCommand(plugin)); + + waitingCommands.incrementAndGet(); + } + + /** + * Mac machines compile amd plugin packages automatically, so skip compilation + */ + plugin.getCommands().add(CommandBuilder.createPackageCommand(project, plugin)); + waitingCommands.incrementAndGet(); + + } else { + waitingCommands.addAndGet(plugin.getCommands().size()); + } + + } + } + + if (ownerPlugins > 0) { + this.queue.offerLast(() -> { + System.out.println(bundle.renderWithCommand(false)); + System.out.println("\n\nplease wait build or package plugins..."); + }); + } + + AtomicReference runningTimeout = new AtomicReference<>(); + Timeout timeout = TimerHolder.getTimer().newTimeout(tt -> { + if (tt.isCancelled()) { + return; + } + + if (bundle.commandComplete()) { + tt.cancel(); + + return; + } + + this.queue.offerLast(() -> { + + /** + * clear screen + */ + clearConsole(); + + System.out.println(bundle.renderWithCommand(false)); + System.out.println("\n\nPlease wait build or package plugins..."); + }); + + /** + * schedule next time + * + */ + runningTimeout.set(tt.timer().newTimeout(tt.task(), 1, TimeUnit.SECONDS)); + + }, 1, TimeUnit.SECONDS); + runningTimeout.set(timeout); + + /** + * ready to run command + */ + List failedCmd = new CopyOnWriteArrayList<>(); + for (PluginBundle.Plugin plugin : bundle.getBundles()) { + if (plugin.getOwner()) { + for (Command command : plugin.getCommands()) { + + command.resetStatus(); + + if (command.getCallback() == null) { + command.callback = status -> { + + if (status != 0) { + plugin.setStatus(PluginStatus.FAIL); + + failedCmd.add(command); + } + + command.setStatus(status == 0 ? PluginStatus.SUCCESS : PluginStatus.FAIL); + + /** + * notify update bundle status + */ + + this.queue.offerLast(() -> { + + /** + * clear screen + */ + clearConsole(); + + System.out.println(bundle.renderWithCommand(false)); + System.out.println("\n\nPlease wait build or package plugins..."); + }); + + /** + * notify task complete + */ + int remain = waitingCommands.decrementAndGet(); + if (remain <= 0) { + + /** + * cancel scheduled render + */ + runningTimeout.get().cancel(); + + /** + * update bundle render + * + */ + + this.queue.offerLast(() -> { + + /** + * clear screen + */ + clearConsole(); + + System.out.println(bundle.renderWithCommand(true)); + }); + + /** + * task already failed + */ + if (!bundle.commandComplete()) { + System.out.println("\n\nThe current plugin failed to compile or package. Please try again"); + + /** + * dump failed task stack: + */ + if (!failedCmd.isEmpty()) { + for (Command cmd : failedCmd) { + if (cmd.output != null && !cmd.output.isEmpty()) { + System.out.println(cmd); + for (String line : cmd.output) { + System.out.println(line); + } + } + } + } + + this.notifyQuit(); + + return; + } + + System.out.println("\n\nPlease wait upload plugins..."); + + TerminalCompiler.submit(() -> { + + /** + * upload plugins + */ + + uploadPlugins(bundle); + + }); + + + } + }; + } + } + + TerminalCompiler.compile(project, plugin); + } + } + } + + protected void uploadPlugins(PluginBundle bundle) { + + List filters = new ArrayList<>(); + List transcoders = new ArrayList<>(); + List codecs = new ArrayList<>(); + List traces = new ArrayList<>(); + for (PluginBundle.Plugin plugin : bundle.getBundles()) { + + /** + * skip not current project plugin + */ + if (!plugin.getOwner()) continue; + + switch (plugin.getKind()) { + case PluginBundle.KIND_FILTER: + filters.add(plugin); + break; + case PluginBundle.KIND_TRANSCODER: + transcoders.add(plugin); + break; + case PluginBundle.KIND_PROTOCOL: + codecs.add(plugin); + break; + case PluginBundle.KIND_TRACE: + traces.add(plugin); + break; + } + } + + List sortedPlugins = new ArrayList<>(); + sortedPlugins.addAll(filters); + sortedPlugins.addAll(transcoders); + sortedPlugins.addAll(codecs); + sortedPlugins.addAll(traces); + + AtomicReference taskTimeout = new AtomicReference<>(); + Timeout timeout = TimerHolder.getTimer().newTimeout(tt -> { + if (tt.isCancelled()) { + return; + } + + if (bundle.bundleComplete()) { + tt.cancel(); + + return; + } + + + queue.offerLast(() -> { + + /** + * clear screen + */ + clearConsole(); + + System.out.println(bundle.renderAllTasks(false)); + System.out.println("\n\nPlease wait upload plugins..."); + }); + + + /** + * schedule next time + * + */ + taskTimeout.set(tt.timer().newTimeout(tt.task(), 1, TimeUnit.SECONDS)); + + }, 1, TimeUnit.SECONDS); + taskTimeout.set(timeout); + + /** + * invoke sys_init_task + * @see InitTaskFactory + */ + + String initTask = bundle.renderInitTask(); + Protocol.Request initTaskRequest = RequestBuilder.newInitTaskRequest(project); + initTaskRequest.appendHead(Protocol.WrapCommand.RULE_ID, bundle.getRuleId()); + initTaskRequest.appendHead(Protocol.WrapCommand.UPGRADE_ID, bundle.getUpgradeId()); + initTaskRequest.setContents(initTask.getBytes()); + + RpcClient rpc = null; + + try { + + if (initTaskRequest.getInstanceId() == null || initTaskRequest.getInstanceId().length() == 0) { + throw new IllegalArgumentException("instance id not found"); + } + + rpc = RpcClient.getClient(this.address); + + /** + * register disconnect callback + */ + rpc.addCloseCallback(() -> taskTimeout.get().cancel()); + + /** + * set running + */ + bundle.resetTaskStatus(PluginStatus.COMMAND_RUNNING); + + Protocol.Response initTaskResponse = rpc.request(initTaskRequest); + if (!updateTaskStatus(bundle, initTaskResponse)) taskTimeout.get().cancel(); + + if (!initTaskResponse.isSuccess()) { + bundle.updateTaskStatus(PluginStatus.FAIL); + + /** + * cancel task + */ + taskTimeout.get().cancel(); + + this.queue.offerLast(() -> { + + /** + * clear screen + */ + clearConsole(); + + System.out.println(bundle.renderAllTasks(true)); + + System.out.println("\n"); + System.out.println("init task failed:\n"); + + displayErrorStack(initTaskResponse); + }); + + this.retryPluginAction(); + + return; + } + + bundle.setTaskId(initTaskResponse.getTaskId()); + + /** + * invoke sys_upload_file + * @see UploadFileFactory + */ + String root = project; + + for (PluginBundle.Plugin plugin : sortedPlugins) { + + plugin.setStart(System.currentTimeMillis()); + + /** + * check file exist first + */ + + File pluginFile = null; + String prefix = getZipDir(); + + switch (plugin.getKind()) { + case PluginBundle.KIND_FILTER: + pluginFile = new File(root, prefix + "stream_filters/" + plugin.getFullName()); + break; + case PluginBundle.KIND_TRANSCODER: + pluginFile = new File(root, prefix + "transcoders/" + plugin.getFullName()); + break; + case PluginBundle.KIND_PROTOCOL: + pluginFile = new File(root, prefix + "codecs/" + plugin.getFullName()); + break; + case PluginBundle.KIND_TRACE: + pluginFile = new File(root, prefix + "traces/" + plugin.getFullName()); + break; + } + + if (pluginFile == null || !pluginFile.exists()) { + plugin.setStatus(PluginStatus.FAIL); + taskTimeout.get().cancel(); + + this.queue.offerLast(() -> { + + /** + * clear screen + */ + clearConsole(); + + System.out.println(bundle.renderAllTasks(true)); + + System.out.println("\n"); + System.out.println("plugin file '" + plugin.getFullName() + "' not found\n"); + }); + + this.retryPluginAction(); + + return; + } + + /** + * build plugin upload request + */ + long fileLength = pluginFile.length(); + Protocol.Request uploadRequest = RequestBuilder.newUploadTaskRequest(project); + + uploadRequest.appendHead(Protocol.WrapCommand.FILE_OFFSET, "0"); + uploadRequest.appendHead(Protocol.WrapCommand.FILE_LENGTH, String.valueOf(fileLength)); + uploadRequest.appendHead(Protocol.WrapCommand.FILE_NAME, plugin.getFullName()); + uploadRequest.appendHead(Protocol.WrapCommand.FILE_VERSION, plugin.getVersion()); + uploadRequest.appendHead(Protocol.WrapCommand.METADATA + , URLEncoder.encode(JSON.toJSONString(plugin.getMetadata()) + , Charset.defaultCharset())); + + try (FileInputStream in = new FileInputStream(pluginFile)) { + byte[] pluginBytes = in.readAllBytes(); + uploadRequest.setContents(pluginBytes); + } + + Protocol.Response uploadResponse = rpc.request(uploadRequest); + + if (!updateTaskStatus(bundle, uploadResponse)) taskTimeout.get().cancel(); + plugin.setStop(System.currentTimeMillis()); + + if (!uploadResponse.isSuccess()) { + bundle.updateTaskStatus(PluginStatus.FAIL); + taskTimeout.get().cancel(); + + + this.queue.offerLast(() -> { + + /** + * clear screen + */ + clearConsole(); + + System.out.println(bundle.renderAllTasks(true)); + + System.out.println("\n"); + System.out.println("Upload file failed:\n"); + + displayErrorStack(uploadResponse); + }); + + this.retryPluginAction(); + return; + } + } + + /** + * sys_commit_task + * + * @see CommitTaskFactory + */ + + Protocol.Request commitRequest = RequestBuilder.newCommitTaskRequest(project); + Protocol.Response commitResponse = rpc.request(commitRequest); + + updateTaskStatus(bundle, commitResponse); + taskTimeout.get().cancel(); + + if (!commitResponse.isSuccess()) { + bundle.updateTaskStatus(PluginStatus.FAIL); + taskTimeout.get().cancel(); + + + this.queue.offerLast(() -> { + + /** + * clear screen + */ + clearConsole(); + + System.out.println(bundle.renderAllTasks(true)); + + System.out.println("\n"); + System.out.println("commit task failed:\n"); + + displayErrorStack(commitResponse); + }); + + this.retryPluginAction(); + return; + } + + + /** + * clear screen + */ + clearConsole(); + + System.out.println(bundle.renderAllTasks(true)); + + } catch (Exception e) { + + this.queue.offerLast(() -> { + + /** + * clear screen + */ + clearConsole(); + + System.out.println(bundle.renderAllTasks(true)); + + System.out.println("\n"); + System.out.println("deploy failed:\n"); + + System.out.println(e.getMessage()); + }); + + } finally { + taskTimeout.get().cancel(); + if (rpc != null) { + rpc.destroy(); + } + + this.retryPluginAction(); + } + + } + + protected String getZipDir() { + return "build/target/"; + } + + private boolean updateTaskStatus(PluginBundle bundle, Protocol.Response response) { + if (response.getContents() != null) { + try { + List remotePlugins = JSON.parseArray(new String(response.getContents()), PluginBundle.Plugin.class); + /** + * update remote bundle status + */ + for (PluginBundle.Plugin plugin : bundle.getBundles()) { + for (PluginBundle.Plugin remote : remotePlugins) { + if (plugin.getKind().equals(remote.getKind()) + && plugin.getName().equals(remote.getName())) { + plugin.setStatus(remote.getStatus()); + } + } + } + } catch (Exception e) { + + System.out.println("\nerror:\n"); + System.out.println(e.getMessage()); + + return false; + } + + } + + return true; + } + + private void displayErrorStack(Protocol.Response response) { + /** + * rpc request failed. + */ + + String desc = response.getHeaders().get(Protocol.WrapCommand.DESCRIPTION); + String stack = response.getHeaders().get(Protocol.WrapCommand.STACK); + + if (desc != null) { + + System.out.println("\nmessage: " + desc); + + if (stack != null) { + System.out.println("\nstack: "); + System.out.println("\n" + stack); + } + + } + } + + public void retryPluginAction() { + /** + * quit main thread. + */ + queue.offerLast(quit); + } + + protected void notifyQuit() { + this.retryPluginAction(); + } + + private void removeContainer(ArrayList exec, String finalContainerId) { + /** + * remove container id first + */ + Command rm = new Command(); + + ArrayList run = new ArrayList<>(); + run.add("docker"); + run.add("container"); + run.add("rm"); + run.add(finalContainerId); + rm.exec = exec; + rm.title = CustomCommandUtil.CUSTOM_CONSOLE; + TerminalCompiler.compile(project, rm); + } + + private static void clearConsole() { + System.out.println("\033[H\033[2J"); + System.out.flush(); + } + + protected void waitQuit() { + /** + * main thread running task + */ + try { + Runnable task; + while (true) { + task = queue.takeFirst(); + + if (task == quit) { + break; + } + + task.run(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/io/mosn/coder/cli/Cli.java b/src/main/java/io/mosn/coder/cli/Cli.java new file mode 100644 index 0000000..0b6e9cf --- /dev/null +++ b/src/main/java/io/mosn/coder/cli/Cli.java @@ -0,0 +1,43 @@ +package io.mosn.coder.cli; + + +import io.mosn.coder.cli.minimesh.CliMiniMesh; +import io.mosn.coder.cli.offline.CliOfflineDeploy; +import io.mosn.coder.cli.offline.CliOfflineUpgrade; + +/** + * @author yiji@apache.org + */ +@CommandLine.Command(name = "sofactl", mixinStandardHelpOptions = true, sortOptions = false, + version = "sofactl version 1.0.13", + description = "mosn plugin code generator", + commandListHeading = "%nCommands:%n%nThe most commonly used mecha commands are:%n", + footer = "%nSee 'sofactl help ' to read about a specific subcommand or concept.", + subcommands = { + CliProtocol.class, + CliFilter.class, + CliTranscoder.class, + CliDeploy.class, + CliUpgrade.class, + CliRefresh.class, + CliOfflineDeploy.class, + CliOfflineUpgrade.class, + CliMiniMesh.class, + CommandLine.HelpCommand.class + }) +public class Cli implements Runnable { + + @CommandLine.Spec + CommandLine.Model.CommandSpec spec; + + @Override + public void run() { + // if the command was invoked without subcommand, show the usage help + spec.commandLine().usage(System.err); + } + + public static void main(String[] args) { + System.exit(new CommandLine(new Cli()).execute(args)); + } + +} diff --git a/src/main/java/io/mosn/coder/cli/CliDeploy.java b/src/main/java/io/mosn/coder/cli/CliDeploy.java new file mode 100644 index 0000000..ffed024 --- /dev/null +++ b/src/main/java/io/mosn/coder/cli/CliDeploy.java @@ -0,0 +1,216 @@ +package io.mosn.coder.cli; + +import io.mosn.coder.intellij.option.PluginType; +import io.mosn.coder.intellij.template.VersionTemplate; +import io.mosn.coder.plugin.model.PluginBundle; + +import java.io.File; +import java.io.FileInputStream; +import java.util.ArrayList; + +import static io.mosn.coder.common.DirUtils.*; + +/** + * @author yiji@apache.org + */ + +@CommandLine.Command(name = "deploy", + sortOptions = false, + headerHeading = "@|bold,underline Usage:|@%n%n", + synopsisHeading = "%n", + descriptionHeading = "%n@|bold,underline Description:|@%n%n", + parameterListHeading = "%n@|bold,underline Parameters:|@%n", + optionListHeading = "%n@|bold,underline Options:|@%n", + header = "Deploy plugin package.", + description = "Automatically deploy plugin package.") +public class CliDeploy extends BaseDeploy implements Runnable { + + @CommandLine.Option(required = true, names = {"--project-dir", "-d"}, paramLabel = "", description = "Set the path to the project including project name") + String path; + + @CommandLine.Option(names = {"--kind", "-k"}, description = "deploy single package or all packages, format: (protocol|filter|transcoder)[s]|all") + String kind; + + + @CommandLine.Option(names = {"--plugin", "-p"}, description = "Set the plugin name") + String plugin; + + @CommandLine.Option(names = {"--version", "-v"}, description = "Set the deploy plugin version") + String version; + + /** + * Supports independent zip package deployment or upgrade + */ + // @CommandLine.Option(names = {"--compile", "-c"}, description = "Set skip compile model, default true") + Boolean compile; + + @Override + public void run() { + this.project = path; + this.mode = DeployMode.Deploy; + + /** + * read user deploy version + */ + this.upgradeVersion = version; + + this.registerCallBack(project); + + /** + * prepare bundle + */ + + PluginBundle bundle = new PluginBundle(); + bundle.setBundles(new ArrayList<>()); + + switch (kind) { + + /** + * scan local plugins + */ + case "all": { + createAllPlugins(project, bundle); + break; + } + /** + * scan all protocol plugins + */ + case "protocols": { + createSameKindPlugins(project + "/" + ROOT_DIR + "/" + CODECS_DIR, PluginType.Protocol, bundle); + break; + } + /** + * scan all filter plugins + */ + case "filters": { + createSameKindPlugins(project + "/" + ROOT_DIR + "/" + STREAM_FILTERS_DIR, PluginType.Filter, bundle); + break; + } + /** + * scan all transcoder plugins + */ + case "transcoders": { + createSameKindPlugins(project + "/" + ROOT_DIR + "/" + TRANSCODER_DIR, PluginType.Transcoder, bundle); + break; + } + case "traces": { + createSameKindPlugins(project + "/" + ROOT_DIR + "/" + TRACE_DIR, PluginType.Trace, bundle); + break; + } + /** + * scan single filter plugin + */ + case PluginBundle.FILTER_ALIAS: { + createSingleCommand(bundle, PluginType.Filter, this.plugin); + break; + } + /** + * scan single transcoder plugin + */ + case PluginBundle.KIND_TRANSCODER: { + createSingleCommand(bundle, PluginType.Transcoder, this.plugin); + break; + } + case PluginBundle.KIND_TRACE: { + createSingleCommand(bundle, PluginType.Trace, this.plugin); + break; + } + /** + * scan single protocol plugin + */ + case PluginBundle.KIND_PROTOCOL: { + createSingleCommand(bundle, PluginType.Protocol, this.plugin); + break; + } + + default: { + System.err.println("unsupported kind '" + kind + "'"); + return; + } + } + + String message = this.deployOrUpgradePlugins(bundle); + if (message != null) { + System.err.println(message); + return; + } + + waitQuit(); + } + + protected void createAllPlugins(String project, PluginBundle bundle) { + File[] files = new File(project, ROOT_DIR).listFiles(); + if (files != null) { + for (File file : files) { + // same kind plugin + if (file.isDirectory() && isPluginDirectory(file.getName(), ROOT_DIR)) { + createSameKindPlugins(file.getPath(), pluginTypeOf(file.getName()), bundle); + } + } + } + } + + protected void createSameKindPlugins(String path, PluginType type, PluginBundle bundle) { + File[] files = new File(path).listFiles(); + if (files != null) { + for (File file : files) { + // plugin name + if (file.isDirectory()) { + createSingleCommand(bundle, type, file.getName()); + } + } + } + } + + protected void createSingleCommand(PluginBundle bundle, PluginType type, String name) { + + /** + * append plugin to current bundle + */ + + PluginBundle.Plugin plugin = new PluginBundle.Plugin(); + switch (type) { + + case Filter: { + plugin.setKind(PluginBundle.KIND_FILTER); + break; + } + case Transcoder: { + plugin.setKind(PluginBundle.KIND_TRANSCODER); + break; + } + case Trace: { + plugin.setKind(PluginBundle.KIND_TRACE); + break; + } + case Protocol: { + plugin.setKind(PluginBundle.KIND_PROTOCOL); + break; + } + } + + plugin.setOwner(true); + plugin.setName(name); + + /** + * update local plugin version + */ + File version = new File(path, VersionTemplate.Name); + if (version.exists()) { + try (FileInputStream in = new FileInputStream(version)) { + byte[] bytes = in.readAllBytes(); + if (bytes != null) { + String v = new String(bytes); + if (this.oldVersion == null) { + this.oldVersion = v; + } + plugin.setRevision(v); + } + } catch (Exception ignored) { + } + } + + if (plugin.getKind() != null) + bundle.getBundles().add(plugin); + } +} diff --git a/src/main/java/io/mosn/coder/cli/CliFilter.java b/src/main/java/io/mosn/coder/cli/CliFilter.java new file mode 100644 index 0000000..b024484 --- /dev/null +++ b/src/main/java/io/mosn/coder/cli/CliFilter.java @@ -0,0 +1,54 @@ +package io.mosn.coder.cli; + + +import io.mosn.coder.intellij.option.AbstractOption; +import io.mosn.coder.intellij.option.FilterOption; +import io.mosn.coder.intellij.option.PluginType; +import io.mosn.coder.intellij.roobot.CodeGenerator; + +import java.io.File; + +/** + * @author yiji@apache.org + */ + +@CommandLine.Command(name = "filter", + sortOptions = false, + headerHeading = "@|bold,underline Usage:|@%n%n", + synopsisHeading = "%n", + descriptionHeading = "%n@|bold,underline Description:|@%n%n", + parameterListHeading = "%n@|bold,underline Parameters:|@%n", + optionListHeading = "%n@|bold,underline Options:|@%n", + header = "Generate filter plugin code.", + description = "Automatically generate stream filter extension code.") +public class CliFilter extends BaseCli implements Runnable { + + @CommandLine.Option(required = true, names = {"--apply-side", "-s"}, description = "Set the effective mode, eg: client,server|all") + String side; + + @Override + public void run() { + + check(); + + FilterOption option = new FilterOption(); + option.setPluginName(this.plugin); + option.setOrganization(this.organization); + + option.setActiveMode( + this.side.contains("all") ? AbstractOption.ActiveMode.ALL : ( + this.side.contains("client") + ? AbstractOption.ActiveMode.Client : AbstractOption.ActiveMode.Server)); + + if (api != null) { + option.setApi(this.api); + } + + if (pkg != null) { + option.setPkg(this.pkg); + } + + CodeGenerator.createCliApplication(new File(path), PluginType.Filter, option); + + } +} \ No newline at end of file diff --git a/src/main/java/io/mosn/coder/cli/CliProtocol.java b/src/main/java/io/mosn/coder/cli/CliProtocol.java new file mode 100644 index 0000000..a53a237 --- /dev/null +++ b/src/main/java/io/mosn/coder/cli/CliProtocol.java @@ -0,0 +1,199 @@ +package io.mosn.coder.cli; + +import io.mosn.coder.intellij.internal.BoltOptionImpl; +import io.mosn.coder.intellij.internal.DubboOptionImpl; +import io.mosn.coder.intellij.internal.SpringCloudOptionImpl; +import io.mosn.coder.intellij.option.*; +import io.mosn.coder.intellij.roobot.CodeGenerator; + +import java.io.File; +import java.util.ArrayList; + +import static io.mosn.coder.intellij.option.AbstractOption.*; +import static io.mosn.coder.intellij.option.CodecType.FixedLength; +import static io.mosn.coder.intellij.util.Constants.COMMA_SPLIT_PATTERN; + +/** + * @author yiji@apache.org + */ + +@CommandLine.Command(name = "protocol", + sortOptions = false, + headerHeading = "@|bold,underline Usage:|@%n%n", + synopsisHeading = "%n", + descriptionHeading = "%n@|bold,underline Description:|@%n%n", + parameterListHeading = "%n@|bold,underline Parameters:|@%n", + optionListHeading = "%n@|bold,underline Options:|@%n", + header = "Generate protocol plugin code.", + description = "Automatically generate protocol extension code, including TCP and HTTP protocol types.") +public class CliProtocol extends BaseCli implements Runnable { + + @CommandLine.Option(names = {"--http", "-h"}, paramLabel = "", description = "Set http protocol type") + boolean http; + + @CommandLine.Option(names = {"--inject", "-i"}, paramLabel = "", description = "Inject decode http body code") + boolean inject; + + @CommandLine.Option(names = {"--server-port", "-s"}, paramLabel = "", description = "Set server port") + Integer serverPort; + + @CommandLine.Option(names = {"--client-port", "-c"}, required = true, paramLabel = "", description = "Set client port") + Integer clientPort; + + @CommandLine.Option(names = {"--pool-mode"}, required = true, paramLabel = "", description = "Set connection pool mode") + String mode; + + @CommandLine.Option(names = {"--request-id-string"}, paramLabel = "", description = "Set request id is string type") + boolean requestIdIsStringType; + + @CommandLine.Option(names = {"--standard-protocol"}, description = "Generate standard protocol, eg: dubbo,bolt,springcloud") + String standardProtocols; + + @CommandLine.Option(names = {"--codec"}, description = "Set protocol codec type, eg: none|FixedLength") + String codec; + + @CommandLine.Option(names = {"--fixed-length"}, description = "Set fixed byte length") + int fixedLength; + + @CommandLine.Option(names = {"--fixed-prefix"}, description = "Set codec prefix, default \"0\"") + String fixedPrefix; + + @CommandLine.Option(names = {"--service-key"}, required = true, defaultValue = "service", prompt = "service", description = "Set service key for sidecar") + String serviceKey; + + @CommandLine.Option(names = {"--service-method-key"}, description = "Set service method key for sidecar") + String serviceMethodKey; + + @CommandLine.Option(names = {"--service-trace-key"}, description = "Set service trace key for sidecar") + String serviceTraceKey; + + @CommandLine.Option(names = {"--service-span-key"}, description = "Set service span key for sidecar") + String serviceSpanKey; + + private PoolMode poolMode; + + private CodecType codecType; + + @Override + void check() { + super.check(); + + boolean bad = false; + + // pool mode + if (!this.mode.equalsIgnoreCase("Multiplex") + && !this.mode.equalsIgnoreCase("PingPong")) { + System.err.println("--pool-mode must be Multiplex or PingPong"); + bad = true; + } + + if (this.mode.equalsIgnoreCase("Multiplex")) { + poolMode = PoolMode.Multiplex; + } else if (this.mode.equalsIgnoreCase("PingPong")) { + poolMode = PoolMode.PingPong; + } + + // codec + if (this.codec != null && !"None".equalsIgnoreCase(this.codec) + && !"FixedLength".equalsIgnoreCase(this.codec)) { + System.err.println("--pool-mode must be None or FixedLength"); + bad = true; + } + + if (this.codec == null || this.codec.equalsIgnoreCase("None")) { + codecType = CodecType.Customize; + } else if (this.codec.equalsIgnoreCase("FixedLength")) { + codecType = CodecType.FixedLength; + } + + if (codecType == FixedLength) { + if (this.fixedLength <= 0) { + System.err.println("--fixed-length must be > 0 "); + bad = true; + } + } + + if (bad) { + System.exit(0); + } + } + + @Override + public void run() { + + /** + * check required field. + */ + check(); + + /** + * init protocol parameter + */ + ProtocolOption option = new ProtocolOption(); + + option.setPluginName(this.plugin); + option.setOrganization(this.organization); + + option.setHttp(this.http); + option.setInjectHead(this.inject); + + option.setServerPort(this.serverPort); + option.setClientPort(this.clientPort); + + if (!option.isHttp()) { + + option.setPoolMode(this.poolMode); + option.setStringRequestId(this.requestIdIsStringType); + + if (codecType == FixedLength) { + int length = this.fixedLength; + if (length > 0 && this.fixedPrefix == null) { + this.fixedPrefix = "0"; + option.setCodecOption( + new AbstractOption.CodecOption(true, length, this.fixedPrefix)); + } + } + } + + // check service key + String dataId = this.serviceKey; + + // service key + option.addRequired(X_MOSN_DATA_ID, COMMA_SPLIT_PATTERN.split(dataId)); + + if (this.serviceMethodKey != null) { + option.addOptional(X_MOSN_METHOD, COMMA_SPLIT_PATTERN.split(this.serviceMethodKey)); + } + if (this.serviceTraceKey != null) { + option.addOptional(X_MOSN_TRACE_ID, COMMA_SPLIT_PATTERN.split(this.serviceTraceKey)); + } + if (this.serviceSpanKey != null) { + option.addOptional(X_MOSN_SPAN_ID, COMMA_SPLIT_PATTERN.split(this.serviceSpanKey)); + } + + ArrayList opts = new ArrayList<>(); + + if (this.standardProtocols != null && this.standardProtocols.contains("bolt")) { + opts.add(new BoltOptionImpl()); + } + if (this.standardProtocols != null && this.standardProtocols.contains("dubbo")) { + opts.add(new DubboOptionImpl()); + } + if (this.standardProtocols != null && this.standardProtocols.contains("springcloud")) { + opts.add(new SpringCloudOptionImpl()); + } + + option.setEmbedded(opts); + + if (api != null) { + option.setApi(this.api); + } + + if (pkg != null) { + option.setPkg(this.pkg); + } + + CodeGenerator.createCliApplication(new File(path), PluginType.Protocol, option); + + } +} \ No newline at end of file diff --git a/src/main/java/io/mosn/coder/cli/CliRefresh.java b/src/main/java/io/mosn/coder/cli/CliRefresh.java new file mode 100644 index 0000000..46eaa50 --- /dev/null +++ b/src/main/java/io/mosn/coder/cli/CliRefresh.java @@ -0,0 +1,577 @@ +package io.mosn.coder.cli; + +import com.alibaba.fastjson.JSON; +import io.mosn.coder.compiler.Command; +import io.mosn.coder.compiler.TerminalCompiler; +import io.mosn.coder.intellij.option.ProtocolOption; +import io.mosn.coder.intellij.option.Source; +import io.mosn.coder.intellij.template.*; +import io.mosn.coder.intellij.util.FileWriter; +import io.mosn.coder.plugin.model.PluginMetadata; +import io.mosn.coder.plugin.model.PluginSimpleMetadata; +import io.mosn.coder.upgrade.ProjectMod; + +import java.io.*; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; + +import static io.mosn.coder.common.DirUtils.*; + +/** + * @author yiji@apache.org + */ + +@CommandLine.Command(name = "fresh", + sortOptions = false, + headerHeading = "@|bold,underline Usage:|@%n%n", + synopsisHeading = "%n", + descriptionHeading = "%n@|bold,underline Description:|@%n%n", + parameterListHeading = "%n@|bold,underline Parameters:|@%n", + optionListHeading = "%n@|bold,underline Options:|@%n", + header = "Auto upgrade plugin project.", + description = "Automatically fresh project code, including shell and configs.") +public class CliRefresh implements Runnable { + + class CliFreshInfo { + int count; + } + + @CommandLine.Option(required = true, names = {"--project-dir", "-d"}, paramLabel = "", description = "Set the path to the project including project name") + String path; + + @Override + public void run() { + + /** + * update plugin project + */ + + if (path != null) { + File mod = new File(path, "go.mod"); + + if (!mod.exists()) { + System.err.println("Invalid project path"); + System.exit(0); + } + + if (mod.exists()) { + /** + * project root + */ + + CliFreshInfo info = new CliFreshInfo(); + + refreshProject(path, info, new MakefileTemplate(), MakefileTemplate.Path, MakefileTemplate.Name, false); + refreshProject(path, info, new StartTemplate(), StartTemplate.Path, StartTemplate.Name, false); + refreshProject(path, info, new StopTemplate(), StopTemplate.Path, StopTemplate.Name, false); + + refreshProject(path, info, new ApplicationPropertyTemplate(), ApplicationPropertyTemplate.Path, ApplicationPropertyTemplate.Name, true); + refreshProject(path, info, new VersionTemplate(), VersionTemplate.Path, VersionTemplate.Name, true); + + // compile + refreshProject(path, info, new CompileTemplate(), CompileTemplate.Path, CompileTemplate.Name, false); + refreshProject(path, info, new CompileCodecTemplate(), CompileCodecTemplate.Path, CompileCodecTemplate.Name, false); + refreshProject(path, info, new CompileFilterTemplate(), CompileFilterTemplate.Path, CompileFilterTemplate.Name, false); + refreshProject(path, info, new CompileTranscoderTemplate(), CompileTranscoderTemplate.Path, CompileTranscoderTemplate.Name, false); + + // package + refreshProject(path, info, new PackageCodecTemplate(), PackageCodecTemplate.Path, PackageCodecTemplate.Name, false); + refreshProject(path, info, new PackageFilterTemplate(), PackageFilterTemplate.Path, PackageFilterTemplate.Name, false); + refreshProject(path, info, new PackageTranscoderTemplate(), PackageTranscoderTemplate.Path, PackageTranscoderTemplate.Name, false); + + // ignore + refreshProject(path, info, new GitIgnoreTemplate(), GitIgnoreTemplate.Path, GitIgnoreTemplate.Name, false); + + refreshRegistry(path, info); + + // upgrade project + recreateEnvConf(path, info); + + recreateImageFile(path, info); + recreateGoMod(path, info); + + displayFreshInfo(path, info); + + } + + } + } + + protected void refreshProject(String project, CliFreshInfo info, Template template, String path, String name, boolean onlyMissing) { + File file = new File(project, path + "/" + name); + + List code = template.create(null); + Source source = code.get(0); + + boolean exist = file.exists(); + + /** + * run runnable + */ + if (!exist && onlyMissing) { + + info.count++; + + FileWriter.writeAndFlush(new File(project), template.create(null)); + System.out.println("create " + name + " complete"); + + return; + } + + + if (exist) { + + /** + * record app exported port + */ + ProtocolOption option = null; + + try { + String prev = new String(Files.readAllBytes(file.toPath())); + if (prev.length() != source.getContent().length() + || !source.getContent().equals(prev)) { + + if (template instanceof StartTemplate) { + option = new ProtocolOption(); + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) { + String line; + do { + line = reader.readLine(); + + /** + * backup listener port + */ + if (line != null && line.contains("LISTENER_PORTS=")) { + String[] items = line.trim().split(" "); + for (String item : items) { + int index = item.indexOf(":"); + /** + * we found port mapping, eg: 12200:12200 + */ + if (index > 0) { + + item = item.trim(); + + + /** + * listener port for protocol + */ + ProtocolOption o = new ProtocolOption(); + o.setServerPort(Integer.parseInt(item.substring(0, index))); + + if (option.getListenerPort() == null) { + option.setListenerPort(new ArrayList<>()); + } + + option.getListenerPort().add(o); + + } + } + } + + /** + * backup export port + */ + if (line != null && line.contains("EXPORT_PORTS=")) { + String[] items = line.trim().split(" "); + for (String item : items) { + int index = item.indexOf(":"); + /** + * we found port mapping, eg: 12200:12200 + */ + if (index > 0) { + + item = item.trim(); + + + /** + * export port for protocol + */ + ProtocolOption o = new ProtocolOption(); + o.setServerPort(Integer.parseInt(item.substring(0, index))); + + if (option.getExportPort() == null) { + option.setExportPort(new ArrayList<>()); + } + + option.getExportPort().add(o); + + } + } + } + + /** + * append to buffer, when complete replaced file will be refreshed + */ + if (line != null && line.contains("BIZ_PORTS=")) { + String[] items = line.trim().split(" "); + for (String item : items) { + int index = item.indexOf(":"); + /** + * we found port mapping, eg: 12200:12200 + */ + if (index > 0) { + + item = item.trim(); + + + /** + * embed for protocol + */ + ProtocolOption o = new ProtocolOption(); + o.setServerPort(Integer.parseInt(item.substring(0, index))); + + if (option.getEmbedded() == null) { + option.setEmbedded(new ArrayList<>()); + } + + option.getEmbedded().add(o); + + } + } + + /** + * we stop reading start script + */ + break; + } + + } while (line != null); + + } catch (Exception e) { + e.printStackTrace(); + } + + + /** + * recreate start shell and compare again + */ + code = template.create(option); + source = code.get(0); + if (prev.length() != source.getContent().length() + || !source.getContent().equals(prev)) { + file.delete(); + } else { + /** + * file not changed + */ + return; + } + + } else { + /** + * other template just delete + */ + file.delete(); + } + } else { + return; + } + + } catch (IOException ex) { + System.out.println("failed to delete file " + name + ""); + return; + } + + info.count++; + + FileWriter.writeAndFlush(new File(project), code); + System.out.println("update " + name + " complete"); + } + + } + + private void refreshRegistry(String project, CliFreshInfo info) { + TerminalCompiler.submit(() -> { + if (project != null && project.length() > 0) { + /** + * Initialize the registry ahead of time + */ + // SubscribeConsoleAddress.getMeshServerAddress(project); + } + }); + } + + private void displayFreshInfo(String project, CliFreshInfo info) { + if (info.count <= 0) { + System.out.println("No need to update"); + } + } + + private static void recreateEnvConf(String path, CliFreshInfo info) { + File file = new File(path, EnvConfTemplate.Path + "/" + EnvConfTemplate.Name); + + EnvConfTemplate template = new EnvConfTemplate(); + List code = template.create(null); + + if (file.exists()) { + try { + + boolean debugMode = false; + boolean openapi = false; + + StringBuilder buffer = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) { + String line; + do { + line = reader.readLine(); + /** + * append to buffer, when complete replaced file will be refreshed + */ + if (line != null) { + + /** + * check DEBUG_MODE exist + */ + if (!debugMode && line.contains("DEBUG_MODE")) { + debugMode = true; + } else if (!openapi && line.contains("MOSN_FEATURE_OPENAPI_ENABLE=false")) { + openapi = true; + + /** + * just replace + */ + line = line.replace("MOSN_FEATURE_OPENAPI_ENABLE=false,", ""); + } + + buffer.append(line).append("\n"); + } + + } while (line != null); + + } catch (Exception ignored) { + } + + if (!debugMode) { + buffer.append("DEBUG_MODE=true").append("\n"); + } + + if (!debugMode || openapi) { + + if (buffer.length() > 0) { + byte[] content = buffer.toString().getBytes(); + try (OutputStream stream = new FileOutputStream(file)) { + stream.write(content); + } + } + + } else { + // file not changed. + return; + } + + } catch (IOException ex) { + System.err.println("failed to update file " + EnvConfTemplate.Name + ""); + return; + } + } + + info.count++; + System.out.println("update " + EnvConfTemplate.Name + " complete"); + } + + private static void recreateImageFile(String path, CliFreshInfo info) { + File file = new File(path, DockerfileTemplate.Path + "/" + DockerfileTemplate.Name); + + DockerfileTemplate template = new DockerfileTemplate(); + List code = template.create(null); + Source source = code.get(0); + + if (file.exists()) { + try { + String prev = new String(Files.readAllBytes(file.toPath())); + if (prev.length() != source.getContent().length() + || !source.getContent().equals(prev)) { + file.delete(); + } else { + // file not changed. + return; + } + + } catch (IOException ex) { + System.err.println("failed to delete file " + DockerfileTemplate.Name + ""); + return; + } + } + + info.count++; + + FileWriter.writeAndFlush(new File(path), code); + System.out.println("update " + DockerfileTemplate.Name + " complete"); + } + + private void recreateGoMod(String path, CliFreshInfo info) { + File file = new File(path, "go.mod"); + + /** + * local.mod exist + */ + ProjectMod current = new ProjectMod(path, "go.mod"); + ProjectMod upgrade = new ProjectMod(path, "build/sidecar/binary/local.mod"); + /** + * refresh project dependencies + */ + try { + + /** + * execute go mod tidy first + */ + ProjectMod tidyMod = new ProjectMod(path, "go.mod"); + tidyMod.readFile(); + if (tidyMod.getGoVersion() != null) { + String go = tidyMod.getGoVersion().trim(); + go = go.substring(2).trim(); // version + + int index = go.indexOf("."); + if (index > 0) { + int major = 0, minor = 0; + try { + major = Integer.parseInt(go.substring(0, index)); + } catch (Exception ignored) { + } + + try { + minor = Integer.parseInt(go.substring(index)); + } catch (Exception ignored) { + } + + if (major == 1 && minor < 18) { + tidyMod.setGoVersion("go 1.18"); + } + + tidyMod.prepareFlush(null); + tidyMod.flush(); + } + } + + /** + * run go mod tidy first + */ + Command command = new Command(); + ArrayList exec = new ArrayList<>(); + exec.add("go"); + exec.add("mod"); + exec.add("tidy"); + command.exec = exec; + + command.callback = status -> { + if (status != 0) { + System.err.println("run to mod tidy failed"); + + if (command.output != null && !command.output.isEmpty()){ + for (String line: command.output){ + System.err.println(line); + } + } + + return; + } + + try { + + if (new File(path, "build/sidecar/binary/local.mod").exists()) { + current.merge(upgrade); + + String updateText = current.getBufferText(); + String prev = new String(Files.readAllBytes(file.toPath())); + if (!updateText.equals(prev)) { + + file.delete(); + + info.count++; + + /** + * flush mod dependency + */ + current.flush(); + + /** + * update all configs metadata ? + */ + File conf = new File(path, ROOT_CONFIG_DIR); + if (conf.exists()) { + File[] dirs = conf.listFiles(); + for (File dir : dirs) { + if (isPluginTypeDir(dir.getName())) { + switch (dir.getName()) { + case CODECS_DIR: { + + File[] protos = dir.listFiles(); + for (File proto : protos) { + File metadata = new File(proto.getAbsolutePath(), "metadata.json"); + if (metadata.exists()) { + PluginMetadata pm = JSON.parseObject(new FileInputStream(metadata), PluginMetadata.class); + updateMetadata(metadata, pm, current); + } + } + + break; + } + case STREAM_FILTERS_DIR: + case TRANSCODER_DIR: + case TRACE_DIR: { + + File[] plugins = dir.listFiles(); + for (File plugin : plugins) { + File metadata = new File(plugin.getAbsolutePath(), "metadata.json"); + if (metadata.exists()) { + PluginSimpleMetadata pm = JSON.parseObject(new FileInputStream(metadata), PluginSimpleMetadata.class); + updateMetadata(metadata, pm, current); + } + } + + break; + } + } + } + } + } + + System.out.println("update go.mod complete"); + } + } + + System.out.println("go mod tidy complete"); + + } catch (Exception ignored) { + System.err.println("failed to update local go.mod "); + } + }; + + TerminalCompiler.runCommand(path, command); + + } catch (Exception ex) { + System.err.println("failed to update file go.mod "); + } + } + + private void updateMetadata(File metadata, PluginSimpleMetadata pm, ProjectMod current) throws IOException { + String mApi = pm.getDependencies().get("mosn_api"); + String mPkg = pm.getDependencies().get("mosn_pkg"); + + if (mApi != null && !mApi.equals(current.getApi()) || + mPkg != null && !mPkg.equals(current.getPkg())) { + pm.getDependencies().put("mosn_api", current.getApi()); + pm.getDependencies().put("mosn_pkg", current.getPkg()); + /** + * flush metadata + */ + try (FileOutputStream out = new FileOutputStream(metadata)) { + out.write(JSON.toJSONString(pm, true).getBytes()); + out.flush(); + } + } + } + + private static ReplaceAction replaceFutureGate() { + return (line, options) -> { + + String override = line.replace("MOSN_FEATURE_OPENAPI_ENABLE=false,", ""); + // return replaced text line + return TextLine.Terminate.with(override); + }; + } + +} diff --git a/src/main/java/io/mosn/coder/cli/CliTranscoder.java b/src/main/java/io/mosn/coder/cli/CliTranscoder.java new file mode 100644 index 0000000..59eecc5 --- /dev/null +++ b/src/main/java/io/mosn/coder/cli/CliTranscoder.java @@ -0,0 +1,64 @@ +package io.mosn.coder.cli; + + +import io.mosn.coder.intellij.option.AbstractOption; +import io.mosn.coder.intellij.option.PluginType; +import io.mosn.coder.intellij.option.TranscoderOption; +import io.mosn.coder.intellij.roobot.CodeGenerator; + +import java.io.File; + +/** + * @author yiji@apache.org + */ + +@CommandLine.Command(name = "trans", + aliases = {"transcoder"}, + sortOptions = false, + headerHeading = "@|bold,underline Usage:|@%n%n", + synopsisHeading = "%n", + descriptionHeading = "%n@|bold,underline Description:|@%n%n", + parameterListHeading = "%n@|bold,underline Parameters:|@%n", + optionListHeading = "%n@|bold,underline Options:|@%n", + header = "Generate transcoder plugin code.", + description = "Automatically generate transcoder extension code.") +public class CliTranscoder extends BaseCli implements Runnable { + + @CommandLine.Option(required = true, names = {"--apply-side", "-s"}, description = "Set the effective mode, eg: client|server") + String side; + + @CommandLine.Option(required = true, names = {"--src-protocol"}, description = "Set the source protocol name") + String srcProtocol; + + @CommandLine.Option(required = true, names = {"--dst-protocol"}, description = "Set the destination protocol name") + String dstProtocol; + + @Override + public void run() { + + check(); + + TranscoderOption option = new TranscoderOption(); + option.setPluginName(this.plugin); + option.setOrganization(this.organization); + + option.setActiveMode( + this.side.contains("client") + ? AbstractOption.ActiveMode.Client : AbstractOption.ActiveMode.Server); + + option.setSrcProtocol(this.srcProtocol); + option.setDstProtocol(this.dstProtocol); + + if (api != null) { + option.setApi(this.api); + } + + if (pkg != null) { + option.setPkg(this.pkg); + } + + + CodeGenerator.createCliApplication(new File(path), PluginType.Transcoder, option); + + } +} \ No newline at end of file diff --git a/src/main/java/io/mosn/coder/cli/CliUpgrade.java b/src/main/java/io/mosn/coder/cli/CliUpgrade.java new file mode 100644 index 0000000..64541ae --- /dev/null +++ b/src/main/java/io/mosn/coder/cli/CliUpgrade.java @@ -0,0 +1,45 @@ +package io.mosn.coder.cli; + +/** + * @author yiji@apache.org + */ + +@CommandLine.Command(name = "upgrade", + sortOptions = false, + headerHeading = "@|bold,underline Usage:|@%n%n", + synopsisHeading = "%n", + descriptionHeading = "%n@|bold,underline Description:|@%n%n", + parameterListHeading = "%n@|bold,underline Parameters:|@%n", + optionListHeading = "%n@|bold,underline Options:|@%n", + header = "Upgrade plugin.", + description = "Automatically upgrade plugin package.") +public class CliUpgrade extends BaseDeploy implements Runnable { + + @CommandLine.Option(required = true, names = {"--project-dir", "-d"}, paramLabel = "", description = "Set the path to the project including project name") + String path; + + @CommandLine.Option(names = {"--version", "-v"}, description = "Set the deploy plugin version") + String version; + + @Override + public void run() { + + this.project = path; + this.mode = DeployMode.Upgrade; + + /** + * read user deploy version + */ + this.upgradeVersion = version; + + this.registerCallBack(project); + + String message = this.deployOrUpgradePlugins(null); + if (message != null) { + System.err.println(message); + return; + } + + waitQuit(); + } +} diff --git a/src/main/java/io/mosn/coder/cli/CommandLine.java b/src/main/java/io/mosn/coder/cli/CommandLine.java new file mode 100644 index 0000000..06d7b9a --- /dev/null +++ b/src/main/java/io/mosn/coder/cli/CommandLine.java @@ -0,0 +1,19006 @@ +/* + Copyright 2017 Remko Popma + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package io.mosn.coder.cli; + +import io.mosn.coder.cli.CommandLine.Help.Ansi.IStyle; +import io.mosn.coder.cli.CommandLine.Help.Ansi.Style; +import io.mosn.coder.cli.CommandLine.Help.Ansi.Text; +import io.mosn.coder.cli.CommandLine.Model.*; +import io.mosn.coder.cli.CommandLine.ParseResult.GroupMatchContainer; + +import java.io.*; +import java.lang.annotation.*; +import java.lang.reflect.Proxy; +import java.lang.reflect.*; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.*; +import java.nio.ByteOrder; +import java.nio.charset.Charset; +import java.text.BreakIterator; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static io.mosn.coder.cli.CommandLine.Help.Column.Overflow.*; +import static java.util.Locale.ENGLISH; + +/** + *

    + * CommandLine interpreter that uses reflection to initialize an annotated user object with values obtained from the + * command line arguments. + *

    + * The full user manual is hosted at https://picocli.info. + *

    Example

    + *

    + * An example that implements {@code Callable} and uses the {@link #execute(String...) CommandLine.execute} convenience API to run in a single line of code: + *

    + *
    + * @Command(name = "checksum", mixinStandardHelpOptions = true, version = "checksum 4.0",
    + *          description = "Prints the checksum (SHA-1 by default) of a file to STDOUT.")
    + * class CheckSum implements Callable<Integer> {
    + *
    + *     @Parameters(index = "0", description = "The file whose checksum to calculate.")
    + *     private File file;
    + *
    + *     @Option(names = {"-a", "--algorithm"}, description = "MD5, SHA-1, SHA-256, ...")
    + *     private String algorithm = "SHA-1";
    + *
    + *     @Override
    + *     public Integer call() throws Exception { // your business logic goes here...
    + *         byte[] fileContents = Files.readAllBytes(file.toPath());
    + *         byte[] digest = MessageDigest.getInstance(algorithm).digest(fileContents);
    + *         System.out.printf("%0" + (digest.length*2) + "x%n", new BigInteger(1,digest));
    + *         return 0;
    + *     }
    + *
    + *     // CheckSum implements Callable, so parsing, error handling and handling user
    + *     // requests for usage help or version help can be done with one line of code.
    + *     public static void main(String[] args) {
    + *         int exitCode = new CommandLine(new CheckSum()).execute(args);
    + *         System.exit(exitCode);
    + *     }
    + * }
    + * 
    + *

    Another example where the application calls {@code parseArgs} and takes responsibility + * for error handling and checking whether the user requested help:

    + *
    import static io.mosn.coder.cli.CommandLine.*;
    + *
    + * @Command(mixinStandardHelpOptions = true, version = "v3.0.0",
    + *         header = "Encrypt FILE(s), or standard input, to standard output or to the output file.")
    + * public class Encrypt {
    + *
    + *     @Parameters(description = "Any number of input files")
    + *     private List<File> files = new ArrayList<File>();
    + *
    + *     @Option(names = { "-o", "--out" }, description = "Output file (default: print to console)")
    + *     private File outputFile;
    + *
    + *     @Option(names = { "-v", "--verbose"}, description = "Verbose mode. Helpful for troubleshooting. Multiple -v options increase the verbosity.")
    + *     private boolean[] verbose;
    + * }
    + * 
    + *

    + * Use {@code CommandLine} to initialize a user object as follows: + *

    + * public static void main(String... args) {
    + *     Encrypt encrypt = new Encrypt();
    + *     try {
    + *         ParseResult parseResult = new CommandLine(encrypt).parseArgs(args);
    + *         if (!CommandLine.printHelpIfRequested(parseResult)) {
    + *             runProgram(encrypt);
    + *         }
    + *     } catch (ParameterException ex) { // command line arguments could not be parsed
    + *         System.err.println(ex.getMessage());
    + *         ex.getCommandLine().usage(System.err);
    + *     }
    + * }
    + * 

    + * Invoke the above program with some command line arguments. The below are all equivalent: + *

    + *
    + * --verbose --out=outfile in1 in2
    + * --verbose --out outfile in1 in2
    + * -v --out=outfile in1 in2
    + * -v -o outfile in1 in2
    + * -v -o=outfile in1 in2
    + * -vo outfile in1 in2
    + * -vo=outfile in1 in2
    + * -v -ooutfile in1 in2
    + * -vooutfile in1 in2
    + * 
    + *

    Classes and Interfaces for Defining a CommandSpec Model

    + *

    + * Classes and Interfaces for Defining a CommandSpec Model + *

    + *

    Classes Related to Parsing Command Line Arguments

    + *

    + * Classes Related to Parsing Command Line Arguments + *

    + */ +public class CommandLine { + + /** This is picocli version {@value}. */ + public static final String VERSION = "4.7.0-SNAPSHOT"; + private static final Tracer TRACER = new Tracer(); + + private CommandSpec commandSpec; + private final Interpreter interpreter; + private final IFactory factory; + + private Object executionResult; + private PrintWriter out; + private PrintWriter err; + private Help.ColorScheme colorScheme = Help.defaultColorScheme(Help.Ansi.AUTO); + private IExitCodeExceptionMapper exitCodeExceptionMapper; + private IExecutionStrategy executionStrategy = new RunLast(); + private IParameterExceptionHandler parameterExceptionHandler = new IParameterExceptionHandler() { + public int handleParseException(ParameterException ex, String[] args) { + CommandLine cmd = ex.getCommandLine(); + DefaultExceptionHandler.internalHandleParseException(ex, cmd.getErr(), cmd.getColorScheme()); + return mappedExitCode(ex, cmd.getExitCodeExceptionMapper(), cmd.getCommandSpec().exitCodeOnInvalidInput()); + } + }; + private IExecutionExceptionHandler executionExceptionHandler = new IExecutionExceptionHandler() { + public int handleExecutionException(Exception ex, CommandLine commandLine, ParseResult parseResult) throws Exception { + throw ex; + } + }; + + /** + * Constructs a new {@code CommandLine} interpreter with the specified object (which may be an annotated user object or a {@link CommandSpec CommandSpec}) and a default {@linkplain IFactory factory}. + *

    The specified object may be a {@link CommandSpec CommandSpec} object, or it may be a {@code @Command}-annotated + * user object with {@code @Option} and {@code @Parameters}-annotated fields and methods, in which case picocli automatically + * constructs a {@code CommandSpec} from this user object. + *

    If the specified command object is an interface {@code Class} with {@code @Option} and {@code @Parameters}-annotated methods, + * picocli creates a {@link java.lang.reflect.Proxy Proxy} whose methods return the matched command line values. + * If the specified command object is a concrete {@code Class}, picocli delegates to the default factory to get an instance. + *

    + * If the specified object implements {@code Runnable} or {@code Callable}, or if it is a {@code Method} object, + * the command can be run as an application in a single line of code by using the + * {@link #execute(String...) execute} method to omit some boilerplate code for handling help requests and invalid input. + * See {@link #getCommandMethods(Class, String) getCommandMethods} for a convenient way to obtain a command {@code Method}. + *

    + * When the {@link #parseArgs(String...)} method is called, the {@link CommandSpec CommandSpec} object will be + * initialized based on command line arguments. If the commandSpec is created from an annotated user object, this + * user object will be initialized based on the command line arguments. + *

    + * @param command an annotated user object or a {@code CommandSpec} object to initialize from the command line arguments + * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation + */ + public CommandLine(Object command) { + this(command, new DefaultFactory()); + } + /** + * Constructs a new {@code CommandLine} interpreter with the specified object (which may be an annotated user object or a {@link CommandSpec CommandSpec}) and object factory. + *

    The specified object may be a {@link CommandSpec CommandSpec} object, or it may be a {@code @Command}-annotated + * user object with {@code @Option} and {@code @Parameters}-annotated fields and methods, in which case picocli automatically + * constructs a {@code CommandSpec} from this user object. + *

    If the specified command object is an interface {@code Class} with {@code @Option} and {@code @Parameters}-annotated methods, + * picocli creates a {@link java.lang.reflect.Proxy Proxy} whose methods return the matched command line values. + * If the specified command object is a concrete {@code Class}, picocli delegates to the {@linkplain IFactory factory} to get an instance. + *

    + * If the specified object implements {@code Runnable} or {@code Callable}, or if it is a {@code Method} object, + * the command can be run as an application in a single line of code by using the + * {@link #execute(String...) execute} method to omit some boilerplate code for handling help requests and invalid input. + * See {@link #getCommandMethods(Class, String) getCommandMethods} for a convenient way to obtain a command {@code Method}. + *

    + * When the {@link #parseArgs(String...)} method is called, the {@link CommandSpec CommandSpec} object will be + * initialized based on command line arguments. If the commandSpec is created from an annotated user object, this + * user object will be initialized based on the command line arguments. + *

    + * @param command an annotated user object or a {@code CommandSpec} object to initialize from the command line arguments + * @param factory the factory used to create instances of {@linkplain Command#subcommands() subcommands}, {@linkplain Option#converter() converters}, etc., that are registered declaratively with annotation attributes + * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation + * @since 2.2 */ + public CommandLine(Object command, IFactory factory) { + this(command, factory, true); + } + + private CommandLine(Object command, IFactory factory, boolean userCalled) { + this.factory = Assert.notNull(factory, "factory"); + interpreter = new Interpreter(); + commandSpec = CommandSpec.forAnnotatedObject(command, factory); + commandSpec.commandLine(this); + if (userCalled) { this.applyModelTransformations(); } + commandSpec.validate(); + if (commandSpec.unmatchedArgsBindings().size() > 0) { setUnmatchedArgumentsAllowed(true); } + } + + /** Apply transformers to command spec recursively. */ + private void applyModelTransformations() { + if (commandSpec.modelTransformer != null) { + commandSpec = commandSpec.modelTransformer.transform(commandSpec); + } + for (CommandLine cmd : getSubcommands().values()) { + cmd.applyModelTransformations(); + } + } + + private CommandLine copy() { + CommandLine result = new CommandLine(commandSpec.copy(), factory); // create a new sub-hierarchy + result.err = err; + result.out = out; + result.colorScheme = colorScheme; + result.executionStrategy = executionStrategy; + result.exitCodeExceptionMapper = exitCodeExceptionMapper; + result.executionExceptionHandler = executionExceptionHandler; + result.parameterExceptionHandler = parameterExceptionHandler; + + result.interpreter.converterRegistry.clear(); + result.interpreter.converterRegistry.putAll(interpreter.converterRegistry); + return result; + } + + /** + * Returns the {@code CommandSpec} model that this {@code CommandLine} was constructed with. + * @return the {@code CommandSpec} model + * @since 3.0 */ + public CommandSpec getCommandSpec() { return commandSpec; } + + /** + * Adds the options and positional parameters in the specified mixin to this command. + *

    The specified object may be a {@link CommandSpec CommandSpec} object, or it may be a user object with + * {@code @Option} and {@code @Parameters}-annotated fields, in which case picocli automatically + * constructs a {@code CommandSpec} from this user object. + *

    + * @param name the name by which the mixin object may later be retrieved + * @param mixin an annotated user object or a {@link CommandSpec CommandSpec} object whose options and positional parameters to add to this command + * @return this CommandLine object, to allow method chaining + * @since 3.0 */ + public CommandLine addMixin(String name, Object mixin) { + getCommandSpec().addMixin(name, CommandSpec.forAnnotatedObject(mixin, factory)); + return this; + } + + /** + * Returns a map of user objects whose options and positional parameters were added to ("mixed in" with) this command. + * @return a new Map containing the user objects mixed in with this command. If {@code CommandSpec} objects without + * user objects were programmatically added, use the {@link CommandSpec#mixins() underlying model} directly. + * @since 3.0 */ + public Map getMixins() { + Map mixins = getCommandSpec().mixins(); + Map result = new LinkedHashMap(); + for (String name : mixins.keySet()) { result.put(name, mixins.get(name).userObject.getInstance()); } + return result; + } + + /** Registers a subcommand with the name obtained from the {@code @Command(name = "...")} {@linkplain Command#name() annotation attribute} of the specified command. + * @param command the object to initialize with command line arguments following the subcommand name. + * This may be a {@code Class} that has a {@code @Command} annotation, or an instance of such a + * class, or a {@code CommandSpec} or {@code CommandLine} instance with its own (nested) subcommands. + * @return this CommandLine object, to allow method chaining + * @since 4.0 + * @throws InitializationException if no name could be found for the specified subcommand, + * or if another subcommand was already registered under the same name, or if one of the aliases + * of the specified subcommand was already used by another subcommand. + * @see #addSubcommand(String, Object) */ + public CommandLine addSubcommand(Object command) { + return addSubcommand(null, command, new String[0]); + } + + /** Registers a subcommand with the specified name. For example: + *
    +     * CommandLine commandLine = new CommandLine(new Git())
    +     *         .addSubcommand("status",   new GitStatus())
    +     *         .addSubcommand("commit",   new GitCommit();
    +     *         .addSubcommand("add",      new GitAdd())
    +     *         .addSubcommand("branch",   new GitBranch())
    +     *         .addSubcommand("checkout", new GitCheckout())
    +     *         //...
    +     *         ;
    +     * 
    + * + *

    The specified object can be an annotated object or a + * {@code CommandLine} instance with its own nested subcommands. For example:

    + *
    +     * CommandLine commandLine = new CommandLine(new MainCommand())
    +     *         .addSubcommand("cmd1",                 new ChildCommand1()) // subcommand
    +     *         .addSubcommand("cmd2",                 new ChildCommand2())
    +     *         .addSubcommand("cmd3", new CommandLine(new ChildCommand3()) // subcommand with nested sub-subcommands
    +     *                 .addSubcommand("cmd3sub1",                 new GrandChild3Command1())
    +     *                 .addSubcommand("cmd3sub2",                 new GrandChild3Command2())
    +     *                 .addSubcommand("cmd3sub3", new CommandLine(new GrandChild3Command3()) // deeper nesting
    +     *                         .addSubcommand("cmd3sub3sub1", new GreatGrandChild3Command3_1())
    +     *                         .addSubcommand("cmd3sub3sub2", new GreatGrandChild3Command3_2())
    +     *                 )
    +     *         );
    +     * 
    + *

    The default type converters are available on all subcommands and nested sub-subcommands, but custom type + * converters are registered only with the subcommand hierarchy as it existed when the custom type was registered. + * To ensure a custom type converter is available to all subcommands, register the type converter last, after + * adding subcommands.

    + *

    See also the {@link Command#subcommands()} annotation to register subcommands declaratively.

    + * + * @param name the string to recognize on the command line as a subcommand. + * If {@code null}, the {@linkplain CommandSpec#name() name} of the specified subcommand is used; + * if this is also {@code null}, the first {@linkplain CommandSpec#aliases() alias} is used. + * @param command the object to initialize with command line arguments following the subcommand name. + * This may be a {@code Class} that has a {@code @Command} annotation, or an instance of such a + * class, or a {@code CommandSpec} or {@code CommandLine} instance with its own (nested) subcommands. + * @return this CommandLine object, to allow method chaining + * @see #registerConverter(Class, ITypeConverter) + * @since 0.9.7 + * @see Command#subcommands() + * @throws InitializationException if the specified name is {@code null}, and no alternative name could be found, + * or if another subcommand was already registered under the same name, or if one of the aliases + * of the specified subcommand was already used by another subcommand. + */ + public CommandLine addSubcommand(String name, Object command) { + return addSubcommand(name, command, new String[0]); + } + + /** Registers a subcommand with the specified name and all specified aliases. See also {@link #addSubcommand(String, Object)}. + * @param name the string to recognize on the command line as a subcommand. + * If {@code null}, the {@linkplain CommandSpec#name() name} of the specified subcommand is used; + * if this is also {@code null}, the first {@linkplain CommandSpec#aliases() alias} is used. + * @param command the object to initialize with command line arguments following the subcommand name. + * This may be a {@code Class} that has a {@code @Command} annotation, or an instance of such a + * class, or a {@code CommandSpec} or {@code CommandLine} instance with its own (nested) subcommands. + * @param aliases zero or more alias names that are also recognized on the command line as this subcommand + * @return this CommandLine object, to allow method chaining + * @since 3.1 + * @see #addSubcommand(String, Object) + * @throws InitializationException if the specified name is {@code null}, and no alternative name could be found, + * or if another subcommand was already registered under the same name, or if one of the aliases + * of the specified subcommand was already used by another subcommand. + */ + public CommandLine addSubcommand(String name, Object command, String... aliases) { + CommandLine subcommandLine = toCommandLine(command, factory); + subcommandLine.getCommandSpec().aliases.addAll(Arrays.asList(aliases)); + getCommandSpec().addSubcommand(name, subcommandLine); + return this; + } + /** Returns a map with the subcommands {@linkplain #addSubcommand(String, Object) registered} on this instance. + * @return a map with the registered subcommands + * @since 0.9.7 + */ + public Map getSubcommands() { + return new CaseAwareLinkedMap(getCommandSpec().commands); + } + /** + * Returns the command that this is a subcommand of, or {@code null} if this is a top-level command. + * @return the command that this is a subcommand of, or {@code null} if this is a top-level command + * @see #addSubcommand(String, Object) + * @see Command#subcommands() + * @since 0.9.8 + */ + public CommandLine getParent() { + CommandSpec parent = getCommandSpec().parent(); + return parent == null ? null : parent.commandLine(); + } + + /** Returns the annotated user object that this {@code CommandLine} instance was constructed with. + * @param the type of the variable that the return value is being assigned to + * @return the annotated object that this {@code CommandLine} instance was constructed with + * @since 0.9.7 + */ + @SuppressWarnings("unchecked") + public T getCommand() { + return (T) getCommandSpec().userObject(); + } + + /** Returns the factory that this {@code CommandLine} was constructed with. + * @return the factory that this {@code CommandLine} was constructed with, never {@code null} + * @since 4.6 */ + public IFactory getFactory() { return factory; } + + /** Returns {@code true} if an option annotated with {@link Option#usageHelp()} was specified on the command line. + * @return whether the parser encountered an option annotated with {@link Option#usageHelp()}. + * @since 0.9.8 */ + public boolean isUsageHelpRequested() { return interpreter.parseResultBuilder != null && interpreter.parseResultBuilder.usageHelpRequested; } + + /** Returns {@code true} if an option annotated with {@link Option#versionHelp()} was specified on the command line. + * @return whether the parser encountered an option annotated with {@link Option#versionHelp()}. + * @since 0.9.8 */ + public boolean isVersionHelpRequested() { return interpreter.parseResultBuilder != null && interpreter.parseResultBuilder.versionHelpRequested; } + /** Returns a new {@code Help} object created by the {@code IHelpFactory} with the {@code CommandSpec} and {@code ColorScheme} of this command. + * @see Help#Help(CommandSpec, Help.ColorScheme) + * @see #getHelpFactory() + * @see #getCommandSpec() + * @see #getColorScheme() + * @since 4.1 + */ + public Help getHelp() { + return getHelpFactory().create(getCommandSpec(), getColorScheme()); + } + /** Returns the {@code IHelpFactory} that is used to construct the usage help message. + * @see #setHelpFactory(IHelpFactory) + * @since 3.9 + */ + public IHelpFactory getHelpFactory() { + return getCommandSpec().usageMessage().helpFactory(); + } + + /** Sets a new {@code IHelpFactory} to customize the usage help message. + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param helpFactory the new help factory. Must be non-{@code null}. + * @return this {@code CommandLine} object, to allow method chaining + * @since 3.9 + */ + public CommandLine setHelpFactory(IHelpFactory helpFactory) { + getCommandSpec().usageMessage().helpFactory(helpFactory); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setHelpFactory(helpFactory); + } + return this; + } + + /** + * Returns the section keys in the order that the usage help message should render the sections. + * This ordering may be modified with {@link #setHelpSectionKeys(List) setSectionKeys}. The default keys are (in order): + *
      + *
    1. {@link UsageMessageSpec#SECTION_KEY_HEADER_HEADING SECTION_KEY_HEADER_HEADING}
    2. + *
    3. {@link UsageMessageSpec#SECTION_KEY_HEADER SECTION_KEY_HEADER}
    4. + *
    5. {@link UsageMessageSpec#SECTION_KEY_SYNOPSIS_HEADING SECTION_KEY_SYNOPSIS_HEADING}
    6. + *
    7. {@link UsageMessageSpec#SECTION_KEY_SYNOPSIS SECTION_KEY_SYNOPSIS}
    8. + *
    9. {@link UsageMessageSpec#SECTION_KEY_DESCRIPTION_HEADING SECTION_KEY_DESCRIPTION_HEADING}
    10. + *
    11. {@link UsageMessageSpec#SECTION_KEY_DESCRIPTION SECTION_KEY_DESCRIPTION}
    12. + *
    13. {@link UsageMessageSpec#SECTION_KEY_PARAMETER_LIST_HEADING SECTION_KEY_PARAMETER_LIST_HEADING}
    14. + *
    15. {@link UsageMessageSpec#SECTION_KEY_AT_FILE_PARAMETER SECTION_KEY_AT_FILE_PARAMETER}
    16. + *
    17. {@link UsageMessageSpec#SECTION_KEY_PARAMETER_LIST SECTION_KEY_PARAMETER_LIST}
    18. + *
    19. {@link UsageMessageSpec#SECTION_KEY_OPTION_LIST_HEADING SECTION_KEY_OPTION_LIST_HEADING}
    20. + *
    21. {@link UsageMessageSpec#SECTION_KEY_OPTION_LIST SECTION_KEY_OPTION_LIST}
    22. + *
    23. {@link UsageMessageSpec#SECTION_KEY_COMMAND_LIST_HEADING SECTION_KEY_COMMAND_LIST_HEADING}
    24. + *
    25. {@link UsageMessageSpec#SECTION_KEY_COMMAND_LIST SECTION_KEY_COMMAND_LIST}
    26. + *
    27. {@link UsageMessageSpec#SECTION_KEY_EXIT_CODE_LIST_HEADING SECTION_KEY_EXIT_CODE_LIST_HEADING}
    28. + *
    29. {@link UsageMessageSpec#SECTION_KEY_EXIT_CODE_LIST SECTION_KEY_EXIT_CODE_LIST}
    30. + *
    31. {@link UsageMessageSpec#SECTION_KEY_FOOTER_HEADING SECTION_KEY_FOOTER_HEADING}
    32. + *
    33. {@link UsageMessageSpec#SECTION_KEY_FOOTER SECTION_KEY_FOOTER}
    34. + *
    + * @since 3.9 + */ + public List getHelpSectionKeys() { return getCommandSpec().usageMessage().sectionKeys(); } + + /** + * Sets the section keys in the order that the usage help message should render the sections. + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + *

    Use {@link UsageMessageSpec#sectionKeys(List)} to customize a command without affecting its subcommands.

    + * @see #getHelpSectionKeys + * @since 3.9 + */ + public CommandLine setHelpSectionKeys(List keys) { + getCommandSpec().usageMessage().sectionKeys(keys); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setHelpSectionKeys(keys); + } + return this; + } + + /** + * Returns the map of section keys and renderers used to construct the usage help message. + * The usage help message can be customized by adding, replacing and removing section renderers from this map. + * Sections can be reordered with {@link #setHelpSectionKeys(List) setSectionKeys}. + * Sections that are either not in this map or not in the list returned by {@link #getHelpSectionKeys() getSectionKeys} are omitted. + *

    + * NOTE: By modifying the returned {@code Map}, only the usage help message of this command is affected. + * Use {@link #setHelpSectionMap(Map)} to customize the usage help message for this command and all subcommands. + *

    + * @since 3.9 + */ + public Map getHelpSectionMap() { return getCommandSpec().usageMessage().sectionMap(); } + + /** + * Sets the map of section keys and renderers used to construct the usage help message. + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + *

    Use {@link UsageMessageSpec#sectionMap(Map)} to customize a command without affecting its subcommands.

    + * @see #getHelpSectionMap + * @since 3.9 + */ + public CommandLine setHelpSectionMap(Map map) { + getCommandSpec().usageMessage().sectionMap(map); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setHelpSectionMap(map); + } + return this; + } + /** + * Returns whether line breaks should take wide Chinese, Japanese and Korean characters into account for line-breaking purposes. The default is {@code true}. + * @return true if wide Chinese, Japanese and Korean characters are counted as double the size of other characters for line-breaking purposes + * @since 4.0 */ + public boolean isAdjustLineBreaksForWideCJKCharacters() { return getCommandSpec().usageMessage().adjustLineBreaksForWideCJKCharacters(); } + /** Sets whether line breaks should take wide Chinese, Japanese and Korean characters into account, and returns this UsageMessageSpec. The default is {@code true}. + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param adjustForWideChars if true, wide Chinese, Japanese and Korean characters are counted as double the size of other characters for line-breaking purposes + * @since 4.0 */ + public CommandLine setAdjustLineBreaksForWideCJKCharacters(boolean adjustForWideChars) { + getCommandSpec().usageMessage().adjustLineBreaksForWideCJKCharacters(adjustForWideChars); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setAdjustLineBreaksForWideCJKCharacters(adjustForWideChars); + } + return this; + } + + /** Returns whether the value of boolean flag options should be "toggled" when the option is matched. + * From 4.0, this is {@code false} by default, and when a flag option is specified on the command line picocli + * will set its value to the opposite of its default value. + * If this method returns {@code true}, flags are toggled, so if the value is {@code true} it is + * set to {@code false}, and when the value is {@code false} it is set to {@code true}. + * When toggling is enabled, specifying a flag option twice on the command line will have no effect because they cancel each other out. + * @return {@code true} the value of boolean flag options should be "toggled" when the option is matched, {@code false} otherwise + * @since 3.0 + */ + public boolean isToggleBooleanFlags() { + return getCommandSpec().parser().toggleBooleanFlags(); + } + + /** Sets whether the value of boolean flag options should be "toggled" when the option is matched. The default is {@code false}, + * and when a flag option is specified on the command line picocli will set its value to the opposite of its default value. + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param newValue the new setting + * @return this {@code CommandLine} object, to allow method chaining + * @since 3.0 + */ + public CommandLine setToggleBooleanFlags(boolean newValue) { + getCommandSpec().parser().toggleBooleanFlags(newValue); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setToggleBooleanFlags(newValue); + } + return this; + } + + /** Returns whether variables should be interpolated in String values. The default is {@code true}. + * @since 4.0 */ + public boolean isInterpolateVariables() { return getCommandSpec().interpolateVariables(); } + /** Sets whether variables should be interpolated in String values. The default is {@code true}. + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @since 4.0 */ + public CommandLine setInterpolateVariables(boolean interpolate) { + getCommandSpec().interpolateVariables(interpolate); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setInterpolateVariables(interpolate); + } + return this; + } + + /** Returns whether options for single-value fields can be specified multiple times on the command line. + * The default is {@code false} and a {@link OverwrittenOptionException} is thrown if this happens. + * When {@code true}, the last specified value is retained. + * @return {@code true} if options for single-value fields can be specified multiple times on the command line, {@code false} otherwise + * @since 0.9.7 + */ + public boolean isOverwrittenOptionsAllowed() { + return getCommandSpec().parser().overwrittenOptionsAllowed(); + } + + /** Sets whether options for single-value fields can be specified multiple times on the command line without a {@link OverwrittenOptionException} being thrown. + * The default is {@code false}. + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param newValue the new setting + * @return this {@code CommandLine} object, to allow method chaining + * @since 0.9.7 + */ + public CommandLine setOverwrittenOptionsAllowed(boolean newValue) { + getCommandSpec().parser().overwrittenOptionsAllowed(newValue); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setOverwrittenOptionsAllowed(newValue); + } + return this; + } + + /** Returns whether the parser accepts clustered short options. The default is {@code true}. + * @return {@code true} if short options like {@code -x -v -f SomeFile} can be clustered together like {@code -xvfSomeFile}, {@code false} otherwise + * @since 3.0 */ + public boolean isPosixClusteredShortOptionsAllowed() { return getCommandSpec().parser().posixClusteredShortOptionsAllowed(); } + + /** Sets whether short options like {@code -x -v -f SomeFile} can be clustered together like {@code -xvfSomeFile}. The default is {@code true}. + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param newValue the new setting + * @return this {@code CommandLine} object, to allow method chaining + * @since 3.0 + */ + public CommandLine setPosixClusteredShortOptionsAllowed(boolean newValue) { + getCommandSpec().parser().posixClusteredShortOptionsAllowed(newValue); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setPosixClusteredShortOptionsAllowed(newValue); + } + return this; + } + + /** Returns whether the parser should ignore case when converting arguments to {@code enum} values. The default is {@code false}. + * @return {@code true} if enum values can be specified that don't match the {@code toString()} value of the enum constant, {@code false} otherwise; + * e.g., for an option of type java.time.DayOfWeek, + * values {@code MonDaY}, {@code monday} and {@code MONDAY} are all recognized if {@code true}. + * @since 3.4 */ + public boolean isCaseInsensitiveEnumValuesAllowed() { return getCommandSpec().parser().caseInsensitiveEnumValuesAllowed(); } + + /** Sets whether the parser should ignore case when converting arguments to {@code enum} values. The default is {@code false}. + * When set to true, for example, for an option of type java.time.DayOfWeek, + * values {@code MonDaY}, {@code monday} and {@code MONDAY} are all recognized if {@code true}. + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param newValue the new setting + * @return this {@code CommandLine} object, to allow method chaining + * @since 3.4 + */ + public CommandLine setCaseInsensitiveEnumValuesAllowed(boolean newValue) { + getCommandSpec().parser().caseInsensitiveEnumValuesAllowed(newValue); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setCaseInsensitiveEnumValuesAllowed(newValue); + } + return this; + } + + /** Returns whether the parser should trim quotes from command line arguments. The default is + * read from the system property "picocli.trimQuotes" and will be {@code true} if the property is present and empty, + * or if its value is "true". + *

    If this property is set to {@code true}, the parser will remove quotes from the command line arguments, as follows:

    + *
      + *
    • if the command line argument contains just the leading and trailing quote, these quotes are removed
    • + *
    • if the command line argument contains more quotes than just the leading and trailing quote, the parser first + * tries to process the parameter with the quotes intact. For example, the {@code split} regular expression inside + * a quoted region should be ignored, so arguments like {@code "a,b","x,y"} are handled correctly. + * For arguments with nested quotes, quotes are removed later in the processing pipeline, after {@code split} operations are applied.
    • + *
    + * @return {@code true} if the parser should trim quotes from command line arguments before processing them, {@code false} otherwise; + * @see ParserSpec#trimQuotes() + * @since 3.7 */ + public boolean isTrimQuotes() { return getCommandSpec().parser().trimQuotes(); } + + /** Sets whether the parser should trim quotes from command line arguments before processing them. The default is + * read from the system property "picocli.trimQuotes" and will be {@code true} if the property is set and empty, or + * if its value is "true". + *

    If this property is set to {@code true}, the parser will remove quotes from the command line arguments, as follows:

    + *
      + *
    • if the command line argument contains just the leading and trailing quote, these quotes are removed
    • + *
    • if the command line argument contains more quotes than just the leading and trailing quote, the parser first + * tries to process the parameter with the quotes intact. For example, the {@code split} regular expression inside + * a quoted region should be ignored, so arguments like {@code "a,b","x,y"} are handled correctly. + * For arguments with nested quotes, quotes are removed later in the processing pipeline, after {@code split} operations are applied.
    • + *
    + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + *

    Calling this method will cause the "picocli.trimQuotes" property to have no effect.

    + * @param newValue the new setting + * @return this {@code CommandLine} object, to allow method chaining + * @see ParserSpec#trimQuotes(boolean) + * @since 3.7 + */ + public CommandLine setTrimQuotes(boolean newValue) { + getCommandSpec().parser().trimQuotes(newValue); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setTrimQuotes(newValue); + } + return this; + } + + /** Returns whether the parser is allowed to split quoted Strings or not. The default is {@code false}, + * so quotes are respected: quoted strings are treated as a single value that should not be broken up. + *

    + * For example, take a single command line parameter {@code "a,b","x,y"}. With a comma split regex, the default of {@code splitQuotedStrings = false} + * means that this value will be split into two strings: {@code "a,b"} and {@code "x,y"}. This is usually what you want. + *

    + * If {@code splitQuotedStrings} is set to {@code true}, quotes are not respected, and the value is split up into four parts: + * the first is {@code "a}, the second is {@code b"}, the third is {@code "x}, and the last part is {@code y"}. This is generally not what you want. + *

    + * @deprecated Most applications should not change the default. The rare application that does need to split parameter values + * without respecting quotes should use {@link ParserSpec#splitQuotedStrings(boolean)}. + * @return {@code true} if the parser is allowed to split quoted Strings, {@code false} otherwise; + * @see ArgSpec#splitRegex() + * @see ParserSpec#splitQuotedStrings() + * @since 3.7 */ + @Deprecated public boolean isSplitQuotedStrings() { return getCommandSpec().parser().splitQuotedStrings(); } + + /** Sets whether the parser is allowed to split quoted Strings. The default is {@code false}, + * so quotes are respected: quoted strings are treated as a single value that should not be broken up. + *

    + * For example, take a single command line parameter {@code "a,b","x,y"}. With a comma split regex, the default of {@code splitQuotedStrings = false} + * means that this value will be split into two strings: {@code "a,b"} and {@code "x,y"}. This is usually what you want. + *

    + * However, if {@code splitQuotedStrings} is set to {@code true}, quotes are not respected, and the value is split up into four parts: + * the first is {@code "a}, the second is {@code b"}, the third is {@code "x}, and the last part is {@code y"}. This is generally not what you want. + *

    + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @deprecated Most applications should not change the default. The rare application that does need to split parameter values + * without respecting quotes should use {@link ParserSpec#splitQuotedStrings(boolean)}. + * @param newValue the new setting + * @return this {@code CommandLine} object, to allow method chaining + * @see ArgSpec#splitRegex() + * @see ParserSpec#splitQuotedStrings(boolean) + * @since 3.7 + */ + @Deprecated public CommandLine setSplitQuotedStrings(boolean newValue) { + getCommandSpec().parser().splitQuotedStrings(newValue); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setSplitQuotedStrings(newValue); + } + return this; + } + + /** Returns the end-of-options delimiter that signals that the remaining command line arguments should be treated as positional parameters. + * @return the end-of-options delimiter. The default is {@code "--"}. + * @since 3.5 */ + public String getEndOfOptionsDelimiter() { return getCommandSpec().parser().endOfOptionsDelimiter(); } + + /** Sets the end-of-options delimiter that signals that the remaining command line arguments should be treated as positional parameters. + * @param delimiter the end-of-options delimiter; must not be {@code null}. The default is {@code "--"}. + * @return this {@code CommandLine} object, to allow method chaining + * @since 3.5 */ + public CommandLine setEndOfOptionsDelimiter(String delimiter) { + getCommandSpec().parser().endOfOptionsDelimiter(delimiter); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setEndOfOptionsDelimiter(delimiter); + } + return this; + } + + /** Returns whether upper case and lower case should be ignored when matching subcommands. The default is {@code false}. + * @return {@code true} if subcommands can be matched when they differ only in case from the {@code getCommandName()} value of a registered one, {@code false} otherwise. + * For example, if true, for a subcommand with name {@code help}, inputs like {@code help}, {@code HeLp} and {@code HELP} are all recognized. + * @since 4.3 */ + public boolean isSubcommandsCaseInsensitive() { return getCommandSpec().subcommandsCaseInsensitive(); } + + /** Sets whether upper case and lower case should be ignored when matching subcommands. The default is {@code false}. + * For example, when set to {@code true}, for a subcommand with name {@code help}, inputs like {@code help}, {@code HeLp} and {@code HELP} are all recognized. + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param newValue the new setting + * @return this {@code CommandLine} object, to allow method chaining + * @since 4.3 + */ + public CommandLine setSubcommandsCaseInsensitive(boolean newValue) { + getCommandSpec().subcommandsCaseInsensitive(newValue); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setSubcommandsCaseInsensitive(newValue); + } + return this; + } + + /** Returns whether upper case and lower case should be ignored when matching option names. The default is {@code false}. + * @return {@code true} if options can be matched when they differ only in case from the {@code names()} value of a registered one, {@code false} otherwise; + * For example, if true, for an option with name {@code -h}, inputs like {@code -h}, {@code -H} are both recognized. + * @since 4.3 */ + public boolean isOptionsCaseInsensitive() { return getCommandSpec().optionsCaseInsensitive(); } + + /** Sets whether upper case and lower case should be ignored when matching option names. The default is {@code false}. + * For example, when set to {@code true}, for an option with name {@code -h}, inputs like {@code -h}, {@code -H} are both recognized. + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * Note that changing case sensitivity will also change the case sensitivity of {@linkplain Option#negatable() negatable} options: + * any custom {@link INegatableOptionTransformer} that was previously installed will be replaced by the case-insensitive + * version of the default transformer. To ensure your custom transformer is used, install it last, after changing case sensitivity. + * @param newValue the new setting + * @return this {@code CommandLine} object, to allow method chaining + * @since 4.3 + */ + public CommandLine setOptionsCaseInsensitive(boolean newValue) { + getCommandSpec().optionsCaseInsensitive(newValue); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setOptionsCaseInsensitive(newValue); + } + return this; + } + + /** Returns whether abbreviation of subcommands should be allowed when matching subcommands. The default is {@code false}. + * @return {@code true} if subcommands can be matched when they are abbrevations of the {@code getCommandName()} value of a registered one, {@code false} otherwise. + * For example, if true, for a subcommand with name {@code helpCommand}, inputs like {@code h}, {@code h-c} and {@code hC} are all recognized. + * @since 4.4 */ + public boolean isAbbreviatedSubcommandsAllowed() { + return getCommandSpec().parser().abbreviatedSubcommandsAllowed(); + } + + /** Sets whether abbreviated subcommands should be matched. The default is {@code false}. + * For example, when set to {@code true}, for a subcommand {@code helpCommand}, inputs like {@code h}, {@code h-c} and {@code hC} are all recognized. + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param newValue the new setting + * @return this {@code CommandLine} object, to allow method chaining + * @since 4.4 + */ + public CommandLine setAbbreviatedSubcommandsAllowed(boolean newValue) { + getCommandSpec().parser().abbreviatedSubcommandsAllowed(newValue); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setAbbreviatedSubcommandsAllowed(newValue); + } + return this; + } + + /** Returns whether abbreviation of option names should be allowed when matching options. The default is {@code false}. + * @return {@code true} if options can be matched when they are abbrevations of the {@code names()} value of a registered one, {@code false} otherwise. + * For example, if true, for a subcommand with name {@code --helpMe}, inputs like {@code --h}, {@code --h-m} and {@code --hM} are all recognized. + * @since 4.4 */ + public boolean isAbbreviatedOptionsAllowed() { + return getCommandSpec().parser().abbreviatedOptionsAllowed(); + } + + /** Sets whether abbreviated option names should be matched. The default is {@code false}. + * For example, when set to {@code true}, for an option with name {@code --helpMe}, inputs like {@code --h}, {@code --h-m} and {@code --hM} are all recognized. + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param newValue the new setting + * @return this {@code CommandLine} object, to allow method chaining + * @since 4.4 + */ + public CommandLine setAbbreviatedOptionsAllowed(boolean newValue) { + getCommandSpec().parser().abbreviatedOptionsAllowed(newValue); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setAbbreviatedOptionsAllowed(newValue); + } + return this; + } + + /** Returns the default value provider for the command, or {@code null} if none has been set. + * @return the default value provider for this command, or {@code null} + * @since 3.6 + * @see Command#defaultValueProvider() + * @see CommandSpec#defaultValueProvider() + * @see ArgSpec#defaultValueString() + */ + public IDefaultValueProvider getDefaultValueProvider() { + return getCommandSpec().defaultValueProvider(); + } + + /** Sets a default value provider for the command and sub-commands + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * sub-commands and nested sub-subcommands at the moment this method is called. Sub-commands added + * later will have the default setting. To ensure a setting is applied to all + * sub-commands, call the setter last, after adding sub-commands.

    + * @param newValue the default value provider to use + * @return this {@code CommandLine} object, to allow method chaining + * @since 3.6 + */ + public CommandLine setDefaultValueProvider(IDefaultValueProvider newValue) { + getCommandSpec().defaultValueProvider(newValue); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setDefaultValueProvider(newValue); + } + return this; + } + + /** Returns whether the parser interprets the first positional parameter as "end of options" so the remaining + * arguments are all treated as positional parameters. The default is {@code false}. + * @return {@code true} if all values following the first positional parameter should be treated as positional parameters, {@code false} otherwise + * @since 2.3 + */ + public boolean isStopAtPositional() { + return getCommandSpec().parser().stopAtPositional(); + } + + /** Sets whether the parser interprets the first positional parameter as "end of options" so the remaining + * arguments are all treated as positional parameters. The default is {@code false}. + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param newValue {@code true} if all values following the first positional parameter should be treated as positional parameters, {@code false} otherwise + * @return this {@code CommandLine} object, to allow method chaining + * @since 2.3 + */ + public CommandLine setStopAtPositional(boolean newValue) { + getCommandSpec().parser().stopAtPositional(newValue); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setStopAtPositional(newValue); + } + return this; + } + + /** Returns whether the parser should stop interpreting options and positional parameters as soon as it encounters an + * unmatched option. Unmatched options are arguments that look like an option but are not one of the known options, or + * positional arguments for which there is no available slots (the command has no positional parameters or their size is limited). + * The default is {@code false}. + *

    Setting this flag to {@code true} automatically sets the {@linkplain #isUnmatchedArgumentsAllowed() unmatchedArgumentsAllowed} flag to {@code true} also.

    + * @return {@code true} when an unmatched option should result in the remaining command line arguments to be added to the + * {@linkplain #getUnmatchedArguments() unmatchedArguments list} + * @since 2.3 + */ + public boolean isStopAtUnmatched() { + return getCommandSpec().parser().stopAtUnmatched(); + } + + /** Sets whether the parser should stop interpreting options and positional parameters as soon as it encounters an + * unmatched option. Unmatched options are arguments that look like an option but are not one of the known options, or + * positional arguments for which there is no available slots (the command has no positional parameters or their size is limited). + * The default is {@code false}. + *

    Setting this flag to {@code true} automatically sets the {@linkplain #setUnmatchedArgumentsAllowed(boolean) unmatchedArgumentsAllowed} flag to {@code true} also.

    + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param newValue {@code true} when an unmatched option should result in the remaining command line arguments to be added to the + * {@linkplain #getUnmatchedArguments() unmatchedArguments list} + * @return this {@code CommandLine} object, to allow method chaining + * @since 2.3 + */ + public CommandLine setStopAtUnmatched(boolean newValue) { + getCommandSpec().parser().stopAtUnmatched(newValue); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setStopAtUnmatched(newValue); + } + if (newValue) { setUnmatchedArgumentsAllowed(true); } + return this; + } + /** Returns whether options can have parameter values that match subcommand names or aliases, + * or whether such values should be rejected with a missing parameter exception. + * The default is {@code false}, so by default input like {@code -x=subcommand} is rejected if {@code -x} is an option that takes a String parameter, and {@code subcommand} is a subcommand of this command. + * @return {@code true} when options can have parameter values that match subcommand names or aliases, {@code false} when such values should be rejected with a missing parameter exception + * @since 4.7.0 + * @see ParserSpec#allowSubcommandsAsOptionParameters() + */ + public boolean isAllowSubcommandsAsOptionParameters() { + return getCommandSpec().parser().allowSubcommandsAsOptionParameters(); + } + /** Sets whether options can have parameter values that match subcommand names or aliases, or whether such values should be rejected with a missing parameter exception. + * The default is {@code false}, so by default + * input like {@code -x=subcommand} is rejected if {@code -x} is an option that takes a String parameter, and {@code subcommand} is a subcommand of this command. + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param newValue the new setting. When {@code true}, options can have parameter values that match subcommand names or aliases, when {@code false}, such values are rejected with a missing parameter exception + * @return this {@code CommandLine} object, to allow method chaining + * @since 4.7.0 + * @see ParserSpec#allowSubcommandsAsOptionParameters(boolean) + */ + public CommandLine setAllowSubcommandsAsOptionParameters(boolean newValue) { + getCommandSpec().parser().allowSubcommandsAsOptionParameters(newValue); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setAllowSubcommandsAsOptionParameters(newValue); + } + return this; + } + /** Returns whether options can have parameter values that match the name of an option in this command, + * or whether such values should be rejected with a missing parameter exception. + * The default is {@code false}, so by default input like {@code -x=--some-option} is rejected if {@code -x} is an option that takes a String parameter, and {@code --some-option} is an option of this command. + *

    This method only considers actual options of this command, as opposed to {@link #isUnmatchedOptionsAllowedAsOptionParameters()}, which considers values that resemble options.

    + * @return {@code true} when options can have parameter values that match the name of an option in this command, {@code false} when such values should be rejected with a missing parameter exception + * @since 4.7.0 + * @see #isUnmatchedOptionsAllowedAsOptionParameters() + * @see ParserSpec#allowOptionsAsOptionParameters() + */ + public boolean isAllowOptionsAsOptionParameters() { + return getCommandSpec().parser().allowOptionsAsOptionParameters(); + } + /** Sets whether options can have parameter values that match the name of an option in this command, or whether such values should be rejected with a missing parameter exception. + * The default is {@code false}, so by default + * input like {@code -x=--some-option} is rejected if {@code -x} is an option that takes a String parameter, and {@code --some-option} is an option of this command. + *

    This method only considers actual options of this command, as opposed to {@link #setUnmatchedOptionsAllowedAsOptionParameters(boolean)}, which considers values that resemble options.

    + *

    Use with caution! When set to {@code true}, any option in the command will consume the maximum number of arguments possible for its arity. + * This means that an option with {@code arity = "*"} will consume all command line arguments following that option. + * If this is not what you want, consider custom parameter processing.

    + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param newValue the new setting. When {@code true}, options can have parameter values that match the name of an option in this command, when {@code false}, such values are rejected with a missing parameter exception + * @return this {@code CommandLine} object, to allow method chaining + * @since 4.7.0 + * @see #setUnmatchedOptionsAllowedAsOptionParameters(boolean) + * @see ParserSpec#allowOptionsAsOptionParameters(boolean) + */ + public CommandLine setAllowOptionsAsOptionParameters(boolean newValue) { + getCommandSpec().parser().allowOptionsAsOptionParameters(newValue); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setAllowOptionsAsOptionParameters(newValue); + } + return this; + } + + /** Returns whether options can have parameter values that resemble an option, or whether such values should be rejected as unknown options. + * The default is {@code true}, so by default input like {@code -x=-unknown} is accepted if {@code -x} is an option that takes a String parameter. + *

    This method only considers values that resemble options, as opposed to {@link #isAllowOptionsAsOptionParameters()}, which considers actual options of this command.

    + * @return {@code true} when options can have parameter values that resemble an option, {@code false} when such values should be rejected as unknown options + * @since 4.4 + * @see #isAllowOptionsAsOptionParameters() + * @see ParserSpec#unmatchedOptionsAllowedAsOptionParameters() + */ + public boolean isUnmatchedOptionsAllowedAsOptionParameters() { + return getCommandSpec().parser().unmatchedOptionsAllowedAsOptionParameters(); + } + + /** Sets whether options can have parameter values that resemble an option, or whether such values should be rejected as unknown options. + * The default is {@code true}, so by default + * input like {@code -x=-unknown} is accepted if {@code -x} is an option that takes a String parameter. + *

    This method only considers values that resemble options, as opposed to {@link #setAllowOptionsAsOptionParameters(boolean)}, which considers actual options of this command.

    + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param newValue the new setting. When {@code true}, options can have parameter values that resemble an option, when {@code false}, such values are rejected as unknown options + * @return this {@code CommandLine} object, to allow method chaining + * @since 4.4 + * @see #setAllowOptionsAsOptionParameters(boolean) + * @see ParserSpec#unmatchedOptionsAllowedAsOptionParameters(boolean) + */ + public CommandLine setUnmatchedOptionsAllowedAsOptionParameters(boolean newValue) { + getCommandSpec().parser().unmatchedOptionsAllowedAsOptionParameters(newValue); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setUnmatchedOptionsAllowedAsOptionParameters(newValue); + } + return this; + } + /** Returns whether arguments on the command line that resemble an option should be treated as positional parameters. + * The default is {@code false} and the parser behaviour depends on {@link #isUnmatchedArgumentsAllowed()}. + * @return {@code true} arguments on the command line that resemble an option should be treated as positional parameters, {@code false} otherwise + * @see #getUnmatchedArguments() + * @since 3.0 + */ + public boolean isUnmatchedOptionsArePositionalParams() { + return getCommandSpec().parser().unmatchedOptionsArePositionalParams(); + } + + /** Sets whether arguments on the command line that resemble an option should be treated as positional parameters. + * The default is {@code false}. + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param newValue the new setting. When {@code true}, arguments on the command line that resemble an option should be treated as positional parameters. + * @return this {@code CommandLine} object, to allow method chaining + * @since 3.0 + * @see #getUnmatchedArguments() + * @see #isUnmatchedArgumentsAllowed + */ + public CommandLine setUnmatchedOptionsArePositionalParams(boolean newValue) { + getCommandSpec().parser().unmatchedOptionsArePositionalParams(newValue); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setUnmatchedOptionsArePositionalParams(newValue); + } + return this; + } + + /** Returns whether the end user may specify arguments on the command line that are not matched to any option or parameter fields. + * The default is {@code false} and a {@link UnmatchedArgumentException} is thrown if this happens. + * When {@code true}, the last unmatched arguments are available via the {@link #getUnmatchedArguments()} method. + * @return {@code true} if the end use may specify unmatched arguments on the command line, {@code false} otherwise + * @see #getUnmatchedArguments() + * @since 0.9.7 + */ + public boolean isUnmatchedArgumentsAllowed() { + return getCommandSpec().parser().unmatchedArgumentsAllowed(); + } + + /** Sets whether the end user may specify unmatched arguments on the command line without a {@link UnmatchedArgumentException} being thrown. + * The default is {@code false}. + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param newValue the new setting. When {@code true}, the last unmatched arguments are available via the {@link #getUnmatchedArguments()} method. + * @return this {@code CommandLine} object, to allow method chaining + * @since 0.9.7 + * @see #getUnmatchedArguments() + */ + public CommandLine setUnmatchedArgumentsAllowed(boolean newValue) { + getCommandSpec().parser().unmatchedArgumentsAllowed(newValue); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setUnmatchedArgumentsAllowed(newValue); + } + return this; + } + + /** Returns the list of unmatched command line arguments, if any. + * @return the list of unmatched command line arguments or an empty list + * @see #isUnmatchedArgumentsAllowed() + * @since 0.9.7 + */ + public List getUnmatchedArguments() { + return interpreter.parseResultBuilder == null ? Collections.emptyList() : UnmatchedArgumentException.stripErrorMessage(interpreter.parseResultBuilder.unmatched); + } + + /** + * Defines some exit codes used by picocli as default return values from the {@link #execute(String...) execute} + * and {@link #executeHelpRequest(ParseResult) executeHelpRequest} methods. + *

    Commands can override these defaults with annotations (e.g. {@code @Command(exitCodeOnInvalidInput = 64, exitCodeOnExecutionException = 70)} + * or programmatically (e.g. {@link CommandSpec#exitCodeOnInvalidInput(int)}).

    + *

    Additionally, there are several mechanisms for commands to return custom exit codes. + * See the javadoc of the {@link #execute(String...) execute} method for details.

    + *

    Standard Exit Codes

    + *

    There are a few conventions, but there is no + * standard. The specific set of codes returned is unique to the program that sets it. + * Typically an exit code of zero indicates success, any non-zero exit code indicates failure. For reference, here are a few conventions:

    + * + *

    Valid Ranges

    + *

    Note that *nix shells may restrict exit codes to the 0-255 range, DOS seems to allow larger numbers. + * See this StackOverflow question.

    + * @since 4.0 */ + public static final class ExitCode { + /** Return value from the {@link #execute(String...) execute} and + * {@link #executeHelpRequest(ParseResult) executeHelpRequest} methods signifying successful termination. + *

    The value of this constant is {@value}.

    */ + public static final int OK = 0; + /** Return value from the {@link #execute(String...) execute} method signifying internal software error: an exception occurred when invoking the Runnable, Callable or Method user object of a command.

    The value of this constant is {@value}.

    */ + public static final int SOFTWARE = 1; + /** Return value from the {@link #execute(String...) execute} method signifying command line usage error: user input for the command was incorrect, e.g., the wrong number of arguments, a bad flag, a bad syntax in a parameter, or whatever.

    The value of this constant is {@value}.

    */ + public static final int USAGE = 2; + private ExitCode() {} // don't instantiate + } + + /** {@code @Command}-annotated classes can implement this interface to specify an exit code that will be returned + * from the {@link #execute(String...) execute} method when the command is successfully invoked. + * + *

    Example usage:

    + *
    +     * @Command
    +     * class MyCommand implements Runnable, IExitCodeGenerator {
    +     *     public void run() { System.out.println("Hello"); }
    +     *     public int getExitCode() { return 123; }
    +     * }
    +     * CommandLine cmd = new CommandLine(new MyCommand());
    +     * int exitCode = cmd.execute(args);
    +     * assert exitCode == 123;
    +     * System.exit(exitCode);
    +     * 
    + * @since 4.0 + */ + public interface IExitCodeGenerator { + /** Returns the exit code that should be returned from the {@link #execute(String...) execute} method. + * @return the exit code + */ + int getExitCode(); + } + /** Interface that provides the appropriate exit code that will be returned from the {@link #execute(String...) execute} + * method for an exception that occurred during parsing or while invoking the command's Runnable, Callable, or Method. + *

    Example usage:

    + *
    +     * @Command
    +     * class FailingCommand implements Callable<Void> {
    +     *     public Void call() throws IOException {
    +     *         throw new IOException("error");
    +     *     }
    +     * }
    +     * IExitCodeExceptionMapper mapper = new IExitCodeExceptionMapper() {
    +     *     public int getExitCode(Throwable t) {
    +     *         if (t instanceof IOException && "error".equals(t.getMessage())) {
    +     *             return 123;
    +     *         }
    +     *         return 987;
    +     *     }
    +     * }
    +     *
    +     * CommandLine cmd = new CommandLine(new FailingCommand());
    +     * cmd.setExitCodeExceptionMapper(mapper);
    +     * int exitCode = cmd.execute(args);
    +     * assert exitCode == 123;
    +     * System.exit(exitCode);
    +     * 
    + * @see #setExitCodeExceptionMapper(IExitCodeExceptionMapper) + * @since 4.0 + */ + public interface IExitCodeExceptionMapper { + /** Returns the exit code that should be returned from the {@link #execute(String...) execute} method. + * @param exception the exception that occurred during parsing or while invoking the command's Runnable, Callable, or Method. + * @return the exit code + */ + int getExitCode(Throwable exception); + } + private static int mappedExitCode(Throwable t, IExitCodeExceptionMapper mapper, int defaultExitCode) { + try { + return (mapper != null) ? mapper.getExitCode(t) : defaultExitCode; + } catch (Exception ex) { + ex.printStackTrace(); + return defaultExitCode; + } + } + + /** Returns the color scheme to use when printing help. + * The default value is the {@linkplain io.mosn.coder.cli.CommandLine.Help#defaultColorScheme(CommandLine.Help.Ansi) default color scheme} with {@link Help.Ansi#AUTO Ansi.AUTO}. + * @see #execute(String...) + * @see #usage(PrintStream) + * @see #usage(PrintWriter) + * @see #getUsageMessage() + * @see Help#defaultColorScheme(CommandLine.Help.Ansi) + * @since 4.0 + */ + public Help.ColorScheme getColorScheme() { return colorScheme; } + + /** Sets the color scheme to use when printing help. + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param colorScheme the new color scheme + * @see #execute(String...) + * @see #usage(PrintStream) + * @see #usage(PrintWriter) + * @see #getUsageMessage() + * @since 4.0 + */ + public CommandLine setColorScheme(Help.ColorScheme colorScheme) { + this.colorScheme = Assert.notNull(colorScheme, "colorScheme"); + for (CommandLine sub : getSubcommands().values()) { sub.setColorScheme(colorScheme); } + return this; + } + + /** Returns the writer used when printing user-requested usage help or version help during command {@linkplain #execute(String...) execution}. + * Defaults to a PrintWriter wrapper around {@code System.out} unless {@link #setOut(PrintWriter)} was called with a different writer. + *

    This method is used by {@link #execute(String...)}. Custom {@link IExecutionStrategy IExecutionStrategy} implementations should also use this writer. + *

    + * By convention, when the user requests + * help with a {@code --help} or similar option, the usage help message is printed to the standard output stream so that it can be easily searched and paged.

    + * @since 4.0 */ + public PrintWriter getOut() { + if (out == null) { setOut(newPrintWriter(System.out, getStdoutEncoding())); } + return out; + } + + /** Sets the writer to use when printing user-requested usage help or version help during command {@linkplain #execute(String...) execution}. + *

    This method is used by {@link #execute(String...)}. Custom {@link IExecutionStrategy IExecutionStrategy} implementations should also use this writer.

    + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param out the new PrintWriter to use + * @return this CommandLine for method chaining + * @since 4.0 + */ + public CommandLine setOut(PrintWriter out) { + this.out = Assert.notNull(out, "out"); + for (CommandLine sub : getSubcommands().values()) { sub.setOut(out); } + return this; + } + + /** Returns the writer to use when printing diagnostic (error) messages during command {@linkplain #execute(String...) execution}. + * Defaults to a PrintWriter wrapper around {@code System.err}, unless {@link #setErr(PrintWriter)} was called with a different writer. + *

    This method is used by {@link #execute(String...)}. + * {@link IParameterExceptionHandler IParameterExceptionHandler} and {@link IExecutionExceptionHandler IExecutionExceptionHandler} implementations + * should use this writer to print error messages (which may include a usage help message) when an unexpected error occurs.

    + * @since 4.0 */ + public PrintWriter getErr() { + if (err == null) { setErr(newPrintWriter(System.err, getStderrEncoding())); } + return err; + } + + /** Sets the writer to use when printing diagnostic (error) messages during command {@linkplain #execute(String...) execution}. + *

    This method is used by {@link #execute(String...)}. + * {@link IParameterExceptionHandler IParameterExceptionHandler} and {@link IExecutionExceptionHandler IExecutionExceptionHandler} implementations + * should use this writer to print error messages (which may include a usage help message) when an unexpected error occurs.

    + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param err the new PrintWriter to use + * @return this CommandLine for method chaining + * @since 4.0 */ + public CommandLine setErr(PrintWriter err) { + this.err = Assert.notNull(err, "err"); + for (CommandLine sub : getSubcommands().values()) { sub.setErr(err); } + return this; + } + + /** + * Returns the mapper that was set by the application to map from exceptions to exit codes, for use by the {@link #execute(String...) execute} method. + * @return the mapper that was {@linkplain #setExitCodeExceptionMapper(IExitCodeExceptionMapper) set}, or {@code null} if none was set + * @since 4.0 */ + public IExitCodeExceptionMapper getExitCodeExceptionMapper() { return exitCodeExceptionMapper; } + + /** Sets the mapper used by the {@link #execute(String...) execute} method to map exceptions to exit codes. + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param exitCodeExceptionMapper the new value + * @return this CommandLine for method chaining + * @since 4.0 */ + public CommandLine setExitCodeExceptionMapper(IExitCodeExceptionMapper exitCodeExceptionMapper) { + this.exitCodeExceptionMapper = Assert.notNull(exitCodeExceptionMapper, "exitCodeExceptionMapper"); + for (CommandLine sub : getSubcommands().values()) { sub.setExitCodeExceptionMapper(exitCodeExceptionMapper); } + return this; + } + + /** Returns the execution strategy used by the {@link #execute(String...) execute} method to invoke + * the business logic on the user objects of this command and/or the user-specified subcommand(s). + * The default value is {@link RunLast RunLast}. + * @return the execution strategy to run the user-specified command + * @since 4.0 */ + public IExecutionStrategy getExecutionStrategy() { return executionStrategy; } + + /** Sets the execution strategy that the {@link #execute(String...) execute} method should use to invoke + * the business logic on the user objects of this command and/or the user-specified subcommand(s). + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param executionStrategy the new execution strategy to run the user-specified command + * @return this CommandLine for method chaining + * @since 4.0 */ + public CommandLine setExecutionStrategy(IExecutionStrategy executionStrategy) { + this.executionStrategy = Assert.notNull(executionStrategy, "executionStrategy"); + for (CommandLine sub : getSubcommands().values()) { sub.setExecutionStrategy(executionStrategy); } + return this; + } + + /** + * Returns the handler for dealing with invalid user input when the command is {@linkplain #execute(String...) executed}. + *

    The default implementation prints an error message describing the problem, followed by either {@linkplain UnmatchedArgumentException#printSuggestions(PrintWriter) suggested alternatives} + * for mistyped options, or the full {@linkplain #usage(PrintWriter, Help.ColorScheme) usage} help message of the {@linkplain ParameterException#getCommandLine() problematic command}; + * it then delegates to the {@linkplain #getExitCodeExceptionMapper() exit code exception mapper} for an exit code, with + * {@link CommandSpec#exitCodeOnInvalidInput() exitCodeOnInvalidInput} as the default exit code.

    + *

    + * Alternatively, you can install a "short error message handler" like this: + *

    + *
    +     * static class ShortErrorMessageHandler implements IParameterExceptionHandler {
    +     *     public int handleParseException(ParameterException ex, String[] args) {
    +     *         CommandLine cmd = ex.getCommandLine();
    +     *         PrintWriter writer = cmd.getErr();
    +     *
    +     *         writer.println(ex.getMessage());
    +     *         UnmatchedArgumentException.printSuggestions(ex, writer);
    +     *         writer.print(cmd.getHelp().fullSynopsis());
    +     *
    +     *         CommandSpec spec = cmd.getCommandSpec();
    +     *         writer.printf("Try '%s --help' for more information.%n", spec.qualifiedName());
    +     *
    +     *         return cmd.getExitCodeExceptionMapper() != null
    +     *                     ? cmd.getExitCodeExceptionMapper().getExitCode(ex)
    +     *                     : spec.exitCodeOnInvalidInput();
    +     *     }
    +     * }
    +     * 
    + *

    Install this error handler like this:

    + *
    +     * new CommandLine(new MyApp())
    +     *     .setParameterExceptionHandler(new ShortErrorMessageHandler())
    +     *     .execute(args);
    +     * 
    + * @return the handler for dealing with invalid user input + * @since 4.0 */ + public IParameterExceptionHandler getParameterExceptionHandler() { return parameterExceptionHandler; } + + /** + * Sets the handler for dealing with invalid user input when the command is {@linkplain #execute(String...) executed}. + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param parameterExceptionHandler the new handler for dealing with invalid user input + * @return this CommandLine for method chaining + * @see #getParameterExceptionHandler() an example short exception handler + * @since 4.0 */ + public CommandLine setParameterExceptionHandler(IParameterExceptionHandler parameterExceptionHandler) { + this.parameterExceptionHandler = Assert.notNull(parameterExceptionHandler, "parameterExceptionHandler"); + for (CommandLine sub : getSubcommands().values()) { sub.setParameterExceptionHandler(parameterExceptionHandler); } + return this; + } + + /** Returns the handler for dealing with exceptions that occurred in the {@code Callable}, {@code Runnable} or {@code Method} + * user object of a command when the command was {@linkplain #execute(String...) executed}. + *

    The default implementation rethrows the specified exception.

    + * @return the handler for dealing with exceptions that occurred in the business logic when the {@link #execute(String...) execute} method was invoked. + * @since 4.0 */ + public IExecutionExceptionHandler getExecutionExceptionHandler() { return executionExceptionHandler; } + + /** + * Sets a custom handler for dealing with exceptions that occurred in the {@code Callable}, {@code Runnable} or {@code Method} + * user object of a command when the command was executed via the {@linkplain #execute(String...) execute} method. + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param executionExceptionHandler the handler for dealing with exceptions that occurred in the business logic when the {@link #execute(String...) execute} method was invoked. + * @return this CommandLine for method chaining + * @since 4.0 */ + public CommandLine setExecutionExceptionHandler(IExecutionExceptionHandler executionExceptionHandler) { + this.executionExceptionHandler = Assert.notNull(executionExceptionHandler, "executionExceptionHandler"); + for (CommandLine sub : getSubcommands().values()) { sub.setExecutionExceptionHandler(executionExceptionHandler); } + return this; + } + + /** + *

    + * Convenience method that initializes the specified annotated object from the specified command line arguments. + *

    + * This is equivalent to + *

    +     * new CommandLine(command).parseArgs(args);
    +     * return command;
    +     * 
    + *

    All this method does is parse the arguments and populate the annotated fields and methods. + * The caller is responsible for catching any exceptions, handling requests for usage help + * or version information, and invoking the business logic. + * Applications may be interested in using the {@link #execute(String...)} method instead.

    + * + * @param command the object to initialize. This object contains fields annotated with + * {@code @Option} or {@code @Parameters}. + * @param args the command line arguments to parse + * @param the type of the annotated object + * @return the specified annotated object + * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation + * @throws ParameterException if the specified command line arguments are invalid + * @see #execute(String...) + * @since 0.9.7 + */ + public static T populateCommand(T command, String... args) { + CommandLine cli = toCommandLine(command, new DefaultFactory()); + cli.parse(args); + return command; + } + + /** + *

    + * Convenience method that derives the command specification from the specified interface class, and returns an + * instance of the specified interface. The interface is expected to have annotated getter methods. Picocli will + * instantiate the interface and the getter methods will return the option and positional parameter values matched on the command line. + *

    + * This is equivalent to + *

    +     * CommandLine cli = new CommandLine(spec);
    +     * cli.parse(args);
    +     * return cli.getCommand();
    +     * 
    + *

    All this method does is parse the arguments and return an instance whose annotated methods return the specified values. + * The caller is responsible for catching any exceptions, handling requests for usage help + * or version information, and invoking the business logic. + * Applications may be interested in using the {@link #execute(String...)} method instead.

    + * + * @param spec the interface that defines the command specification. This object contains getter methods annotated with + * {@code @Option} or {@code @Parameters}. + * @param args the command line arguments to parse + * @param the type of the annotated object + * @return an instance of the specified annotated interface + * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation + * @throws ParameterException if the specified command line arguments are invalid + * @see #execute(String...) + * @since 3.1 + */ + public static T populateSpec(Class spec, String... args) { + CommandLine cli = toCommandLine(spec, new DefaultFactory()); + cli.parse(args); + return cli.getCommand(); + } + + /** Expands any {@linkplain CommandLine#isExpandAtFiles() @-files} in the specified command line arguments, then + * parses the arguments and returns a list of {@code CommandLine} objects representing the + * top-level command and any subcommands (if any) that were recognized and initialized during the parsing process. + *

    + * If parsing succeeds, the first element in the returned list is always {@code this CommandLine} object. The + * returned list may contain more elements if subcommands were {@linkplain #addSubcommand(String, Object) registered} + * and these subcommands were initialized by matching command line arguments. If parsing fails, a + * {@link ParameterException} is thrown. + *

    + *

    All this method does is parse the arguments and populate the annotated fields and methods. + * The caller is responsible for catching any exceptions, handling requests for usage help + * or version information, and invoking the business logic. + * Applications may be interested in using the {@link #execute(String...)} method instead.

    + * + * @param args the command line arguments to parse + * @return a list with the top-level command and any subcommands initialized by this method + * @throws ParameterException if the specified command line arguments are invalid; use + * {@link ParameterException#getCommandLine()} to get the command or subcommand whose user input was invalid + * @deprecated use {@link #parseArgs(String...)} instead + */ + @Deprecated public List parse(String... args) { + return interpreter.parse(args); + } + /** Expands any {@linkplain CommandLine#isExpandAtFiles() @-files} in the specified command line arguments, then + * parses the arguments and returns a {@code ParseResult} with the options, positional + * parameters, and subcommands (if any) that were recognized and initialized during the parsing process. + *

    If parsing fails, a {@link ParameterException} is thrown.

    + *

    All this method does is parse the arguments and populate the annotated fields and methods. + * The caller is responsible for catching any exceptions, handling requests for usage help + * or version information, and invoking the business logic. + * Applications may be interested in using the {@link #execute(String...)} method instead.

    + * + * @param args the command line arguments to parse + * @return a list with the top-level command and any subcommands initialized by this method + * @throws ParameterException if the specified command line arguments are invalid; use + * {@link ParameterException#getCommandLine()} to get the command or subcommand whose user input was invalid + * @see #execute(String...) + */ + public ParseResult parseArgs(String... args) { + interpreter.parse(args); + return getParseResult(); + } + public ParseResult getParseResult() { return interpreter.parseResultBuilder == null ? null : interpreter.parseResultBuilder.build(); } + + /** Returns the result of calling the user object {@code Callable} or invoking the user object {@code Method} + * after parsing the user input, or {@code null} if this command has not been {@linkplain #execute(String...) executed} + * or if this {@code CommandLine} is for a subcommand that was not specified by the end user on the command line. + *

    Implementation note:

    + *

    It is the responsibility of the {@link IExecutionStrategy IExecutionStrategy} to set this value.

    + * @param type of the result value + * @return the result of the user object {@code Callable} or {@code Method} (may be {@code null}), or {@code null} if this (sub)command was not executed + * @since 4.0 + */ + @SuppressWarnings("unchecked") public T getExecutionResult() { return (T) executionResult; } + + /** Sets the result of calling the business logic on the command's user object. + * @param result the business logic result, may be {@code null} + * @see #execute(String...) + * @see IExecutionStrategy + * @since 4.0 + */ + public void setExecutionResult(Object result) { executionResult = result; } + + /** Clears the {@linkplain #getExecutionResult() execution result} of a previous invocation from this {@code CommandLine} and all subcommands. + * @since 4.0 */ + public void clearExecutionResults() { + executionResult = null; + for (CommandLine sub : getSubcommands().values()) { sub.clearExecutionResults(); } + } + /** + * Represents a function that can process a List of {@code CommandLine} objects resulting from successfully + * {@linkplain #parse(String...) parsing} the command line arguments. This is a + * functional interface + * whose functional method is {@link #handleParseResult(List, PrintStream, CommandLine.Help.Ansi)}. + *

    + * Implementations of this functions can be passed to the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) CommandLine::parseWithHandler} + * methods to take some next step after the command line was successfully parsed. + *

    + * @see RunFirst + * @see RunLast + * @see RunAll + * @deprecated Use {@link IExecutionStrategy} instead. + * @since 2.0 */ + @Deprecated public interface IParseResultHandler { + /** Processes a List of {@code CommandLine} objects resulting from successfully + * {@linkplain #parse(String...) parsing} the command line arguments and optionally returns a list of results. + * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments + * @param out the {@code PrintStream} to print help to if requested + * @param ansi for printing help messages using ANSI styles and colors + * @return a list of results, or an empty list if there are no results + * @throws ParameterException if a help command was invoked for an unknown subcommand. Any {@code ParameterExceptions} + * thrown from this method are treated as if this exception was thrown during parsing and passed to the {@link IExceptionHandler} + * @throws ExecutionException if a problem occurred while processing the parse results; use + * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed + */ + List handleParseResult(List parsedCommands, PrintStream out, Help.Ansi ansi) throws ExecutionException; + } + + /** + * Represents a function that can process the {@code ParseResult} object resulting from successfully + * {@linkplain #parseArgs(String...) parsing} the command line arguments. This is a + * functional interface + * whose functional method is {@link IParseResultHandler2#handleParseResult(CommandLine.ParseResult)}. + *

    + * Implementations of this function can be passed to the {@link #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...) CommandLine::parseWithHandlers} + * methods to take some next step after the command line was successfully parsed. + *

    + * This interface replaces the {@link IParseResultHandler} interface; it takes the parse result as a {@code ParseResult} + * object instead of a List of {@code CommandLine} objects, and it has the freedom to select the {@link Help.Ansi} style + * to use and what {@code PrintStreams} to print to. + *

    + * @param the return type of this handler + * @see RunFirst + * @see RunLast + * @see RunAll + * @deprecated use {@link IExecutionStrategy} instead, see {@link #execute(String...)} + * @since 3.0 */ + @Deprecated public interface IParseResultHandler2 { + /** Processes the {@code ParseResult} object resulting from successfully + * {@linkplain CommandLine#parseArgs(String...) parsing} the command line arguments and returns a return value. + * @param parseResult the {@code ParseResult} that resulted from successfully parsing the command line arguments + * @throws ParameterException if a help command was invoked for an unknown subcommand. Any {@code ParameterExceptions} + * thrown from this method are treated as if this exception was thrown during parsing and passed to the {@link IExceptionHandler2} + * @throws ExecutionException if a problem occurred while processing the parse results; use + * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed + */ + R handleParseResult(ParseResult parseResult) throws ExecutionException; + } + + /** + * Implementations are responsible for "executing" the user input and returning an exit code. + * The {@link #execute(String...)} method delegates to a {@linkplain #setExecutionStrategy(IExecutionStrategy) configured} execution strategy. + *

    Implementation Requirements:

    + *

    Implementers responsibilities are:

    + *
      + *
    • From the {@code ParseResult}, select which {@code CommandSpec} should be executed. This is especially important for commands that have subcommands.
    • + *
    • "Execute" the selected {@code CommandSpec}. Often this means invoking a method on the spec's {@linkplain CommandSpec#userObject() user object}.
    • + *
    • Call {@link CommandLine#setExecutionResult(Object) setExecutionResult} to make the return value of that method invocation available to the application
    • + *
    • Return an exit code. Common sources of exit values are the invoked method's return value, or the user object if it implements {@link IExitCodeGenerator}.
    • + *
    + *

    Implementors that need to print messages to the console should use the {@linkplain #getOut() output} and {@linkplain #getErr() error} PrintWriters, + * and the {@linkplain #getColorScheme() color scheme} from the CommandLine object obtained from ParseResult's CommandSpec.

    + *

    API Note:

    + *

    This interface supersedes {@link IParseResultHandler2}.

    + * @since 4.0 */ + public interface IExecutionStrategy { + /** + * "Executes" the user input and returns an exit code. + * Execution often means invoking a method on the selected CommandSpec's {@linkplain CommandSpec#userObject() user object}, + * and making the return value of that invocation available via {@link CommandLine#setExecutionResult(Object) setExecutionResult}. + * @param parseResult the parse result from which to select one or more {@code CommandSpec} instances to execute. + * @return an exit code + * @throws ParameterException if the invoked method on the CommandSpec's user object threw a ParameterException to signify invalid user input. + * @throws ExecutionException if any problem occurred while executing the command. Any exceptions (other than ParameterException) should be wrapped in a ExecutionException and not thrown as is. + */ + int execute(ParseResult parseResult) throws ExecutionException, ParameterException; + } + + /** + * Represents a function that can handle a {@code ParameterException} that occurred while + * {@linkplain #parse(String...) parsing} the command line arguments. This is a + * functional interface + * whose functional method is {@link #handleException(CommandLine.ParameterException, PrintStream, CommandLine.Help.Ansi, String...)}. + *

    + * Implementations of this function can be passed to the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) CommandLine::parseWithHandlers} + * methods to handle situations when the command line could not be parsed. + *

    + * @deprecated see {@link #execute(String...)}, {@link IParameterExceptionHandler} and {@link IExecutionExceptionHandler} + * @since 2.0 */ + @Deprecated public interface IExceptionHandler { + /** Handles a {@code ParameterException} that occurred while {@linkplain #parse(String...) parsing} the command + * line arguments and optionally returns a list of results. + * @param ex the ParameterException describing the problem that occurred while parsing the command line arguments, + * and the CommandLine representing the command or subcommand whose input was invalid + * @param out the {@code PrintStream} to print help to if requested + * @param ansi for printing help messages using ANSI styles and colors + * @param args the command line arguments that could not be parsed + * @return a list of results, or an empty list if there are no results + */ + List handleException(ParameterException ex, PrintStream out, Help.Ansi ansi, String... args); + } + /** + * Classes implementing this interface know how to handle {@code ParameterExceptions} (usually from invalid user input) + * and {@code ExecutionExceptions} that occurred while executing the {@code Runnable} or {@code Callable} command. + *

    + * Implementations of this interface can be passed to the + * {@link #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...) CommandLine::parseWithHandlers} method. + *

    + * This interface replaces the {@link IParseResultHandler} interface. + *

    + * @param the return type of this handler + * @see DefaultExceptionHandler + * @deprecated see {@link #execute(String...)}, {@link IParameterExceptionHandler} and {@link IExecutionExceptionHandler} + * @since 3.0 */ + @Deprecated public interface IExceptionHandler2 { + /** Handles a {@code ParameterException} that occurred while {@linkplain #parseArgs(String...) parsing} the command + * line arguments and optionally returns a list of results. + * @param ex the ParameterException describing the problem that occurred while parsing the command line arguments, + * and the CommandLine representing the command or subcommand whose input was invalid + * @param args the command line arguments that could not be parsed + * @return an object resulting from handling the exception + */ + R handleParseException(ParameterException ex, String[] args); + /** Handles a {@code ExecutionException} that occurred while executing the {@code Runnable} or + * {@code Callable} command and optionally returns a list of results. + * @param ex the ExecutionException describing the problem that occurred while executing the {@code Runnable} or + * {@code Callable} command, and the CommandLine representing the command or subcommand that was being executed + * @param parseResult the result of parsing the command line arguments + * @return an object resulting from handling the exception + */ + R handleExecutionException(ExecutionException ex, ParseResult parseResult); + } + + /** Classes implementing this interface know how to handle {@code ParameterExceptions} (usually from invalid user input). + *

    Implementation Requirements:

    + *

    Implementors that need to print messages to the console should use the {@linkplain #getOut() output} and {@linkplain #getErr() error} PrintWriters, + * and the {@linkplain #getColorScheme() color scheme} from the CommandLine object obtained from the exception.

    + *

    Implementation Note:

    + *

    See {@link #getParameterExceptionHandler()} for a description of the default handler.

    + *

    API Note:

    + *

    This interface supersedes {@link IExceptionHandler2}.

    + * @see CommandLine#setParameterExceptionHandler(IParameterExceptionHandler) + * @since 4.0 + */ + public interface IParameterExceptionHandler { + /** Handles a {@code ParameterException} that occurred while {@linkplain #parseArgs(String...) parsing} the command + * line arguments and returns an exit code suitable for returning from {@link #execute(String...)}. + * @param ex the ParameterException describing the problem that occurred while parsing the command line arguments, + * and the CommandLine representing the command or subcommand whose input was invalid + * @param args the command line arguments that could not be parsed + * @return an exit code + */ + int handleParseException(ParameterException ex, String[] args) throws Exception; + } + /** + * Classes implementing this interface know how to handle Exceptions that occurred while executing the {@code Runnable}, {@code Callable} or {@code Method} user object of the command. + *

    Implementation Requirements:

    + *

    Implementors that need to print messages to the console should use the {@linkplain #getOut() output} and {@linkplain #getErr() error} PrintWriters, + * and the {@linkplain #getColorScheme() color scheme} from the CommandLine object obtained from the exception.

    + *

    API Note:

    + *

    This interface supersedes {@link IExceptionHandler2}.

    + *

    Example usage:

    + *
    +     * IExecutionExceptionHandler errorHandler = new IExecutionExceptionHandler() {
    +     *     public int handleExecutionException(Exception ex,
    +     *                                         CommandLine commandLine,
    +     *                                         ParseResult parseResult) {
    +     *         //ex.printStackTrace(); // no stack trace
    +     *         commandLine.getErr().println(ex.getMessage());
    +     *         commandLine.usage(commandLine.getErr());
    +     *         return commandLine.getCommandSpec().exitCodeOnExecutionException();
    +     *     }
    +     * };
    +     * int exitCode = new CommandLine(new App())
    +     *         .setExecutionExceptionHandler(errorHandler)
    +     *         .execute(args);
    +     * 
    + * @see CommandLine#setExecutionExceptionHandler(IExecutionExceptionHandler) + * @since 4.0 + */ + public interface IExecutionExceptionHandler { + /** Handles an {@code Exception} that occurred while executing the {@code Runnable} or + * {@code Callable} command and returns an exit code suitable for returning from {@link #execute(String...)}. + * @param ex the Exception thrown by the {@code Runnable}, {@code Callable} or {@code Method} user object of the command + * @param commandLine the CommandLine representing the command or subcommand where the exception occurred + * @param parseResult the result of parsing the command line arguments + * @return an exit code + */ + int handleExecutionException(Exception ex, CommandLine commandLine, ParseResult parseResult) throws Exception; + } + + /** Abstract superclass for {@link IParseResultHandler2} and {@link IExceptionHandler2} implementations. + *

    Note that {@code AbstractHandler} is a generic type. This, along with the abstract {@code self} method, + * allows method chaining to work properly in subclasses, without the need for casts. An example subclass can look like this:

    + *
    {@code
    +     * class MyResultHandler extends AbstractHandler implements IParseResultHandler2 {
    +     *
    +     *     public MyReturnType handleParseResult(ParseResult parseResult) { ... }
    +     *
    +     *     protected MyResultHandler self() { return this; }
    +     * }
    +     * }
    + * @param the return type of this handler + * @param The type of the handler subclass; for fluent API method chaining + * @deprecated see {@link #execute(String...)} + * @since 3.0 */ + @Deprecated public static abstract class AbstractHandler> { + private Help.ColorScheme colorScheme = Help.defaultColorScheme(Help.Ansi.AUTO); + private Integer exitCode; + private PrintStream out = System.out; + private PrintStream err = System.err; + + /** Returns the stream to print command output to. Defaults to {@code System.out}, unless {@link #useOut(PrintStream)} + * was called with a different stream. + *

    {@code IParseResultHandler2} implementations should use this stream. + * By convention, when the user requests + * help with a {@code --help} or similar option, the usage help message is printed to the standard output stream so that it can be easily searched and paged.

    */ + public PrintStream out() { return out; } + /** Returns the stream to print diagnostic messages to. Defaults to {@code System.err}, unless {@link #useErr(PrintStream)} + * was called with a different stream.

    {@code IExceptionHandler2} implementations should use this stream to print error + * messages (which may include a usage help message) when an unexpected error occurs.

    */ + public PrintStream err() { return err; } + /** Returns the ANSI style to use. Defaults to {@code Help.Ansi.AUTO}, unless {@link #useAnsi(CommandLine.Help.Ansi)} was called with a different setting. + * @deprecated use {@link #colorScheme()} instead */ + @Deprecated public Help.Ansi ansi() { return colorScheme.ansi(); } + /** Returns the ColorScheme to use. Defaults to {@code Help#defaultColorScheme(Help.Ansi.AUTO)}. + * @since 4.0*/ + public Help.ColorScheme colorScheme() { return colorScheme; } + /** Returns the exit code to use as the termination status, or {@code null} (the default) if the handler should + * not call {@link System#exit(int)} after processing completes. + * @see #andExit(int) */ + public Integer exitCode() { return exitCode; } + /** Returns {@code true} if an exit code was set with {@link #andExit(int)}, or {@code false} (the default) if + * the handler should not call {@link System#exit(int)} after processing completes. */ + public boolean hasExitCode() { return exitCode != null; } + + /** Convenience method for subclasses that returns the specified result object if no exit code was set, + * or otherwise, if an exit code {@linkplain #andExit(int) was set}, calls {@code System.exit} with the configured + * exit code to terminate the currently running Java virtual machine. */ + protected R returnResultOrExit(R result) { + if (hasExitCode()) { exit(exitCode()); } + return result; + } + + /** Convenience method for subclasses that throws the specified ExecutionException if no exit code was set, + * or otherwise, if an exit code {@linkplain #andExit(int) was set}, prints the stacktrace of the specified exception + * to the diagnostic error stream and calls {@code System.exit} with the configured + * exit code to terminate the currently running Java virtual machine. */ + protected R throwOrExit(ExecutionException ex) { + if (hasExitCode()) { + ex.printStackTrace(this.err()); + exit(exitCode()); + return null; + } + throw ex; + } + /** Calls {@code System.exit(int)} with the specified exit code. */ + protected void exit(int exitCode) { System.exit(exitCode); } + + /** Returns {@code this} to allow method chaining when calling the setters for a fluent API. */ + protected abstract T self(); + + /** Sets the stream to print command output to. + * @deprecated use {@link CommandLine#setOut(PrintWriter)} and {@link CommandLine#execute(String...)} instead */ + @Deprecated public T useOut(PrintStream out) { this.out = Assert.notNull(out, "out"); return self(); } + /** Sets the stream to print diagnostic messages to. + * @deprecated use {@link CommandLine#setErr(PrintWriter)} and {@link CommandLine#execute(String...)} instead */ + @Deprecated public T useErr(PrintStream err) { this.err = Assert.notNull(err, "err"); return self(); } + /** Sets the ANSI style to use and resets the color scheme to the default. + * @deprecated use {@link CommandLine#setColorScheme(Help.ColorScheme)} and {@link CommandLine#execute(String...)} instead + * @see #ansi() */ + @Deprecated public T useAnsi(Help.Ansi ansi) { this.colorScheme = Help.defaultColorScheme(Assert.notNull(ansi, "ansi")); return self(); } + /** Indicates that the handler should call {@link System#exit(int)} after processing completes and sets the exit code to use as the termination status. + * @deprecated use {@link CommandLine#execute(String...)} instead, and call {@code System.exit()} in the application. */ + @Deprecated public T andExit(int exitCode) { this.exitCode = exitCode; return self(); } + } + + /** + * Default exception handler that handles invalid user input by printing the exception message, followed by the usage + * message for the command or subcommand whose input was invalid. + *

    {@code ParameterExceptions} (invalid user input) is handled like this:

    + *
    +     *     err().println(paramException.getMessage());
    +     *     paramException.getCommandLine().usage(err(), ansi());
    +     *     if (hasExitCode()) System.exit(exitCode()); else return returnValue;
    +     * 
    + *

    {@code ExecutionExceptions} that occurred while executing the {@code Runnable} or {@code Callable} command are simply rethrown and not handled.

    + * @deprecated see {@link #execute(String...)}, {@link #getParameterExceptionHandler()} and {@link #getExecutionExceptionHandler()} + * @since 2.0 */ + @Deprecated public static class DefaultExceptionHandler extends AbstractHandler> implements IExceptionHandler, IExceptionHandler2 { + public List handleException(ParameterException ex, PrintStream out, Help.Ansi ansi, String... args) { + internalHandleParseException(ex, newPrintWriter(out, getStdoutEncoding()), Help.defaultColorScheme(ansi)); return Collections.emptyList(); } + + /** Prints the message of the specified exception, followed by the usage message for the command or subcommand + * whose input was invalid, to the stream returned by {@link #err()}. + * @param ex the ParameterException describing the problem that occurred while parsing the command line arguments, + * and the CommandLine representing the command or subcommand whose input was invalid + * @param args the command line arguments that could not be parsed + * @return the empty list + * @since 3.0 */ + public R handleParseException(ParameterException ex, String[] args) { + internalHandleParseException(ex, newPrintWriter(err(), getStderrEncoding()), colorScheme()); return returnResultOrExit(null); } + + static void internalHandleParseException(ParameterException ex, PrintWriter writer, Help.ColorScheme colorScheme) { + writer.println(colorScheme.errorText(ex.getMessage())); + if (!UnmatchedArgumentException.printSuggestions(ex, writer)) { + ex.getCommandLine().usage(writer, colorScheme); + } + Tracer tracer = CommandLine.tracer(); + if (tracer.isDebug()) { // #956 show error details if DEBUG is enabled + ex.printStackTrace(tracer.stream); + } + } + /** This implementation always simply rethrows the specified exception. + * @param ex the ExecutionException describing the problem that occurred while executing the {@code Runnable} or {@code Callable} command + * @param parseResult the result of parsing the command line arguments + * @return nothing: this method always rethrows the specified exception + * @throws ExecutionException always rethrows the specified exception + * @since 3.0 */ + public R handleExecutionException(ExecutionException ex, ParseResult parseResult) { return throwOrExit(ex); } + + @Override protected DefaultExceptionHandler self() { return this; } + } + /** Convenience method that returns {@code new DefaultExceptionHandler>()}. */ + public static DefaultExceptionHandler> defaultExceptionHandler() { return new DefaultExceptionHandler>(); } + + /** @deprecated use {@link #printHelpIfRequested(ParseResult)} instead + * @since 2.0 */ + @Deprecated public static boolean printHelpIfRequested(List parsedCommands, PrintStream out, Help.Ansi ansi) { + return printHelpIfRequested(parsedCommands, out, out, ansi); + } + /** + * Delegates to {@link #executeHelpRequest(ParseResult)}. + * @param parseResult contains the {@code CommandLine} objects found during parsing; check these to see if help was requested + * @return {@code true} if help was printed, {@code false} otherwise + * @since 3.0 */ + public static boolean printHelpIfRequested(ParseResult parseResult) { + return executeHelpRequest(parseResult) != null; + } + /** + * Delegates to the implementation of {@link #executeHelpRequest(ParseResult)}. + * @deprecated use {@link #executeHelpRequest(ParseResult)} instead + * @param parsedCommands the list of {@code CommandLine} objects to check if help was requested + * @param out the {@code PrintStream} to print help to if requested + * @param err the error string to print diagnostic messages to, in addition to the output from the exception handler + * @param ansi for printing help messages using ANSI styles and colors + * @return {@code true} if help was printed, {@code false} otherwise + * @since 3.0 */ + @Deprecated public static boolean printHelpIfRequested(List parsedCommands, PrintStream out, PrintStream err, Help.Ansi ansi) { + return printHelpIfRequested(parsedCommands, out, err, Help.defaultColorScheme(ansi)); + } + /** + * Delegates to the implementation of {@link #executeHelpRequest(ParseResult)}. + * @deprecated use {@link #executeHelpRequest(ParseResult)} instead + * @param parsedCommands the list of {@code CommandLine} objects to check if help was requested + * @param out the {@code PrintStream} to print help to if requested + * @param err the error string to print diagnostic messages to, in addition to the output from the exception handler + * @param colorScheme for printing help messages using ANSI styles and colors + * @return {@code true} if help was printed, {@code false} otherwise + * @since 3.6 */ + @Deprecated public static boolean printHelpIfRequested(List parsedCommands, PrintStream out, PrintStream err, Help.ColorScheme colorScheme) { + // for backwards compatibility + for (CommandLine cmd : parsedCommands) { cmd.setOut(newPrintWriter(out, getStdoutEncoding())).setErr(newPrintWriter(err, getStderrEncoding())).setColorScheme(colorScheme); } + return executeHelpRequest(parsedCommands) != null; + } + + /** + * Helper method that may be useful when processing the {@code ParseResult} that results from successfully + * {@linkplain #parseArgs(String...) parsing} command line arguments. This method prints out + * {@linkplain #usage(PrintWriter, Help.ColorScheme) usage help} to the {@linkplain CommandLine#getOut() configured output writer} + * if {@linkplain #isUsageHelpRequested() requested} or {@linkplain #printVersionHelp(PrintWriter, Help.Ansi, Object...) version help} + * to the {@linkplain CommandLine#getOut() configured output writer} if {@linkplain #isVersionHelpRequested() requested} + * and returns {@link CommandSpec#exitCodeOnUsageHelp()} or {@link CommandSpec#exitCodeOnVersionHelp()}, respectively. + * If the command is a {@link Command#helpCommand()} and {@code runnable} or {@code callable}, + * that command is executed and this method returns {@link CommandSpec#exitCodeOnUsageHelp()}. + * Otherwise, if none of the specified {@code CommandLine} objects have help requested, + * this method returns {@code null}.

    + * Note that this method only looks at the {@link Option#usageHelp() usageHelp} and + * {@link Option#versionHelp() versionHelp} attributes. The {@link Option#help() help} attribute is ignored. + *

    Implementation note:

    + * When an error occurs while processing the help request, it is recommended custom Help commands throw a + * {@link ParameterException} with a reference to the parent command. This will print the error message and the + * usage for the parent command, and will use the exit code of the exception handler if one was set. + *

    + * @param parseResult contains the {@code CommandLine} objects found during parsing; check these to see if help was requested + * @return {@link CommandSpec#exitCodeOnUsageHelp()} if usage help was requested, + * {@link CommandSpec#exitCodeOnVersionHelp()} if version help was requested, and {@code null} otherwise + * @see IHelpCommandInitializable2 + * @since 4.0 */ + public static Integer executeHelpRequest(ParseResult parseResult) { + return executeHelpRequest(parseResult.asCommandLineList()); + } + /** @since 4.0 */ + static Integer executeHelpRequest(List parsedCommands) { + Tracer t = CommandLine.tracer(); + for (CommandLine parsed : parsedCommands) { + Help.ColorScheme colorScheme = parsed.getColorScheme(); + PrintWriter out = parsed.getOut(); + if (parsed.isUsageHelpRequested()) { + t.debug("Printing usage help for '%s' as requested.", parsed.commandSpec.qualifiedName()); + parsed.usage(out, colorScheme); + return parsed.getCommandSpec().exitCodeOnUsageHelp(); + } else if (parsed.isVersionHelpRequested()) { + t.debug("Printing version info for '%s' as requested.", parsed.commandSpec.qualifiedName()); + parsed.printVersionHelp(out, colorScheme.ansi); + return parsed.getCommandSpec().exitCodeOnVersionHelp(); + } else if (parsed.getCommandSpec().helpCommand()) { + String fullName = parsed.commandSpec.qualifiedName(); + PrintWriter err = parsed.getErr(); + if (((Object) parsed.getCommand()) instanceof IHelpCommandInitializable2) { + t.debug("Initializing helpCommand '%s' (IHelpCommandInitializable2::init)...", fullName); + ((IHelpCommandInitializable2) parsed.getCommand()).init(parsed, colorScheme, out, err); + } else if (((Object) parsed.getCommand()) instanceof IHelpCommandInitializable) { + t.debug("Initializing helpCommand '%s' (IHelpCommandInitializable::init)...", fullName); + ((IHelpCommandInitializable) parsed.getCommand()).init(parsed, colorScheme.ansi, System.out, System.err); + } else { + t.debug("helpCommand '%s' does not implement IHelpCommandInitializable2 or IHelpCommandInitializable...", fullName); + } + t.debug("Executing helpCommand '%s'...", fullName); + executeUserObject(parsed, new ArrayList()); + return parsed.getCommandSpec().exitCodeOnUsageHelp(); + } + } + t.debug("Help was not requested. Continuing to process ParseResult..."); + return null; + } + private static List executeUserObject(CommandLine parsed, List executionResultList) { + Tracer tracer = CommandLine.tracer(); + + Object command = parsed.getCommand(); + if (command instanceof Runnable) { + try { + tracer.debug("Invoking Runnable::run on user object %s@%s...", command.getClass().getName(), Integer.toHexString(command.hashCode())); + ((Runnable) command).run(); + parsed.setExecutionResult(null); // 4.0 + executionResultList.add(null); // for compatibility with picocli 2.x + return executionResultList; + } catch (ParameterException ex) { + throw ex; + } catch (ExecutionException ex) { + throw ex; + } catch (Exception ex) { + throw new ExecutionException(parsed, "Error while running command (" + command + "): " + ex, ex); + } + } else if (command instanceof Callable) { + try { + tracer.debug("Invoking Callable::call on user object %s@%s...", command.getClass().getName(), Integer.toHexString(command.hashCode())); + @SuppressWarnings("unchecked") Callable callable = (Callable) command; + Object executionResult = callable.call(); + parsed.setExecutionResult(executionResult); + executionResultList.add(executionResult); + return executionResultList; + } catch (ParameterException ex) { + throw ex; + } catch (ExecutionException ex) { + throw ex; + } catch (Exception ex) { + throw new ExecutionException(parsed, "Error while calling command (" + command + "): " + ex, ex); + } + } else if (command instanceof Method) { + try { + Method method = (Method) command; + Object[] parsedArgs = parsed.getCommandSpec().commandMethodParamValues(); + Object executionResult; + if (Modifier.isStatic(method.getModifiers())) { + tracer.debug("Invoking static method %s with parameters %s", method, Arrays.toString(parsedArgs)); + executionResult = method.invoke(null, parsedArgs); // invoke static method + } else { + Object instance = (parsed.getCommandSpec().parent() != null) + ? parsed.getCommandSpec().parent().userObject() + : parsed.factory.create(method.getDeclaringClass()); + tracer.debug("Invoking method %s on %s@%s with parameters %s", + method, instance.getClass().getName(), Integer.toHexString(instance.hashCode()), Arrays.toString(parsedArgs)); + executionResult = method.invoke(instance, parsedArgs); + } + parsed.setExecutionResult(executionResult); + executionResultList.add(executionResult); + return executionResultList; + } catch (InvocationTargetException ex) { + Throwable t = ex.getTargetException(); + if (t instanceof ParameterException) { + throw (ParameterException) t; + } else if (t instanceof ExecutionException) { + throw (ExecutionException) t; + } else { + throw new ExecutionException(parsed, "Error while calling command (" + command + "): " + t, t); + } + } catch (Exception ex) { + throw new ExecutionException(parsed, "Unhandled error while calling command (" + command + "): " + ex, ex); + } + } + if (parsed.getSubcommands().isEmpty()) { + throw new ExecutionException(parsed, "Parsed command (" + command + ") is not a Method, Runnable or Callable"); + } else { + throw new ParameterException(parsed, "Missing required subcommand"); + } + } + + /** + * Convenience method to allow command line application authors to avoid some boilerplate code in their application. + * To use this method, the annotated object that this {@code CommandLine} is constructed with needs to + * either implement {@link Runnable}, {@link Callable}, or be a {@code Method} object. + * See {@link #getCommandMethods(Class, String) getCommandMethods} for a convenient way to obtain a command {@code Method}. + *

    This method replaces the {@link #run(Runnable, String...) run}, {@link #call(Callable, String...) call} + * and {@link #invoke(String, Class, String...) invoke} convenience methods that were available with previous versions of picocli. + *

    + * Exit Code + *

    + * This method returns an exit code that applications can use to call {@code System.exit}. + * (The return value of the {@code Callable} or {@code Method} can still be obtained via {@link #getExecutionResult() getExecutionResult}.) + * If the user object {@code Callable} or {@code Method} returns an {@code int} or {@code Integer}, + * this will be used as the exit code. Additionally, if the user object implements {@link CommandLine.IExitCodeGenerator IExitCodeGenerator}, + * an exit code is obtained by calling its {@code getExitCode()} method (after invoking the user object). + *

    + * In the case of multiple exit codes the highest value will be used (or if all values are negative, the lowest value will be used). + *

    + * Exception Handling + *

    + * This method never throws an exception. + *

    + * If the user specified invalid input, the {@linkplain #getParameterExceptionHandler() parameter exception handler} is invoked. + * By default this prints an error message and the usage help message, and returns an exit code. + *

    + * If an exception occurred while the user object {@code Runnable}, {@code Callable}, or {@code Method} + * was invoked, this exception is caught and passed to the {@linkplain #getExecutionExceptionHandler() execution exception handler}. + * The default {@code IExecutionExceptionHandler} will rethrow this Exception. + *

    + * Any exception thrown from the {@code IParameterExceptionHandler} or {@code IExecutionExceptionHandler} is caught, + * it stacktrace is printed and is mapped to an exit code, using the following logic: + *

    + * If an {@link CommandLine.IExitCodeExceptionMapper IExitCodeExceptionMapper} is {@linkplain #setExitCodeExceptionMapper(IExitCodeExceptionMapper) configured}, + * this mapper is used to determine the exit code based on the exception. + *

    + * If an {@code IExitCodeExceptionMapper} is not set, by default this method will return the {@code @Command} annotation's + * {@link Command#exitCodeOnInvalidInput() exitCodeOnInvalidInput} or {@link Command#exitCodeOnExecutionException() exitCodeOnExecutionException} value, respectively. + *

    Example Usage:

    + *
    +     * @Command
    +     * class MyCommand implements Callable<Integer> {
    +     *     public Integer call() { return 123; }
    +     * }
    +     * CommandLine cmd = new CommandLine(new MyCommand());
    +     * int exitCode = cmd.execute(args);
    +     * assert exitCode == 123;
    +     * System.exit(exitCode);
    +     * 
    + *

    Since {@code execute} is an instance method, not a static method, applications can do configuration before invoking the command. For example:

    + *
    {@code
    +     * CommandLine cmd = new CommandLine(new MyCallable())
    +     *         .setCaseInsensitiveEnumValuesAllowed(true) // configure a non-default parser option
    +     *         .setOut(myOutWriter()) // configure an alternative to System.out
    +     *         .setErr(myErrWriter()) // configure an alternative to System.err
    +     *         .setColorScheme(myColorScheme()); // configure a custom color scheme
    +     * int exitCode = cmd.execute(args);
    +     * System.exit(exitCode);
    +     * }
    + *

    + * If the specified command has subcommands, the {@linkplain RunLast last} subcommand specified on the + * command line is executed. This can be configured by setting the {@linkplain #setExecutionStrategy(IExecutionStrategy) execution strategy}. + * Built-in alternatives are executing the {@linkplain RunFirst first} subcommand, or executing {@linkplain RunAll all} specified subcommands. + *

    + * @param args the command line arguments to parse + * @return the exit code + * @see ExitCode + * @see IExitCodeGenerator + * @see #getExecutionResult() + * @see #getExecutionStrategy() + * @see #getParameterExceptionHandler() + * @see #getExecutionExceptionHandler() + * @see #getExitCodeExceptionMapper() + * @since 4.0 + */ + public int execute(String... args) { + ParseResult[] parseResult = new ParseResult[1]; + clearExecutionResults(); + try { + parseResult[0] = parseArgs(args); + return enrichForBackwardsCompatibility(getExecutionStrategy()).execute(parseResult[0]); + } catch (ParameterException ex) { + try { + return getParameterExceptionHandler().handleParseException(ex, args); + } catch (Exception ex2) { + return handleUnhandled(ex2, ex.getCommandLine(), ex.getCommandLine().getCommandSpec().exitCodeOnInvalidInput()); + } + } catch (ExecutionException ex) { + try { + Exception cause = ex.getCause() instanceof Exception ? (Exception) ex.getCause() : ex; + return getExecutionExceptionHandler().handleExecutionException(cause, ex.getCommandLine(), parseResult[0]); + } catch (Exception ex2) { + return handleUnhandled(ex2, ex.getCommandLine(), ex.getCommandLine().getCommandSpec().exitCodeOnExecutionException()); + } + } catch (Exception ex) { + return handleUnhandled(ex, this, getCommandSpec().exitCodeOnExecutionException()); + } + } + private static int handleUnhandled(Exception ex, CommandLine cmd, int defaultExitCode) { + cmd.getErr().print(throwableToColorString(ex, cmd.getColorScheme())); + cmd.getErr().flush(); + return mappedExitCode(ex, cmd.getExitCodeExceptionMapper(), defaultExitCode); + } + + /** + * Convert a {@code Throwable} to a {@code String} , with message and stack traces extracted and colored + * according to {@code ColorScheme}. + * @param t the {@code Throwable} to be converted + * @param existingColorScheme the {@code ColorScheme} to use + * @return converted and colored {@code String} + */ + private static String throwableToColorString(Throwable t, Help.ColorScheme existingColorScheme) { + Help.ColorScheme colorScheme = new Help.ColorScheme.Builder(existingColorScheme).applySystemProperties().build(); + StringWriter stringWriter = new ColoredStackTraceWriter(colorScheme); + t.printStackTrace(new PrintWriter(stringWriter)); + return stringWriter.toString(); + } + + /** + * Extends StringWriter to use ColorScheme. Allows separating + * exception messages from stack traces by intercepting write method. + */ + static class ColoredStackTraceWriter extends StringWriter { + Help.ColorScheme colorScheme; + + public ColoredStackTraceWriter(Help.ColorScheme colorScheme) { this.colorScheme = colorScheme; } + + @Override + public void write(String str, int off, int len) { + List styles = str.startsWith("\t") ? colorScheme.stackTraceStyles() : colorScheme.errorStyles(); + super.write(colorScheme.apply(str.substring(off, len), styles).toString()); + } + } + + private T enrichForBackwardsCompatibility(T obj) { + // in case the IExecutionStrategy is a built-in like RunLast, + // and the application called #useOut, #useErr or #useAnsi on it + if (obj instanceof AbstractHandler) { + AbstractHandler handler = (AbstractHandler) obj; + if (handler.out() != System.out) { setOut(newPrintWriter(handler.out(), getStdoutEncoding())); } + if (handler.err() != System.err) { setErr(newPrintWriter(handler.err(), getStderrEncoding())); } + if (handler.ansi() != Help.Ansi.AUTO) { setColorScheme(handler.colorScheme()); } + } + return obj; + } + /** Command line parse result handler that returns a value. This handler prints help if requested, and otherwise calls + * {@link #handle(CommandLine.ParseResult)} with the parse result. Facilitates implementation of the {@link IParseResultHandler2} interface. + *

    Note that {@code AbstractParseResultHandler} is a generic type. This, along with the abstract {@code self} method, + * allows method chaining to work properly in subclasses, without the need for casts. An example subclass can look like this:

    + *
    {@code
    +     * class MyResultHandler extends AbstractParseResultHandler {
    +     *
    +     *     protected MyReturnType handle(ParseResult parseResult) throws ExecutionException { ... }
    +     *
    +     *     protected MyResultHandler self() { return this; }
    +     * }
    +     * }
    + * @deprecated see {@link #execute(String...)}, {@link #getExecutionStrategy()}, {@link #getParameterExceptionHandler()}, {@link #getExecutionExceptionHandler()} + * @since 3.0 */ + @Deprecated public abstract static class AbstractParseResultHandler extends AbstractHandler> implements IParseResultHandler2, IExecutionStrategy { + /** Prints help if requested, and otherwise calls {@link #handle(CommandLine.ParseResult)}. + * Finally, either a list of result objects is returned, or the JVM is terminated if an exit code {@linkplain #andExit(int) was set}. + * + * @param parseResult the {@code ParseResult} that resulted from successfully parsing the command line arguments + * @return the result of {@link #handle(CommandLine.ParseResult) processing parse results} + * @throws ParameterException if the {@link HelpCommand HelpCommand} was invoked for an unknown subcommand. Any {@code ParameterExceptions} + * thrown from this method are treated as if this exception was thrown during parsing and passed to the {@link IExceptionHandler2} + * @throws ExecutionException if a problem occurred while processing the parse results; client code can use + * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed + */ + public R handleParseResult(ParseResult parseResult) throws ExecutionException { + if (printHelpIfRequested(parseResult.asCommandLineList(), out(), err(), colorScheme())) { + return returnResultOrExit(null); + } + return returnResultOrExit(handle(parseResult)); + } + + public int execute(ParseResult parseResult) throws ExecutionException { + Integer helpExitCode = executeHelpRequest(parseResult); + if (helpExitCode != null) { return helpExitCode; } + + Tracer t = CommandLine.tracer(); + t.debug("%s: handling ParseResult...", getClass().getSimpleName()); + R executionResult = handle(parseResult); + List exitCodeGenerators = extractExitCodeGenerators(parseResult); + t.debug("%s: ParseResult has %s exit code generators", getClass().getSimpleName(), exitCodeGenerators.size()); + return resolveExitCode(parseResult.commandSpec().exitCodeOnSuccess(), executionResult, exitCodeGenerators); + } + + // Use the highest value (or if all values are negative, use the lowest value). + private int resolveExitCode(int exitCodeOnSuccess, R executionResult, List exitCodeGenerators) { + int result = 0; + for (IExitCodeGenerator generator : exitCodeGenerators) { + try { + int exitCode = generator.getExitCode(); + if ((exitCode > 0 && exitCode > result) || (exitCode < result && result <= 0)) { + result = exitCode; + } + } catch (Exception ex) { + result = (result == 0) ? 1 : result; + ex.printStackTrace(); + } + } + Tracer t = CommandLine.tracer(); + t.debug("resolveExitCode: exit code generators resulted in exit code=%d", result); + if (executionResult instanceof List) { + List resultList = (List) executionResult; + for (Object obj : resultList) { + if (obj instanceof Integer) { + int exitCode = (Integer) obj; + if ((exitCode > 0 && exitCode > result) || (exitCode < result && result <= 0)) { + result = exitCode; + } + } + } + } + t.debug("resolveExitCode: execution results resulted in exit code=%d", result); + t.debug("resolveExitCode: returning exit code=%d", result == 0 ? exitCodeOnSuccess : result); + return result == 0 ? exitCodeOnSuccess : result; + } + + /** Processes the specified {@code ParseResult} and returns the result as a list of objects. + * Implementations are responsible for catching any exceptions thrown in the {@code handle} method, and + * rethrowing an {@code ExecutionException} that details the problem and captures the offending {@code CommandLine} object. + * + * @param parseResult the {@code ParseResult} that resulted from successfully parsing the command line arguments + * @return the result of processing parse results + * @throws ExecutionException if a problem occurred while processing the parse results; client code can use + * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed + */ + protected abstract R handle(ParseResult parseResult) throws ExecutionException; + + protected List extractExitCodeGenerators(ParseResult parseResult) { return Collections.emptyList(); } + } + /** + * Command line {@linkplain IExecutionStrategy execution strategy} that prints help if requested, and otherwise executes the top-level + * {@code Runnable} or {@code Callable} command. + * For use by the {@link #execute(String...) execute} method. + * @since 2.0 */ + public static class RunFirst extends AbstractParseResultHandler> implements IParseResultHandler { + /** {@inheritDoc} */ + public int execute(ParseResult parseResult) throws ExecutionException { return super.execute(parseResult); } + + /** Prints help if requested, and otherwise executes the top-level {@code Runnable} or {@code Callable} command. + * Finally, either a list of result objects is returned, or the JVM is terminated if an exit code {@linkplain #andExit(int) was set}. + * If the top-level command does not implement either {@code Runnable} or {@code Callable}, an {@code ExecutionException} + * is thrown detailing the problem and capturing the offending {@code CommandLine} object. + * + * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments + * @param out the {@code PrintStream} to print help to if requested + * @param ansi for printing help messages using ANSI styles and colors + * @return an empty list if help was requested, or a list containing a single element: the result of calling the + * {@code Callable}, or a {@code null} element if the top-level command was a {@code Runnable} + * @throws ParameterException if the {@link HelpCommand HelpCommand} was invoked for an unknown subcommand. Any {@code ParameterExceptions} + * thrown from this method are treated as if this exception was thrown during parsing and passed to the {@link IExceptionHandler} + * @throws ExecutionException if a problem occurred while processing the parse results; use + * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed + */ + public List handleParseResult(List parsedCommands, PrintStream out, Help.Ansi ansi) { + if (printHelpIfRequested(parsedCommands, out, err(), ansi)) { return returnResultOrExit(Collections.emptyList()); } + return returnResultOrExit(executeUserObject(parsedCommands.get(0), new ArrayList())); + } + /** Executes the top-level {@code Runnable} or {@code Callable} subcommand. + * If the top-level command does not implement either {@code Runnable} or {@code Callable} and is not a {@code Method}, an {@code ExecutionException} + * is thrown detailing the problem and capturing the offending {@code CommandLine} object. + * + * @param parseResult the {@code ParseResult} that resulted from successfully parsing the command line arguments + * @return an empty list if help was requested, or a list containing a single element: the result of calling the + * {@code Callable}, or a {@code null} element if the last (sub)command was a {@code Runnable} + * @throws ExecutionException if a problem occurred while processing the parse results; use + * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed + * @since 3.0 */ + protected List handle(ParseResult parseResult) throws ExecutionException { + Tracer t = CommandLine.tracer(); + t.debug("RunFirst: executing user object for '%s'...", parseResult.commandSpec().qualifiedName()); + return executeUserObject(parseResult.commandSpec().commandLine(), new ArrayList()); // first + } + + protected List extractExitCodeGenerators(ParseResult parseResult) { + if (parseResult.commandSpec().userObject() instanceof IExitCodeGenerator) { + return Collections.singletonList((IExitCodeGenerator) parseResult.commandSpec().userObject()); + } + return Collections.emptyList(); + } + + @Override protected RunFirst self() { return this; } + } + /** + * Command line {@linkplain IExecutionStrategy execution strategy} that prints help if requested, and otherwise executes the most specific + * {@code Runnable} or {@code Callable} subcommand. + * For use by the {@link #execute(String...) execute} method. + *

    + * Something like this:

    + *
    {@code
    +     *     // RunLast implementation: print help if requested, otherwise execute the most specific subcommand
    +     *     List parsedCommands = parseResult.asCommandLineList();
    +     *     if (CommandLine.printHelpIfRequested(parsedCommands, out(), err(), ansi())) {
    +     *         return emptyList();
    +     *     }
    +     *     CommandLine last = parsedCommands.get(parsedCommands.size() - 1);
    +     *     Object command = last.getCommand();
    +     *     Object result = null;
    +     *     if (command instanceof Runnable) {
    +     *         try {
    +     *             ((Runnable) command).run();
    +     *         } catch (Exception ex) {
    +     *             throw new ExecutionException(last, "Error in runnable " + command, ex);
    +     *         }
    +     *     } else if (command instanceof Callable) {
    +     *         try {
    +     *             result = ((Callable) command).call();
    +     *         } catch (Exception ex) {
    +     *             throw new ExecutionException(last, "Error in callable " + command, ex);
    +     *         }
    +     *     } else {
    +     *         throw new ExecutionException(last, "Parsed command (" + command + ") is not Runnable or Callable");
    +     *     }
    +     *     last.setExecutionResult(result);
    +     *     return Arrays.asList(result);
    +     * }
    + *

    + * From picocli v2.0, {@code RunLast} is used to implement the {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...) run} + * and {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...) call} convenience methods. + *

    + * @since 2.0 */ + public static class RunLast extends AbstractParseResultHandler> implements IParseResultHandler { + /** {@inheritDoc} */ + public int execute(ParseResult parseResult) throws ExecutionException { return super.execute(parseResult); } + + /** Prints help if requested, and otherwise executes the most specific {@code Runnable} or {@code Callable} subcommand. + *

    For {@linkplain Command#subcommandsRepeatable() repeatable subcommands}, this method + * may execute multiple subcommands: the most deeply nested subcommands that have the same parent command.

    + *

    Finally, either a list of result objects is returned, or the JVM is terminated if an exit code {@linkplain #andExit(int) was set}.

    + *

    If the last (sub)command does not implement either {@code Runnable} or {@code Callable}, an {@code ExecutionException} + * is thrown detailing the problem and capturing the offending {@code CommandLine} object.

    + * + * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments + * @param out the {@code PrintStream} to print help to if requested + * @param ansi for printing help messages using ANSI styles and colors + * @return an empty list if help was requested, or a list containing a single element: the result of calling the + * {@code Callable}, or a {@code null} element if the last (sub)command was a {@code Runnable} + * @throws ParameterException if the {@link HelpCommand HelpCommand} was invoked for an unknown subcommand. Any {@code ParameterExceptions} + * thrown from this method are treated as if this exception was thrown during parsing and passed to the {@link IExceptionHandler} + * @throws ExecutionException if a problem occurred while processing the parse results; use + * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed + */ + public List handleParseResult(List parsedCommands, PrintStream out, Help.Ansi ansi) { + if (printHelpIfRequested(parsedCommands, out, err(), ansi)) { return returnResultOrExit(Collections.emptyList()); } + return returnResultOrExit(executeUserObjectOfLastSubcommandWithSameParent(parsedCommands)); + } + /** Executes the most specific {@code Runnable} or {@code Callable} subcommand. + *

    For {@linkplain Command#subcommandsRepeatable() repeatable subcommands}, this method + * may execute multiple subcommands: the most deeply nested subcommands that have the same parent command.

    + *

    If the user object of the executed (sub)command does not implement either {@code Runnable} or {@code Callable} and is not a {@code Method}, an {@code ExecutionException} + * is thrown detailing the problem and capturing the offending {@code CommandLine} object.

    + * + * @param parseResult the {@code ParseResult} that resulted from successfully parsing the command line arguments + * @return an empty list if help was requested, or a list containing a single element: the result of calling the + * {@code Callable}, or a {@code null} element if the last (sub)command was a {@code Runnable} + * @throws ExecutionException if a problem occurred while processing the parse results; use + * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed + * @since 3.0 */ + protected List handle(ParseResult parseResult) throws ExecutionException { + return executeUserObjectOfLastSubcommandWithSameParent(parseResult.asCommandLineList()); + } + private static List executeUserObjectOfLastSubcommandWithSameParent(List parsedCommands) { + Tracer t = CommandLine.tracer(); + int start = indexOfLastSubcommandWithSameParent(parsedCommands); + List result = new ArrayList(); + for (int i = start; i < parsedCommands.size(); i++) { + t.debug("RunLast: executing user object for '%s'...", parsedCommands.get(i).commandSpec.qualifiedName()); + executeUserObject(parsedCommands.get(i), result); + } + return result; + } + // find list of most deeply nested sub-(sub*)-commands + private static int indexOfLastSubcommandWithSameParent(List parsedCommands) { + int start = parsedCommands.size() - 1; + for (int i = parsedCommands.size() - 2; i >= 0; i--) { + if (parsedCommands.get(i).getParent() != parsedCommands.get(i + 1).getParent()) { break; } + start = i; + } + return start; + } + + protected List extractExitCodeGenerators(ParseResult parseResult) { + List parsedCommands = parseResult.asCommandLineList(); + int start = indexOfLastSubcommandWithSameParent(parsedCommands); + List result = new ArrayList(); + for (int i = start; i < parsedCommands.size(); i++) { + Object userObject = parsedCommands.get(i).getCommandSpec().userObject(); + if (userObject instanceof IExitCodeGenerator) { result.add((IExitCodeGenerator) userObject); } + } + return result; + } + @Override protected RunLast self() { return this; } + } + /** + * Command line {@linkplain IExecutionStrategy execution strategy} that prints help if requested, and otherwise executes the top-level command and + * all subcommands as {@code Runnable}, {@code Callable} or {@code Method}. + * For use by the {@link #execute(String...) execute} method. + * @since 2.0 */ + public static class RunAll extends AbstractParseResultHandler> implements IParseResultHandler { + /** {@inheritDoc} */ + public int execute(ParseResult parseResult) throws ExecutionException { return super.execute(parseResult); } + + /** Prints help if requested, and otherwise executes the top-level command and all subcommands as {@code Runnable}, + * {@code Callable} or {@code Method}. Finally, either a list of result objects is returned, or the JVM is terminated if an exit + * code {@linkplain #andExit(int) was set}. If any of the {@code CommandLine} commands does not implement either + * {@code Runnable} or {@code Callable}, an {@code ExecutionException} + * is thrown detailing the problem and capturing the offending {@code CommandLine} object. + * + * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments + * @param out the {@code PrintStream} to print help to if requested + * @param ansi for printing help messages using ANSI styles and colors + * @return an empty list if help was requested, or a list containing the result of executing all commands: + * the return values from calling the {@code Callable} commands, {@code null} elements for commands that implement {@code Runnable} + * @throws ParameterException if the {@link HelpCommand HelpCommand} was invoked for an unknown subcommand. Any {@code ParameterExceptions} + * thrown from this method are treated as if this exception was thrown during parsing and passed to the {@link IExceptionHandler} + * @throws ExecutionException if a problem occurred while processing the parse results; use + * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed + */ + public List handleParseResult(List parsedCommands, PrintStream out, Help.Ansi ansi) { + if (printHelpIfRequested(parsedCommands, out, err(), ansi)) { return returnResultOrExit(Collections.emptyList()); } + List result = new ArrayList(); + for (CommandLine parsed : parsedCommands) { + executeUserObject(parsed, result); + } + return returnResultOrExit(result); + } + /** Executes the top-level command and all subcommands as {@code Runnable} or {@code Callable}. + * If any of the {@code CommandLine} commands does not implement either {@code Runnable} or {@code Callable} and is not a {@code Method}, an {@code ExecutionException} + * is thrown detailing the problem and capturing the offending {@code CommandLine} object. + * + * @param parseResult the {@code ParseResult} that resulted from successfully parsing the command line arguments + * @return an empty list if help was requested, or a list containing the result of executing all commands: + * the return values from calling the {@code Callable} commands, {@code null} elements for commands that implement {@code Runnable} + * @throws ExecutionException if a problem occurred while processing the parse results; use + * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed + * @since 3.0 */ + protected List handle(ParseResult parseResult) throws ExecutionException { + Tracer t = CommandLine.tracer(); + return returnResultOrExit(recursivelyExecuteUserObject(parseResult, new ArrayList(), t)); + } + private List recursivelyExecuteUserObject(ParseResult parseResult, List result, Tracer t) throws ExecutionException { + t.debug("%s: executing user object for '%s'...", getClass().getSimpleName(), parseResult.commandSpec.qualifiedName()); + executeUserObject(parseResult.commandSpec().commandLine(), result); + for (ParseResult pr : parseResult.subcommands()) { + recursivelyExecuteUserObject(pr, result, t); + } + return result; + } + protected List extractExitCodeGenerators(ParseResult parseResult) { + return recursivelyExtractExitCodeGenerators(parseResult, new ArrayList()); + } + private List recursivelyExtractExitCodeGenerators(ParseResult parseResult, List result) throws ExecutionException { + if (parseResult.commandSpec().userObject() instanceof IExitCodeGenerator) { result.add((IExitCodeGenerator) parseResult.commandSpec().userObject()); } + for (ParseResult pr : parseResult.subcommands()) { + recursivelyExtractExitCodeGenerators(pr, result); + } + return result; + } + @Override protected RunAll self() { return this; } + } + + /** + * @deprecated use {@link #execute(String...)} and {@link #getExecutionResult()} instead + * @since 2.0 */ + @Deprecated public List parseWithHandler(IParseResultHandler handler, PrintStream out, String... args) { + return parseWithHandlers(handler, out, Help.Ansi.AUTO, defaultExceptionHandler(), args); + } + /** + * Returns the result of calling {@link #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)} with + * a new {@link DefaultExceptionHandler} in addition to the specified parse result handler and the specified command line arguments. + *

    + * This is a convenience method intended to offer the same ease of use as the {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...) run} + * and {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...) call} methods, but with more flexibility and better + * support for nested subcommands. + *

    + *

    Calling this method roughly expands to:

    + *
    {@code
    +     * try {
    +     *     ParseResult parseResult = parseArgs(args);
    +     *     return handler.handleParseResult(parseResult);
    +     * } catch (ParameterException ex) {
    +     *     return new DefaultExceptionHandler().handleParseException(ex, args);
    +     * }
    +     * }
    + *

    + * Picocli provides some default handlers that allow you to accomplish some common tasks with very little code. + * The following handlers are available:

    + *
      + *
    • {@link RunLast} handler prints help if requested, and otherwise gets the last specified command or subcommand + * and tries to execute it as a {@code Runnable} or {@code Callable}.
    • + *
    • {@link RunFirst} handler prints help if requested, and otherwise executes the top-level command as a {@code Runnable} or {@code Callable}.
    • + *
    • {@link RunAll} handler prints help if requested, and otherwise executes all recognized commands and subcommands as {@code Runnable} or {@code Callable} tasks.
    • + *
    • {@link DefaultExceptionHandler} prints the error message followed by usage help
    • + *
    + * @param the return type of this handler + * @param handler the function that will handle the result of successfully parsing the command line arguments + * @param args the command line arguments + * @return an object resulting from handling the parse result or the exception that occurred while parsing the input + * @throws ExecutionException if the command line arguments were parsed successfully but a problem occurred while processing the + * parse results; use {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed + * @see RunLast + * @see RunAll + * @deprecated use {@link #execute(String...)} and {@link #getExecutionResult()} instead + * @since 3.0 */ + @Deprecated public R parseWithHandler(IParseResultHandler2 handler, String[] args) { + return parseWithHandlers(handler, new DefaultExceptionHandler(), args); + } + + /** + * @deprecated use {@link #execute(String...)} and {@link #getExecutionResult()} instead + * @since 2.0 */ + @Deprecated public List parseWithHandlers(IParseResultHandler handler, PrintStream out, Help.Ansi ansi, IExceptionHandler exceptionHandler, String... args) { + clearExecutionResults(); + try { + List result = parse(args); + return handler.handleParseResult(result, out, ansi); + } catch (ParameterException ex) { + return exceptionHandler.handleException(ex, out, ansi, args); + } + } + /** + * Tries to {@linkplain #parseArgs(String...) parse} the specified command line arguments, and if successful, delegates + * the processing of the resulting {@code ParseResult} object to the specified {@linkplain IParseResultHandler2 handler}. + * If the command line arguments were invalid, the {@code ParameterException} thrown from the {@code parse} method + * is caught and passed to the specified {@link IExceptionHandler2}. + *

    + * This is a convenience method intended to offer the same ease of use as the {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...) run} + * and {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...) call} methods, but with more flexibility and better + * support for nested subcommands. + *

    + *

    Calling this method roughly expands to:

    + *
    +     * ParseResult parseResult = null;
    +     * try {
    +     *     parseResult = parseArgs(args);
    +     *     return handler.handleParseResult(parseResult);
    +     * } catch (ParameterException ex) {
    +     *     return exceptionHandler.handleParseException(ex, (String[]) args);
    +     * } catch (ExecutionException ex) {
    +     *     return exceptionHandler.handleExecutionException(ex, parseResult);
    +     * }
    +     * 
    + *

    + * Picocli provides some default handlers that allow you to accomplish some common tasks with very little code. + * The following handlers are available:

    + *
      + *
    • {@link RunLast} handler prints help if requested, and otherwise gets the last specified command or subcommand + * and tries to execute it as a {@code Runnable} or {@code Callable}.
    • + *
    • {@link RunFirst} handler prints help if requested, and otherwise executes the top-level command as a {@code Runnable} or {@code Callable}.
    • + *
    • {@link RunAll} handler prints help if requested, and otherwise executes all recognized commands and subcommands as {@code Runnable} or {@code Callable} tasks.
    • + *
    • {@link DefaultExceptionHandler} prints the error message followed by usage help
    • + *
    + * + * @param handler the function that will handle the result of successfully parsing the command line arguments + * @param exceptionHandler the function that can handle the {@code ParameterException} thrown when the command line arguments are invalid + * @param args the command line arguments + * @return an object resulting from handling the parse result or the exception that occurred while parsing the input + * @throws ExecutionException if the command line arguments were parsed successfully but a problem occurred while processing the parse + * result {@code ParseResult} object; use {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed + * @param the return type of the result handler and exception handler + * @see RunLast + * @see RunAll + * @see DefaultExceptionHandler + * @deprecated use {@link #execute(String...)} and {@link #getExecutionResult()} instead + * @since 3.0 */ + @Deprecated public R parseWithHandlers(IParseResultHandler2 handler, IExceptionHandler2 exceptionHandler, String... args) { + clearExecutionResults(); + ParseResult parseResult = null; + try { + parseResult = parseArgs(args); + return handler.handleParseResult(parseResult); + } catch (ParameterException ex) { + return exceptionHandler.handleParseException(ex, args); + } catch (ExecutionException ex) { + return exceptionHandler.handleExecutionException(ex, parseResult); + } + } + static String versionString() { + return String.format("%s, JVM: %s (%s %s %s), OS: %s %s %s", VERSION, + System.getProperty("java.version"), System.getProperty("java.vendor"), System.getProperty("java.vm.name"), System.getProperty("java.vm.version"), + System.getProperty("os.name"), System.getProperty("os.version"), System.getProperty("os.arch")); + } + /** + * Equivalent to {@code new CommandLine(command).usage(out)}. See {@link #usage(PrintStream)} for details. + * @param command the object annotated with {@link Command}, {@link Option} and {@link Parameters} + * @param out the print stream to print the help message to + * @throws IllegalArgumentException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation + */ + public static void usage(Object command, PrintStream out) { + toCommandLine(command, new DefaultFactory()).usage(out); + } + + /** + * Equivalent to {@code new CommandLine(command).usage(out, ansi)}. + * See {@link #usage(PrintStream, Help.Ansi)} for details. + * @param command the object annotated with {@link Command}, {@link Option} and {@link Parameters} + * @param out the print stream to print the help message to + * @param ansi whether the usage message should contain ANSI escape codes or not + * @throws IllegalArgumentException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation + */ + public static void usage(Object command, PrintStream out, Help.Ansi ansi) { + toCommandLine(command, new DefaultFactory()).usage(out, ansi); + } + + /** + * Equivalent to {@code new CommandLine(command).usage(out, colorScheme)}. + * See {@link #usage(PrintStream, Help.ColorScheme)} for details. + * @param command the object annotated with {@link Command}, {@link Option} and {@link Parameters} + * @param out the print stream to print the help message to + * @param colorScheme the {@code ColorScheme} defining the styles for options, parameters and commands when ANSI is enabled + * @throws IllegalArgumentException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation + */ + public static void usage(Object command, PrintStream out, Help.ColorScheme colorScheme) { + toCommandLine(command, new DefaultFactory()).usage(out, colorScheme); + } + + /** + * Delegates to {@link #usage(PrintStream, Help.ColorScheme)} with the {@linkplain #getColorScheme() configured} color scheme. + * @param out the printStream to print to + * @see #usage(PrintStream, Help.ColorScheme) + */ + public void usage(PrintStream out) { usage(out, getColorScheme()); } + /** + * Delegates to {@link #usage(PrintWriter, Help.ColorScheme)} with the {@linkplain #getColorScheme() configured} color scheme. + * @param writer the PrintWriter to print to + * @see #usage(PrintWriter, Help.ColorScheme) + * @since 3.0 */ + public void usage(PrintWriter writer) { usage(writer, getColorScheme()); } + + /** + * Delegates to {@link #usage(PrintStream, Help.ColorScheme)} with the {@linkplain Help#defaultColorScheme(CommandLine.Help.Ansi) default color scheme}. + * @param out the printStream to print to + * @param ansi whether the usage message should include ANSI escape codes or not + * @see #usage(PrintStream, Help.ColorScheme) + */ + public void usage(PrintStream out, Help.Ansi ansi) { usage(out, Help.defaultColorScheme(ansi)); } + /** Similar to {@link #usage(PrintStream, Help.Ansi)} but with the specified {@code PrintWriter} instead of a {@code PrintStream}. + * @since 3.0 */ + public void usage(PrintWriter writer, Help.Ansi ansi) { usage(writer, Help.defaultColorScheme(ansi)); } + + /** + * Prints a usage help message for the annotated command class to the specified {@code PrintStream}. + * Delegates construction of the usage help message to the {@link Help} inner class and is equivalent to: + *
    +     * Help.ColorScheme colorScheme = Help.defaultColorScheme(Help.Ansi.AUTO);
    +     * Help help = getHelpFactory().create(getCommandSpec(), colorScheme)
    +     * StringBuilder sb = new StringBuilder();
    +     * for (String key : getHelpSectionKeys()) {
    +     *     IHelpSectionRenderer renderer = getHelpSectionMap().get(key);
    +     *     if (renderer != null) { sb.append(renderer.render(help)); }
    +     * }
    +     * out.print(sb);
    +     * 
    + *

    Annotate your class with {@link Command} to control many aspects of the usage help message, including + * the program name, text of section headings and section contents, and some aspects of the auto-generated sections + * of the usage help message. + *

    To customize the auto-generated sections of the usage help message, like how option details are displayed, + * instantiate a {@link Help} object and use a {@link Help.TextTable} with more of fewer columns, a custom + * {@linkplain Help.Layout layout}, and/or a custom option {@linkplain Help.IOptionRenderer renderer} + * for ultimate control over which aspects of an Option or Field are displayed where.

    + * @param out the {@code PrintStream} to print the usage help message to + * @param colorScheme the {@code ColorScheme} defining the styles for options, parameters and commands when ANSI is enabled + * @see UsageMessageSpec + */ + public void usage(PrintStream out, Help.ColorScheme colorScheme) { + out.print(usage(new StringBuilder(), getHelpFactory().create(getCommandSpec(), colorScheme))); + out.flush(); + } + /** Similar to {@link #usage(PrintStream, Help.ColorScheme)}, but with the specified {@code PrintWriter} instead of a {@code PrintStream}. + * @since 3.0 */ + public void usage(PrintWriter writer, Help.ColorScheme colorScheme) { + writer.print(usage(new StringBuilder(), getHelpFactory().create(getCommandSpec(), colorScheme))); + writer.flush(); + } + /** Similar to {@link #usage(PrintStream)}, but returns the usage help message as a String instead of printing it to the {@code PrintStream}. + * @since 3.2 */ + public String getUsageMessage() { + return usage(new StringBuilder(), getHelp()).toString(); + } + /** Similar to {@link #usage(PrintStream, Help.Ansi)}, but returns the usage help message as a String instead of printing it to the {@code PrintStream}. + * @since 3.2 */ + public String getUsageMessage(Help.Ansi ansi) { + return usage(new StringBuilder(), getHelpFactory().create(getCommandSpec(), Help.defaultColorScheme(ansi))).toString(); + } + /** Similar to {@link #usage(PrintStream, Help.ColorScheme)}, but returns the usage help message as a String instead of printing it to the {@code PrintStream}. + * @since 3.2 */ + public String getUsageMessage(Help.ColorScheme colorScheme) { + return usage(new StringBuilder(), getHelpFactory().create(getCommandSpec(), colorScheme)).toString(); + } + + private StringBuilder usage(StringBuilder sb, Help help) { + for (String key : getHelpSectionKeys()) { + IHelpSectionRenderer renderer = getHelpSectionMap().get(key); + if (renderer != null) { sb.append(renderer.render(help)); } + } + return sb; + } + + /** + * Delegates to {@link #printVersionHelp(PrintStream, Help.Ansi)} with the ANSI setting of the {@linkplain #getColorScheme() configured} color scheme. + * @param out the printStream to print to + * @see #printVersionHelp(PrintStream, Help.Ansi) + * @since 0.9.8 + */ + public void printVersionHelp(PrintStream out) { printVersionHelp(out, getColorScheme().ansi()); } + + /** + * Prints version information from the {@link Command#version()} annotation to the specified {@code PrintStream}. + * Each element of the array of version strings is printed on a separate line. Version strings may contain + * markup for colors and style. + * @param out the printStream to print to + * @param ansi whether the usage message should include ANSI escape codes or not + * @see Command#version() + * @see Option#versionHelp() + * @see #isVersionHelpRequested() + * @since 0.9.8 + */ + public void printVersionHelp(PrintStream out, Help.Ansi ansi) { + for (String versionInfo : getCommandSpec().version()) { + out.println(ansi.new Text(versionInfo, getColorScheme())); + } + out.flush(); + } + /** + * Prints version information from the {@link Command#version()} annotation to the specified {@code PrintStream}. + * Each element of the array of version strings is {@linkplain String#format(String, Object...) formatted} with the + * specified parameters, and printed on a separate line. Both version strings and parameters may contain + * markup for colors and style. + * @param out the printStream to print to + * @param ansi whether the usage message should include ANSI escape codes or not + * @param params Arguments referenced by the format specifiers in the version strings + * @see Command#version() + * @see Option#versionHelp() + * @see #isVersionHelpRequested() + * @since 1.0.0 + */ + public void printVersionHelp(PrintStream out, Help.Ansi ansi, Object... params) { + for (String versionInfo : getCommandSpec().version()) { + out.println(ansi.new Text(format(versionInfo, params), getColorScheme())); + } + out.flush(); + } + /** + * Delegates to {@link #printVersionHelp(PrintWriter, Help.Ansi, Object...)} with the ANSI setting of the {@linkplain #getColorScheme() configured} color scheme. + * @param out the PrintWriter to print to + * @since 4.0 */ + public void printVersionHelp(PrintWriter out) { printVersionHelp(out, getColorScheme().ansi()); } + /** + * Prints version information from the {@link Command#version()} annotation to the specified {@code PrintWriter}. + * Each element of the array of version strings is {@linkplain String#format(String, Object...) formatted} with the + * specified parameters, and printed on a separate line. Both version strings and parameters may contain + * markup for colors and style. + * @param out the PrintWriter to print to + * @param ansi whether the usage message should include ANSI escape codes or not + * @param params Arguments referenced by the format specifiers in the version strings + * @see Command#version() + * @see Option#versionHelp() + * @see #isVersionHelpRequested() + * @since 4.0 */ + public void printVersionHelp(PrintWriter out, Help.Ansi ansi, Object... params) { + for (String versionInfo : getCommandSpec().version()) { + out.println(ansi.new Text(format(versionInfo, params), getColorScheme())); + } + out.flush(); + } + + /** + * Equivalent to {@code new CommandLine(callable).execute(args)}, except for the return value. + * @param callable the command to call when {@linkplain #parseArgs(String...) parsing} succeeds. + * @param args the command line arguments to parse + * @param the annotated object must implement Callable + * @param the return type of the most specific command (must implement {@code Callable}) + * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation + * @throws ExecutionException if the Callable throws an exception + * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable + * @see #execute(String...) + * @since 3.0 + * @deprecated use {@link #execute(String...)} and {@link #getExecutionResult()} instead + */ + @Deprecated public static , T> T call(C callable, String... args) { + CommandLine cmd = new CommandLine(callable); + List results = cmd.parseWithHandler(new RunLast(), args); + return CommandLine.firstElement(results); + } + + /** + * Delegates to {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.err} for + * diagnostic error messages and {@link Help.Ansi#AUTO}. + * @param callable the command to call when {@linkplain #parseArgs(String...) parsing} succeeds. + * @param out the printStream to print the usage help message to when the user requested help + * @param args the command line arguments to parse + * @param the annotated object must implement Callable + * @param the return type of the most specific command (must implement {@code Callable}) + * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation + * @throws ExecutionException if the Callable throws an exception + * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable + * @deprecated use {@link #execute(String...)} and {@link #getExecutionResult()} instead + * @see RunLast + */ + @Deprecated public static , T> T call(C callable, PrintStream out, String... args) { + return call(callable, out, System.err, Help.Ansi.AUTO, args); + } + /** + * Delegates to {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.err} for diagnostic error messages. + * @param callable the command to call when {@linkplain #parseArgs(String...) parsing} succeeds. + * @param out the printStream to print the usage help message to when the user requested help + * @param ansi the ANSI style to use + * @param args the command line arguments to parse + * @param the annotated object must implement Callable + * @param the return type of the most specific command (must implement {@code Callable}) + * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation + * @throws ExecutionException if the Callable throws an exception + * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable + * @deprecated use {@link #execute(String...)} and {@link #getExecutionResult()} instead + * @see RunLast + */ + @Deprecated public static , T> T call(C callable, PrintStream out, Help.Ansi ansi, String... args) { + return call(callable, out, System.err, ansi, args); + } + /** + * Convenience method to allow command line application authors to avoid some boilerplate code in their application. + * The annotated object needs to implement {@link Callable}. + *

    Consider using the {@link #execute(String...)} method instead:

    + *
    {@code
    +     * CommandLine cmd = new CommandLine(callable)
    +     *         .setOut(myOutWriter()) // System.out by default
    +     *         .setErr(myErrWriter()) // System.err by default
    +     *         .setColorScheme(myColorScheme()); // default color scheme, Ansi.AUTO by default
    +     * int exitCode = cmd.execute(args);
    +     * //System.exit(exitCode);
    +     * }
    + *

    + * If the specified Callable command has subcommands, the {@linkplain RunLast last} subcommand specified on the + * command line is executed. + *

    + * @param callable the command to call when {@linkplain #parse(String...) parsing} succeeds. + * @param out the printStream to print the usage help message to when the user requested help + * @param err the printStream to print diagnostic messages to + * @param ansi including whether the usage message should include ANSI escape codes or not + * @param args the command line arguments to parse + * @param the annotated object must implement Callable + * @param the return type of the specified {@code Callable} + * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation + * @throws ExecutionException if the Callable throws an exception + * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable + * @deprecated use {@link #execute(String...)} and {@link #getExecutionResult()} instead + * @since 3.0 + */ + @Deprecated public static , T> T call(C callable, PrintStream out, PrintStream err, Help.Ansi ansi, String... args) { + CommandLine cmd = new CommandLine(callable); + List results = cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi), new DefaultExceptionHandler>().useErr(err).useAnsi(ansi), args); + return CommandLine.firstElement(results); + } + /** + * Equivalent to {@code new CommandLine(callableClass, factory).execute(args)}, except for the return value. + * @param callableClass class of the command to call when {@linkplain #parseArgs(String...) parsing} succeeds. + * @param factory the factory responsible for instantiating the specified callable class and potentially inject other components + * @param args the command line arguments to parse + * @param the annotated class must implement Callable + * @param the return type of the most specific command (must implement {@code Callable}) + * @throws InitializationException if the specified class cannot be instantiated by the factory, or does not have a {@link Command}, {@link Option} or {@link Parameters} annotation + * @throws ExecutionException if the Callable throws an exception + * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable + * @see #execute(String...) + * @since 3.2 + * @deprecated use {@link #execute(String...)} and {@link #getExecutionResult()} instead + */ + @Deprecated public static , T> T call(Class callableClass, IFactory factory, String... args) { + CommandLine cmd = new CommandLine(callableClass, factory); + List results = cmd.parseWithHandler(new RunLast(), args); + return CommandLine.firstElement(results); + } + /** + * Delegates to {@link #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)} with + * {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}. + * @param callableClass class of the command to call when {@linkplain #parseArgs(String...) parsing} succeeds. + * @param factory the factory responsible for instantiating the specified callable class and potentially injecting other components + * @param out the printStream to print the usage help message to when the user requested help + * @param args the command line arguments to parse + * @param the annotated class must implement Callable + * @param the return type of the most specific command (must implement {@code Callable}) + * @throws InitializationException if the specified class cannot be instantiated by the factory, or does not have a {@link Command}, {@link Option} or {@link Parameters} annotation + * @throws ExecutionException if the Callable throws an exception + * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable + * @deprecated use {@link #execute(String...)} and {@link #getExecutionResult()} instead + * @since 3.2 + */ + @Deprecated public static , T> T call(Class callableClass, IFactory factory, PrintStream out, String... args) { + return call(callableClass, factory, out, System.err, Help.Ansi.AUTO, args); + } + /** + * Delegates to {@link #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)} with + * {@code System.err} for diagnostic error messages. + * @param callableClass class of the command to call when {@linkplain #parseArgs(String...) parsing} succeeds. + * @param factory the factory responsible for instantiating the specified callable class and potentially injecting other components + * @param out the printStream to print the usage help message to when the user requested help + * @param ansi the ANSI style to use + * @param args the command line arguments to parse + * @param the annotated class must implement Callable + * @param the return type of the most specific command (must implement {@code Callable}) + * @throws InitializationException if the specified class cannot be instantiated by the factory, or does not have a {@link Command}, {@link Option} or {@link Parameters} annotation + * @throws ExecutionException if the Callable throws an exception + * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable + * @deprecated use {@link #execute(String...)} and {@link #getExecutionResult()} instead + * @since 3.2 + */ + @Deprecated public static , T> T call(Class callableClass, IFactory factory, PrintStream out, Help.Ansi ansi, String... args) { + return call(callableClass, factory, out, System.err, ansi, args); + } + + /** + * Convenience method to allow command line application authors to avoid some boilerplate code in their application. + * The specified {@linkplain IFactory factory} will create an instance of the specified {@code callableClass}; + * use this method instead of {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...) call(Callable, ...)} + * if you want to use a factory that performs Dependency Injection. + * The annotated class needs to implement {@link Callable}. + *

    Consider using the {@link #execute(String...)} method instead:

    + *
    {@code
    +     * CommandLine cmd = new CommandLine(callableClass, factory)
    +     *         .setOut(myOutWriter()) // System.out by default
    +     *         .setErr(myErrWriter()) // System.err by default
    +     *         .setColorScheme(myColorScheme()); // default color scheme, Ansi.AUTO by default
    +     * int exitCode = cmd.execute(args);
    +     * //System.exit(exitCode);
    +     * }
    + *

    + * If the specified Callable command has subcommands, the {@linkplain RunLast last} subcommand specified on the + * command line is executed. + *

    + * @param callableClass class of the command to call when {@linkplain #parseArgs(String...) parsing} succeeds. + * @param factory the factory responsible for instantiating the specified callable class and potentially injecting other components + * @param out the printStream to print the usage help message to when the user requested help + * @param err the printStream to print diagnostic messages to + * @param ansi the ANSI style to use + * @param args the command line arguments to parse + * @param the annotated class must implement Callable + * @param the return type of the most specific command (must implement {@code Callable}) + * @throws InitializationException if the specified class cannot be instantiated by the factory, or does not have a {@link Command}, {@link Option} or {@link Parameters} annotation + * @throws ExecutionException if the Callable throws an exception + * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable + * @deprecated use {@link #execute(String...)} and {@link #getExecutionResult()} instead + * @since 3.2 + */ + @Deprecated public static , T> T call(Class callableClass, IFactory factory, PrintStream out, PrintStream err, Help.Ansi ansi, String... args) { + CommandLine cmd = new CommandLine(callableClass, factory); + List results = cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi), new DefaultExceptionHandler>().useErr(err).useAnsi(ansi), args); + return CommandLine.firstElement(results); + } + + @SuppressWarnings("unchecked") private static T firstElement(List results) { + return (results == null || results.isEmpty()) ? null : (T) results.get(0); + } + + /** + * Equivalent to {@code new CommandLine(runnable).execute(args)}. + * @param runnable the command to run when {@linkplain #parseArgs(String...) parsing} succeeds. + * @param args the command line arguments to parse + * @param the annotated object must implement Runnable + * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation + * @throws ExecutionException if the Runnable throws an exception + * @see #execute(String...) + * @since 3.0 + * @deprecated use {@link #execute(String...)} instead + */ + @Deprecated public static void run(R runnable, String... args) { + run(runnable, System.out, System.err, Help.Ansi.AUTO, args); + } + /** + * Delegates to {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.err} for diagnostic error messages and {@link Help.Ansi#AUTO}. + * @param runnable the command to run when {@linkplain #parseArgs(String...) parsing} succeeds. + * @param out the printStream to print the usage help message to when the user requested help + * @param args the command line arguments to parse + * @param the annotated object must implement Runnable + * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation + * @throws ExecutionException if the Runnable throws an exception + * @deprecated use {@link #execute(String...)} instead + * @see RunLast + */ + @Deprecated public static void run(R runnable, PrintStream out, String... args) { + run(runnable, out, System.err, Help.Ansi.AUTO, args); + } + /** + * Delegates to {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.err} for diagnostic error messages. + * @param runnable the command to run when {@linkplain #parseArgs(String...) parsing} succeeds. + * @param out the printStream to print the usage help message to when the user requested help + * @param ansi whether the usage message should include ANSI escape codes or not + * @param args the command line arguments to parse + * @param the annotated object must implement Runnable + * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation + * @throws ExecutionException if the Runnable throws an exception + * @deprecated use {@link #execute(String...)} instead + * @see RunLast + */ + @Deprecated public static void run(R runnable, PrintStream out, Help.Ansi ansi, String... args) { + run(runnable, out, System.err, ansi, args); + } + /** + * Convenience method to allow command line application authors to avoid some boilerplate code in their application. + * The annotated object needs to implement {@link Runnable}. + *

    Consider using the {@link #execute(String...)} method instead:

    + *
    {@code
    +     * CommandLine cmd = new CommandLine(runnable)
    +     *         .setOut(myOutWriter()) // System.out by default
    +     *         .setErr(myErrWriter()) // System.err by default
    +     *         .setColorScheme(myColorScheme()); // default color scheme, Ansi.AUTO by default
    +     * int exitCode = cmd.execute(args);
    +     * //System.exit(exitCode);
    +     * }
    + *

    + * If the specified Runnable command has subcommands, the {@linkplain RunLast last} subcommand specified on the + * command line is executed. + *

    + * From picocli v2.0, this method prints usage help or version help if {@linkplain #printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi) requested}, + * and any exceptions thrown by the {@code Runnable} are caught and rethrown wrapped in an {@code ExecutionException}. + *

    + * @param runnable the command to run when {@linkplain #parse(String...) parsing} succeeds. + * @param out the printStream to print the usage help message to when the user requested help + * @param err the printStream to print diagnostic messages to + * @param ansi whether the usage message should include ANSI escape codes or not + * @param args the command line arguments to parse + * @param the annotated object must implement Runnable + * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation + * @throws ExecutionException if the Runnable throws an exception + * @deprecated use {@link #execute(String...)} instead + * @since 3.0 + */ + @Deprecated public static void run(R runnable, PrintStream out, PrintStream err, Help.Ansi ansi, String... args) { + CommandLine cmd = new CommandLine(runnable); + cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi), new DefaultExceptionHandler>().useErr(err).useAnsi(ansi), args); + } + /** + * Equivalent to {@code new CommandLine(runnableClass, factory).execute(args)}. + * @param runnableClass class of the command to run when {@linkplain #parseArgs(String...) parsing} succeeds. + * @param factory the factory responsible for instantiating the specified Runnable class and potentially injecting other components + * @param args the command line arguments to parse + * @param the annotated class must implement Runnable + * @throws InitializationException if the specified class cannot be instantiated by the factory, or does not have a {@link Command}, {@link Option} or {@link Parameters} annotation + * @throws ExecutionException if the Runnable throws an exception + * @see #execute(String...) + * @since 3.2 + * @deprecated use {@link #execute(String...)} instead + */ + @Deprecated public static void run(Class runnableClass, IFactory factory, String... args) { + run(runnableClass, factory, System.out, System.err, Help.Ansi.AUTO, args); + } + /** + * Delegates to {@link #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)} with + * {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}. + * @param runnableClass class of the command to run when {@linkplain #parseArgs(String...) parsing} succeeds. + * @param factory the factory responsible for instantiating the specified Runnable class and potentially injecting other components + * @param out the printStream to print the usage help message to when the user requested help + * @param args the command line arguments to parse + * @param the annotated class must implement Runnable + * @see #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...) + * @throws InitializationException if the specified class cannot be instantiated by the factory, or does not have a {@link Command}, {@link Option} or {@link Parameters} annotation + * @throws ExecutionException if the Runnable throws an exception + * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...) + * @see RunLast + * @deprecated use {@link #execute(String...)} instead + * @since 3.2 + */ + @Deprecated public static void run(Class runnableClass, IFactory factory, PrintStream out, String... args) { + run(runnableClass, factory, out, System.err, Help.Ansi.AUTO, args); + } + /** + * Delegates to {@link #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)} with + * {@code System.err} for diagnostic error messages. + * @param runnableClass class of the command to run when {@linkplain #parseArgs(String...) parsing} succeeds. + * @param factory the factory responsible for instantiating the specified Runnable class and potentially injecting other components + * @param out the printStream to print the usage help message to when the user requested help + * @param ansi whether the usage message should include ANSI escape codes or not + * @param args the command line arguments to parse + * @param the annotated class must implement Runnable + * @see #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...) + * @throws InitializationException if the specified class cannot be instantiated by the factory, or does not have a {@link Command}, {@link Option} or {@link Parameters} annotation + * @throws ExecutionException if the Runnable throws an exception + * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...) + * @see RunLast + * @deprecated use {@link #execute(String...)} instead + * @since 3.2 + */ + @Deprecated public static void run(Class runnableClass, IFactory factory, PrintStream out, Help.Ansi ansi, String... args) { + run(runnableClass, factory, out, System.err, ansi, args); + } + + /** + * Convenience method to allow command line application authors to avoid some boilerplate code in their application. + * The specified {@linkplain IFactory factory} will create an instance of the specified {@code runnableClass}; + * use this method instead of {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...) run(Runnable, ...)} + * if you want to use a factory that performs Dependency Injection. + * The annotated class needs to implement {@link Runnable}. + *

    Consider using the {@link #execute(String...)} method instead:

    + *
    {@code
    +     * CommandLine cmd = new CommandLine(runnableClass, factory)
    +     *         .setOut(myOutWriter()) // System.out by default
    +     *         .setErr(myErrWriter()) // System.err by default
    +     *         .setColorScheme(myColorScheme()); // default color scheme, Ansi.AUTO by default
    +     * int exitCode = cmd.execute(args);
    +     * //System.exit(exitCode);
    +     * }
    + *

    + * If the specified Runnable command has subcommands, the {@linkplain RunLast last} subcommand specified on the + * command line is executed. + *

    + * This method prints usage help or version help if {@linkplain #printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi) requested}, + * and any exceptions thrown by the {@code Runnable} are caught and rethrown wrapped in an {@code ExecutionException}. + *

    + * @param runnableClass class of the command to run when {@linkplain #parseArgs(String...) parsing} succeeds. + * @param factory the factory responsible for instantiating the specified Runnable class and potentially injecting other components + * @param out the printStream to print the usage help message to when the user requested help + * @param err the printStream to print diagnostic messages to + * @param ansi whether the usage message should include ANSI escape codes or not + * @param args the command line arguments to parse + * @param the annotated class must implement Runnable + * @deprecated use {@link #execute(String...)} instead + * @since 3.2 + */ + @Deprecated public static void run(Class runnableClass, IFactory factory, PrintStream out, PrintStream err, Help.Ansi ansi, String... args) { + CommandLine cmd = new CommandLine(runnableClass, factory); + cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi), new DefaultExceptionHandler>().useErr(err).useAnsi(ansi), args); + } + + /** + * Delegates to {@link #invoke(String, Class, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.out} for + * requested usage help messages, {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}. + * @param methodName the {@code @Command}-annotated method to build a {@link CommandSpec} model from, + * and run when {@linkplain #parseArgs(String...) parsing} succeeds. + * @param cls the class where the {@code @Command}-annotated method is declared, or a subclass + * @param args the command line arguments to parse + * @see #invoke(String, Class, PrintStream, PrintStream, Help.Ansi, String...) + * @throws InitializationException if the specified method does not have a {@link Command} annotation, + * or if the specified class contains multiple {@code @Command}-annotated methods with the specified name + * @throws ExecutionException if the Runnable throws an exception + * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...) + * @since 3.6 + * @deprecated use {@link #execute(String...)} and {@link #getExecutionResult()} instead + */ + @Deprecated public static Object invoke(String methodName, Class cls, String... args) { + return invoke(methodName, cls, System.out, System.err, Help.Ansi.AUTO, args); + } + /** + * Delegates to {@link #invoke(String, Class, PrintStream, PrintStream, Help.Ansi, String...)} with the specified stream for + * requested usage help messages, {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}. + * @param methodName the {@code @Command}-annotated method to build a {@link CommandSpec} model from, + * and run when {@linkplain #parseArgs(String...) parsing} succeeds. + * @param cls the class where the {@code @Command}-annotated method is declared, or a subclass + * @param out the printstream to print requested help message to + * @param args the command line arguments to parse + * @see #invoke(String, Class, PrintStream, PrintStream, Help.Ansi, String...) + * @throws InitializationException if the specified method does not have a {@link Command} annotation, + * or if the specified class contains multiple {@code @Command}-annotated methods with the specified name + * @throws ExecutionException if the Runnable throws an exception + * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...) + * @deprecated use {@link #execute(String...)} and {@link #getExecutionResult()} instead + * @since 3.6 + */ + @Deprecated public static Object invoke(String methodName, Class cls, PrintStream out, String... args) { + return invoke(methodName, cls, out, System.err, Help.Ansi.AUTO, args); + } + /** + * Delegates to {@link #invoke(String, Class, PrintStream, PrintStream, Help.Ansi, String...)} with the specified stream for + * requested usage help messages, {@code System.err} for diagnostic error messages, and the specified Ansi mode. + * @param methodName the {@code @Command}-annotated method to build a {@link CommandSpec} model from, + * and run when {@linkplain #parseArgs(String...) parsing} succeeds. + * @param cls the class where the {@code @Command}-annotated method is declared, or a subclass + * @param out the printstream to print requested help message to + * @param ansi whether the usage message should include ANSI escape codes or not + * @param args the command line arguments to parse + * @see #invoke(String, Class, PrintStream, PrintStream, Help.Ansi, String...) + * @throws InitializationException if the specified method does not have a {@link Command} annotation, + * or if the specified class contains multiple {@code @Command}-annotated methods with the specified name + * @throws ExecutionException if the Runnable throws an exception + * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...) + * @deprecated use {@link #execute(String...)} and {@link #getExecutionResult()} instead + * @since 3.6 + */ + @Deprecated public static Object invoke(String methodName, Class cls, PrintStream out, Help.Ansi ansi, String... args) { + return invoke(methodName, cls, out, System.err, ansi, args); + } + + /** + * Convenience method to allow command line application authors to avoid some boilerplate code in their application. + * Constructs a {@link CommandSpec} model from the {@code @Option} and {@code @Parameters}-annotated method parameters + * of the {@code @Command}-annotated method, parses the specified command line arguments and invokes the specified method. + *

    Consider using the {@link #execute(String...)} method instead:

    + *
    {@code
    +     * Method commandMethod = getCommandMethods(cls, methodName).get(0);
    +     * CommandLine cmd = new CommandLine(commandMethod)
    +     *         .setOut(myOutWriter()) // System.out by default
    +     *         .setErr(myErrWriter()) // System.err by default
    +     *         .setColorScheme(myColorScheme()); // default color scheme, Ansi.AUTO by default
    +     * int exitCode = cmd.execute(args);
    +     * //System.exit(exitCode);
    +     * }
    + * @param methodName the {@code @Command}-annotated method to build a {@link CommandSpec} model from, + * and run when {@linkplain #parseArgs(String...) parsing} succeeds. + * @param cls the class where the {@code @Command}-annotated method is declared, or a subclass + * @param out the printStream to print the usage help message to when the user requested help + * @param err the printStream to print diagnostic messages to + * @param ansi whether the usage message should include ANSI escape codes or not + * @param args the command line arguments to parse + * @throws InitializationException if the specified method does not have a {@link Command} annotation, + * or if the specified class contains multiple {@code @Command}-annotated methods with the specified name + * @throws ExecutionException if the method throws an exception + * @deprecated use {@link #execute(String...)} and {@link #getExecutionResult()} instead + * @since 3.6 + */ + @Deprecated public static Object invoke(String methodName, Class cls, PrintStream out, PrintStream err, Help.Ansi ansi, String... args) { + List candidates = getCommandMethods(cls, methodName); + if (candidates.size() != 1) { throw new InitializationException("Expected exactly one @Command-annotated method for " + cls.getName() + "::" + methodName + "(...), but got: " + candidates); } + Method method = candidates.get(0); + CommandLine cmd = new CommandLine(method); + List list = cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi), new DefaultExceptionHandler>().useErr(err).useAnsi(ansi), args); + return list == null ? null : list.get(0); + } + /** + * Helper to get methods of a class annotated with {@link Command @Command} via reflection, optionally filtered by method name (not {@link Command#name() @Command.name}). + * Methods have to be either public (inherited) members or be declared by {@code cls}, that is "inherited" static or protected methods will not be picked up. + * + * @param cls the class to search for methods annotated with {@code @Command} + * @param methodName if not {@code null}, return only methods whose method name (not {@link Command#name() @Command.name}) equals this string. Ignored if {@code null}. + * @return the matching command methods, or an empty list + * @see #invoke(String, Class, String...) + * @since 3.6.0 + */ + public static List getCommandMethods(Class cls, String methodName) { + return getCommandMethods(cls, methodName, true); + } + private static List getCommandMethods(Class cls, String methodName, boolean includeInherited) { + Set candidates = new HashSet(); + if (includeInherited) { + // traverse public member methods (excludes static/non-public, includes inherited) + candidates.addAll(Arrays.asList(Assert.notNull(cls, "class").getMethods())); + } + // traverse directly declared methods (includes static/non-public, excludes inherited) + candidates.addAll(Arrays.asList(Assert.notNull(cls, "class").getDeclaredMethods())); + + List result = new ArrayList(); + for (Method method : candidates) { + if (method.isAnnotationPresent(Command.class)) { + if (methodName == null || methodName.equals(method.getName())) { result.add(method); } + } + } + Collections.sort(result, new Comparator() { + public int compare(Method o1, Method o2) { return o1.getName().compareTo(o2.getName()); } + }); + return result; + } + + /** + * Registers the specified type converter for the specified class. When initializing fields annotated with + * {@link Option}, the field's type is used as a lookup key to find the associated type converter, and this + * type converter converts the original command line argument string value to the correct type. + *

    + * Java 8 lambdas make it easy to register custom type converters: + *

    + *
    +     * commandLine.registerConverter(java.nio.file.Path.class, s -> java.nio.file.Paths.get(s));
    +     * commandLine.registerConverter(java.time.Duration.class, s -> java.time.Duration.parse(s));
    + *

    + * Built-in type converters are pre-registered for the following java 1.5 types: + *

    + *
      + *
    • all primitive types
    • + *
    • all primitive wrapper types: Boolean, Byte, Character, Double, Float, Integer, Long, Short
    • + *
    • any enum
    • + *
    • java.io.File
    • + *
    • java.math.BigDecimal
    • + *
    • java.math.BigInteger
    • + *
    • java.net.InetAddress
    • + *
    • java.net.URI
    • + *
    • java.net.URL
    • + *
    • java.nio.charset.Charset
    • + *
    • java.sql.Time
    • + *
    • java.util.Date
    • + *
    • java.util.UUID
    • + *
    • java.util.regex.Pattern
    • + *
    • StringBuilder
    • + *
    • CharSequence
    • + *
    • String
    • + *
    + *

    The specified converter will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment the converter is registered. Subcommands added + * later will not have this converter added automatically. To ensure a custom type converter is available to all + * subcommands, register the type converter last, after adding subcommands.

    + * + * @param cls the target class to convert parameter string values to + * @param converter the class capable of converting string values to the specified target type + * @param the target type + * @return this CommandLine object, to allow method chaining + * @see #addSubcommand(String, Object) + */ + public CommandLine registerConverter(Class cls, ITypeConverter converter) { + interpreter.converterRegistry.put(Assert.notNull(cls, "class"), Assert.notNull(converter, "converter")); + for (CommandLine command : getCommandSpec().commands.values()) { + command.registerConverter(cls, converter); + } + return this; + } + + /** Returns the String that separates option names from option values when parsing command line options. + * @return the String the parser uses to separate option names from option values + * @see ParserSpec#separator() */ + public String getSeparator() { return getCommandSpec().parser().separator(); } + + /** Sets the String the parser uses to separate option names from option values to the specified value. + * The separator may also be set declaratively with the {@link CommandLine.Command#separator()} annotation attribute. + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param separator the String that separates option names from option values + * @see ParserSpec#separator(String) + * @return this {@code CommandLine} object, to allow method chaining */ + public CommandLine setSeparator(String separator) { + getCommandSpec().parser().separator(Assert.notNull(separator, "separator")); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setSeparator(separator); + } + return this; + } + + /** Returns the ResourceBundle of this command or {@code null} if no resource bundle is set. + * @see Command#resourceBundle() + * @see CommandSpec#resourceBundle() + * @since 3.6 */ + public ResourceBundle getResourceBundle() { return getCommandSpec().resourceBundle(); } + + /** Sets the ResourceBundle containing usage help message strings. + *

    The specified bundle will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will not be impacted. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param bundle the ResourceBundle containing usage help message strings + * @return this {@code CommandLine} object, to allow method chaining + * @see Command#resourceBundle() + * @see CommandSpec#resourceBundle(ResourceBundle) + * @since 3.6 */ + public CommandLine setResourceBundle(ResourceBundle bundle) { + getCommandSpec().resourceBundle(bundle); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setResourceBundle(bundle); + } + return this; + } + + /** Returns the maximum width of the usage help message. The default is 80. + * @see UsageMessageSpec#width() */ + public int getUsageHelpWidth() { return getCommandSpec().usageMessage().width(); } + + /** Sets the maximum width of the usage help message. Longer lines are wrapped. + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param width the maximum width of the usage help message + * @see UsageMessageSpec#width(int) + * @return this {@code CommandLine} object, to allow method chaining */ + public CommandLine setUsageHelpWidth(int width) { + getCommandSpec().usageMessage().width(width); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setUsageHelpWidth(width); + } + return this; + } + + /** Returns the maximum usage help long options column max width to the specified value. + * This value controls the maximum width of the long options column: any positional parameter + * labels or long options that are longer than the specified value will overflow into + * the description column, and cause the description to be displayed on the next line. + * @see UsageMessageSpec#longOptionsMaxWidth() + * @since 4.2*/ + public int getUsageHelpLongOptionsMaxWidth() { return getCommandSpec().usageMessage().longOptionsMaxWidth(); } + + /** Returns the maximum usage help long options column max width to the specified value. + * This value controls the maximum width of the long options column: any positional parameter + * labels or long options that are longer than the specified value will overflow into + * the description column, and cause the description to be displayed on the next line. + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param columnWidth the new maximum usage help long options column max width. Must be 20 or greater. + * @see UsageMessageSpec#longOptionsMaxWidth(int) + * @return this {@code CommandLine} object, to allow method chaining + * @since 4.2 */ + public CommandLine setUsageHelpLongOptionsMaxWidth(int columnWidth) { + getCommandSpec().usageMessage().longOptionsMaxWidth(columnWidth); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setUsageHelpLongOptionsMaxWidth(columnWidth); + } + return this; + } + + /** Returns whether picocli should attempt to detect the terminal size and adjust the usage help message width + * to take the full terminal width. End users may enable this by setting system property {@code "picocli.usage.width"} to {@code AUTO}, + * and may disable this by setting this system property to a {@linkplain UsageMessageSpec#width() numeric value}. + * This feature requires Java 7 or greater. The default is {@code false}. + * @see UsageMessageSpec#autoWidth() + * @since 4.0 */ + public boolean isUsageHelpAutoWidth() { return getCommandSpec().usageMessage().autoWidth(); } + + /** Sets whether picocli should attempt to detect the terminal size and adjust the usage help message width + * to take the full terminal width. The default is {@code false}. + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * @param detectTerminalSize whether picocli should attempt to detect the terminal size + * @see UsageMessageSpec#autoWidth(boolean) + * @return this {@code CommandLine} object, to allow method chaining + * @since 4.0 */ + public CommandLine setUsageHelpAutoWidth(boolean detectTerminalSize) { + getCommandSpec().usageMessage().autoWidth(detectTerminalSize); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setUsageHelpAutoWidth(detectTerminalSize); + } + return this; + } + + /** Returns the command name (also called program name) displayed in the usage help synopsis. + * @return the command name (also called program name) displayed in the usage + * @see CommandSpec#name() + * @since 2.0 */ + public String getCommandName() { return getCommandSpec().name(); } + + /** Sets the command name (also called program name) displayed in the usage help synopsis to the specified value. + * Note that this method only modifies the usage help message, it does not impact parsing behaviour. + * The command name may also be set declaratively with the {@link CommandLine.Command#name()} annotation attribute. + * @param commandName command name (also called program name) displayed in the usage help synopsis + * @return this {@code CommandLine} object, to allow method chaining + * @see CommandSpec#name(String) + * @since 2.0 */ + public CommandLine setCommandName(String commandName) { + getCommandSpec().name(Assert.notNull(commandName, "commandName")); + return this; + } + + /** Returns whether arguments starting with {@code '@'} should be treated as the path to an argument file and its + * contents should be expanded into separate arguments for each line in the specified file. + * This property is {@code true} by default. + * @return whether "argument files" or {@code @files} should be expanded into their content + * @see ParserSpec#expandAtFiles() + * @since 2.1 */ + public boolean isExpandAtFiles() { return getCommandSpec().parser().expandAtFiles(); } + + /** Sets whether arguments starting with {@code '@'} should be treated as the path to an argument file and its + * contents should be expanded into separate arguments for each line in the specified file. ({@code true} by default.) + * @param expandAtFiles whether "argument files" or {@code @files} should be expanded into their content + * @return this {@code CommandLine} object, to allow method chaining + * @see ParserSpec#expandAtFiles(boolean) + * @since 2.1 */ + public CommandLine setExpandAtFiles(boolean expandAtFiles) { + getCommandSpec().parser().expandAtFiles(expandAtFiles); + return this; + } + + /** Returns the character that starts a single-line comment or {@code null} if all content of argument files should + * be interpreted as arguments (without comments). + * If specified, all characters from the comment character to the end of the line are ignored. + * @return the character that starts a single-line comment or {@code null}. The default is {@code '#'}. + * @see ParserSpec#atFileCommentChar() + * @since 3.5 */ + public Character getAtFileCommentChar() { return getCommandSpec().parser().atFileCommentChar(); } + + /** Sets the character that starts a single-line comment or {@code null} if all content of argument files should + * be interpreted as arguments (without comments). + * If specified, all characters from the comment character to the end of the line are ignored. + * @param atFileCommentChar the character that starts a single-line comment or {@code null}. The default is {@code '#'}. + * @return this {@code CommandLine} object, to allow method chaining + * @see ParserSpec#atFileCommentChar(Character) + * @since 3.5 */ + public CommandLine setAtFileCommentChar(Character atFileCommentChar) { + getCommandSpec().parser().atFileCommentChar(atFileCommentChar); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setAtFileCommentChar(atFileCommentChar); + } + return this; + } + + /** Returns whether to use a simplified argument file format that is compatible with JCommander. + * In this format, every line (except empty lines and comment lines) + * is interpreted as a single argument. Arguments containing whitespace do not need to be quoted. + * When system property {@code "picocli.useSimplifiedAtFiles"} is defined, the system property value overrides the programmatically set value. + * @return whether to use a simplified argument file format. The default is {@code false}. + * @see ParserSpec#useSimplifiedAtFiles() + * @since 3.9 */ + public boolean isUseSimplifiedAtFiles() { return getCommandSpec().parser().useSimplifiedAtFiles(); } + + /** Sets whether to use a simplified argument file format that is compatible with JCommander. + * In this format, every line (except empty lines and comment lines) + * is interpreted as a single argument. Arguments containing whitespace do not need to be quoted. + * When system property {@code "picocli.useSimplifiedAtFiles"} is defined, the system property value overrides the programmatically set value. + * @param simplifiedAtFiles whether to use a simplified argument file format. The default is {@code false}. + * @return this {@code CommandLine} object, to allow method chaining + * @see ParserSpec#useSimplifiedAtFiles(boolean) + * @since 3.9 */ + public CommandLine setUseSimplifiedAtFiles(boolean simplifiedAtFiles) { + getCommandSpec().parser().useSimplifiedAtFiles(simplifiedAtFiles); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setUseSimplifiedAtFiles(simplifiedAtFiles); + } + return this; + } + /** Returns the {@code INegatableOptionTransformer} used to create the negative form of {@linkplain Option#negatable() negatable} options. + * By default this returns the result of {@link RegexTransformer#createDefault()}. + * @return the {@code INegatableOptionTransformer} used to create negative option names. + * @see Option#negatable() + * @see CommandSpec#negatableOptionTransformer() + * @since 4.0 */ + public INegatableOptionTransformer getNegatableOptionTransformer() { return getCommandSpec().negatableOptionTransformer(); } + + /** Sets the {@code INegatableOptionTransformer} used to create the negative form of {@linkplain Option#negatable() negatable} options. + *

    The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its + * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added + * later will have the default setting. To ensure a setting is applied to all + * subcommands, call the setter last, after adding subcommands.

    + * Note that {@link CommandLine#setOptionsCaseInsensitive} will also change the case sensitivity of {@linkplain Option#negatable() negatable} options: + * any custom {@link INegatableOptionTransformer} that was previously installed will be replaced by the case-insensitive + * version of the default transformer. To ensure your custom transformer is used, install it last, after changing case sensitivity. + * @param transformer the {@code INegatableOptionTransformer} used to create negative option names. + * @return this {@code CommandLine} object, to allow method chaining + * @see Option#negatable() + * @see CommandSpec#negatableOptionTransformer(CommandLine.INegatableOptionTransformer) + * @since 4.0 */ + public CommandLine setNegatableOptionTransformer(INegatableOptionTransformer transformer) { + getCommandSpec().negatableOptionTransformer(transformer); + for (CommandLine command : getCommandSpec().subcommands().values()) { + command.setNegatableOptionTransformer(transformer); + } + return this; + } + private static boolean empty(String str) { return str == null || str.trim().length() == 0; } + private static boolean empty(Object[] array) { return array == null || array.length == 0; } + private static String str(String[] arr, int i) { return (arr == null || arr.length <= i) ? "" : arr[i]; } + private static boolean isBoolean(Class[] types) { return isBoolean(types[0]) || (isOptional(types[0]) && isBoolean(types[1])); } + private static boolean isBoolean(Class type) { return type == Boolean.class || type == Boolean.TYPE; } + private static CommandLine toCommandLine(Object obj, IFactory factory) { return obj instanceof CommandLine ? (CommandLine) obj : new CommandLine(obj, factory, false);} + private static boolean isMultiValue(Class cls) { return cls.isArray() || Collection.class.isAssignableFrom(cls) || Map.class.isAssignableFrom(cls); } + private static boolean isOptional(Class cls) { return cls != null && "java.util.Optional".equals(cls.getName()); } // #1108 + private static Object getOptionalEmpty() throws Exception { + return Class.forName("java.util.Optional").getMethod("empty").invoke(null); + } + private static Object getOptionalOfNullable(Object newValue) throws Exception { + return Class.forName("java.util.Optional").getMethod("ofNullable", Object.class).invoke(null, newValue); + } + private static String format(String formatString, Object... params) { + try { + return formatString == null ? "" : String.format(formatString, params); + } catch (IllegalFormatException ex) { + CommandLine.tracer().warn("Could not format '%s' (Underlying error: %s). " + + "Using raw String: '%%n' format strings have not been replaced with newlines. " + + "Please ensure to escape '%%' characters with another '%%'.", formatString, ex.getMessage()); + return formatString; + } + } + private static Map mapOf(String key, Object value, Object... other) { + LinkedHashMap result = new LinkedHashMap(); + result.put(key, value); + for (int i = 0; i < other.length - 1; i += 2) { + result.put(String.valueOf(other[i]), other[i + 1]); + } + return result; + } + + private static class NoCompletionCandidates implements Iterable { + public Iterator iterator() { throw new UnsupportedOperationException(); } + } + /** Specifies the scope of the element. + * @since 4.3 */ + public enum ScopeType { + /** The element only exists in the current command. */ + LOCAL, + /** The element exists in the command where the element is defined and all descendents (subcommands, sub-subcommands, etc.). */ + INHERIT, + } + /** + *

    + * Annotate fields in your class with {@code @Option} and picocli will initialize these fields when matching + * arguments are specified on the command line. In the case of command methods (annotated with {@code @Command}), + * command options can be defined by annotating method parameters with {@code @Option}. + *

    + * Command class example: + *

    + *
    +     * import static io.mosn.coder.cli.CommandLine.*;
    +     *
    +     * public class MyClass {
    +     *     @Parameters(description = "Any number of input files")
    +     *     private List<File> files = new ArrayList<File>();
    +     *
    +     *     @Option(names = { "-o", "--out" }, description = "Output file (default: print to console)")
    +     *     private File outputFile;
    +     *
    +     *     @Option(names = { "-v", "--verbose"}, description = "Verbose mode. Helpful for troubleshooting. Multiple -v options increase the verbosity.")
    +     *     private boolean[] verbose;
    +     *
    +     *     @Option(names = { "-h", "--help", "-?", "-help"}, usageHelp = true, description = "Display this help and exit")
    +     *     private boolean help;
    +     * }
    +     * 
    + *

    + * A field cannot be annotated with both {@code @Parameters} and {@code @Option} or a + * {@code ParameterException} is thrown. + *

    + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) + public @interface Option { + /** Special value that can be used in some annotation attributes to designate {@code null}. + * @see Option#defaultValue() + * @see Option#fallbackValue() + * @see Option#mapFallbackValue() + * @since 4.6 */ + public static final String NULL_VALUE = ArgSpec.NULL_VALUE; + /** + * One or more option names. At least one option name is required. + *

    + * Different environments have different conventions for naming options, but usually options have a prefix + * that sets them apart from parameters. + * Picocli supports all of the below styles. The default separator is {@code '='}, but this can be configured. + *

    + * *nix + *

    + * In Unix and Linux, options have a short (single-character) name, a long name or both. + * Short options + * (POSIX + * style are single-character and are preceded by the {@code '-'} character, e.g., {@code `-v'}. + * GNU-style long + * (or mnemonic) options start with two dashes in a row, e.g., {@code `--file'}. + *

    Picocli supports the POSIX convention that short options can be grouped, with the last option + * optionally taking a parameter, which may be attached to the option name or separated by a space or + * a {@code '='} character. The below examples are all equivalent: + *

    +         * -xvfFILE
    +         * -xvf FILE
    +         * -xvf=FILE
    +         * -xv --file FILE
    +         * -xv --file=FILE
    +         * -x -v --file FILE
    +         * -x -v --file=FILE
    +         * 

    + * DOS + *

    + * DOS options mostly have upper case single-character names and start with a single slash {@code '/'} character. + * Option parameters are separated by a {@code ':'} character. Options cannot be grouped together but + * must be specified separately. For example: + *

    +         * DIR /S /A:D /T:C
    +         * 

    + * PowerShell + *

    + * Windows PowerShell options generally are a word preceded by a single {@code '-'} character, e.g., {@code `-Help'}. + * Option parameters are separated by a space or by a {@code ':'} character. + *

    + * @return one or more option names + */ + String[] names(); + + /** + * Indicates whether this option is required. By default this is false. + *

    If an option is required, but a user invokes the program without specifying the required option, + * a {@link MissingParameterException} is thrown from the {@link #parse(String...)} method.

    + *

    Required options that are part of a {@linkplain ArgGroup group} are required within the group, not required within the command: + * the group's {@linkplain ArgGroup#multiplicity() multiplicity} determines whether the group itself is required or optional.

    + * @return whether this option is required + */ + boolean required() default false; + + /** + *

    This should rarely be used: the recommended attributes are {@link #usageHelp() usageHelp} and {@link #versionHelp() versionHelp}. + *

    + * Only set {@code help=true} when this option should disable validation of the remaining + * arguments, and no error message should be generated for missing required options. + *

    + * This is useful for custom help options that are in addition to the standard help and + * version options. For example if your application has many hidden options or + * subcommands, and there is a custom help option like {@code --detailed-help} that prints + * the usage help message for these hidden options and subcommands. + *

    + *

    Note:

    + *

    + * Use the {@link #usageHelp() usageHelp} for "normal" help options (like {@code -h} and {@code --help} on unix, + * {@code -?} and {@code -Help} on Windows) + * and use {@link #versionHelp() versionHelp} for "normal" version help ({@code -V} and {@code --version} on unix, + * {@code -Version} on Windows): + * picocli has built-in logic so that options with {@code usageHelp=true} or {@code versionHelp=true} + * will automatically cause the requested help message to be printed in applications + * that use the {@link #execute(String...)} method, without any code in the application. + *

    + * Note that there is no such automatic help printing for options with {@code help=true}; + * applications need to check whether the end user specified this option and take appropriate action + * in the business logic of the application. + *

    + * @return whether this option disables validation of the other arguments + */ + boolean help() default false; + + /** + * Set {@code usageHelp=true} for the {@code --help} option that triggers display of the usage help message. + * The convenience methods {@code Commandline.call}, + * {@code Commandline.run}, and {@code Commandline.parseWithHandler(s)} will automatically print usage help + * when an option with {@code usageHelp=true} was specified on the command line. + *

    + * By default, all options and positional parameters are included in the usage help message + * except when explicitly marked {@linkplain #hidden() hidden}. + *

    + * If this option is specified on the command line, picocli will not validate the remaining arguments (so no "missing required + * option" errors) and the {@link CommandLine#isUsageHelpRequested()} method will return {@code true}. + *

    + * Alternatively, consider annotating your command with {@linkplain Command#mixinStandardHelpOptions() @Command(mixinStandardHelpOptions = true)}. + *

    + * @return whether this option allows the user to request usage help + * @since 0.9.8 + * @see #hidden() + * @see #run(Runnable, String...) + * @see #call(Callable, String...) + * @see #parseWithHandler(IParseResultHandler2, String[]) + * @see #printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi) + */ + boolean usageHelp() default false; + + /** + * Set {@code versionHelp=true} for the {@code --version} option that triggers display of the version information. + * The convenience methods {@code Commandline.call}, + * {@code Commandline.run}, and {@code Commandline.parseWithHandler(s)} will automatically print version information + * when an option with {@code versionHelp=true} was specified on the command line. + *

    + * The version information string is obtained from the command's {@linkplain Command#version() version} annotation + * or from the {@linkplain Command#versionProvider() version provider}. + *

    + * If this option is specified on the command line, picocli will not validate the remaining arguments (so no "missing required + * option" errors) and the {@link CommandLine#isUsageHelpRequested()} method will return {@code true}. + *

    + * Alternatively, consider annotating your command with {@linkplain Command#mixinStandardHelpOptions() @Command(mixinStandardHelpOptions = true)}. + *

    + * @return whether this option allows the user to request version information + * @since 0.9.8 + * @see #hidden() + * @see #run(Runnable, String...) + * @see #call(Callable, String...) + * @see #parseWithHandler(IParseResultHandler2, String[]) + * @see #printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi) + */ + boolean versionHelp() default false; + + /** + * Description of this option, used when generating the usage documentation. Each element of the array is rendered on a separate line. + *

    May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}. + *

    + * The description may contain variables that are rendered when help is requested. + * The string {@code ${DEFAULT-VALUE}} is replaced with the default value of the option. This is regardless of + * the command's {@link Command#showDefaultValues() showDefaultValues} setting or the option's {@link #showDefaultValue() showDefaultValue} setting. + * The string {@code ${COMPLETION-CANDIDATES}} is replaced with the completion candidates generated by + * {@link #completionCandidates()} in the description for this option. + * Also, embedded {@code %n} newline markers are converted to actual newlines. + *

    + * @return the description of this option + * @see Variable Interpolation section of the user manual + */ + String[] description() default {}; + + /** + * Specifies the minimum number of required parameters and the maximum number of accepted parameters. + * If an option declares a positive arity, and the user specifies an insufficient number of parameters on the + * command line, a {@link MissingParameterException} is thrown by the {@link #parse(String...)} method. + *

    + * In many cases picocli can deduce the number of required parameters from the field's type. + * By default, flags (boolean options) have arity "0..1", + * and single-valued type fields (String, int, Integer, double, Double, File, Date, etc) have arity one. + * Generally, fields with types that cannot hold multiple values can omit the {@code arity} attribute. + *

    + * Fields used to capture options with arity two or higher should have a type that can hold multiple values, + * like arrays or Collections. See {@link #type()} for strongly-typed Collection fields. + *

    + * For example, if an option has 2 required parameters and any number of optional parameters, + * specify {@code @Option(names = "-example", arity = "2..*")}. + *

    + * A note on boolean options + *

    + * By default picocli allows boolean options (also called "flags" or "switches") to have an optional parameter, + * which must be either "true" or "false" (lowercase, other values are rejected). + * You can make a boolean option take a required parameter by annotating your field with {@code arity="1"}. + * For example:

    + *
    @Option(names = "-v", arity = "1") boolean verbose;
    + *

    + * Because this boolean field is defined with arity 1, the user must specify either {@code -v false} + * or {@code -v true} + * on the command line, or a {@link MissingParameterException} is thrown by the {@link #parse(String...)} + * method. + *

    + * To remove the optional parameter, define the field with {@code arity = "0"}. + * For example:

    + *
    @Option(names="-v", arity="0") boolean verbose;
    + *

    This will reject any of the below:

    + *
    +         * -v true
    +         * -v false
    +         * 
    + * @return how many arguments this option requires + */ + String arity() default ""; + + /** + * Specify a {@code paramLabel} for the option parameter to be used in the usage help message. If omitted, + * picocli uses the field name in fish brackets ({@code '<'} and {@code '>'}) by default. Example: + *
    class Example {
    +         *     @Option(names = {"-o", "--output"}, paramLabel="FILE", description="path of the output file")
    +         *     private File out;
    +         *     @Option(names = {"-j", "--jobs"}, arity="0..1", description="Allow N jobs at once; infinite jobs with no arg.")
    +         *     private int maxJobs = -1;
    +         * }
    + *

    By default, the above gives a usage help message like the following:

    +         * Usage: <main class> [OPTIONS]
    +         * -o, --output FILE       path of the output file
    +         * -j, --jobs [<maxJobs>]  Allow N jobs at once; infinite jobs with no arg.
    +         * 
    + * @return name of the option parameter used in the usage help message + */ + String paramLabel() default ""; + + /** Returns whether usage syntax decorations around the {@linkplain #paramLabel() paramLabel} should be suppressed. + * The default is {@code false}: by default, the paramLabel is surrounded with {@code '['} and {@code ']'} characters + * if the value is optional and followed by ellipses ("...") when multiple values can be specified. + * @since 3.6.0 */ + boolean hideParamSyntax() default false; + + /**

    + * Optionally specify a {@code type} to control exactly what Class the option parameter should be converted + * to. This may be useful when the field type is an interface or an abstract class. For example, a field can + * be declared to have type {@code java.lang.Number}, and annotating {@code @Option(type=Short.class)} + * ensures that the option parameter value is converted to a {@code Short} before setting the field value. + *

    + * For array fields whose component type is an interface or abstract class, specify the concrete component type. + * For example, a field with type {@code Number[]} may be annotated with {@code @Option(type=Short.class)} + * to ensure that option parameter values are converted to {@code Short} before adding an element to the array. + *

    + * Picocli will use the {@link ITypeConverter} that is + * {@linkplain #registerConverter(Class, ITypeConverter) registered} for the specified type to convert + * the raw String values before modifying the field value. + *

    + * Prior to 2.0, the {@code type} attribute was necessary for {@code Collection} and {@code Map} fields, + * but starting from 2.0 picocli will infer the component type from the generic type's type arguments. + * For example, for a field of type {@code Map} picocli will know the option parameter + * should be split up in key=value pairs, where the key should be converted to a {@code java.util.concurrent.TimeUnit} + * enum value, and the value should be converted to a {@code Long}. No {@code @Option(type=...)} type attribute + * is required for this. For generic types with wildcards, picocli will take the specified upper or lower bound + * as the Class to convert to, unless the {@code @Option} annotation specifies an explicit {@code type} attribute. + *

    + * If the field type is a raw collection or a raw map, and you want it to contain other values than Strings, + * or if the generic type's type arguments are interfaces or abstract classes, you may + * specify a {@code type} attribute to control the Class that the option parameter should be converted to. + * @return the type(s) to convert the raw String values + */ + Class[] type() default {}; + + /** + * Optionally specify one or more {@link ITypeConverter} classes to use to convert the command line argument into + * a strongly typed value (or key-value pair for map fields). This is useful when a particular field should + * use a custom conversion that is different from the normal conversion for the field's type. + *

    For example, for a specific field you may want to use a converter that maps the constant names defined + * in {@link java.sql.Types java.sql.Types} to the {@code int} value of these constants, but any other {@code int} fields should + * not be affected by this and should continue to use the standard int converter that parses numeric values.

    + * @return the type converter(s) to use to convert String values to strongly typed values for this field + * @see CommandLine#registerConverter(Class, ITypeConverter) + */ + Class>[] converter() default {}; + + /** + * Specify a regular expression to use to split option parameter values before applying them to the field. + * All elements resulting from the split are added to the array or Collection. Previously ignored for single-value fields, + * from picocli 4.0 a {@code split} regex can only be specified on multi-value options and positional parameters. + * @return a regular expression to split option parameter values or {@code ""} if the value should not be split + * @see String#split(String) + */ + String split() default ""; + + /** + * Specify the string to display for the {@link #split split} regular expression in the usage help synopsis. + * @since 4.3 */ + String splitSynopsisLabel() default ""; + + /** + * Set {@code hidden=true} if this option should not be included in the usage help message. + * @return whether this option should be excluded from the usage documentation + */ + boolean hidden() default false; + + /** Returns the default value of this option, before splitting and type conversion. + *

    To get a {@code null} default value, omit specifying a default value or use the special value {@link Option#NULL_VALUE} - + * for options of type {@code Optional} that will result in the {@code Optional.empty()} + * value being assigned when the option is not specified on the command line.

    + * @return a String that (after type conversion) will be used as the value for this option if the option was not specified on the command line + * @see #fallbackValue() + * @since 3.2 */ + String defaultValue() default "__no_default_value__"; + + /** Use this attribute to control for a specific option whether its default value should be shown in the usage + * help message. If not specified, the default value is only shown when the {@link Command#showDefaultValues()} + * is set {@code true} on the command. Use this attribute to specify whether the default value + * for this specific option should always be shown or never be shown, regardless of the command setting. + *

    Note that picocli 3.2 allows {@linkplain #description() embedding default values} by specifying the variable + * {@code ${DEFAULT-VALUE}} anywhere in the description that ignores this setting.

    + * @return whether this option's default value should be shown in the usage help message + */ + Help.Visibility showDefaultValue() default Help.Visibility.ON_DEMAND; + + /** Use this attribute to specify an {@code Iterable} class that generates completion candidates for this option. + * For map fields, completion candidates should be in {@code key=value} form. + *

    + * Completion candidates are used in bash completion scripts generated by the {@code picocli.AutoComplete} class. + * Bash has special completion options to generate file names and host names, and the bash completion scripts + * generated by {@code AutoComplete} delegate to these bash built-ins for {@code @Options} whose {@code type} is + * {@code java.io.File}, {@code java.nio.file.Path} or {@code java.net.InetAddress}. + *

    + * For {@code @Options} whose {@code type} is a Java {@code enum}, {@code AutoComplete} can generate completion + * candidates from the type. For other types, use this attribute to specify completion candidates. + *

    + * + * @return a class whose instances can iterate over the completion candidates for this option + * @see io.mosn.coder.cli.CommandLine.IFactory + * @since 3.2 */ + Class> completionCandidates() default NoCompletionCandidates.class; + + /** + * Set {@code interactive=true} to make this option prompt the end user for a value (like a password). + * Only supported for single-value options and {@code char[]} arrays (no collections, maps or other array types). + * When running on Java 6 or greater and {@link Option#echo() echo = false} (the default), + * this will use the {@link Console#readPassword()} API to get a value without echoing input to the console, + * otherwise it will simply read a value from {@code System.in}. + *

    + * For passwords, best security practice is to use type {@code char[]} instead of {@code String}, and to to null out the array after use. + *

    + * When defined with {@code arity = "0..1"}, the option can also take a value from the command line. + * (The user will still be prompted if no option parameter was specified on the command line.) + * This is useful for commands that need to be run interactively as well as in batch mode. + *

    + * @return whether this option prompts the end user for a value to be entered on the command line + * @since 3.5 + */ + boolean interactive() default false; + + /** Use this attribute to control whether user input for an interactive option is echoed to the console or not. + * If {@code echo = true}, the user input is echoed to the console. + * This attribute is ignored when {@code interactive = false} (the default). + * @return whether the user input for an interactive option should be echoed to the console or not + * @see OptionSpec#echo() + * @since 4.6 */ + boolean echo() default false; + + /** Use this attribute to customize the text displayed to the end user for an interactive option when asking for user input. + * When omitted, the displayed text is derived from the option name and the first description line. + * This attribute is ignored when {@code interactive = false} (the default). + * @return the text to display to the end user for an interactive option when asking for user input + * @see OptionSpec#prompt() + * @since 4.6 */ + String prompt() default ""; + + /** ResourceBundle key for this option. If not specified, (and a ResourceBundle {@linkplain Command#resourceBundle() exists for this command}) an attempt + * is made to find the option description using any of the option names (without leading hyphens) as key. + * @see OptionSpec#description() + * @since 3.6 + */ + String descriptionKey() default ""; + + /** + * When {@link Command#sortOptions() @Command(sortOptions = false)} is specified, this attribute can be used to control the order in which options are listed in the usage help message. + * When {@link Command#sortSynopsis() @Command(sortSynopsis = false)} is specified, this attribute controls the order in which options appear in the synopsis of the usage help message. + * @return the position in the options list at which this option should be shown. Options with a lower number are shown before options with a higher number. Gaps are allowed. + * @since 3.9 + */ + int order() default -1; + + /** (Only for boolean options): set this to automatically add a negative version for this boolean option. + * For example, for a {@code --force} option the negative version would be {@code --no-force}, + * and for a {@code -XX:+PrintGCDetails} option, the negative version would be {@code -XX:-PrintGCDetails}. + * The synopsis would show {@code --[no-]force} and {@code -XX:(+|-)PrintGCDetails}, respectively. + *

    The form of the negative name can be customized by modifying the regular expressions + * used by {@linkplain RegexTransformer#createDefault() default}, or by replacing the default + * {@link INegatableOptionTransformer} with a custom implementation entirely.

    + *

    Negative option names used to parse the command line are collected when the command is constructed + * (so any variables in the option names will be resolved at that time). + * Documentation strings for negatable options are generated on demand when the usage help message is shown.

    + * @see CommandLine#getNegatableOptionTransformer() + * @see CommandLine#setNegatableOptionTransformer(INegatableOptionTransformer) + * @since 4.0 */ + boolean negatable() default false; + + /** Determines on which command(s) this option exists: on this command only (the default), or + * whether this is a "global" option that is applied to this command and all subcommands, sub-subcommands, etc. + * @since 4.3 + */ + ScopeType scope() default ScopeType.LOCAL; + /** + * For options with an optional parameter (for example, {@code arity = "0..1"}), this value is assigned to the annotated element + * if the option is specified on the command line without an option parameter. + *

    + * This is different from the {@link #defaultValue()}, which is assigned if the option is not specified at all on the command line. + *

    + * Using a {@code fallbackValue} allows applications to distinguish between

    + *
      + *
    • option was not specified on the command line (default value assigned)
    • + *
    • option was specified without parameter on the command line (fallback value assigned)
    • + *
    • option was specified with parameter on the command line (command line argument value assigned)
    • + *
    + *

    This is useful to define options that can function as a boolean "switch" + * and optionally allow users to provide a (strongly typed) extra parameter value. + *

    + *

    Use the special value {@link Option#NULL_VALUE} to specify {@code null} - + * for options of type {@code Optional} that will result in the {@code Optional.empty()} + * value being assigned when the option name is specified without a parameter on the command line.

    + * @see OptionSpec#fallbackValue() + * @since 4.0 */ + String fallbackValue() default ""; + + /** For options of type Map, setting the {@code mapFallbackValue} to any value allows end user + * to specify key-only parameters for this option. For example, {@code -Dkey} instead of {@code -Dkey=value}. + *

    The value specified in this annotation is the value that is put into the Map for the user-specified key. + * Use the special value {@link Option#NULL_VALUE} to specify {@code null} - + * for maps of type {@code Map>} that will result in {@code Optional.empty()} + * values in the map when only the key is specified.

    + *

    If no {@code mapFallbackValue} is set, key-only Map parameters like {@code -Dkey} + * are considered invalid user input and cause a {@link ParameterException} to be thrown.

    + * @see ArgSpec#mapFallbackValue() + * @since 4.6 */ + String mapFallbackValue() default ArgSpec.UNSPECIFIED; + + /** + * Optionally specify a custom {@code IParameterConsumer} to temporarily suspend picocli's parsing logic + * and process one or more command line arguments in a custom manner. + * This may be useful when passing arguments through to another program. + * @since 4.0 */ + Class parameterConsumer() default NullParameterConsumer.class; + + /** Returns the preprocessor for this option. + * @see IParameterPreprocessor + * @since 4.6 */ + Class preprocessor() default NoOpParameterPreprocessor.class; + } + /** + *

    + * Fields annotated with {@code @Parameters} will be initialized with positional parameters. By specifying the + * {@link #index()} attribute you can pick the exact position or a range of positional parameters to apply. If no + * index is specified, the field will get all positional parameters (and so it should be an array or a collection). + *

    + * In the case of command methods (annotated with {@code @Command}), method parameters may be annotated with {@code @Parameters}, + * but are are considered positional parameters by default, unless they are annotated with {@code @Option}. + *

    + * Command class example: + *

    + *
    +     * import static io.mosn.coder.cli.CommandLine.*;
    +     *
    +     * public class MyCalcParameters {
    +     *     @Parameters(description = "Any number of input numbers")
    +     *     private List<BigDecimal> files = new ArrayList<BigDecimal>();
    +     *
    +     *     @Option(names = { "-h", "--help" }, usageHelp = true, description = "Display this help and exit")
    +     *     private boolean help;
    +     * }
    +     * 

    + * A field cannot be annotated with both {@code @Parameters} and {@code @Option} or a {@code ParameterException} + * is thrown.

    + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) + public @interface Parameters { + /** Special value that can be used in some annotation attributes to designate {@code null}. + * @see Parameters#defaultValue() + * @see Parameters#mapFallbackValue() + * @since 4.6 */ + public static final String NULL_VALUE = ArgSpec.NULL_VALUE; + /** Specify an index ("0", or "1", etc.) to pick which of the command line arguments should be assigned to this + * field. For array or Collection fields, you can also specify an index range ("0..3", or "2..*", etc.) to assign + * a subset of the command line arguments to this field. The default is "*", meaning all command line arguments. + * @return an index or range specifying which of the command line arguments should be assigned to this field + */ + String index() default ""; + + /** Description of the parameter(s), used when generating the usage documentation. Each element of the array is rendered on a separate line. + *

    May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}. + *

    + * The description may contain variables that are rendered when help is requested. + * The string {@code ${DEFAULT-VALUE}} is replaced with the default value of the positional parameter. This is regardless of + * the command's {@link Command#showDefaultValues() showDefaultValues} setting or the positional parameter's {@link #showDefaultValue() showDefaultValue} setting. + * The string {@code ${COMPLETION-CANDIDATES}} is replaced with the completion candidates generated by + * {@link #completionCandidates()} in the description for this positional parameter. + * Also, embedded {@code %n} newline markers are converted to actual newlines. + *

    + * @return the description of the parameter(s) + * @see Variable Interpolation section of the user manual + */ + String[] description() default {}; + + /** + * Specifies the minimum number of required parameters and the maximum number of accepted parameters. If a + * positive arity is declared, and the user specifies an insufficient number of parameters on the command line, + * {@link MissingParameterException} is thrown by the {@link #parse(String...)} method. + *

    The default depends on the type of the parameter: booleans require no parameters, arrays and Collections + * accept zero to any number of parameters, and any other type accepts one parameter.

    + *

    For single-value parameters, setting {@code arity = "0..1"} makes a positional parameter optional, while setting {@code arity = "1"} makes it required.

    + *

    Required parameters that are part of a {@linkplain ArgGroup group} are required within the group, not required within the command: + * the group's {@linkplain ArgGroup#multiplicity() multiplicity} determines whether the group itself is required or optional.

    + * @return the range of minimum and maximum parameters accepted by this command + */ + String arity() default ""; + + /** + * Specify a {@code paramLabel} for the parameter to be used in the usage help message. If omitted, + * picocli uses the field name in fish brackets ({@code '<'} and {@code '>'}) by default. Example: + *
    class Example {
    +         *     @Parameters(paramLabel="FILE", description="path of the input FILE(s)")
    +         *     private File[] inputFiles;
    +         * }
    + *

    By default, the above gives a usage help message like the following:

    +         * Usage: <main class> [FILE...]
    +         * [FILE...]       path of the input FILE(s)
    +         * 
    + * @return name of the positional parameter used in the usage help message + */ + String paramLabel() default ""; + + /** Returns whether usage syntax decorations around the {@linkplain #paramLabel() paramLabel} should be suppressed. + * The default is {@code false}: by default, the paramLabel is surrounded with {@code '['} and {@code ']'} characters + * if the value is optional and followed by ellipses ("...") when multiple values can be specified. + * @since 3.6.0 */ + boolean hideParamSyntax() default false; + + /** + *

    + * Optionally specify a {@code type} to control exactly what Class the positional parameter should be converted + * to. This may be useful when the field type is an interface or an abstract class. For example, a field can + * be declared to have type {@code java.lang.Number}, and annotating {@code @Parameters(type=Short.class)} + * ensures that the positional parameter value is converted to a {@code Short} before setting the field value. + *

    + * For array fields whose component type is an interface or abstract class, specify the concrete component type. + * For example, a field with type {@code Number[]} may be annotated with {@code @Parameters(type=Short.class)} + * to ensure that positional parameter values are converted to {@code Short} before adding an element to the array. + *

    + * Picocli will use the {@link ITypeConverter} that is + * {@linkplain #registerConverter(Class, ITypeConverter) registered} for the specified type to convert + * the raw String values before modifying the field value. + *

    + * Prior to 2.0, the {@code type} attribute was necessary for {@code Collection} and {@code Map} fields, + * but starting from 2.0 picocli will infer the component type from the generic type's type arguments. + * For example, for a field of type {@code Map} picocli will know the positional parameter + * should be split up in key=value pairs, where the key should be converted to a {@code java.util.concurrent.TimeUnit} + * enum value, and the value should be converted to a {@code Long}. No {@code @Parameters(type=...)} type attribute + * is required for this. For generic types with wildcards, picocli will take the specified upper or lower bound + * as the Class to convert to, unless the {@code @Parameters} annotation specifies an explicit {@code type} attribute. + *

    + * If the field type is a raw collection or a raw map, and you want it to contain other values than Strings, + * or if the generic type's type arguments are interfaces or abstract classes, you may + * specify a {@code type} attribute to control the Class that the positional parameter should be converted to. + * @return the type(s) to convert the raw String values + */ + Class[] type() default {}; + + /** + * Optionally specify one or more {@link ITypeConverter} classes to use to convert the command line argument into + * a strongly typed value (or key-value pair for map fields). This is useful when a particular field should + * use a custom conversion that is different from the normal conversion for the field's type. + *

    For example, for a specific field you may want to use a converter that maps the constant names defined + * in {@link java.sql.Types java.sql.Types} to the {@code int} value of these constants, but any other {@code int} fields should + * not be affected by this and should continue to use the standard int converter that parses numeric values.

    + * @return the type converter(s) to use to convert String values to strongly typed values for this field + * @see CommandLine#registerConverter(Class, ITypeConverter) + */ + Class>[] converter() default {}; + + /** + * Specify a regular expression to use to split positional parameter values before applying them to the field. + * All elements resulting from the split are added to the array or Collection. Previously ignored for single-value fields, + * from picocli 4.0 a {@code split} regex can only be specified on multi-value options and positional parameters. + * @return a regular expression to split operand values or {@code ""} if the value should not be split + * @see String#split(String) + */ + String split() default ""; + + /** + * Specify a string to show the split option parameter values in usage + * @since 4.3 + */ + String splitSynopsisLabel() default ""; + + /** + * Set {@code hidden=true} if this parameter should not be included in the usage message. + * @return whether this parameter should be excluded from the usage message + */ + boolean hidden() default false; + + /** Returns the default value of this positional parameter, before splitting and type conversion. + *

    To get a {@code null} default value, omit specifying a default value or use the special value {@link Parameters#NULL_VALUE} - + * for positional parameters of type {@code Optional} that will result in the {@code Optional.empty()} + * value being assigned when the positional parameters is not specified on the command line.

    + * @return a String that (after type conversion) will be used as the value for this positional parameter if no value was specified on the command line + * @since 3.2 */ + String defaultValue() default "__no_default_value__"; + + /** Use this attribute to control for a specific positional parameter whether its default value should be shown in the usage + * help message. If not specified, the default value is only shown when the {@link Command#showDefaultValues()} + * is set {@code true} on the command. Use this attribute to specify whether the default value + * for this specific positional parameter should always be shown or never be shown, regardless of the command setting. + *

    Note that picocli 3.2 allows {@linkplain #description() embedding default values} by specifying the variable + * {@code ${DEFAULT-VALUE}} anywhere in the description that ignores this setting.

    + * @return whether this positional parameter's default value should be shown in the usage help message + */ + Help.Visibility showDefaultValue() default Help.Visibility.ON_DEMAND; + + /** Use this attribute to specify an {@code Iterable} class that generates completion candidates for + * this positional parameter. For map fields, completion candidates should be in {@code key=value} form. + *

    + * Completion candidates are used in bash completion scripts generated by the {@code picocli.AutoComplete} class. + * Unfortunately, {@code picocli.AutoComplete} is not very good yet at generating completions for positional parameters. + *

    + * + * @return a class whose instances can iterate over the completion candidates for this positional parameter + * @see io.mosn.coder.cli.CommandLine.IFactory + * @since 3.2 */ + Class> completionCandidates() default NoCompletionCandidates.class; + + /** + * Set {@code interactive=true} if this positional parameter will prompt the end user for a value (like a password). + * Only supported for single-value positional parameters (not arrays, collections or maps). + * When running on Java 6 or greater and {@link Option#echo() echo = false} (the default), + * this will use the {@link Console#readPassword()} API to get a value without echoing input to the console, + * otherwise it will simply read a value from {@code System.in}. + * @return whether this positional parameter prompts the end user for a value to be entered on the command line + * @since 3.5 + */ + boolean interactive() default false; + + /** Use this attribute to control whether user input for an interactive positional parameter is echoed to the console or not. + * If {@code echo = true}, the user input is echoed to the console. + * This attribute is ignored when {@code interactive = false} (the default). + * @return whether the user input for an interactive positional parameter should be echoed to the console or not + * @see PositionalParamSpec#echo() + * @since 4.6 */ + boolean echo() default false; + + /** Use this attribute to customize the text displayed to the end user for an interactive positional parameter when asking for user input. + * When omitted, the displayed text is derived from the positional parameter's + * position (index) and the first description line. + * This attribute is ignored when {@code interactive = false} (the default). + * @return the text to display to the end user for an interactive positional parameter when asking for user input + * @see PositionalParamSpec#prompt() + * @since 4.6 */ + String prompt() default ""; + + /** ResourceBundle key for this option. If not specified, (and a ResourceBundle {@linkplain Command#resourceBundle() exists for this command}) an attempt + * is made to find the positional parameter description using {@code paramLabel() + "[" + index() + "]"} as key. + * + * @see PositionalParamSpec#description() + * @since 3.6 + */ + String descriptionKey() default ""; + + /** Determines on which command(s) this positional parameter exists: on this command only (the default), or + * whether this is a "global" parameter that is applied to this command and all subcommands, sub-subcommands, etc. + * @since 4.3 + */ + ScopeType scope() default ScopeType.LOCAL; + /** + * Optionally specify a custom {@code IParameterConsumer} to temporarily suspend picocli's parsing logic + * and process one or more command line arguments in a custom manner. + * @since 4.0 */ + Class parameterConsumer() default NullParameterConsumer.class; + + /** For positional parameters of type Map, setting the {@code mapFallbackValue} to any value allows end user + * to specify key-only parameters for this parameter. For example, {@code key} instead of {@code key=value}. + *

    The value specified in this annotation is the value that is put into the Map for the user-specified key. + * Use the special value {@link Parameters#NULL_VALUE} to specify {@code null} - + * for maps of type {@code Map>} that will result in {@code Optional.empty()} + * values in the map when only the key is specified.

    + *

    If no {@code mapFallbackValue} is set, key-only Map parameters like {@code -Dkey} + * are considered invalid user input and cause a {@link ParameterException} to be thrown.

    + * @see ArgSpec#mapFallbackValue() + * @since 4.6 */ + String mapFallbackValue() default ArgSpec.UNSPECIFIED; + + /** Returns the preprocessor for this positional parameter. + * @see IParameterPreprocessor + * @since 4.6 */ + Class preprocessor() default NoOpParameterPreprocessor.class; + } + + /** + *

    + * Fields annotated with {@code @ParentCommand} will be initialized with the parent command of the current subcommand. + * If the current command does not have a parent command, this annotation has no effect. + *

    + * Parent commands often define options that apply to all the subcommands. + * This annotation offers a convenient way to inject a reference to the parent command into a subcommand, so the + * subcommand can access its parent options. For example: + *

    +     * @Command(name = "top", subcommands = Sub.class)
    +     * class Top implements Runnable {
    +     *
    +     *     @Option(names = {"-d", "--directory"}, description = "this option applies to all subcommands")
    +     *     File baseDirectory;
    +     *
    +     *     public void run() { System.out.println("Hello from top"); }
    +     * }
    +     *
    +     * @Command(name = "sub")
    +     * class Sub implements Runnable {
    +     *
    +     *     @ParentCommand
    +     *     private Top parent;
    +     *
    +     *     public void run() {
    +     *         System.out.println("Subcommand: parent command 'directory' is " + parent.baseDirectory);
    +     *     }
    +     * }
    +     * 
    + * @since 2.2 + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface ParentCommand { } + + /** + * Fields annotated with {@code @Unmatched} will be initialized with the list of unmatched command line arguments, if any. + * If this annotation is found, picocli automatically sets {@linkplain CommandLine#setUnmatchedArgumentsAllowed(boolean) unmatchedArgumentsAllowed} to {@code true}. + * @see CommandLine#isUnmatchedArgumentsAllowed() + * @since 3.0 + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface Unmatched { } + + /** + *

    + * Fields annotated with {@code @Mixin} are "expanded" into the current command: {@link Option @Option} and + * {@link Parameters @Parameters} in the mixin class are added to the options and positional parameters of this command. + * A {@link DuplicateOptionAnnotationsException} is thrown if any of the options in the mixin has the same name as + * an option in this command. + *

    + * The {@code Mixin} annotation provides a way to reuse common options and parameters without subclassing. For example: + *

    +     * @Command(name="HelloWorld")
    +     * class HelloWorld implements Runnable {
    +     *
    +     *     // adds the --help and --version options to this command
    +     *     @Mixin
    +     *     private HelpOptions options = new HelpOptions();
    +     *
    +     *     @Option(names = {"-u", "--userName"}, required = true, description = "The user name")
    +     *     String userName;
    +     *
    +     *     public void run() { System.out.println("Hello, " + userName); }
    +     * }
    +     *
    +     * // Common reusable help options.
    +     * class HelpOptions {
    +     *
    +     *     @Option(names = { "-h", "--help"}, usageHelp = true, description = "Display this help and exit")
    +     *     private boolean help;
    +     *
    +     *     @Option(names = { "-V", "--version"}, versionHelp = true, description = "Display version info and exit")
    +     *     private boolean versionHelp;
    +     * }
    +     * 
    + * @since 3.0 + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.PARAMETER}) + public @interface Mixin { + /** Optionally specify a name that the mixin object can be retrieved with from the {@code CommandSpec}. + * If not specified the name of the annotated field is used. + * @return a String to register the mixin object with, or an empty String if the name of the annotated field should be used */ + String name() default ""; + } + /** + * Fields annotated with {@code @Spec} will be initialized with the {@code CommandSpec} for the command the field is part of. Example usage: + *
    +     * class InjectSpecExample implements Runnable {
    +     *     @Spec CommandSpec commandSpec;
    +     *     //...
    +     *     public void run() {
    +     *         // do something with the injected objects
    +     *     }
    +     * }
    +     * 
    + * @since 3.2 + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.METHOD}) + public @interface Spec { + /** Identifies what kind of {@code CommandSpec} should be injected. + * @since 4.3.0 */ + enum Target { + /** Injects the {@code CommandSpec} of the command where this {@code @Spec}-annotated program element is declared. */ + SELF, + /** Injects the {@code CommandSpec} of the "mixee" command that receives the options and other command elements defined here, + * or {@code null} if this commands is not {@linkplain Mixin mixed into} another command. + * The "mixee" command has a {@code @Mixin}-annotated program element with the type of the class where this {@code @Spec}-annotated program element is declared. */ + MIXEE} + + /** Whether to inject the {@code CommandSpec} of this command (the default) or the {@code CommandSpec} + * of the "mixee" command that receives the options and other command elements defined here. + * @see Mixin + * @since 4.3.0 */ + Target value() default Target.SELF; + } + + /** + *

    Annotate your class with {@code @Command} when you want more control over the format of the generated help + * message. From 3.6, methods can also be annotated with {@code @Command}, where the method parameters define the + * command options and positional parameters. + *

    +     * @Command(name              = "Encrypt", mixinStandardHelpOptions = true,
    +     *        description         = "Encrypt FILE(s), or standard input, to standard output or to the output file.",
    +     *        version             = "Encrypt version 1.0",
    +     *        footer              = "Copyright (c) 2017",
    +     *        exitCodeListHeading = "Exit Codes:%n",
    +     *        exitCodeList        = { " 0:Successful program execution.",
    +     *                                "64:Invalid input: an unknown option or invalid parameter was specified.",
    +     *                                "70:Execution exception: an exception occurred while executing the business logic."}
    +     *        )
    +     * public class Encrypt {
    +     *     @Parameters(paramLabel = "FILE", description = "Any number of input files")
    +     *     private List<File> files = new ArrayList<File>();
    +     *
    +     *     @Option(names = { "-o", "--out" }, description = "Output file (default: print to console)")
    +     *     private File outputFile;
    +     *
    +     *     @Option(names = { "-v", "--verbose"}, description = "Verbose mode. Helpful for troubleshooting. Multiple -v options increase the verbosity.")
    +     *     private boolean[] verbose;
    +     * }
    + *

    + * The structure of a help message looks like this: + *

      + *
    • [header]
    • + *
    • [synopsis]: {@code Usage: [OPTIONS] [FILE...]}
    • + *
    • [description]
    • + *
    • [parameter list]: {@code [FILE...] Any number of input files}
    • + *
    • [option list]: {@code -h, --help prints this help message and exits}
    • + *
    • [exit code list]
    • + *
    • [footer]
    • + *
    */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.TYPE, ElementType.LOCAL_VARIABLE, ElementType.FIELD, ElementType.PACKAGE, ElementType.METHOD}) + public @interface Command { + /** Program name to show in the synopsis. If omitted, {@code "
    "} is used. + * For {@linkplain #subcommands() declaratively added} subcommands, this attribute is also used + * by the parser to recognize subcommands in the command line arguments. + * @return the program name to show in the synopsis + * @see CommandSpec#name() + * @see Help#commandName() */ + String name() default CommandSpec.DEFAULT_COMMAND_NAME; + + /** Alternative command names by which this subcommand is recognized on the command line. + * @return one or more alternative command names + * @since 3.1 */ + String[] aliases() default {}; + + /** A list of classes to instantiate and register as subcommands. When registering subcommands declaratively + * like this, you don't need to call the {@link CommandLine#addSubcommand(String, Object)} method. For example, this: + *
    +         * @Command(subcommands = {
    +         *         GitStatus.class,
    +         *         GitCommit.class,
    +         *         GitBranch.class })
    +         * public class Git { ... }
    +         *
    +         * CommandLine commandLine = new CommandLine(new Git());
    +         * 
    is equivalent to this: + *
    +         * // alternative: programmatically add subcommands.
    +         * // NOTE: in this case there should be no `subcommands` attribute on the @Command annotation.
    +         * @Command public class Git { ... }
    +         *
    +         * CommandLine commandLine = new CommandLine(new Git())
    +         *         .addSubcommand("status",   new GitStatus())
    +         *         .addSubcommand("commit",   new GitCommit())
    +         *         .addSubcommand("branch",   new GitBranch());
    +         * 
    + * Applications may be interested in the following built-in commands in picocli + * that can be used as subcommands: + *
      + *
    • {@link HelpCommand} - a {@code help} subcommand that prints help on the following or preceding command
    • + *
    • @link AutoComplete.GenerateCompletion - a {@code generate-completion} subcommand that prints a Bash/ZSH completion script for its parent command, so that clients can install autocompletion in one line by running {@code source <(parent-command generate-completion)} in the shell
    • + *
    + * @return the declaratively registered subcommands of this command, or an empty array if none + * @see CommandLine#addSubcommand(String, Object) + * @see HelpCommand + * @since 0.9.8 + */ + Class[] subcommands() default {}; + + /** Returns whether the subcommands of this command are repeatable, that is, whether such subcommands can + * occur multiple times and may be followed by sibling commands instead of only by child commands of the subcommand. + * @since 4.2 */ + boolean subcommandsRepeatable() default false; + + /** Specify whether methods annotated with {@code @Command} should be registered as subcommands of their + * enclosing {@code @Command} class. + * The default is {@code true}. For example: + *
    +         * @Command
    +         * public class Git {
    +         *     @Command
    +         *     void status() { ... }
    +         * }
    +         *
    +         * CommandLine git = new CommandLine(new Git());
    +         * 
    is equivalent to this: + *
    +         * // don't add command methods as subcommands automatically
    +         * @Command(addMethodSubcommands = false)
    +         * public class Git {
    +         *     @Command
    +         *     void status() { ... }
    +         * }
    +         *
    +         * // add command methods as subcommands programmatically
    +         * CommandLine git = new CommandLine(new Git());
    +         * CommandLine status = new CommandLine(CommandLine.getCommandMethods(Git.class, "status").get(0));
    +         * git.addSubcommand("status", status);
    +         * 
    + * @return whether methods annotated with {@code @Command} should be registered as subcommands + * @see CommandLine#addSubcommand(String, Object) + * @see CommandLine#getCommandMethods(Class, String) + * @see CommandSpec#addMethodSubcommands() + * @since 3.6.0 */ + boolean addMethodSubcommands() default true; + + /** String that separates options from option parameters. Default is {@code "="}. Spaces are also accepted. + * @return the string that separates options from option parameters, used both when parsing and when generating usage help + * @see CommandLine#setSeparator(String) */ + String separator() default "="; + + /** Version information for this command, to print to the console when the user specifies an + * {@linkplain Option#versionHelp() option} to request version help. Each element of the array is rendered on a separate line. + *

    May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.

    + *

    This is not part of the usage help message.

    + * + * @return a string or an array of strings with version information about this command (each string in the array is displayed on a separate line). + * @since 0.9.8 + * @see CommandLine#printVersionHelp(PrintStream) + */ + String[] version() default {}; + + /** Class that can provide version information dynamically at runtime. An implementation may return version + * information obtained from the JAR manifest, a properties file or some other source. + * @return a Class that can provide version information dynamically at runtime + * @since 2.2 */ + Class versionProvider() default NoVersionProvider.class; + + /** + * Adds the standard {@code -h} and {@code --help} {@linkplain Option#usageHelp() usageHelp} options and {@code -V} + * and {@code --version} {@linkplain Option#versionHelp() versionHelp} options to the options of this command. + *

    + * Note that if no {@link #version()} or {@link #versionProvider()} is specified, the {@code --version} option will not print anything. + *

    + * For {@linkplain #resourceBundle() internationalization}: the help option has {@code descriptionKey = "mixinStandardHelpOptions.help"}, + * and the version option has {@code descriptionKey = "mixinStandardHelpOptions.version"}. + *

    + * @return whether the auto-help mixin should be added to this command + * @since 3.0 */ + boolean mixinStandardHelpOptions() default false; + + /** Set this attribute to {@code true} if this subcommand is a help command, and required options and positional + * parameters of the parent command should not be validated. If a subcommand marked as {@code helpCommand} is + * specified on the command line, picocli will not validate the parent arguments (so no "missing required + * option" errors) and the {@link CommandLine#printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi)} method will return {@code true}. + * @return {@code true} if this subcommand is a help command and picocli should not check for missing required + * options and positional parameters on the parent command + * @since 3.0 */ + boolean helpCommand() default false; + + /** Set the heading preceding the header section. + *

    May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.

    + * @return the heading preceding the header section + * @see UsageMessageSpec#headerHeading() + * @see Help#headerHeading(Object...) */ + String headerHeading() default ""; + + /** Optional summary description of the command, shown before the synopsis. Each element of the array is rendered on a separate line. + *

    May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.

    + * @return summary description of the command + * @see UsageMessageSpec#header() + * @see Help#header(Object...) */ + String[] header() default {}; + + /** Set the heading preceding the synopsis text. The default heading is {@code "Usage: "} (without a line break between the heading and the synopsis text). + *

    May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.

    + * @return the heading preceding the synopsis text + * @see Help#synopsisHeading(Object...) */ + String synopsisHeading() default "Usage: "; + + /** Specify {@code true} to generate an abbreviated synopsis like {@code "
    [OPTIONS] [PARAMETERS...] [COMMAND]"}. + * By default, a detailed synopsis with individual option names and parameters is generated. + * @return whether the synopsis should be abbreviated + * @see Help#abbreviatedSynopsis() + * @see Help#detailedSynopsis(int, Comparator, boolean) */ + boolean abbreviateSynopsis() default false; + + /** Specify one or more custom synopsis lines to display instead of an auto-generated synopsis. Each element of the array is rendered on a separate line. + *

    May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.

    + * @return custom synopsis text to replace the auto-generated synopsis + * @see Help#customSynopsis(Object...) */ + String[] customSynopsis() default {}; + + /** + * Specify the String to show in the synopsis for the subcommands of this command. The default is + * {@code "[COMMAND]"}. Ignored if this command has no {@linkplain #subcommands() subcommands}. + * @since 4.0 + */ + String synopsisSubcommandLabel() default "[COMMAND]"; + + /** Set the heading preceding the description section. + *

    May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.

    + * @return the heading preceding the description section + * @see Help#descriptionHeading(Object...) */ + String descriptionHeading() default ""; + + /** Optional text to display between the synopsis line(s) and the list of options. Each element of the array is rendered on a separate line. + *

    May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.

    + * @return description of this command + * @see Help#description(Object...) */ + String[] description() default {}; + + /** Set the heading preceding the parameters list. + *

    May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.

    + * @return the heading preceding the parameters list + * @see Help#parameterListHeading(Object...) */ + String parameterListHeading() default ""; + + /** Set the heading preceding the options list. + *

    May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.

    + * @return the heading preceding the options list + * @see Help#optionListHeading(Object...) */ + String optionListHeading() default ""; + + /** Specify {@code false} to show Options in declaration order in the option list of the usage help message (or to sort options by their {@linkplain Option#order() order index} if set). + * Note that picocli cannot reliably detect declaration order in commands that have both {@code @Option}-annotated methods and {@code @Option}-annotated fields. + * The default ({@code true}) is to sort alphabetically. + * @return whether options should be shown in alphabetic order. */ + boolean sortOptions() default true; + + /** Specify {@code false} to show options in declaration order in the synopsis of the usage help message (or to sort options by their {@linkplain Option#order() order index} if set). + * Note that picocli cannot reliably detect declaration order in commands that have both {@code @Option}-annotated methods and {@code @Option}-annotated fields. + * The default ({@code true}) is to sort alphabetically. + * @return whether options in the synopsis should be shown in alphabetic order. + * @since 4.7.0 */ + boolean sortSynopsis() default true; + + /** Prefix required options with this character in the options list. The default is no marker: the synopsis + * indicates which options and parameters are required. + * @return the character to show in the options list to mark required options */ + char requiredOptionMarker() default ' '; + + /** Class that can provide default values dynamically at runtime. An implementation may return default + * value obtained from a configuration file like a properties file or some other source. + *

    + * Applications may be interested in the {@link PropertiesDefaultProvider} built-in default provider + * that allows end users to maintain their own default values for options and positional parameters, + * which may override the defaults that are hard-coded in the application. + *

    + * @return a Class that can provide default values dynamically at runtime + * @since 3.6 */ + Class defaultValueProvider() default NoDefaultProvider.class; + + /** Specify {@code true} to show default values in the description column of the options list (except for + * boolean options). False by default. + *

    Note that picocli 3.2 allows {@linkplain Option#description() embedding default values} anywhere in the + * option or positional parameter description that ignores this setting.

    + * @return whether the default values for options and parameters should be shown in the description column */ + boolean showDefaultValues() default false; + + /** Specify {@code true} to show a {@code [@...]} entry + * in the synopsis and parameter list of the usage help message. + * (The entry is not shown if {@linkplain CommandLine#isExpandAtFiles() expanding parameter files} is disabled.) + * @since 4.2 */ + boolean showAtFileInUsageHelp() default false; + + /** Specify {@code true} to show a {@code [--]} "End of options" entry + * in the synopsis and option list of the usage help message. + * @since 4.3 */ + boolean showEndOfOptionsDelimiterInUsageHelp() default false; + + /** Set the heading preceding the subcommands list. The default heading is {@code "Commands:%n"} (with a line break at the end). + *

    May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.

    + * @return the heading preceding the subcommands list + * @see Help#commandListHeading(Object...) */ + String commandListHeading() default "Commands:%n"; + + /** Set the heading preceding the footer section. + *

    May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.

    + * @return the heading preceding the footer section + * @see Help#footerHeading(Object...) */ + String footerHeading() default ""; + + /** Optional text to display after the list of options. Each element of the array is rendered on a separate line. + *

    May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.

    + * @return text to display after the list of options + * @see Help#footer(Object...) */ + String[] footer() default {}; + + /** + * Set {@code hidden=true} if this command should not be included in the list of commands in the usage help of the parent command. + * @return whether this command should be excluded from the usage message + * @since 3.0 + */ + boolean hidden() default false; + + /** Set the base name of the ResourceBundle to find option and positional parameters descriptions, as well as + * usage help message sections and section headings.

    See {@link Messages} for more details and an example.

    + * @return the base name of the ResourceBundle for usage help strings + * @see ArgSpec#messages() + * @see UsageMessageSpec#messages() + * @see CommandSpec#resourceBundle() + * @see CommandLine#setResourceBundle(ResourceBundle) + * @since 3.6 + */ + String resourceBundle() default ""; + + /** Set the {@link UsageMessageSpec#width(int) usage help message width}. The default is 80. + * @see UsageMessageSpec#width() + * @since 3.7 + */ + int usageHelpWidth() default 80; + + /** If {@code true}, picocli will attempt to detect the terminal width and adjust the usage help message accordingly. + * End users may enable this by setting system property {@code "picocli.usage.width"} to {@code AUTO}, + * and may disable this by setting this system property to a {@linkplain UsageMessageSpec#width() numeric value}. + * This feature requires Java 7 or greater. The default is {@code false} + * @see UsageMessageSpec#autoWidth() + * @since 4.0 */ + boolean usageHelpAutoWidth() default false; + + /** Exit code for successful termination. {@value io.mosn.coder.cli.CommandLine.ExitCode#OK} by default. + * @see #execute(String...) + * @since 4.0 */ + int exitCodeOnSuccess() default ExitCode.OK; + + /** Exit code for successful termination after printing usage help on user request. {@value io.mosn.coder.cli.CommandLine.ExitCode#OK} by default. + * @see #execute(String...) + * @since 4.0 */ + int exitCodeOnUsageHelp() default ExitCode.OK; + + /** Exit code for successful termination after printing version help on user request. {@value io.mosn.coder.cli.CommandLine.ExitCode#OK} by default. + * @see #execute(String...) + * @since 4.0 */ + int exitCodeOnVersionHelp() default ExitCode.OK; + + /** Exit code for command line usage error. {@value io.mosn.coder.cli.CommandLine.ExitCode#USAGE} by default. + * @see #execute(String...) + * @since 4.0 */ + int exitCodeOnInvalidInput() default ExitCode.USAGE; + + /** Exit code signifying that an exception occurred when invoking the Runnable, Callable or Method user object of a command. + * {@value io.mosn.coder.cli.CommandLine.ExitCode#SOFTWARE} by default. + * @see #execute(String...) + * @since 4.0 */ + int exitCodeOnExecutionException() default ExitCode.SOFTWARE; + + /** Set the heading preceding the exit codes section, may contain {@code "%n"} line separators. {@code ""} (empty string) by default. + * @see Help#exitCodeListHeading(Object...) + * @since 4.0 */ + String exitCodeListHeading() default ""; + + /** Set the values to be displayed in the exit codes section as a list of {@code "key:value"} pairs: + * keys are exit codes, values are descriptions. Descriptions may contain {@code "%n"} line separators. + *

    For example:

    + *
    +         * @Command(exitCodeListHeading = "Exit Codes:%n",
    +         *          exitCodeList = { " 0:Successful program execution.",
    +         *                           "64:Invalid input: an unknown option or invalid parameter was specified.",
    +         *                           "70:Execution exception: an exception occurred while executing the business logic."})
    +         * 
    + * @since 4.0 */ + String[] exitCodeList() default {}; + + /** Returns whether subcommands inherit their attributes from this parent command. + * @since 4.6 */ + ScopeType scope() default ScopeType.LOCAL; + + /** Returns the model transformer for this command. + * @since 4.6 */ + Class modelTransformer() default NoOpModelTransformer.class; + + /** Returns the preprocessor for this command. + * @see IParameterPreprocessor + * @since 4.6 */ + Class preprocessor() default NoOpParameterPreprocessor.class; + } + /** A {@code Command} may define one or more {@code ArgGroups}: a group of options, positional parameters or a mixture of the two. + * Groups can be used to: + *
      + *
    • define mutually exclusive arguments. By default, options and positional parameters + * in a group are mutually exclusive. This can be controlled with the {@link #exclusive() exclusive} attribute. + * Picocli will throw a {@link MutuallyExclusiveArgsException} if the command line contains multiple arguments that are mutually exclusive.
    • + *
    • define a set of arguments that must co-occur. Set {@link #exclusive() exclusive = false} + * to define a group of options and positional parameters that must always be specified together. + * Picocli will throw a {@link MissingParameterException MissingParameterException} if not all the options and positional parameters in a co-occurring group are specified together.
    • + *
    • create an option section in the usage help message. + * To be shown in the usage help message, a group needs to have a {@link #heading() heading} (which may come from a {@linkplain #headingKey() resource bundle}). + * Groups without a heading are only used for validation. + * Set {@link #validate() validate = false} for groups whose purpose is only to customize the usage help message.
    • + *
    • define composite repeating argument groups. Groups may contain other groups to create composite groups.
    • + *
    + *

    Groups may be optional ({@code multiplicity = "0..1"}), required ({@code multiplicity = "1"}), or repeating groups ({@code multiplicity = "0..*"} or {@code multiplicity = "1..*"}). + * For a group of mutually exclusive arguments, making the group required means that one of the arguments in the group must appear on the command line, or a {@link MissingParameterException MissingParameterException} is thrown. + * For a group of co-occurring arguments, all arguments in the group must appear on the command line. + *

    + *

    Groups can be composed for validation purposes:

    + *
      + *
    • When the parent group is mutually exclusive, only one of the subgroups may be present.
    • + *
    • When the parent group is a co-occurring group, all subgroups must be present.
    • + *
    • When the parent group is required, at least one subgroup must be present.
    • + *
    + *

    + * Below is an example of an {@code ArgGroup} defining a set of dependent options that must occur together. + * All options are required within the group, while the group itself is optional:

    + *
    +     * public class DependentOptions {
    +     *     @ArgGroup(exclusive = false, multiplicity = "0..1")
    +     *     Dependent group;
    +     *
    +     *     static class Dependent {
    +     *         @Option(names = "-a", required = true) int a;
    +     *         @Option(names = "-b", required = true) int b;
    +     *         @Option(names = "-c", required = true) int c;
    +     *     }
    +     * }
    + * @see ArgGroupSpec + * @since 4.0 */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) + public @interface ArgGroup { + /** The heading of this group, used when generating the usage documentation. + * When neither a {@link #heading() heading} nor a {@link #headingKey() headingKey} are specified, + * this group is used for validation only and does not change the usage help message. */ + String heading() default "__no_heading__"; + + /** ResourceBundle key for this group's usage help message section heading. + * When neither a {@link #heading() heading} nor a {@link #headingKey() headingKey} are specified, + * this group is used for validation only and does not change the usage help message. */ + String headingKey() default "__no_heading_key__"; + /** Determines whether this is a mutually exclusive group; {@code true} by default. + * If {@code false}, this is a co-occurring group. Ignored if {@link #validate()} is {@code false}. */ + boolean exclusive() default true; + /** Determines how often this group can be specified on the command line; {@code "0..1"} (optional) by default. + * For a group of mutually exclusive arguments, making the group required {@code multiplicity = "1"} means that + * one of the arguments in the group must appear on the command line, or a MissingParameterException is thrown. + * For a group of co-occurring arguments, making the group required means that all arguments in the group must appear on the command line. + * Ignored if {@link #validate()} is {@code false}. */ + String multiplicity() default "0..1"; + /** Determines whether picocli should validate the rules of this group ({@code true} by default). + * For a mutually exclusive group validation means verifying that no more than one elements of the group is specified on the command line; + * for a co-ocurring group validation means verifying that all elements of the group are specified on the command line. + * Set {@link #validate() validate = false} for groups whose purpose is only to customize the usage help message. + * @see #multiplicity() + * @see #heading() */ + boolean validate() default true; + /** Determines the position in the options list in the usage help message at which this group should be shown. + * Groups with a lower number are shown before groups with a higher number. + * This attribute is only honored for groups that have a {@link #heading() heading} (or a {@link #headingKey() headingKey} with a non-{@code null} resource bundle value).*/ + int order() default -1; + } + /** + *

    + * When parsing command line arguments and initializing + * fields annotated with {@link Option @Option} or {@link Parameters @Parameters}, + * String values can be converted to any type for which a {@code ITypeConverter} is registered. + *

    + * This interface defines the contract for classes that know how to convert a String into some domain object. + * Custom converters can be registered with the {@link #registerConverter(Class, ITypeConverter)} method. + *

    + * Java 8 lambdas make it easy to register custom type converters: + *

    + *
    +     * commandLine.registerConverter(java.nio.file.Path.class, s -> java.nio.file.Paths.get(s));
    +     * commandLine.registerConverter(java.time.Duration.class, s -> java.time.Duration.parse(s));
    + *

    + * Built-in type converters are pre-registered for the following java 1.5 types: + *

    + *
      + *
    • all primitive types
    • + *
    • all primitive wrapper types: Boolean, Byte, Character, Double, Float, Integer, Long, Short
    • + *
    • any enum
    • + *
    • java.io.File
    • + *
    • java.math.BigDecimal
    • + *
    • java.math.BigInteger
    • + *
    • java.net.InetAddress
    • + *
    • java.net.URI
    • + *
    • java.net.URL
    • + *
    • java.nio.charset.Charset
    • + *
    • java.sql.Time
    • + *
    • java.util.Date
    • + *
    • java.util.UUID
    • + *
    • java.util.regex.Pattern
    • + *
    • StringBuilder
    • + *
    • CharSequence
    • + *
    • String
    • + *
    + * @param the type of the object that is the result of the conversion + */ + public interface ITypeConverter { + /** + * Converts the specified command line argument value to some domain object. + * @param value the command line argument String value + * @return the resulting domain object + * @throws Exception an exception detailing what went wrong during the conversion. + * Any exception thrown from this method will be caught and shown to the end user. + * An example error message shown to the end user could look something like this: + * {@code Invalid value for option '--some-option': cannot convert 'xxxinvalidinput' to SomeType (java.lang.IllegalArgumentException: Invalid format: must be 'x:y:z' but was 'xxxinvalidinput')} + * @throws TypeConversionException throw this exception to have more control over the error + * message that is shown to the end user when type conversion fails. + * An example message shown to the user could look like this: + * {@code Invalid value for option '--some-option': Invalid format: must be 'x:y:z' but was 'xxxinvalidinput'} + */ + K convert(String value) throws Exception; + } + + /** + * Provides version information for a command. Commands may configure a provider with the + * {@link Command#versionProvider()} annotation attribute. + * @since 2.2 */ + public interface IVersionProvider { + /** + * Returns version information for a command. + * @return version information (each string in the array is displayed on a separate line) + * @throws Exception an exception detailing what went wrong when obtaining version information + */ + String[] getVersion() throws Exception; + } + + /** + * Converter that can be used to signal to picocli that it should use the default converter. + * This can be useful with maps: + *
    +     *   class App {
    +     *       @Option(names = "-D", converter = {UseDefaultConverter.class, GenericValueConverter.class})
    +     *       Map<String, GenericValue<?>> values;
    +     *  }
    +     * 
    + * + * The {@link #convert(String)} method of this class always throws an UnsupportedOperationException. + * @since 4.7.0 + */ + public static final class UseDefaultConverter implements ITypeConverter { + /** Always throws UnsupportedOperationException. + * @throws UnsupportedOperationException always */ + public Object convert(String value) throws Exception { + throw new UnsupportedOperationException("This method should never be called."); + } + } + + private static class NoVersionProvider implements IVersionProvider { + public String[] getVersion() throws Exception { throw new UnsupportedOperationException(); } + } + + /** + * Provides a way to modify how the command model is built. + * This is useful for applications that need to modify the model dynamically depending on the runtime environment. + *

    + * Commands may configure a model transformer using the + * {@link Command#modelTransformer()} annotation attribute, or via the + * {@link CommandSpec#modelTransformer(IModelTransformer)} programmatic API. + *

    + * Model transformers are invoked only once, after the full command hierarchy is constructed. + * @since 4.6 + */ + public interface IModelTransformer { + /** + * Given an original CommandSpec, return the object that should be used + * instead. Implementors may modify the specified CommandSpec and return it, + * or create a full or partial copy of the specified CommandSpec, and return + * that, or even return a completely new CommandSpec. + *

    + * Implementors are free to add or remove options, positional parameters, + * subcommands or modify the command in any other way. + *

    + * This method is called once, after the full command hierarchy is + * constructed, and before any command line arguments are parsed. + *

    + * @return the CommandSpec to use instead of the specified one + */ + CommandSpec transform(CommandSpec commandSpec); + } + + private static class NoOpModelTransformer implements IModelTransformer { + public CommandSpec transform(CommandSpec commandSpec) { return commandSpec; } + } + + /** + * Provides default value for a command. Commands may configure a provider with the + * {@link Command#defaultValueProvider()} annotation attribute. + * @since 3.6 */ + public interface IDefaultValueProvider { + + /** Returns the default value for an option or positional parameter or {@code null}. + * The returned value is converted to the type of the option/positional parameter + * via the same type converter used when populating this option/positional + * parameter from a command line argument. + * @param argSpec the option or positional parameter, never {@code null} + * @return the default value for the option or positional parameter, or {@code null} if + * this provider has no default value for the specified option or positional parameter + * @throws Exception when there was a problem obtaining the default value + */ + String defaultValue(ArgSpec argSpec) throws Exception; + } + private static class NoDefaultProvider implements IDefaultValueProvider { + public String defaultValue(ArgSpec argSpec) { throw new UnsupportedOperationException(); } + } + + /** + * Options or positional parameters can be assigned a {@code IParameterConsumer} that implements + * custom logic to process the parameters for this option or this position. + * When an option or positional parameter with a custom {@code IParameterConsumer} is matched on the + * command line, picocli's internal parser is temporarily suspended, and this object becomes + * responsible for consuming and processing as many command line arguments as needed. + *

    This may be useful when passing through parameters to another command.

    + *

    Example usage:

    + *
    +     * @Command(name = "find")
    +     * class Find {
    +     *     @Option(names = "-exec", parameterConsumer = Find.ExecParameterConsumer.class)
    +     *     List<String> list = new ArrayList<String>();
    +     *
    +     *     static class ExecParameterConsumer implements IParameterConsumer {
    +     *         public void consumeParameters(Stack<String> args, ArgSpec argSpec, CommandSpec commandSpec) {
    +     *             List<String> list = argSpec.getValue();
    +     *             while (!args.isEmpty()) {
    +     *                 String arg = args.pop();
    +     *                 list.add(arg);
    +     *
    +     *                 // `find -exec` semantics: stop processing after a ';' or '+' argument
    +     *                 if (";".equals(arg) || "+".equals(arg)) {
    +     *                     break;
    +     *                 }
    +     *             }
    +     *         }
    +     *     }
    +     * }
    + *

    If this interface does not meet your requirements, you may have a look at the more powerful + * and flexible {@link IParameterPreprocessor} interface introduced with picocli 4.6.

    + * @see Option#parameterConsumer() + * @see Parameters#parameterConsumer() + * @since 4.0 */ + public interface IParameterConsumer { + /** + * Consumes as many of the specified command line arguments as needed by popping them off + * the specified Stack. Implementors are free to ignore the {@linkplain ArgSpec#arity() arity} + * of the option or positional parameter, they are free to consume arguments that would + * normally be matched as other options of the command, and they are free to consume + * arguments that would normally be matched as an end-of-options delimiter. + *

    Implementors are responsible for saving the consumed values; + * if the user object of the option or positional parameter is a Collection + * or a Map, a common approach would be to obtain the current instance via the + * {@link ArgSpec#getValue()}, and add to this instance. If the user object is an + * array, the implementation would need to create a new array that contains the + * old values as well as the newly consumed values, and store this array in the + * user object via the {@link ArgSpec#setValue(Object)}. + *

    + * If the user input is invalid, implementations should throw a {@link ParameterException} + * with a message to display to the user. + *

    + * When this method returns, the picocli parser will process the remaining arguments on the Stack. + *

    + * @param args the command line arguments + * @param argSpec the option or positional parameter for which to consume command line arguments + * @param commandSpec the command that the option or positional parameter belongs to + * @throws ParameterException if the user input is invalid + */ + void consumeParameters(Stack args, ArgSpec argSpec, CommandSpec commandSpec); + } + private static class NullParameterConsumer implements IParameterConsumer { + public void consumeParameters(Stack args, ArgSpec argSpec, CommandSpec commandSpec) { throw new UnsupportedOperationException(); } + } + /** + * Options, positional parameters and commands can be assigned a {@code IParameterPreprocessor} that + * implements custom logic to preprocess the parameters for this option, position or command. + * When an option, positional parameter or command with a custom {@code IParameterPreprocessor} is matched + * on the command line, picocli's internal parser is temporarily suspended, and this custom logic is invoked. + *

    + * This custom logic may completely replace picocli's internal parsing for this option, positional parameter + * or command, or it may do some preprocessing before picocli's internal parsing is resumed for this option, + * positional parameter or command. + *

    + * The "preprocessing" can include modifying the stack of command line parameters, or modifying the model. + *

    + *

    This may be useful when disambiguating input for commands that have both a positional parameter and an + * option with an optional parameter.

    + *

    Example usage:

    + *
    +     * @Command(name = "edit")
    +     * class Edit {
    +     *     @Parameters(index = "0", description = "The file to edit.")
    +     *     File file;
    +     *
    +     *     enum Editor { defaultEditor, eclipse, idea, netbeans }
    +     *
    +     *     @Option(names = "--open", arity = "0..1", preprocessor = Edit.MyPreprocessor.class,
    +     *         description = {
    +     *             "Optionally specify the editor to use; if omitted the default editor is used. ",
    +     *             "Example: edit --open=idea FILE opens IntelliJ IDEA (notice the '=' separator)",
    +     *             "         edit --open FILE opens the specified file in the default editor"
    +     *         })
    +     *     Editor editor = Editor.defaultEditor;
    +     *
    +     *     static class MyPreprocessor implements IParameterPreprocessor {
    +     *         public boolean preprocess(Stack<String> args, CommandSpec commandSpec, ArgSpec argSpec, Map<String, Object> info) {
    +     *             // we need to decide whether the next arg is the file to edit or the name of the editor to use...
    +     *             if (" ".equals(info.get("separator"))) { // parameter was not attached to option
    +     *                 args.push(Editor.defaultEditor.name()); // act as if the user specified --open=defaultEditor
    +     *             }
    +     *             return false; // picocli's internal parsing is resumed for this option
    +     *         }
    +     *     }
    +     * }
    + * @see Option#preprocessor() + * @see Parameters#preprocessor() + * @since 4.6 */ + public interface IParameterPreprocessor { + /** + * Called when either the command, option or positional parameter that has this preprocessor configured was + * recognized by the picocli parser. + *

    Implementors are free to modify one or more of the specified command line arguments before they are + * processed by the picocli parser (or by the option's {@link IParameterConsumer parameter consumer}, if one is specified). + *

    + * Implementors may optionally consume one or more of the specified command line arguments: + * a return value of {@code true} signals that the preprocessor consumed the parameter: + * picocli should skip further processing of this option or positional parameter, + * and the preprocessor implementation takes responsibility for assigning the + * option or positional parameter a value. A return value of {@code false} means that picocli should + * process the stack as usual for this option or positional parameter, and picocli is responsible for + * assigning this option or positional parameter a value. + *

    + * For a command, returning {@code true} signals that the preprocessor takes responsibility for parsing all + * options and positional parameters for this command, and takes responsibility for validating constraints like + * whether all required options and positional parameters were specified. + * A return value of {@code false} means that picocli should + * process the stack as usual for this command, and picocli is responsible for validation. + * Command preprocessors can signal back to the picocli parser when they detect that the user requested version + * information or usage help by putting a value of {@code true} in the specified + * {@code info} map for keys {@code versionHelpRequested} or {@code usageHelpRequested}, respectively. + *

    + * If the user input is invalid, implementations should throw a {@link ParameterException} + * with a message to display to the user. + *

    + * @param args the remaining command line arguments that follow the matched argument + * (the matched argument is not on the stack anymore) + * @param commandSpec the command or subcommand that was matched (if the specified {@code argSpec} is + * {@code null}), or the command that the matched option or positional parameter belongs to + * @param argSpec the option or positional parameter for which to pre-process command line arguments + * (may be {@code null} when this method is called for a subcommand that was matched) + * @param info a map containing additional information on the current parser state, + * including whether the option parameter was attached to the option name with + * a `=` separator, whether quotes have already been stripped off the option, etc. + * Implementations may modify this map to communicate back to the picocli parser. + * Supported values: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    Supported values in the info Map
    keyvalid valuestype
    separator'' (empty string): attached without separator, ' ' (space): not attached, (any other string): option name was attached to option param with specified separatorjava.lang.String
    negated{@code true}: the option or positional parameter is a {@linkplain Option#negatable() negated} option/parameter, {@code false}: the option or positional parameter is not a negated option/parameterjava.lang.Boolean
    unquoted{@code true}: quotes surrounding the value have already been stripped off, {@code false}: quotes surrounding the value have not yet been stripped offjava.lang.Boolean
    versionHelpRequested{@code true}: version help was requested, {@code false}: version help was not requestedjava.lang.Boolean
    usageHelpRequested{@code true}: usage help was requested, {@code false}: usage help was not requestedjava.lang.Boolean
    + * @return true if the preprocessor consumed the parameter + * and picocli should skip further processing of the stack for this option or positional parameter; + * false if picocli should continue processing the stack for this option or positional parameter + * @throws ParameterException if the user input is invalid + */ + boolean preprocess(Stack args, CommandSpec commandSpec, ArgSpec argSpec, Map info); + } + private static class NoOpParameterPreprocessor implements IParameterPreprocessor { + public boolean preprocess(Stack args, CommandSpec commandSpec, ArgSpec argSpec, Map info) { return false; } + public boolean equals(Object obj) { return obj instanceof NoOpParameterPreprocessor; } + public int hashCode() { return NoOpParameterPreprocessor.class.hashCode() + 7; } + } + + /** Determines the option name transformation of {@linkplain Option#negatable() negatable} boolean options. + * Making an option negatable has two aspects: + *
      + *
    • the negative form recognized by the parser while parsing the command line
    • + *
    • the documentation string showing both the positive and the negative form in the usage help message
    • + *

    + * Additionally, this transformer controls which names of a negatable option are actually negatable: + * for example, by default short options like {@code -v} do not have a negative form, even if the same option's + * long form, {@code --verbose}, may have a negative form, {@code --no-verbose}. + *

    + * @see RegexTransformer + * @since 4.0 + */ + public interface INegatableOptionTransformer { + /** Returns the negative form of the specified option name for the parser to recognize when parsing command line arguments. + * @param optionName the option name to create a negative form for, for example {@code --force} + * @param cmd the command that the option is part of + * @return the negative form of the specified option name, for example {@code --no-force} + */ + String makeNegative(String optionName, CommandSpec cmd); + /** Returns the documentation string to show in the synopsis and usage help message for the specified option. + * The returned value should be concise and clearly suggest that both the positive and the negative form are valid option names + * @param optionName the option name to create a documentation string for, for example {@code --force}, or {@code -XX:+