diff --git a/.github/dco.yml b/.github/dco.yml new file mode 100644 index 000000000..0c4b142e9 --- /dev/null +++ b/.github/dco.yml @@ -0,0 +1,2 @@ +require: + members: false diff --git a/.github/labels-manage.yml b/.github/labels-manage.yml index 5939c36cc..986e68c3c 100644 --- a/.github/labels-manage.yml +++ b/.github/labels-manage.yml @@ -118,7 +118,7 @@ description: Something needs to get done - name: type/technical-debt color: D4C5F9 - description: Techical Dept + description: Technical Dept - name: type/question color: D4C5F9 description: Is a question diff --git a/.github/workflows/ci-native.yml b/.github/workflows/ci-native.yml new file mode 100644 index 000000000..8213d543e --- /dev/null +++ b/.github/workflows/ci-native.yml @@ -0,0 +1,34 @@ +name: CI Native + +on: + workflow_dispatch: + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - nickname: linux jdk17 + java: 17 + os: ubuntu-latest + - nickname: macos jdk17 + java: 17 + os: macos-latest + - nickname: windows jdk17 + java: 17 + os: windows-latest + name: CI Build ${{ matrix.nickname }} + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 + with: + distribution: adopt + java-version: | + 22 + 17 + cache: gradle + - name: Build + run: ./gradlew build + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 503985e80..72fbff57e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,9 @@ jobs: - uses: actions/setup-java@v3 with: distribution: adopt - java-version: ${{ matrix.java }} + java-version: | + 22 + 17 cache: gradle - name: Build run: ./gradlew build @@ -41,21 +43,22 @@ jobs: - uses: actions/setup-java@v3 with: distribution: adopt - java-version: 17 + java-version: | + 22 + 17 cache: gradle - uses: jfrog/setup-jfrog-cli@v3 - with: - version: 2.21.5 env: + JF_URL: 'https://repo.spring.io' JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }} - name: Configure JFrog Cli run: | - jf rt gradlec \ + jf gradlec \ --use-wrapper \ --uses-plugin \ --deploy-ivy-desc=false \ - --server-id-resolve repo.spring.io \ - --server-id-deploy repo.spring.io \ + --server-id-resolve=${{ vars.JF_SERVER_ID }} \ + --server-id-deploy=${{ vars.JF_SERVER_ID }} \ --repo-resolve snapshot \ --repo-deploy snapshot echo JFROG_CLI_BUILD_NAME=spring-shell-main >> $GITHUB_ENV @@ -68,5 +71,5 @@ jobs: GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} run: | - jf rt gradle build distZip artifactoryPublish + jf gradle build artifactoryPublish jf rt build-publish diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index d6cb37a62..87f13ce04 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -16,7 +16,7 @@ jobs: graal: latest musl: false - nickname: macos - os: macos-12 + os: macos-latest graal: latest musl: false - nickname: linux @@ -34,7 +34,7 @@ jobs: - uses: graalvm/setup-graalvm@v1 with: version: ${{ matrix.graal }} - java-version: 21 + java-version: 22 components: native-image native-image-musl: ${{ matrix.musl }} github-token: ${{ secrets.GITHUB_TOKEN }} @@ -74,9 +74,9 @@ jobs: nickname: win # - os: windows-2019 # nickname: win - - os: macos-12 + - os: macos-14 nickname: macos - - os: macos-11 + - os: macos-15 nickname: macos - os: ubuntu-22.04 nickname: linux @@ -93,12 +93,8 @@ jobs: - uses: actions/setup-java@v3 with: distribution: adopt - java-version: 21 + java-version: 22 cache: gradle - - name: Use Python 3.11 - uses: actions/setup-python@v4 - with: - python-version: '3.11' - uses: actions/setup-node@v2 with: node-version: '16' diff --git a/.github/workflows/release-ga.yml b/.github/workflows/release-ga.yml index 35c868582..a318003cc 100644 --- a/.github/workflows/release-ga.yml +++ b/.github/workflows/release-ga.yml @@ -14,23 +14,21 @@ jobs: - uses: actions/setup-java@v3 with: distribution: adopt - java-version: 17 - - uses: jvalkeal/setup-maven@v1 - with: - maven-version: 3.8.4 + java-version: | + 22 + 17 - uses: jfrog/setup-jfrog-cli@v3 - with: - version: 2.21.5 env: + JF_URL: 'https://repo.spring.io' JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }} - name: Configure JFrog Cli run: | - jf rt gradlec \ + jf gradlec \ --use-wrapper \ --uses-plugin \ --deploy-ivy-desc=false \ - --server-id-resolve repo.spring.io \ - --server-id-deploy repo.spring.io \ + --server-id-resolve=${{ vars.JF_SERVER_ID }} \ + --server-id-deploy=${{ vars.JF_SERVER_ID }} \ --repo-resolve release \ --repo-deploy libs-staging-local echo JFROG_CLI_BUILD_NAME=spring-shell-main-release >> $GITHUB_ENV @@ -59,7 +57,7 @@ jobs: GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} run: | - jf rt gradle build distZip artifactoryPublish + jf gradle build artifactoryPublish jf rt build-publish - name: Push Release env: @@ -81,9 +79,8 @@ jobs: steps: - uses: actions/checkout@v3 - uses: jfrog/setup-jfrog-cli@v3 - with: - version: 2.21.5 env: + JF_URL: 'https://repo.spring.io' JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }} - name: Configure JFrog Cli run: | @@ -157,9 +154,8 @@ jobs: steps: - uses: actions/checkout@v3 - uses: jfrog/setup-jfrog-cli@v3 - with: - version: 2.21.5 env: + JF_URL: 'https://repo.spring.io' JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }} - name: Configure JFrog Cli run: | diff --git a/.github/workflows/release-milestone.yml b/.github/workflows/release-milestone.yml index a8dd1d084..c0c005638 100644 --- a/.github/workflows/release-milestone.yml +++ b/.github/workflows/release-milestone.yml @@ -17,14 +17,12 @@ jobs: - uses: actions/setup-java@v3 with: distribution: adopt - java-version: 17 - - uses: jvalkeal/setup-maven@v1 - with: - maven-version: 3.8.4 + java-version: | + 22 + 17 - uses: jfrog/setup-jfrog-cli@v3 - with: - version: 2.21.5 env: + JF_URL: 'https://repo.spring.io' JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }} - name: Configure JFrog Cli run: | @@ -32,8 +30,8 @@ jobs: --use-wrapper \ --uses-plugin \ --deploy-ivy-desc=false \ - --server-id-resolve repo.spring.io \ - --server-id-deploy repo.spring.io \ + --server-id-resolve=${{ vars.JF_SERVER_ID }} \ + --server-id-deploy=${{ vars.JF_SERVER_ID }} \ --repo-resolve milestone \ --repo-deploy libs-staging-local echo JFROG_CLI_BUILD_NAME=spring-shell-main-milestone >> $GITHUB_ENV @@ -63,7 +61,7 @@ jobs: GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} run: | - jf rt gradle build distZip artifactoryPublish + jf rt gradle build artifactoryPublish jf rt build-publish - name: Push Release env: @@ -85,9 +83,8 @@ jobs: steps: - uses: actions/checkout@v3 - uses: jfrog/setup-jfrog-cli@v3 - with: - version: 2.21.5 env: + JF_URL: 'https://repo.spring.io' JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }} - name: Configure JFrog Cli run: | diff --git a/.github/workflows/schedule-e2e.yml b/.github/workflows/schedule-e2e.yml index b8e2b78af..7185a64ba 100644 --- a/.github/workflows/schedule-e2e.yml +++ b/.github/workflows/schedule-e2e.yml @@ -3,7 +3,7 @@ name: Schedule e2e on: workflow_dispatch: schedule: - - cron: '0 0 * * 1,3,5' + - cron: '0 0 * * 1,4' permissions: actions: write @@ -14,7 +14,7 @@ jobs: if: github.repository_owner == 'spring-projects' strategy: matrix: - branch: [ main, 3.2.x, 3.1.x ] + branch: [ main, 3.3.x, 3.2.x ] runs-on: ubuntu-latest steps: - name: Checkout diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc index ad8326dab..e3158115f 100644 --- a/CONTRIBUTING.adoc +++ b/CONTRIBUTING.adoc @@ -1,2 +1,6 @@ -If you have not previously done so, please fill out and -submit the https://cla.pivotal.io/sign/spring[Contributor License Agreement]. +### Sign-off commits according to the Developer Certificate of Origin + +All commits must include a Signed-off-by trailer at the end of each commit message to indicate that the contributor agrees to the [Developer Certificate of Origin](https://developercertificate.org). + +For additional details, please refer to the blog post [Hello DCO, Goodbye CLA: Simplifying Contributions to Spring](https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring). + diff --git a/build.gradle b/build.gradle index 0294e4c8c..485d2ac43 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,5 @@ plugins { id "base" - id 'org.springframework.shell.root' } description = 'Spring Shell' diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index f02991c67..3cb238fef 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -26,7 +26,6 @@ dependencies { implementation(platform("org.springframework.boot:spring-boot-dependencies:${springBootVersion}")) implementation "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}" implementation("org.springframework:spring-core") - implementation 'org.asciidoctor:asciidoctor-gradle-jvm:3.3.2' implementation 'org.jfrog.buildinfo:build-info-extractor-gradle:4.29.0' implementation "org.graalvm.buildtools:native-gradle-plugin:${nativeBuildToolsVersion}" } @@ -49,13 +48,13 @@ gradlePlugin { id = "org.springframework.shell.docs" implementationClass = "org.springframework.shell.gradle.DocsPlugin" } - distPlugin { - id = "org.springframework.shell.root" - implementationClass = "org.springframework.shell.gradle.RootPlugin" - } samplePlugin { id = "org.springframework.shell.sample" implementationClass = "org.springframework.shell.gradle.SamplePlugin" } + toolchainPlugin { + id = "org.springframework.shell.toolchain" + implementationClass = "org.springframework.shell.gradle.ToolchainPlugin" + } } } diff --git a/buildSrc/src/main/java/org/springframework/shell/gradle/DocsPlugin.java b/buildSrc/src/main/java/org/springframework/shell/gradle/DocsPlugin.java index 8ce5d3b76..3a35cf4c6 100644 --- a/buildSrc/src/main/java/org/springframework/shell/gradle/DocsPlugin.java +++ b/buildSrc/src/main/java/org/springframework/shell/gradle/DocsPlugin.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 the original author or authors. + * Copyright 2022-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,36 +16,25 @@ package org.springframework.shell.gradle; import java.io.File; -import java.time.LocalDate; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import org.asciidoctor.gradle.base.AsciidoctorAttributeProvider; -import org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask; -import org.asciidoctor.gradle.jvm.AsciidoctorJExtension; -import org.asciidoctor.gradle.jvm.AsciidoctorJPlugin; -import org.asciidoctor.gradle.jvm.AsciidoctorTask; -import org.gradle.api.Action; import org.gradle.api.Plugin; import org.gradle.api.Project; -import org.gradle.api.Task; import org.gradle.api.plugins.JavaLibraryPlugin; import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.plugins.PluginManager; import org.gradle.api.publish.tasks.GenerateModuleMetadata; -import org.gradle.api.tasks.PathSensitivity; -import org.gradle.api.tasks.Sync; -import org.springframework.util.StringUtils; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.javadoc.Javadoc; +import org.gradle.external.javadoc.CoreJavadocOptions; +import org.gradle.external.javadoc.JavadocMemberLevel; +import org.gradle.external.javadoc.JavadocOutputLevel; /** * @author Janne Valkealahti */ class DocsPlugin implements Plugin { - private static final String ASCIIDOCTORJ_VERSION = "2.4.3"; - private static final String EXTENSIONS_CONFIGURATION_NAME = "asciidoctorExtensions"; - @Override public void apply(Project project) { PluginManager pluginManager = project.getPluginManager(); @@ -53,129 +42,41 @@ public void apply(Project project) { pluginManager.apply(JavaLibraryPlugin.class); pluginManager.apply(ManagementConfigurationPlugin.class); pluginManager.apply(SpringMavenPlugin.class); - pluginManager.apply(AsciidoctorJPlugin.class); - ExtractVersionConstraints dependencyVersions = project.getTasks().create("dependencyVersions", - ExtractVersionConstraints.class, task -> { - task.enforcedPlatform(":spring-shell-management"); - }); + createApiTask(project); - project.getPlugins().withType(AsciidoctorJPlugin.class, (asciidoctorPlugin) -> { - // makeAllWarningsFatal(project); - upgradeAsciidoctorJVersion(project); - createAsciidoctorExtensionsConfiguration(project); - project.getTasks() - .withType(AbstractAsciidoctorTask.class, - (asciidoctorTask) -> configureAsciidoctorTask(project, asciidoctorTask, dependencyVersions)); - }); project.getTasks().withType(GenerateModuleMetadata.class, metadata -> { metadata.setEnabled(false); }); } - private void upgradeAsciidoctorJVersion(Project project) { - project.getExtensions().getByType(AsciidoctorJExtension.class).setVersion(ASCIIDOCTORJ_VERSION); - } - - private void createAsciidoctorExtensionsConfiguration(Project project) { - project.getConfigurations().create(EXTENSIONS_CONFIGURATION_NAME, (configuration) -> { - configuration.getDependencies() - .add(project.getDependencies() - .create("io.spring.asciidoctor.backends:spring-asciidoctor-backends:0.0.5")); + private Javadoc createApiTask(Project project) { + Javadoc api = project.getTasks().create("aggregatedJavadoc", Javadoc.class, a -> { + a.setGroup("Documentation"); + a.setDescription("Generates aggregated Javadoc API documentation."); + a.setDestinationDir(new File(project.getBuildDir(), "generated-antora-javadocs/modules/ROOT/assets/attachments/api/java")); + a.setTitle(String.format("Spring Shell %s API", project.getVersion())); + CoreJavadocOptions options = (CoreJavadocOptions) a.getOptions(); + options.windowTitle(String.format("Spring Shell %s API", project.getVersion())); + options.setMemberLevel(JavadocMemberLevel.PROTECTED); + options.setOutputLevel(JavadocOutputLevel.QUIET); + options.addStringOption("Xdoclint:none", "-quiet"); }); - } - private void configureAsciidoctorTask(Project project, AbstractAsciidoctorTask asciidoctorTask, ExtractVersionConstraints dependencyVersions) { - asciidoctorTask.configurations(EXTENSIONS_CONFIGURATION_NAME); - configureCommonAttributes(project, asciidoctorTask, dependencyVersions); - configureOptions(asciidoctorTask); - asciidoctorTask.baseDirFollowsSourceDir(); - createSyncDocumentationSourceTask(project, asciidoctorTask, dependencyVersions); - if (asciidoctorTask instanceof AsciidoctorTask task) { - task.outputOptions((outputOptions) -> outputOptions.backends("spring-html")); - } - } - - private void configureOptions(AbstractAsciidoctorTask asciidoctorTask) { - asciidoctorTask.options(Collections.singletonMap("doctype", "book")); - } + project.getRootProject().getSubprojects().forEach(p -> { + p.getPlugins().withType(ModulePlugin.class, m -> { + JavaPluginConvention java = p.getConvention().getPlugin(JavaPluginConvention.class); + SourceSet mainSourceSet = java.getSourceSets().getByName("main"); - private Sync createSyncDocumentationSourceTask(Project project, AbstractAsciidoctorTask asciidoctorTask, ExtractVersionConstraints dependencyVersions) { - Sync syncDocumentationSource = project.getTasks() - .create("syncDocumentationSourceFor" + StringUtils.capitalize(asciidoctorTask.getName()), Sync.class); - syncDocumentationSource.preserve(filter -> { - filter.include("**/*"); - }); - File syncedSource = new File(project.getBuildDir(), "docs/src/" + asciidoctorTask.getName()); - syncDocumentationSource.setDestinationDir(syncedSource); - syncDocumentationSource.from("src/main/"); - asciidoctorTask.dependsOn(syncDocumentationSource); - asciidoctorTask.dependsOn(dependencyVersions); - Sync snippetsResources = createSnippetsResourcesTask(project); - asciidoctorTask.dependsOn(snippetsResources); - asciidoctorTask.getInputs() - .dir(syncedSource) - .withPathSensitivity(PathSensitivity.RELATIVE) - .withPropertyName("synced source"); - asciidoctorTask.setSourceDir(project.relativePath(new File(syncedSource, "asciidoc/"))); - return syncDocumentationSource; - } + api.setSource(api.getSource().plus(mainSourceSet.getAllJava())); - private Sync createSnippetsResourcesTask(Project project) { - Sync sync = project.getTasks().create("snippetResources", Sync.class, s -> { - s.from(new File(project.getRootProject().getRootDir(), "spring-shell-docs/src/test/java/org/springframework/shell"), spec -> { - spec.include("docs/*"); - }); - s.preserve(filter -> { - filter.include("**/*"); - }); - File destination = new File(project.getBuildDir(), "docs/src/asciidoctor/asciidoc"); - s.into(destination); - }); - return sync; - } - - private void configureCommonAttributes(Project project, AbstractAsciidoctorTask asciidoctorTask, - ExtractVersionConstraints dependencyVersions) { - asciidoctorTask.doFirst(new Action() { - - @Override - public void execute(Task arg0) { - asciidoctorTask.getAttributeProviders().add(new AsciidoctorAttributeProvider() { - @Override - public Map getAttributes() { - Map versionConstraints = dependencyVersions.getVersionConstraints(); - Map attrs = new HashMap<>(); - attrs.put("spring-version", versionConstraints.get("org.springframework:spring-core")); - attrs.put("spring-boot-version", versionConstraints.get("org.springframework.boot:spring-boot")); - return attrs; - } + p.getTasks().withType(Javadoc.class, j -> { + api.setClasspath(api.getClasspath().plus(j.getClasspath())); }); - } + }); }); - Map attributes = new HashMap<>(); - attributes.put("toc", "left"); - attributes.put("icons", "font"); - attributes.put("idprefix", ""); - attributes.put("idseparator", "-"); - attributes.put("docinfo", "shared"); - attributes.put("sectanchors", ""); - attributes.put("sectnums", ""); - attributes.put("today-year", LocalDate.now().getYear()); - attributes.put("snippets", "docs"); - - asciidoctorTask.getAttributeProviders().add(new AsciidoctorAttributeProvider() { - @Override - public Map getAttributes() { - Object version = project.getVersion(); - Map attrs = new HashMap<>(); - if (version != null && version.toString() != Project.DEFAULT_VERSION) { - attrs.put("project-version", version); - } - return attrs; - } - }); - asciidoctorTask.attributes(attributes); + return api; } + } diff --git a/buildSrc/src/main/java/org/springframework/shell/gradle/JavaConventions.java b/buildSrc/src/main/java/org/springframework/shell/gradle/JavaConventions.java index 56716cba0..b895aa8eb 100644 --- a/buildSrc/src/main/java/org/springframework/shell/gradle/JavaConventions.java +++ b/buildSrc/src/main/java/org/springframework/shell/gradle/JavaConventions.java @@ -39,6 +39,9 @@ class JavaConventions { private static final String SOURCE_AND_TARGET_COMPATIBILITY = "17"; + private static final String INCLUDE_TAGS = "shellIncludeTags"; + private static final String EXCLUDE_TAGS = "shellExcludeTags"; + private static final String[] DEFAULT_EXCLUDE_TAGS = new String[] { }; void apply(Project project) { project.getPlugins().withType(JavaBasePlugin.class, java -> { @@ -52,7 +55,6 @@ void apply(Project project) { private void configureJavadocConventions(Project project) { project.getTasks().withType(Javadoc.class, (javadoc) -> { CoreJavadocOptions options = (CoreJavadocOptions) javadoc.getOptions(); - options.source("17"); options.encoding("UTF-8"); options.addStringOption("Xdoclint:none", "-quiet"); }); @@ -84,7 +86,31 @@ private boolean buildingWithJava17(Project project) { private void configureTestConventions(Project project) { project.getTasks().withType(Test.class, test -> { - test.useJUnitPlatform(); + boolean hasIncludeTags = project.hasProperty(INCLUDE_TAGS); + boolean hasExcludeTags = project.hasProperty(EXCLUDE_TAGS); + test.useJUnitPlatform(options -> { + if (!hasIncludeTags && !hasExcludeTags) { + options.excludeTags(DEFAULT_EXCLUDE_TAGS); + } + else { + if (hasIncludeTags) { + Object includeTagsProperty = project.property(INCLUDE_TAGS); + if (includeTagsProperty instanceof String p) { + if (p.length() > 0) { + options.includeTags(p.split(",")); + } + } + } + if (hasExcludeTags) { + Object excludeTagsProperty = project.property(EXCLUDE_TAGS); + if (excludeTagsProperty instanceof String p) { + if (p.length() > 0) { + options.excludeTags(p.split(",")); + } + } + } + } + }); }); } diff --git a/buildSrc/src/main/java/org/springframework/shell/gradle/RootPlugin.java b/buildSrc/src/main/java/org/springframework/shell/gradle/RootPlugin.java deleted file mode 100644 index b64bc0553..000000000 --- a/buildSrc/src/main/java/org/springframework/shell/gradle/RootPlugin.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * 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. - */ -package org.springframework.shell.gradle; - -import java.io.File; - -import org.asciidoctor.gradle.jvm.AsciidoctorJPlugin; -import org.gradle.api.Plugin; -import org.gradle.api.Project; -import org.gradle.api.plugins.JavaPluginConvention; -import org.gradle.api.plugins.PluginManager; -import org.gradle.api.publish.maven.plugins.MavenPublishPlugin; -import org.gradle.api.tasks.SourceSet; -import org.gradle.api.tasks.bundling.Zip; -import org.gradle.api.tasks.javadoc.Javadoc; -import org.gradle.external.javadoc.CoreJavadocOptions; - -/** - * Manages tasks creating zip file for docs and publishing it. - * - * @author Janne Valkealahti - */ -class RootPlugin implements Plugin { - - @Override - public void apply(Project project) { - PluginManager pluginManager = project.getPluginManager(); - pluginManager.apply(MavenPublishPlugin.class); - pluginManager.apply(PublishLocalPlugin.class); - // docsarchive needs to be created before artifactory - // conventions are used - project.getConfigurations().create("docsarchive"); - new ArtifactoryConventions().apply(project); - Javadoc apiTask = createApiTask(project); - Zip zipTask = createZipTask(project); - zipTask.dependsOn(apiTask); - } - - private Zip createZipTask(Project project) { - Zip zipTask = project.getTasks().create("distZip", Zip.class, zip -> { - zip.setGroup("Distribution"); - zip.from("spring-shell-docs/build/docs/asciidoc", copy -> { - copy.into("docs"); - }); - zip.from("build/api", copy -> { - copy.into("api"); - }); - }); - - project.getRootProject().getAllprojects().forEach(p -> { - p.getPlugins().withType(AsciidoctorJPlugin.class, a -> { - p.getTasksByName("asciidoctor", false).forEach(t -> { - zipTask.dependsOn(t); - });; - }); - }); - - // since gradle 8.3 archives configuration started to fail - // so using custom configuration name - project.getArtifacts().add("docsarchive", zipTask); - return zipTask; - } - - private Javadoc createApiTask(Project project) { - Javadoc api = project.getTasks().create("api", Javadoc.class, a -> { - a.setGroup("Documentation"); - a.setDescription("Generates aggregated Javadoc API documentation."); - a.setDestinationDir(new File(project.getBuildDir(), "api")); - CoreJavadocOptions options = (CoreJavadocOptions) a.getOptions(); - options.source("17"); - options.encoding("UTF-8"); - options.addStringOption("Xdoclint:none", "-quiet"); - }); - - project.getRootProject().getSubprojects().forEach(p -> { - p.getPlugins().withType(ModulePlugin.class, m -> { - JavaPluginConvention java = p.getConvention().getPlugin(JavaPluginConvention.class); - SourceSet mainSourceSet = java.getSourceSets().getByName("main"); - - api.setSource(api.getSource().plus(mainSourceSet.getAllJava())); - - p.getTasks().withType(Javadoc.class, j -> { - api.setClasspath(api.getClasspath().plus(j.getClasspath())); - }); - }); - }); - return api; - } -} diff --git a/buildSrc/src/main/java/org/springframework/shell/gradle/ToolchainExtension.java b/buildSrc/src/main/java/org/springframework/shell/gradle/ToolchainExtension.java new file mode 100644 index 000000000..5d68767e5 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/shell/gradle/ToolchainExtension.java @@ -0,0 +1,42 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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. + */ +package org.springframework.shell.gradle; + +import org.gradle.api.Project; +import org.gradle.api.provider.Property; +import org.gradle.jvm.toolchain.JavaLanguageVersion; + +public class ToolchainExtension { + + private final Property maximumCompatibleJavaVersion; + + private final JavaLanguageVersion javaVersion; + + public ToolchainExtension(Project project) { + this.maximumCompatibleJavaVersion = project.getObjects().property(JavaLanguageVersion.class); + String toolchainVersion = (String) project.findProperty("toolchainVersion"); + this.javaVersion = (toolchainVersion != null) ? JavaLanguageVersion.of(toolchainVersion) : null; + } + + public Property getMaximumCompatibleJavaVersion() { + return this.maximumCompatibleJavaVersion; + } + + JavaLanguageVersion getJavaVersion() { + return this.javaVersion; + } + +} \ No newline at end of file diff --git a/buildSrc/src/main/java/org/springframework/shell/gradle/ToolchainPlugin.java b/buildSrc/src/main/java/org/springframework/shell/gradle/ToolchainPlugin.java new file mode 100644 index 000000000..ef327e92b --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/shell/gradle/ToolchainPlugin.java @@ -0,0 +1,47 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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. + */ +package org.springframework.shell.gradle; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.jvm.toolchain.JavaLanguageVersion; +import org.gradle.jvm.toolchain.JavaToolchainSpec; + +public class ToolchainPlugin implements Plugin { + + @Override + public void apply(Project project) { + configureToolchain(project); + } + + private void configureToolchain(Project project) { + ToolchainExtension toolchain = project.getExtensions().create("toolchain", ToolchainExtension.class, project); + configure(project, toolchain); + } + + private void configure(Project project, ToolchainExtension toolchain) { + JavaLanguageVersion toolchainVersion = toolchain.getJavaVersion(); + if (toolchainVersion == null) { + toolchainVersion = JavaLanguageVersion.of(22); + } + JavaToolchainSpec toolchainSpec = project.getExtensions() + .getByType(JavaPluginExtension.class) + .getToolchain(); + toolchainSpec.getLanguageVersion().set(toolchainVersion); + } + +} \ No newline at end of file diff --git a/e2e/spring-shell-e2e/package-lock.json b/e2e/spring-shell-e2e/package-lock.json index b71035634..321cdb871 100644 --- a/e2e/spring-shell-e2e/package-lock.json +++ b/e2e/spring-shell-e2e/package-lock.json @@ -9,8 +9,8 @@ "version": "2.1.0", "license": "ISC", "dependencies": { - "node-pty": "0.11.0-beta19", - "xterm-headless": "^4.18.0" + "@xterm/headless": "^5.5.0", + "node-pty": "^1.0.0" }, "devDependencies": { "@types/node": "^18.11.9", @@ -24,21 +24,23 @@ "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", "dev": true }, + "node_modules/@xterm/headless": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.5.0.tgz", + "integrity": "sha512-5xXB7kdQlFBP82ViMJTwwEc3gKCLGKR/eoxQm4zge7GPBl86tCdI0IdPJjoKd8mUSFXz5V7i/25sfsEkP4j46g==" + }, "node_modules/nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==" + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz", + "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==" }, "node_modules/node-pty": { - "version": "0.11.0-beta19", - "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-0.11.0-beta19.tgz", - "integrity": "sha512-1cdUx15rCVYEcvOvqNRO4S52vYM9mnFyTAceut+lW8/+YWcYQl1TQ3naDqtWHDcVjZI/FbDX1j4iQFwypmJSLQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.0.0.tgz", + "integrity": "sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==", "hasInstallScript": true, "dependencies": { - "nan": "^2.14.0" - }, - "peerDependencies": { - "node-gyp": "^8.3.0" + "nan": "^2.17.0" } }, "node_modules/prettier": { @@ -68,11 +70,6 @@ "engines": { "node": ">=4.2.0" } - }, - "node_modules/xterm-headless": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/xterm-headless/-/xterm-headless-4.18.0.tgz", - "integrity": "sha512-VwSPG2cyVOwesVVLBVrybYNY6cUMiVkD2fLnIXcBs/1iJ3M7bhD3lP9HdQOHGtOkbxV2Duf7MZNmEfngBXL/iQ==" } }, "dependencies": { @@ -82,17 +79,22 @@ "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", "dev": true }, + "@xterm/headless": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.5.0.tgz", + "integrity": "sha512-5xXB7kdQlFBP82ViMJTwwEc3gKCLGKR/eoxQm4zge7GPBl86tCdI0IdPJjoKd8mUSFXz5V7i/25sfsEkP4j46g==" + }, "nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==" + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz", + "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==" }, "node-pty": { - "version": "0.11.0-beta19", - "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-0.11.0-beta19.tgz", - "integrity": "sha512-1cdUx15rCVYEcvOvqNRO4S52vYM9mnFyTAceut+lW8/+YWcYQl1TQ3naDqtWHDcVjZI/FbDX1j4iQFwypmJSLQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.0.0.tgz", + "integrity": "sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==", "requires": { - "nan": "^2.14.0" + "nan": "^2.17.0" } }, "prettier": { @@ -106,11 +108,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", "dev": true - }, - "xterm-headless": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/xterm-headless/-/xterm-headless-4.18.0.tgz", - "integrity": "sha512-VwSPG2cyVOwesVVLBVrybYNY6cUMiVkD2fLnIXcBs/1iJ3M7bhD3lP9HdQOHGtOkbxV2Duf7MZNmEfngBXL/iQ==" } } } diff --git a/e2e/spring-shell-e2e/package.json b/e2e/spring-shell-e2e/package.json index f71bb9dab..ee3785c25 100644 --- a/e2e/spring-shell-e2e/package.json +++ b/e2e/spring-shell-e2e/package.json @@ -36,7 +36,7 @@ "typescript": "^4.6.4" }, "dependencies": { - "node-pty": "0.11.0-beta19", - "xterm-headless": "^4.18.0" + "node-pty": "^1.0.0", + "@xterm/headless": "^5.5.0" } } diff --git a/gradle.properties b/gradle.properties index cdc69c97e..0c46e876f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,11 +1,11 @@ -version=3.3.1-SNAPSHOT -springBootVersion=3.3.0 -nativeBuildToolsVersion=0.10.1 -commonsIoVersion=2.11.0 -jlineVersion=3.26.1 -st4Version=4.3.3 -jimfsVersion=1.2 -gradleEnterpriseVersion=3.16.2 -springGeConventionsVersion=0.0.15 -findbugsVersion=3.0.2 +version=3.4.1-SNAPSHOT +springBootVersion=3.5.0-SNAPSHOT +nativeBuildToolsVersion=0.10.6 +commonsIoVersion=2.19.0 +jlineVersion=3.29.0 +st4Version=4.3.4 +jimfsVersion=1.3.0 +gradleEnterpriseVersion=3.19.2 +springGeConventionsVersion=0.0.17 org.gradle.caching=true +includeFfm=false \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135c4..d64cd4917 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a80b22ce5..37f853b1c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew.bat b/gradlew.bat index 6689b85be..7101f8e46 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 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. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ 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. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/settings.gradle b/settings.gradle index 545cdaed1..5c5591d29 100644 --- a/settings.gradle +++ b/settings.gradle @@ -49,12 +49,18 @@ include 'spring-shell-table' include 'spring-shell-test' include 'spring-shell-test-autoconfigure' +def ffm = settings.properties.hasProperty('includeFfm') && settings.properties.property('includeFfm').asBoolean() + file("${rootDir}/spring-shell-starters").eachDirMatch(~/spring-shell-starter.*/) { - include "spring-shell-starters:${it.name}" + if((!it.name.endsWith('ffm') && !ffm) || ffm) { // skipping + include "spring-shell-starters:${it.name}" + } } file("${rootDir}/spring-shell-samples").eachDirMatch(~/spring-shell-sample.*/) { - include "spring-shell-samples:${it.name}" + if((!it.name.endsWith('ffm') && !ffm) || ffm) { // skipping + include "spring-shell-samples:${it.name}" + } } rootProject.children.each { project -> diff --git a/spring-shell-autoconfigure/spring-shell-autoconfigure.gradle b/spring-shell-autoconfigure/spring-shell-autoconfigure.gradle index 969ba2ddc..528cac102 100644 --- a/spring-shell-autoconfigure/spring-shell-autoconfigure.gradle +++ b/spring-shell-autoconfigure/spring-shell-autoconfigure.gradle @@ -10,6 +10,11 @@ dependencies { implementation project(':spring-shell-core') implementation project(':spring-shell-standard') implementation project(':spring-shell-standard-commands') - testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation('org.springframework.boot:spring-boot-test') + testImplementation('org.junit.jupiter:junit-jupiter-engine') + testImplementation('org.junit.jupiter:junit-jupiter-params') + testImplementation('org.junit.platform:junit-platform-launcher') + testImplementation("org.mockito:mockito-junit-jupiter") + testImplementation('org.assertj:assertj-core') annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' } diff --git a/spring-shell-core/spring-shell-core.gradle b/spring-shell-core/spring-shell-core.gradle index ae996afbc..5599d6523 100644 --- a/spring-shell-core/spring-shell-core.gradle +++ b/spring-shell-core/spring-shell-core.gradle @@ -17,8 +17,12 @@ dependencies { api('org.jline:jline-terminal') api('org.antlr:ST4') api('commons-io:commons-io') - compileOnly 'com.google.code.findbugs:jsr305' - testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation('org.springframework.boot:spring-boot-test') + testImplementation('org.junit.jupiter:junit-jupiter-engine') + testImplementation('org.junit.jupiter:junit-jupiter-params') + testImplementation('org.junit.platform:junit-platform-launcher') + testImplementation("org.mockito:mockito-junit-jupiter") + testImplementation('org.assertj:assertj-core') testImplementation 'org.awaitility:awaitility' testImplementation 'com.google.jimfs:jimfs' testImplementation 'io.projectreactor:reactor-test' diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/parser/Lexer.java b/spring-shell-core/src/main/java/org/springframework/shell/command/parser/Lexer.java index bb01ad9d2..04a002cc4 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/command/parser/Lexer.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/command/parser/Lexer.java @@ -222,7 +222,13 @@ else if (isLastTokenOfType(tokenList, TokenType.COMMAND)) { } } else if (isLastTokenOfType(tokenList, TokenType.ARGUMENT)) { - tokenList.add(Token.of(argument, TokenType.ARGUMENT, i2)); + int decuceArgumentStyle = decuceArgumentStyle(argument); + if (decuceArgumentStyle < 0) { + tokenList.add(Token.of(argument, TokenType.ARGUMENT, i2)); + } + else { + tokenList.add(Token.of(argument, TokenType.OPTION, i2)); + } } } diff --git a/spring-shell-core/src/main/java/org/springframework/shell/component/StringInput.java b/spring-shell-core/src/main/java/org/springframework/shell/component/StringInput.java index e0855b911..5a972c1bb 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/component/StringInput.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/component/StringInput.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 the original author or authors. + * Copyright 2022-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -94,6 +94,7 @@ protected boolean read(BindingReader bindingReader, KeyMap keyMap, Strin } String input; switch (operation) { + case OPERATION_UNICODE: case OPERATION_CHAR: String lastBinding = bindingReader.getLastBinding(); input = context.getInput(); diff --git a/spring-shell-core/src/main/java/org/springframework/shell/component/support/AbstractComponent.java b/spring-shell-core/src/main/java/org/springframework/shell/component/support/AbstractComponent.java index 2af895f24..058045882 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/component/support/AbstractComponent.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/component/support/AbstractComponent.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 the original author or authors. + * Copyright 2022-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,6 +60,7 @@ public abstract class AbstractComponent> implement public final static String OPERATION_EXIT = "EXIT"; public final static String OPERATION_BACKSPACE = "BACKSPACE"; public final static String OPERATION_CHAR = "CHAR"; + public final static String OPERATION_UNICODE = "UNICODE"; public final static String OPERATION_SELECT = "SELECT"; public final static String OPERATION_DOWN = "DOWN"; public final static String OPERATION_UP = "UP"; diff --git a/spring-shell-core/src/main/java/org/springframework/shell/component/support/AbstractTextComponent.java b/spring-shell-core/src/main/java/org/springframework/shell/component/support/AbstractTextComponent.java index 8d214bd11..6dc26589c 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/component/support/AbstractTextComponent.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/component/support/AbstractTextComponent.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 the original author or authors. + * Copyright 2022-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,6 +62,7 @@ protected void bindKeyMap(KeyMap keyMap) { for (char i = 32; i < KeyMap.KEYMAP_LENGTH - 1; i++) { keyMap.bind(OPERATION_CHAR, Character.toString(i)); } + keyMap.setUnicode(OPERATION_UNICODE); } @Override diff --git a/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/StatusBarView.java b/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/StatusBarView.java index 85c73dce1..a47be39cc 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/StatusBarView.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/component/view/control/StatusBarView.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,14 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.ListIterator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.lang.Nullable; import org.springframework.shell.component.message.ShellMessageBuilder; import org.springframework.shell.component.view.event.MouseEvent; import org.springframework.shell.component.view.event.MouseHandler; @@ -30,17 +32,24 @@ import org.springframework.shell.component.view.screen.Screen.Writer; import org.springframework.shell.geom.Rectangle; import org.springframework.shell.style.StyleSettings; +import org.springframework.util.StringUtils; /** * {@link StatusBarView} shows {@link StatusItem items} horizontally and is * typically used in layouts which builds complete terminal UI's. * + * {@link StatusItem item} {@code primary} denotes if item is drawn to left + * or right, {@code priority} on which order items are drawn until bar runs + * out of space. Default {@code primary} is {@code true} and {@code priority} + * is {@code 0}. + * * @author Janne Valkealahti */ public class StatusBarView extends BoxView { private final Logger log = LoggerFactory.getLogger(StatusBarView.class); private final List items = new ArrayList<>(); + private String itemSeparator = " | "; public StatusBarView() { this(new StatusItem[0]); @@ -59,18 +68,64 @@ protected String getBackgroundStyle() { return StyleSettings.TAG_STATUSBAR_BACKGROUND; } + /** + * Gets the item separator. + * + * @return a separator + */ + @Nullable + public String getItemSeparator() { + return itemSeparator; + } + + /** + * Sets the item separator. Separator can be {@code null} or empty which + * essentially disables it. + * + * @param itemSeparator the item separator + */ + public void setItemSeparator(@Nullable String itemSeparator) { + this.itemSeparator = itemSeparator; + } + @Override protected void drawInternal(Screen screen) { Rectangle rect = getInnerRect(); log.debug("Drawing status bar to {}", rect); Writer writer = screen.writerBuilder().build(); - int x = rect.x(); + + int primaryX = rect.x(); + int nonprimaryX = rect.x() + rect.width(); + boolean primaryWritten = false; + boolean nonprimaryWritten = false; + ListIterator iter = items.listIterator(); while (iter.hasNext()) { StatusItem item = iter.next(); - String text = String.format(" %s%s", item.getTitle(), iter.hasNext() ? " |" : ""); - writer.text(text, x, rect.y()); - x += text.length(); + String text = item.getTitle(); + if (text == null) { + continue; + } + String sep = getItemSeparator(); + if (nonprimaryX - primaryX < (text.length() + (sep != null ? sep.length() : 0))) { + break; + } + if (item.primary) { + if (primaryWritten && StringUtils.hasText(sep)) { + text = sep + text; + } + writer.text(text, primaryX, rect.y()); + primaryX += text.length(); + primaryWritten = true; + } + else { + if (nonprimaryWritten && StringUtils.hasText(sep)) { + text = text + sep; + } + writer.text(text, nonprimaryX - text.length(), rect.y()); + nonprimaryX -= text.length(); + nonprimaryWritten = true; + } } super.drawInternal(screen); } @@ -121,6 +176,19 @@ private StatusItem itemAt(int x, int y) { public void setItems(List items) { this.items.clear(); this.items.addAll(items); + Collections.sort(this.items, (o1, o2) -> { + int ret = o1.priority - o2.priority; + if (ret == 0) { + if (o1.primary && !o2.primary) { + ret = -1; + } + else if (!o1.primary && o2.primary) { + ret = 1; + } + } + return ret; + }); + // this.items.sort(null); registerHotKeys(); } @@ -152,6 +220,8 @@ public static class StatusItem { private String title; private Runnable action; private Integer hotKey; + private boolean primary = true; + private int priority = 0; public StatusItem(String title) { this(title, null); @@ -167,6 +237,14 @@ public StatusItem(String title, Runnable action, Integer hotKey) { this.hotKey = hotKey; } + public StatusItem(String title, Runnable action, Integer hotKey, boolean primary, int priority) { + this.title = title; + this.action = action; + this.hotKey = hotKey; + this.primary = primary; + this.priority = priority; + } + public static StatusItem of(String title) { return new StatusItem(title); } @@ -179,10 +257,18 @@ public static StatusItem of(String title, Runnable action, Integer hotKey) { return new StatusItem(title, action, hotKey); } + public static StatusItem of(String title, Runnable action, Integer hotKey, boolean primary, int priority) { + return new StatusItem(title, action, hotKey, primary, priority); + } + public String getTitle() { return title; } + public void setTitle(String title) { + this.title = title; + } + public Runnable getAction() { return action; } @@ -201,6 +287,22 @@ public StatusItem setHotKey(Integer hotKey) { return this; } + public int getPriority() { + return priority; + } + + public void setPriority(int priority) { + this.priority = priority; + } + + public boolean isPrimary() { + return primary; + } + + public void setPrimary(boolean primary) { + this.primary = primary; + } + } /** diff --git a/spring-shell-core/src/main/java/org/springframework/shell/component/view/screen/DefaultScreen.java b/spring-shell-core/src/main/java/org/springframework/shell/component/view/screen/DefaultScreen.java index 483dd1416..4c5402783 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/component/view/screen/DefaultScreen.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/component/view/screen/DefaultScreen.java @@ -30,6 +30,8 @@ import org.springframework.shell.geom.Position; import org.springframework.shell.geom.Rectangle; import org.springframework.shell.geom.VerticalAlign; +import org.springframework.shell.style.ThemeResolver; +import org.springframework.shell.style.ThemeResolver.ResolvedValues; import org.springframework.util.Assert; /** @@ -312,6 +314,29 @@ public void text(String text, int x, int y) { } } + @Override + public void text(AttributedString text, int x, int y) { + Layer layer = getLayer(index); + for (int i = 0; i < text.length() && i < columns; i++) { + DefaultScreenItem item = layer.getScreenItem(x + i, y); + if (item != null) { + char c = text.charAt(i); + AttributedStyle as = text.styleAt(i); + ResolvedValues rv = ThemeResolver.resolveValues(as); + item.content = Character.toString(c); + if (rv.foreground() > -1) { + item.foreground = rv.foreground(); + } + if (rv.style() > -1) { + item.style = rv.style(); + } + if (rv.background() > -1) { + item.background = rv.background(); + } + } + } + } + @Override public void border(int x, int y, int width, int height) { log.trace("PrintBorder rows={}, columns={}, x={}, y={}, width={}, height={}", rows, columns, x, y, width, diff --git a/spring-shell-core/src/main/java/org/springframework/shell/component/view/screen/Screen.java b/spring-shell-core/src/main/java/org/springframework/shell/component/view/screen/Screen.java index 91f8db553..2598a49b5 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/component/view/screen/Screen.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/component/view/screen/Screen.java @@ -15,6 +15,8 @@ */ package org.springframework.shell.component.view.screen; +import org.jline.utils.AttributedString; + import org.springframework.lang.Nullable; import org.springframework.shell.geom.HorizontalAlign; import org.springframework.shell.geom.Position; @@ -107,6 +109,16 @@ interface Writer { */ void text(String text, int x, int y); + /** + * Write an attributed text horizontally starting from a position defined by + * {@code x} and {@code y} within a bounds of a {@link Screen}. + * + * @param text the text to write + * @param x the x position + * @param y the y position + */ + void text(AttributedString text, int x, int y); + /** * Write a border with a given rectangle coordinates. * diff --git a/spring-shell-core/src/main/java/org/springframework/shell/jline/FileInputProvider.java b/spring-shell-core/src/main/java/org/springframework/shell/jline/FileInputProvider.java index d818137ea..5d9820463 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/jline/FileInputProvider.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/jline/FileInputProvider.java @@ -66,7 +66,13 @@ public Input readInput() { if (line == null) { return null; } else { - ParsedLine parsedLine = parser.parse(sb.toString(), sb.toString().length()); + // gh-277: if it's a commented line then skip as it is equal to NO_INPUT + ParsedLine parsedLine; + if (isCommentedLine(line)) { + parsedLine = parser.parse("", -1, Parser.ParseContext.COMPLETE); + } else { + parsedLine = parser.parse(sb.toString(), sb.toString().length()); + } return new ParsedLineInput(parsedLine); } } @@ -75,4 +81,8 @@ public Input readInput() { public void close() throws IOException { reader.close(); } + + private boolean isCommentedLine(String line) { + return line.matches("\\s*//.*"); + } } diff --git a/spring-shell-core/src/main/java/org/springframework/shell/style/ThemeResolver.java b/spring-shell-core/src/main/java/org/springframework/shell/style/ThemeResolver.java index 76c1c1f9e..b0591e73a 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/style/ThemeResolver.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/style/ThemeResolver.java @@ -70,7 +70,7 @@ public record ResolvedValues(int style, int foreground, int background){} * @param attributedStyle the attibuted style * @return resolved values */ - public ResolvedValues resolveValues(AttributedStyle attributedStyle) { + public static ResolvedValues resolveValues(AttributedStyle attributedStyle) { long style = attributedStyle.getStyle(); long s = style & ~(F_FOREGROUND | F_BACKGROUND); s = (s & 0x00007FFF); diff --git a/spring-shell-core/src/test/java/org/springframework/shell/command/parser/AbstractParsingTests.java b/spring-shell-core/src/test/java/org/springframework/shell/command/parser/AbstractParsingTests.java index c54e0b52b..fe5b2a580 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/command/parser/AbstractParsingTests.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/command/parser/AbstractParsingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -416,4 +416,11 @@ ParseResult parse(ParserConfig config, String... arguments) { Parser parser = new Parser.DefaultParser(commandModel, lexer(config), ast, config); return parser.parse(Arrays.asList(arguments)); } + + record RegAndArgs(CommandRegistration reg, String... args) { + static RegAndArgs of(CommandRegistration reg, String... args) { + return new RegAndArgs(reg, args); + } + } + } diff --git a/spring-shell-core/src/test/java/org/springframework/shell/command/parser/LexerTests.java b/spring-shell-core/src/test/java/org/springframework/shell/command/parser/LexerTests.java index 8cc82302a..87f633d35 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/command/parser/LexerTests.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/command/parser/LexerTests.java @@ -274,6 +274,38 @@ void notDefinedOptionShouldBeOptionAfterDefinedOption() { }); } + @Test + void notDefinedOptionShouldBeOptionAfterDefinedOptionHavingArgument() { + register(ROOT3); + List tokens = tokenize("root3", "--arg1", "value1", "--arg2"); + + assertThat(tokens).satisfiesExactly( + token -> { + ParserAssertions.assertThat(token) + .isType(TokenType.COMMAND) + .hasPosition(0) + .hasValue("root3"); + }, + token -> { + ParserAssertions.assertThat(token) + .isType(TokenType.OPTION) + .hasPosition(1) + .hasValue("--arg1"); + }, + token -> { + ParserAssertions.assertThat(token) + .isType(TokenType.ARGUMENT) + .hasPosition(2) + .hasValue("value1"); + }, + token -> { + ParserAssertions.assertThat(token) + .isType(TokenType.OPTION) + .hasPosition(3) + .hasValue("--arg2"); + }); + } + @Test void optionsWithoutValuesFromRoot() { register(ROOT5); diff --git a/spring-shell-core/src/test/java/org/springframework/shell/command/parser/ParserTests.java b/spring-shell-core/src/test/java/org/springframework/shell/command/parser/ParserTests.java index 511ba33cc..5ec3a1759 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/command/parser/ParserTests.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/command/parser/ParserTests.java @@ -15,8 +15,12 @@ */ package org.springframework.shell.command.parser; +import java.util.stream.Stream; + import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.springframework.shell.command.parser.Parser.ParseResult; import org.springframework.shell.command.parser.ParserConfig.Feature; @@ -161,6 +165,31 @@ void shouldHaveErrorResult2() { ); } + static Stream shouldHaveErrorForUnrecognisedOption() { + return Stream.of( + RegAndArgs.of(ROOT1, "root1", "--arg1"), + RegAndArgs.of(ROOT3, "root3", "--arg2", "--arg1"), + RegAndArgs.of(ROOT3, "root3", "--arg1", "--arg2"), + RegAndArgs.of(ROOT3, "root3", "--arg2", "fake1", "--arg1"), + RegAndArgs.of(ROOT3, "root3", "--arg1", "--arg2", "fake1"), + RegAndArgs.of(ROOT3, "root3", "--arg2", "fake1", "--arg1", "fake2"), + RegAndArgs.of(ROOT3, "root3", "--arg1", "fake2", "--arg2", "fake1") + ); + } + + @ParameterizedTest + @MethodSource + void shouldHaveErrorForUnrecognisedOption(RegAndArgs regAndArgs) { + register(regAndArgs.reg()); + ParseResult result = parse(regAndArgs.args()); + assertThat(result.messageResults()).satisfiesExactlyInAnyOrder( + message -> { + ParserAssertions.assertThat(message.parserMessage()).hasCode(2001).hasType(ParserMessage.Type.ERROR); + } + ); + + } + @Test void lexerMessageShouldGetPropagated() { ParseResult parse = parse("--"); diff --git a/spring-shell-core/src/test/java/org/springframework/shell/component/StringInputTests.java b/spring-shell-core/src/test/java/org/springframework/shell/component/StringInputTests.java index 1ff6f29cf..a0cf26e02 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/component/StringInputTests.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/component/StringInputTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 the original author or authors. + * Copyright 2022-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -166,6 +166,29 @@ public void testResultUserInput() throws InterruptedException { assertThat(run1Context.getResultValue()).isEqualTo("test"); } + @Test + public void testResultUserInputUnicode() throws InterruptedException { + ComponentContext empty = ComponentContext.empty(); + StringInput component1 = new StringInput(getTerminal(), "component1", "component1ResultValue"); + component1.setResourceLoader(new DefaultResourceLoader()); + component1.setTemplateExecutor(getTemplateExecutor()); + + service.execute(() -> { + StringInputContext run1Context = component1.run(empty); + result1.set(run1Context); + latch1.countDown(); + }); + + TestBuffer testBuffer = new TestBuffer().append("😂").cr(); + write(testBuffer.getBytes()); + + latch1.await(2, TimeUnit.SECONDS); + StringInputContext run1Context = result1.get(); + + assertThat(run1Context).isNotNull(); + assertThat(run1Context.getResultValue()).isEqualTo("😂"); + } + @Test public void testPassingViaContext() throws InterruptedException { ComponentContext empty = ComponentContext.empty(); diff --git a/spring-shell-core/src/test/java/org/springframework/shell/component/view/control/StatusBarViewTests.java b/spring-shell-core/src/test/java/org/springframework/shell/component/view/control/StatusBarViewTests.java index 1f7f4e5b9..b517aec61 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/component/view/control/StatusBarViewTests.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/component/view/control/StatusBarViewTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,8 @@ import java.time.Duration; import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -55,6 +57,10 @@ void constructView() { view = new StatusBarView(Arrays.asList(StatusItem.of("item1"))); assertThat(view.getItems()).hasSize(1); + + view = new StatusBarView(Arrays.asList(StatusItem.of("item1", null, null, false, 1))); + assertThat(view.getItems()).hasSize(1); + } @Test @@ -96,6 +102,66 @@ void itemPosition() { } + @Nested + class Sorting { + + StatusItem p_0_1; + StatusItem p_0_2; + StatusItem p_0_3; + StatusItem p_1_1; + StatusItem p_2_1; + StatusItem n_0_1; + StatusItem n_0_2; + StatusItem n_0_3; + StatusBarView view; + + @BeforeEach + void setup() { + p_0_1 = new StatusItem("p_0_1"); + p_0_2 = new StatusItem("p_0_2"); + p_0_3 = new StatusItem("p_0_3"); + + p_1_1 = new StatusItem("p_1_1"); + p_1_1.setPriority(1); + p_2_1 = new StatusItem("p_2_1"); + p_2_1.setPriority(2); + + n_0_1 = new StatusItem("n_0_1"); + n_0_1.setPrimary(false); + n_0_2 = new StatusItem("n_0_2"); + n_0_2.setPrimary(false); + n_0_3 = new StatusItem("n_0_3"); + n_0_3.setPrimary(false); + } + + @Test + void defaultsOrderNotChanged() { + view = new StatusBarView(Arrays.asList(p_0_1, p_0_2, p_0_3)); + assertThat(extractTitles()).containsExactly("p_0_1", "p_0_2", "p_0_3"); + } + + @Test + void primaryBeforeNonprimary() { + view = new StatusBarView(Arrays.asList(p_0_1, n_0_1)); + assertThat(extractTitles()).containsExactly("p_0_1", "n_0_1"); + view = new StatusBarView(Arrays.asList(n_0_1, p_0_1)); + assertThat(extractTitles()).containsExactly("p_0_1", "n_0_1"); + } + + @Test + void priorityTakesOrder() { + view = new StatusBarView(Arrays.asList(p_0_1, p_1_1, p_2_1)); + assertThat(extractTitles()).containsExactly("p_0_1", "p_1_1", "p_2_1"); + view = new StatusBarView(Arrays.asList(p_2_1, p_0_1, p_1_1)); + assertThat(extractTitles()).containsExactly("p_0_1", "p_1_1", "p_2_1"); + } + + private List extractTitles() { + return view.getItems().stream().map(StatusItem::getTitle).collect(Collectors.toList()); + } + + } + @Nested class Styling { @@ -107,6 +173,108 @@ void hasBorder() { view.draw(screen24x80); assertThat(forScreen(screen24x80)).hasBorder(0, 0, 80, 24); } + + @Test + void primaryItems() { + StatusItem item1 = new StatusItem("item1"); + StatusItem item2 = new StatusItem("item2"); + StatusBarView view = new StatusBarView(Arrays.asList(item1, item2)); + view.setItemSeparator(null); + view.setRect(0, 0, 80, 1); + view.draw(screen1x80); + assertThat(forScreen(screen1x80)).hasHorizontalText("item1", 0, 0, 5); + assertThat(forScreen(screen1x80)).hasHorizontalText("item2", 5, 0, 5); + } + + @Test + void nonprimaryItems() { + StatusItem item1 = new StatusItem("item1"); + StatusItem item2 = new StatusItem("item2"); + item1.setPrimary(false); + item2.setPrimary(false); + StatusBarView view = new StatusBarView(Arrays.asList(item1, item2)); + view.setItemSeparator(null); + view.setRect(0, 0, 80, 1); + view.draw(screen1x80); + assertThat(forScreen(screen1x80)).hasHorizontalText("item1", 75, 0, 5); + assertThat(forScreen(screen1x80)).hasHorizontalText("item2", 70, 0, 5); + } + + @Test + void primaryAndNonprimaryItems() { + StatusItem item1 = new StatusItem("item1"); + StatusItem item2 = new StatusItem("item2"); + item2.setPrimary(false); + StatusBarView view = new StatusBarView(Arrays.asList(item1, item2)); + view.setItemSeparator(null); + view.setRect(0, 0, 80, 1); + view.draw(screen1x80); + assertThat(forScreen(screen1x80)).hasHorizontalText("item1", 0, 0, 5); + assertThat(forScreen(screen1x80)).hasHorizontalText("item2", 75, 0, 5); + } + + @Test + void canChangeText() { + StatusItem item1 = new StatusItem("item1"); + StatusBarView view = new StatusBarView(Arrays.asList(item1)); + view.setItemSeparator(null); + view.setRect(0, 0, 80, 1); + view.draw(screen1x80); + assertThat(forScreen(screen1x80)).hasHorizontalText("item1", 0, 0, 5); + item1.setTitle("fake"); + view.draw(screen1x80); + assertThat(forScreen(screen1x80)).hasHorizontalText("fake", 0, 0, 4); + } + + @Test + void itemSeparator() { + StatusItem item1 = new StatusItem("item1"); + StatusItem item2 = new StatusItem("item2"); + StatusItem item3 = new StatusItem("item3"); + item3.setPrimary(false); + StatusItem item4 = new StatusItem("item4"); + item4.setPrimary(false); + StatusBarView view = new StatusBarView(Arrays.asList(item1, item2, item3, item4)); + view.setRect(0, 0, 80, 1); + view.draw(screen1x80); + assertThat(forScreen(screen1x80)).hasHorizontalText("item1 | ", 0, 0, 8); + assertThat(forScreen(screen1x80)).hasHorizontalText("item2", 8, 0, 5); + assertThat(forScreen(screen1x80)).hasHorizontalText(" | item3", 72, 0, 8); + assertThat(forScreen(screen1x80)).hasHorizontalText("item4", 67, 0, 5); + view.setItemSeparator(null); + view.draw(screen1x80); + assertThat(forScreen(screen1x80)).hasHorizontalText("item1", 0, 0, 5); + assertThat(forScreen(screen1x80)).hasHorizontalText("item2", 5, 0, 5); + assertThat(forScreen(screen1x80)).hasHorizontalText("item3", 75, 0, 5); + assertThat(forScreen(screen1x80)).hasHorizontalText("item4", 70, 0, 5); + } + + @Test + void skipItemsWhenOverflow() { + StatusItem item1 = new StatusItem("item11111111111111111111111111"); + StatusItem item2 = new StatusItem("item22222222222222222222222222"); + StatusItem item3 = new StatusItem("item33333333333333333333333333"); + StatusBarView view = new StatusBarView(Arrays.asList(item1, item2, item3)); + view.setItemSeparator(null); + view.setRect(0, 0, 80, 1); + view.draw(screen1x80); + assertThat(forScreen(screen1x80)).hasHorizontalText("item11111111111111111111111111", 0, 0, 30); + assertThat(forScreen(screen1x80)).hasHorizontalText("item22222222222222222222222222", 30, 0, 30); + assertThat(forScreen(screen1x80)).hasHorizontalText("", 60, 0, 20); + } + + @Test + void nullTitleDontDraw() { + StatusItem item1 = new StatusItem("item1"); + StatusItem item2 = new StatusItem(null); + StatusBarView view = new StatusBarView(Arrays.asList(item1, item2)); + view.setItemSeparator(null); + view.setRect(0, 0, 80, 1); + view.draw(screen1x80); + assertThat(forScreen(screen1x80)).hasHorizontalText("item1", 0, 0, 5); + assertThat(forScreen(screen1x80)).hasHorizontalText("", 5, 0, 5); + } + } @Nested diff --git a/spring-shell-core/src/test/java/org/springframework/shell/component/view/screen/ScreenTests.java b/spring-shell-core/src/test/java/org/springframework/shell/component/view/screen/ScreenTests.java index cc0a4e6ca..63fe58e89 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/component/view/screen/ScreenTests.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/component/view/screen/ScreenTests.java @@ -15,6 +15,7 @@ */ package org.springframework.shell.component.view.screen; +import org.jline.utils.AttributedString; import org.junit.jupiter.api.Test; import org.springframework.shell.component.view.control.AbstractViewTests; @@ -56,6 +57,13 @@ void printsText() { assertThat(forScreen(screen24x80)).hasHorizontalText("text", 0, 0, 4); } + @Test + void printsAttributedText() { + AttributedString text = new AttributedString("text"); + screen24x80.writerBuilder().build().text(text, 0, 0); + assertThat(forScreen(screen24x80)).hasHorizontalText("text", 0, 0, 4); + } + @Test void printsTextWithForegroundColor() { Writer writer = screen24x80.writerBuilder().color(Color.RED).build(); diff --git a/spring-shell-core/src/test/java/org/springframework/shell/jline/FileInputProviderTests.java b/spring-shell-core/src/test/java/org/springframework/shell/jline/FileInputProviderTests.java new file mode 100644 index 000000000..2c32e8412 --- /dev/null +++ b/spring-shell-core/src/test/java/org/springframework/shell/jline/FileInputProviderTests.java @@ -0,0 +1,83 @@ +package org.springframework.shell.jline; + +import org.jline.reader.EOFError; +import org.jline.reader.impl.DefaultParser; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.Reader; +import java.io.StringReader; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +class FileInputProviderTests { + private final ExtendedDefaultParser springParser = new ExtendedDefaultParser(); + private final DefaultParser jlineParser = new DefaultParser(); + private FileInputProvider fileInputProvider; + + static Stream regularLinesUnclosedQuotes() { + return Stream.of( + Arguments.of("Regular line with unclosed 'quote"), + Arguments.of("Regular line with unclosed \"quote") + ); + } + + static Stream commentsUnclosedQuotes() { + return Stream.of( + Arguments.of("//Commented line with unclosed 'quote"), + Arguments.of("//Commented line with unclosed \"quote") + ); + } + + @ParameterizedTest + @MethodSource("regularLinesUnclosedQuotes") + void shouldThrowOnUnclosedQuoteDefaultParser(String line) { + jlineParser.setEofOnUnclosedQuote(true); + Reader reader = new StringReader(line); + fileInputProvider = new FileInputProvider(reader, jlineParser); + Exception exception = assertThrows(EOFError.class, () -> { + fileInputProvider.readInput(); + }); + String expectedExceptionMessage = "Missing closing quote"; + String actualExceptionMessage = exception.getMessage(); + assertTrue(actualExceptionMessage.contains(expectedExceptionMessage)); + } + + @ParameterizedTest + @MethodSource("regularLinesUnclosedQuotes") + void shouldThrowOnUnclosedQuoteExtendedParser(String line) { + springParser.setEofOnUnclosedQuote(true); + Reader reader = new StringReader(line); + fileInputProvider = new FileInputProvider(reader, springParser); + Exception exception = assertThrows(EOFError.class, () -> { + fileInputProvider.readInput(); + }); + String expectedExceptionMessage = "Missing closing quote"; + String actualExceptionMessage = exception.getMessage(); + assertTrue(actualExceptionMessage.contains(expectedExceptionMessage)); + } + + @ParameterizedTest + @MethodSource("commentsUnclosedQuotes") + void shoulNotThrowOnUnclosedQuoteDefaultParser(String line) { + jlineParser.setEofOnUnclosedQuote(true); + Reader reader = new StringReader(line); + fileInputProvider = new FileInputProvider(reader, jlineParser); + assertDoesNotThrow(() -> { + fileInputProvider.readInput(); + }); + } + + @ParameterizedTest + @MethodSource("commentsUnclosedQuotes") + void shouldNotThrowOnUnclosedQuoteExtendedParser(String line) { + springParser.setEofOnUnclosedQuote(true); + Reader reader = new StringReader(line); + fileInputProvider = new FileInputProvider(reader, springParser); + assertDoesNotThrow(() -> { + fileInputProvider.readInput(); + }); + } +} \ No newline at end of file diff --git a/spring-shell-docs/antora-playbook.yml b/spring-shell-docs/antora-playbook.yml index 60fc812fe..4d36386a5 100644 --- a/spring-shell-docs/antora-playbook.yml +++ b/spring-shell-docs/antora-playbook.yml @@ -3,12 +3,7 @@ # The purpose of this Antora playbook is to build the docs in the current branch. antora: extensions: - - '@springio/antora-extensions/partial-build-extension' - - require: '@springio/antora-extensions/latest-version-extension' - - require: '@springio/antora-extensions/inject-collector-cache-config-extension' - - '@antora/collector-extension' - - '@antora/atlas-extension' - - require: '@springio/antora-extensions/root-component-extension' + - require: '@springio/antora-extensions' root_component_name: 'shell' - require: '@springio/antora-extensions/asciinema-extension' site: @@ -30,6 +25,7 @@ asciidoc: extensions: - '@asciidoctor/tabs' - '@springio/asciidoctor-extensions' + - '@springio/asciidoctor-extensions/javadoc-extension' sourcemap: true urls: latest_version_segment: '' diff --git a/spring-shell-docs/antora.yml b/spring-shell-docs/antora.yml index 9b34c10b0..100c1d96b 100644 --- a/spring-shell-docs/antora.yml +++ b/spring-shell-docs/antora.yml @@ -5,11 +5,16 @@ nav: - modules/ROOT/nav.adoc ext: collector: - run: - command: gradlew -q "-Dorg.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError" :spring-shell-docs:generateAntoraYml - local: true - scan: - dir: ./build/generated-antora-resources + - run: + command: gradlew -q "-Dorg.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError" :spring-shell-docs:generateAntoraYml + local: true + scan: + dir: ./build/generated-antora-resources + - run: + command: gradlew -q "-Dorg.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError" :spring-shell-docs:aggregatedJavadoc + local: true + scan: + dir: ./build/generated-antora-javadocs asciidoc: attributes: diff --git a/spring-shell-docs/modules/ROOT/nav.adoc b/spring-shell-docs/modules/ROOT/nav.adoc index a927b76e0..154678c79 100644 --- a/spring-shell-docs/modules/ROOT/nav.adoc +++ b/spring-shell-docs/modules/ROOT/nav.adoc @@ -96,4 +96,5 @@ ** xref:appendices/debugging/index.adoc[] ** xref:appendices/tui/index.adoc[] *** xref:appendices/tui/viewdev.adoc[] -*** xref:appendices/tui/catalog.adoc[] \ No newline at end of file +*** xref:appendices/tui/catalog.adoc[] +* xref:attachment$api/java/index.html[Javadoc,role=link-external,window=_blank] diff --git a/spring-shell-docs/modules/ROOT/pages/appendices/techintro/index.adoc b/spring-shell-docs/modules/ROOT/pages/appendices/techintro/index.adoc index 4af2f9d7a..5f306a12d 100644 --- a/spring-shell-docs/modules/ROOT/pages/appendices/techintro/index.adoc +++ b/spring-shell-docs/modules/ROOT/pages/appendices/techintro/index.adoc @@ -1,6 +1,6 @@ [appendix] [#appendix-tech-intro] -= Techical Introduction += Technical Introduction :page-section-summary-toc: 1 This appendix contains information for developers and others who would like to know more about how Spring Shell diff --git a/spring-shell-docs/modules/ROOT/pages/appendices/tui/catalog.adoc b/spring-shell-docs/modules/ROOT/pages/appendices/tui/catalog.adoc index 8c0bf6205..a474c32d2 100644 --- a/spring-shell-docs/modules/ROOT/pages/appendices/tui/catalog.adoc +++ b/spring-shell-docs/modules/ROOT/pages/appendices/tui/catalog.adoc @@ -4,10 +4,10 @@ ifndef::snippets[:snippets: ../../../../../src/test/java/org/springframework/shell/docs] -Catalog application is showing various ways how Terminal UI Framework can be used. -In this section we discuss how this application works. It can be considered to be +Catalog application shows various ways how the Terminal UI Framework can be used. +In this section, we discuss how this application works. It can be considered as a reference application as it's using most of the features available and tries -to follow best practices. +to follow the best practices. [[create-scenario]] == Create Scenario diff --git a/spring-shell-docs/modules/ROOT/pages/appendices/tui/index.adoc b/spring-shell-docs/modules/ROOT/pages/appendices/tui/index.adoc index 2c7b353e1..ac5088f93 100644 --- a/spring-shell-docs/modules/ROOT/pages/appendices/tui/index.adoc +++ b/spring-shell-docs/modules/ROOT/pages/appendices/tui/index.adoc @@ -5,9 +5,9 @@ ifndef::snippets[:snippets: ../../test/java/org/springframework/shell/docs] -This is a technical introduction to _UI Framework_. +This is a technical introduction to the _UI Framework_. -_UI Framework_ is a toolkit to build rich console apps. +The _UI Framework_ is a toolkit to build rich console apps. diff --git a/spring-shell-docs/modules/ROOT/pages/appendices/tui/viewdev.adoc b/spring-shell-docs/modules/ROOT/pages/appendices/tui/viewdev.adoc index d47b2007a..18f17452f 100644 --- a/spring-shell-docs/modules/ROOT/pages/appendices/tui/viewdev.adoc +++ b/spring-shell-docs/modules/ROOT/pages/appendices/tui/viewdev.adoc @@ -2,7 +2,7 @@ = View Development :page-section-summary-toc: 1 -While a _view_ just need to implement `View` it's usually convenient to just +While a _view_ just needs to implement `View`, it's usually convenient to just use `BoxView` as a parent. [[register-bindings]] diff --git a/spring-shell-docs/modules/ROOT/pages/basics/reading.adoc b/spring-shell-docs/modules/ROOT/pages/basics/reading.adoc index fbc3f514a..457f9dbae 100644 --- a/spring-shell-docs/modules/ROOT/pages/basics/reading.adoc +++ b/spring-shell-docs/modules/ROOT/pages/basics/reading.adoc @@ -5,13 +5,13 @@ Throughout this documentation, we make references to configuring something by using annotations or programmatic examples. -NOTE: There are two annotation models, xref:commands/registration/annotation.adoc[annotations] -referred to new annotation model, xref:commands/registration/legacyannotation.adoc[legacy annotations] -referred to old legacy annotation model. +NOTE: There are two annotation models: the xref:commands/registration/annotation.adoc[annotations] model +referred to as the new annotation model, and the xref:commands/registration/legacyannotation.adoc[legacy annotations] +model referred to as the old legacy annotation model. -Old legacy annotation model mostly relates to use of `@ShellMethod` and `@ShellOption` and -new annotation model relates to use of `@Command`. +The old legacy annotation model mostly relates to the use of `@ShellMethod` and `@ShellOption` and +the new annotation model relates to the use of `@Command`. The programmatic model is how things are actually registered, even if you use annotations. -NOTE: The documentation structure is getting revised to clarify how to provide configurations in separate ways. Thank you for understanding while that work is still in progress. +NOTE: The documentation structure is getting revised to clarify how to provide configurations in separate ways. diff --git a/spring-shell-docs/modules/ROOT/pages/building.adoc b/spring-shell-docs/modules/ROOT/pages/building.adoc index 49b131d02..8b407e397 100644 --- a/spring-shell-docs/modules/ROOT/pages/building.adoc +++ b/spring-shell-docs/modules/ROOT/pages/building.adoc @@ -3,6 +3,93 @@ This section covers how to build a Spring Shell application. +== Starters + +. Spring Shell Starters +[] +|=== +|Name |Description + +|spring-shell-starter| Basic Spring Shell modules +|spring-shell-starter-jansi| With JLine jansi provider +|spring-shell-starter-jni| With JLine jni provider +|spring-shell-starter-jna| With JLine jna provider +|spring-shell-starter-ffm| With JLine ffm provider (requires JDK22+) +|spring-shell-starter-test| Spring Shell testing support +|=== + +== Terminal Providers + +Interacting with an underlying terminal where your program is running has +traditionally been relatively complex process while it may look like +there's not that much happening as it's all just text. + +Remember all those old manual typewriters or matrix printers? +A character is printed then a cursor needs to be moved +if printing in a different position. In a nutshell that's how current +terminal emulators work. + +To access and understand existing terminal emulators environment better, +JLine can use native code via its own shared libraries. JLine detects +which providers are present and then makes a choice which one to use. +Traditionally there's been 3 providers, `jansi`, `jni` and `jna` which +should all provide same functionalities. + +Our starters can be used to specifically pick some of these JLine +providers. + +== FFM + +With `JDK22` a _Foreign Function and Memory API_ came out from a preview +which is supposed to be a replacement for `JNI` providing much better +and safer native API. + +Starting from `3.4.x` we've added a support to compile Spring Shell +application with `JLine` `ffm` terminal provider. This obviously mean +that application needs to be run with `JDK22+`. There is a new JDK +intermediate release every 6 months and long term support(LTS) release +every 2 years. Until there's an existing LTS release Spring Shell can +align with Spring Framework we will use latest JDK release. Obviously +this means that you may need to upgrade your JDK in an inconvenient +time if you choose to use `ffm`. We're also bound to JDK version +`JLine` itself uses to compile its `ffm` parts. + +FFM itself will cause jvm to print warnings when some part of it are +used. These warnings are obviously annoying with terminal applications +as it may interfere and cause a little bit of a mess. In future JDK +versions these warnings will also be added for an older JNI modules and +at some point these warnings will be changed into hard errors. User will +be required to enable these native "unsafe" parts manually. + +JVM option for this in a command line is: + +[source, bash] +---- +--enable-native-access=ALL-UNNAMED +---- + +If you have a jar file you can have this setting in its `META-INF/MANIFEST.MF`. + +[source] +---- +Enable-Native-Access: ALL-UNNAMED +---- + +Which can be added during a build i.e. if using gradle: + +[source, groovy] +---- +tasks.named("bootJar") { + manifest { + attributes 'Enable-Native-Access': 'ALL-UNNAMED' + } +} +---- + +IMPORTANT: What comes for enabling native parts in a JDK, JLine has been +proactive and already has a check for this and will throw error if +native access is not enabled. + [[native]] == Native Support @@ -40,7 +127,7 @@ When gradle build is run with `./gradlew nativeCompile` you should get binary under `build/native/nativeCompile` directory. For `maven` use `spring-boot-starter-parent` as parent and you'll get `native` -profile which can be used to do a compilation. You need to configure metadata repository +profile which can be used to do a native compilation. You need to configure metadata repository: [source, xml, subs=attributes+] ---- diff --git a/spring-shell-docs/modules/ROOT/pages/commands/alias.adoc b/spring-shell-docs/modules/ROOT/pages/commands/alias.adoc index b90fa643e..47b179ff1 100644 --- a/spring-shell-docs/modules/ROOT/pages/commands/alias.adoc +++ b/spring-shell-docs/modules/ROOT/pages/commands/alias.adoc @@ -8,10 +8,9 @@ cases where you want to create a shorter version of a command or going through a complete command rename while keeping old one temporarily in place. -Format for _alias_ is slighly different than a _command_. When _command_ +The format of _alias_ is slightly different from a _command_. When _command_ is defined as an array it's concatenated together into a single command. -When _alias_ is defined as an array it's used to create a separate -aliases. +When _alias_ is defined as an array it's used to create separate aliases. Aliases with a plain `CommandRegistration` is simple and clear as you get exactly what you define as there's no "magic" in it. diff --git a/spring-shell-docs/modules/ROOT/pages/commands/builtin/clear.adoc b/spring-shell-docs/modules/ROOT/pages/commands/builtin/clear.adoc index 0d36f1bd5..d1d195500 100644 --- a/spring-shell-docs/modules/ROOT/pages/commands/builtin/clear.adoc +++ b/spring-shell-docs/modules/ROOT/pages/commands/builtin/clear.adoc @@ -2,5 +2,5 @@ = Clear :page-section-summary-toc: 1 -The `clear` command does what you would expect and clears the screen, resetting the prompt +The `clear` command clears the screen, resetting the prompt in the top left corner. diff --git a/spring-shell-docs/modules/ROOT/pages/commands/builtin/completion.adoc b/spring-shell-docs/modules/ROOT/pages/commands/builtin/completion.adoc index 1e69a1293..023c1004c 100644 --- a/spring-shell-docs/modules/ROOT/pages/commands/builtin/completion.adoc +++ b/spring-shell-docs/modules/ROOT/pages/commands/builtin/completion.adoc @@ -2,8 +2,8 @@ = Completion :page-section-summary-toc: 1 -The `completion` command set lets you create script files that can be used -with am OS shell implementations to provide completion. This is very useful when +The `completion` command lets you create script files that can be used +with an OS shell implementation to provide completion. This is very useful when working with non-interactive mode. -Currently, the only implementation is for bash, which works with `bash` sub-command. +Currently, the only implementation is for bash, which works with the `bash` sub-command. diff --git a/spring-shell-docs/modules/ROOT/pages/commands/builtin/help.adoc b/spring-shell-docs/modules/ROOT/pages/commands/builtin/help.adoc index 28e87e51b..682034b78 100644 --- a/spring-shell-docs/modules/ROOT/pages/commands/builtin/help.adoc +++ b/spring-shell-docs/modules/ROOT/pages/commands/builtin/help.adoc @@ -2,9 +2,7 @@ = Help Running a shell application often implies that the user is in a graphically limited -environment. Also, while we are nearly always connected in the era of mobile phones, -accessing a web browser or any other rich UI application (such as a PDF viewer) may not always -be possible. This is why it is important that the shell commands are correctly self-documented, and this is where the `help` +environment. This is why it is important that the shell commands are correctly self-documented, and this is where the `help` command comes in. Typing `help` + `ENTER` lists all the commands known to the shell (including xref:commands/availability.adoc[unavailable] commands) diff --git a/spring-shell-docs/modules/ROOT/pages/commands/exceptionhandling/annotation.adoc b/spring-shell-docs/modules/ROOT/pages/commands/exceptionhandling/annotation.adoc index 76d28871e..e3cef0069 100644 --- a/spring-shell-docs/modules/ROOT/pages/commands/exceptionhandling/annotation.adoc +++ b/spring-shell-docs/modules/ROOT/pages/commands/exceptionhandling/annotation.adoc @@ -6,13 +6,13 @@ ifndef::snippets[:snippets: ../../test/java/org/springframework/shell/docs] `@ShellComponent` classes can have `@ExceptionResolver` methods to handle exceptions from component methods. These are meant for annotated methods. -The exception may match against a top-level exception being propagated (e.g. a direct IOException -being thrown) or against a nested cause within a wrapper exception (e.g. an IOException wrapped -inside an IllegalStateException). This can match at arbitrary cause levels. +The exception may match against a top-level exception being propagated (e.g. a direct `IOException` +being thrown) or against a nested cause within a wrapper exception (e.g. an `IOException` wrapped +inside an `IllegalStateException`). This can match at arbitrary cause levels. For matching exception types, preferably declare the target exception as a method argument, as the preceding example(s) shows. When multiple exception methods match, a root exception match is -generally preferred to a cause exception match. More specifically, the ExceptionDepthComparator +generally preferred to a cause exception match. More specifically, the `ExceptionDepthComparator` is used to sort exceptions based on their depth from the thrown exception type. Alternatively, the annotation declaration may narrow the exception types to match, as the @@ -38,7 +38,7 @@ include::{snippets}/ErrorHandlingSnippets.java[tag=exception-resolver-with-exitc `@ExceptionResolver` with `void` return type is automatically handled as handled exception. You can then also define `@ExitCode` and use `Terminal` if you need to write something -into console. +into the console: [source, java, indent=0] ---- diff --git a/spring-shell-docs/modules/ROOT/pages/commands/exceptionhandling/index.adoc b/spring-shell-docs/modules/ROOT/pages/commands/exceptionhandling/index.adoc index 041ba897c..85dbc52c3 100644 --- a/spring-shell-docs/modules/ROOT/pages/commands/exceptionhandling/index.adoc +++ b/spring-shell-docs/modules/ROOT/pages/commands/exceptionhandling/index.adoc @@ -4,15 +4,15 @@ ifndef::snippets[:snippets: ../../test/java/org/springframework/shell/docs] -Exceptions happen from a user code wether it is intentional or not. This section describes -how `spring-shell` handles exceptions and gives instructions and best practices how to +Exceptions happen from a user code whether it is intentional or not. This section describes +how Spring Shell handles exceptions and gives instructions and best practices on how to work with it. -Many command line applications when applicable return an _exit code_ which running environment -can use to differentiate if command has been executed successfully or not. In a `spring-shell` -this mostly relates when a command is run on a non-interactive mode meaning one command -is always executed once with an instance of a `spring-shell`. Take a note that _exit code_ -always relates to non-interactive shell. +Many command line applications return an _exit code_ which can be used by the +running environment to differentiate if command has been executed successfully or not. +In Spring Shell, this mostly relates to when a command is run in a non-interactive mode, +meaning one command is always executed once with an instance of a `spring-shell`. Take a note +that _exit code_ always relates to non-interactive shell. diff --git a/spring-shell-docs/modules/ROOT/pages/commands/exceptionhandling/mappings.adoc b/spring-shell-docs/modules/ROOT/pages/commands/exceptionhandling/mappings.adoc index b83f91fce..0283e162d 100644 --- a/spring-shell-docs/modules/ROOT/pages/commands/exceptionhandling/mappings.adoc +++ b/spring-shell-docs/modules/ROOT/pages/commands/exceptionhandling/mappings.adoc @@ -5,15 +5,15 @@ ifndef::snippets[:snippets: ../../test/java/org/springframework/shell/docs] Default behaviour of an exit codes is as: -- Errors from a command option parsing will result code of `2` -- Any generic error will result result code of `1` -- Obviously in any other case result code is `0` +- Errors from a command option parsing will result to a code of `2` +- Any generic error will result to a code of `1` +- Obviously in any other case will result to a code of `0` Every `CommandRegistration` can define its own mappings between _Exception_ and _exit code_. -Essentially we're bound to functionality in `Spring Boot` regarding _exit code_ and simply +Spring shell uses a similar approach to `Spring Boot` regarding _exit code_ and simply integrate into that. -Assuming there is an exception show below which would be thrown from a command: +Assuming there is an exception shown below which would be thrown from a command: [source, java, indent=0] ---- diff --git a/spring-shell-docs/modules/ROOT/pages/commands/exceptionhandling/resolving.adoc b/spring-shell-docs/modules/ROOT/pages/commands/exceptionhandling/resolving.adoc index 778d96a26..c30823ff1 100644 --- a/spring-shell-docs/modules/ROOT/pages/commands/exceptionhandling/resolving.adoc +++ b/spring-shell-docs/modules/ROOT/pages/commands/exceptionhandling/resolving.adoc @@ -5,8 +5,8 @@ ifndef::snippets[:snippets: ../../test/java/org/springframework/shell/docs] Unhandled exceptions will bubble up into shell's `ResultHandlerService` and then eventually handled by some instance of `ResultHandler`. Chain of `ExceptionResolver` implementations -can be used to resolve exceptions and gives you flexibility to return message to get written -into console together with exit code which are wrapped within `CommandHandlingResult`. +can be used to resolve exceptions and gives you the flexibility to return a message to get +written into the console together with exit code which are wrapped within `CommandHandlingResult`. `CommandHandlingResult` may contain a _message_ and/or _exit code_. [source, java, indent=0] @@ -14,14 +14,14 @@ into console together with exit code which are wrapped within `CommandHandlingRe include::{snippets}/ErrorHandlingSnippets.java[tag=my-exception-resolver-class] ---- -`CommandExceptionResolver` implementations can be defined globally as bean. +`CommandExceptionResolver` implementations can be defined globally as beans: [source, java, indent=0] ---- include::{snippets}/ErrorHandlingSnippets.java[tag=my-exception-resolver-class-as-bean] ---- -or defined per `CommandRegistration` if it's applicable only for a particular command itself. +or defined per `CommandRegistration` if it's applicable only to a particular command: [source, java, indent=0] ---- @@ -31,18 +31,18 @@ include::{snippets}/ErrorHandlingSnippets.java[tag=example1] NOTE: Resolvers defined with a command are handled before global resolvers. -Use you own exception types which can also be an instance of boot's `ExitCodeGenerator` if -you want to define exit code there. +You can use your own exception types which can also be instances of Spring Boot's `ExitCodeGenerator` +if you want to define exit code there: [source, java, indent=0] ---- include::{snippets}/ErrorHandlingSnippets.java[tag=my-exception-class] ---- -Some build in `CommandExceptionResolver` beans are registered to handle common +Some built-in `CommandExceptionResolver` beans are registered to handle common exceptions thrown from command parsing. These are registered with _order_ -presedence defined in `CommandExceptionResolver.DEFAULT_PRECEDENCE`. +precedence defined in `CommandExceptionResolver.DEFAULT_PRECEDENCE`. As these beans are used in a given order, `@Order` annotation or `Ordered` -interface from can be used just like in any other spring app. This +interface can be used just like in any other Spring app. This is generally useful if you need to control your own beans to get used -either before or after a defaults. +either before or after default ones. diff --git a/spring-shell-docs/modules/ROOT/pages/commands/helpoptions.adoc b/spring-shell-docs/modules/ROOT/pages/commands/helpoptions.adoc index 6c686eefe..de38312d7 100644 --- a/spring-shell-docs/modules/ROOT/pages/commands/helpoptions.adoc +++ b/spring-shell-docs/modules/ROOT/pages/commands/helpoptions.adoc @@ -5,22 +5,22 @@ ifndef::snippets[:snippets: ../../test/java/org/springframework/shell/docs] _Spring Shell_ has a build-in `help` command but not all favour getting command help from it as you always need to call it with arguments for target command. It's -common in many cli frameworks for every command having options _--help_ and _-h_ +common in many CLI frameworks for every command having options _--help_ and _-h_ to print out command help. Default functionality is that every command will get modified to have options _--help_ and _-h_, which if present in a given command will automatically -short circuit command execution into a existing `help` command regardless -what other command-line options is typed. +short circuit command execution into an existing `help` command regardless +what other command-line options are typed. -Below example shows its default settings. +The following example shows its default settings: [source, java, indent=0] ---- include::{snippets}/CommandRegistrationHelpOptionsSnippets.java[tag=defaults] ---- -It is possible to change default behaviour via configuration options. +It is possible to change default behaviour via configuration options: [source, yaml] ---- @@ -33,6 +33,6 @@ spring: command: help ---- -NOTE: Commands defined programmationally or via annotations will automatically add -help options. With annotation model you can only turn things off globally, programmatic -model gives option to modify settings per command. +NOTE: Commands defined programmatically or via annotations will automatically add +help options. With the annotation model you can only turn things off globally, while +with the programmatic model you can modify settings per command. diff --git a/spring-shell-docs/modules/ROOT/pages/commands/hidden.adoc b/spring-shell-docs/modules/ROOT/pages/commands/hidden.adoc index 7014c8b53..b3c0cb694 100644 --- a/spring-shell-docs/modules/ROOT/pages/commands/hidden.adoc +++ b/spring-shell-docs/modules/ROOT/pages/commands/hidden.adoc @@ -4,10 +4,10 @@ ifndef::snippets[:snippets: ../../test/java/org/springframework/shell/docs] It is possible to _hide_ a command which is convenient in cases where it is not yet ready for -prime time, is meant for debugging purposes or you have any other reason you dont want to -advertise its presense. +prime time, or is meant for debugging purposes or you have any other reason you don't want to +advertise its presence. -Hidden command can be executed if you know it and its options. It is effectively removed +A hidden command can be executed if you know it and its options. It is effectively removed from: * Help listing @@ -15,7 +15,7 @@ from: * Command completion in interactive mode * Bash completion -Below is an example how to define command as _hidden_. It shows available builder methods +Below is an example of how to define _hidden_ command. It shows available builder methods to define _hidden_ state. [source, java, indent=0] diff --git a/spring-shell-docs/modules/ROOT/pages/commands/index.adoc b/spring-shell-docs/modules/ROOT/pages/commands/index.adoc index 2662bfe83..6a5a73ee3 100644 --- a/spring-shell-docs/modules/ROOT/pages/commands/index.adoc +++ b/spring-shell-docs/modules/ROOT/pages/commands/index.adoc @@ -5,7 +5,7 @@ ifndef::snippets[:snippets: ../../test/java/org/springframework/shell/docs] In this section, we go through an actual command registration and leave command options -and execution for later in a documentation. You can find more detailed info in +and execution for a later section in the documentation. You can find more details in xref:appendices/techintro/registration.adoc[Command Registration]. diff --git a/spring-shell-docs/modules/ROOT/pages/commands/interactionmode.adoc b/spring-shell-docs/modules/ROOT/pages/commands/interactionmode.adoc index baec80077..1ea32f2e9 100644 --- a/spring-shell-docs/modules/ROOT/pages/commands/interactionmode.adoc +++ b/spring-shell-docs/modules/ROOT/pages/commands/interactionmode.adoc @@ -5,9 +5,9 @@ ifndef::snippets[:snippets: ../../../../src/test/java/org/springframework/shell/docs] Command registration can define `InteractionMode` which is used to hide commands -depending which mode shell is executing. More about that in xref:execution.adoc#using-shell-execution-interactionmode[Interaction Mode]. +depending on which mode the shell is executing in. More about that in xref:execution.adoc#using-shell-execution-interactionmode[Interaction Mode]. -You can define it with `CommandRegisration`. +You can define the interaction mode with `CommandRegisration`: [source, java, indent=0] ---- diff --git a/spring-shell-docs/modules/ROOT/pages/commands/registration/index.adoc b/spring-shell-docs/modules/ROOT/pages/commands/registration/index.adoc index 928ce9f46..917f04571 100644 --- a/spring-shell-docs/modules/ROOT/pages/commands/registration/index.adoc +++ b/spring-shell-docs/modules/ROOT/pages/commands/registration/index.adoc @@ -4,17 +4,16 @@ ifndef::snippets[:snippets: ../../test/java/org/springframework/shell/docs] -There are two different ways to define a command: through an annotation model and -through a programmatic model. In the annotation model, you define your methods -in a class and annotate the class and the methods with specific annotations. -In the programmatic model, you use a more low level approach, defining command -registrations (either as beans or by dynamically registering with a command catalog). +There are two different ways to define a command. through an annotation model and through a programmatic model: -Starting from _3.1.x_ a better support for defining commands using -xref:commands/registration/annotation.adoc[annotations] were added. Firstly because eventually standard +- In the annotation model, you define your methods in a class and annotate the class and the methods with specific annotations. +- In the programmatic model, you use a more low level approach, defining command registrations (either as beans or by dynamically registering with a command catalog). + +Starting from version _3.1.x_, a better support for defining commands using +xref:commands/registration/annotation.adoc[annotations] was added. Firstly because eventually standard package providing xref:commands/registration/legacyannotation.adoc[legacy annotations] will get deprecated and removed. Secondly so that we're able to provide same set of features than using underlying -`CommandRegistration`. Creating new a annotation model allows us to rethink and modernise that +`CommandRegistration`. Creating a new annotation model allows us to rethink and modernise that part without breaking existing applications. diff --git a/spring-shell-docs/modules/ROOT/pages/commands/registration/programmatic.adoc b/spring-shell-docs/modules/ROOT/pages/commands/registration/programmatic.adoc index ac67f8b98..0a957d012 100644 --- a/spring-shell-docs/modules/ROOT/pages/commands/registration/programmatic.adoc +++ b/spring-shell-docs/modules/ROOT/pages/commands/registration/programmatic.adoc @@ -19,7 +19,7 @@ a new builder so you don't need to worry about its internal state. IMPORTANT: Commands registered programmatically automatically add _help options_ mentioned in xref:commands/helpoptions.adoc[Help Options]. -If bean of this supplier type is defined then auto-configuration +If a bean of this supplier type is defined then auto-configuration will back off giving you an option to redefine default functionality. [source, java, indent=0] @@ -28,7 +28,7 @@ include::{snippets}/CommandRegistrationBeanSnippets.java[tag=fromsupplier] ---- `CommandRegistrationCustomizer` beans can be defined if you want to centrally -modify builder instance given you by supplier mentioned above. +modify builder instance given to you by a supplier as mentioned below: [source, java, indent=0] ---- diff --git a/spring-shell-docs/modules/ROOT/pages/completion.adoc b/spring-shell-docs/modules/ROOT/pages/completion.adoc index e13019c66..91b4fd879 100644 --- a/spring-shell-docs/modules/ROOT/pages/completion.adoc +++ b/spring-shell-docs/modules/ROOT/pages/completion.adoc @@ -18,8 +18,8 @@ methods which takes `CompletionContext` and returns a list of `CompletionProposal` instances. `CompletionContext` gives you various information about a current context like command registration and option. -NOTE: Generic resolvers can be registered as a beans if those are useful -for all commands and scenarious. For example existing completion +NOTE: Generic resolvers can be registered as beans if those are useful +for all commands and scenarios. For example existing completion implementation `RegistrationOptionsCompletionResolver` handles completions for a option names. diff --git a/spring-shell-docs/modules/ROOT/pages/components/flow/index.adoc b/spring-shell-docs/modules/ROOT/pages/components/flow/index.adoc index 1ddd76dd6..a26492b5a 100644 --- a/spring-shell-docs/modules/ROOT/pages/components/flow/index.adoc +++ b/spring-shell-docs/modules/ROOT/pages/components/flow/index.adoc @@ -21,9 +21,10 @@ include::{snippets}/FlowComponentSnippets.java[tag=snippet1] include::example$component-flow-showcase-1.cast[] ---- -Normal execution order of a components is same as defined with a builder. It's -possible to conditionally choose where to jump in a flow by using a `next` -function and returning target _component id_. If this returned id is aither _null_ +NOTE: Normal execution order of a components is same as defined with a builder. + +It's possible to conditionally choose where to jump in a flow by using a `next` +function and returning target _component id_. If this returned id is either _null_ or doesn't exist flow is essentially stopped right there. [source, java, indent=0] diff --git a/spring-shell-docs/modules/ROOT/pages/components/index.adoc b/spring-shell-docs/modules/ROOT/pages/components/index.adoc index dcbec06b0..d4a20879e 100644 --- a/spring-shell-docs/modules/ROOT/pages/components/index.adoc +++ b/spring-shell-docs/modules/ROOT/pages/components/index.adoc @@ -2,7 +2,7 @@ = Components :page-section-summary-toc: 1 -Components are a set of features which are either build-in or something +Components are a set of features which are either built-in or something you can re-use or extend for your own needs. Components in question are either built-in _commands_ or UI side components providing higher level features within commands itself. diff --git a/spring-shell-docs/modules/ROOT/pages/customization/commandnotfound.adoc b/spring-shell-docs/modules/ROOT/pages/customization/commandnotfound.adoc index a3ed12f89..f0a951321 100644 --- a/spring-shell-docs/modules/ROOT/pages/customization/commandnotfound.adoc +++ b/spring-shell-docs/modules/ROOT/pages/customization/commandnotfound.adoc @@ -3,7 +3,7 @@ ifndef::snippets[:snippets: ../../test/java/org/springframework/shell/docs] -On default a missing command is handled via `CommandNotFoundResultHandler` +By default, a missing command is handled via `CommandNotFoundResultHandler` and outputs a simple message: [source, text] @@ -12,7 +12,7 @@ shell:>missing No command found for 'missing' ---- -Internally `CommandNotFoundResultHandler` is using `CommandNotFoundMessageProvider` +Internally, `CommandNotFoundResultHandler` is using `CommandNotFoundMessageProvider` which is a simple function taking a `ProviderContext` and returning a text message. Below is an example what a custom message provider might look like. diff --git a/spring-shell-docs/modules/ROOT/pages/customization/logging.adoc b/spring-shell-docs/modules/ROOT/pages/customization/logging.adoc index ab3318297..801f55f88 100644 --- a/spring-shell-docs/modules/ROOT/pages/customization/logging.adoc +++ b/spring-shell-docs/modules/ROOT/pages/customization/logging.adoc @@ -1,11 +1,11 @@ [[using-shell-customization-logging]] = Logging -On default a _Spring Boot_ application will log messages into a console which +By default, a _Spring Boot_ application logs messages into a console which at minimum is annoying and may also mix output from a shell commands. -Fortunately there is a simple way to instruct logging changes via boot properties. +Fortunately, there is a simple way to instruct logging changes via Spring Boot properties. -Completely silence console logging by defining its pattern as an empty value. +To completely silence console logging, set the console's logging pattern to an empty value: [source, yaml] ---- @@ -14,7 +14,7 @@ logging: console: ---- -If you need log from a shell then write those into a file. +If you need log from a shell then write those into a file: [source, yaml] ---- @@ -23,7 +23,7 @@ logging: name: shell.log ---- -If you need different log levels. +If you need different log levels: [source, yaml] ---- @@ -34,8 +34,8 @@ logging: shell: debug ---- -Passing contiguration properties as command line options is not supported but -you can use any other ways supported by boot, for example. +Passing configuration properties as command line options is not supported, +but you can use other ways supported by Spring Boot, for example: [source, bash] ---- @@ -43,5 +43,5 @@ $ java -Dlogging.level.root=debug -jar demo.jar $ LOGGING_LEVEL_ROOT=debug java -jar demo.jar ---- -NOTE: In a GraalVM image settings are locked during compilation which means +NOTE: In a GraalVM image, settings are locked during compilation which means you can't change log levels at runtime. diff --git a/spring-shell-docs/modules/ROOT/pages/customization/singlecommand.adoc b/spring-shell-docs/modules/ROOT/pages/customization/singlecommand.adoc index 93cfe85d6..f2aa35934 100644 --- a/spring-shell-docs/modules/ROOT/pages/customization/singlecommand.adoc +++ b/spring-shell-docs/modules/ROOT/pages/customization/singlecommand.adoc @@ -5,10 +5,9 @@ ifndef::snippets[:snippets: ../../test/java/org/springframework/shell/docs] If your shell application is made for exactly a single purpose having only one -command it may be beneficial to configure it for this. Property -`spring.shell.noninteractive.primary-command` if defined will disable all other -runners than `NonInteractiveShellRunner` and configures it to use -defined _Primary Command_. +command, it may be beneficial to configure it for this. If the property +`spring.shell.noninteractive.primary-command` is defined, it will disable all other +runners than `NonInteractiveShellRunner` and configures it to use the defined _Primary Command_. [source, yaml] ---- @@ -18,6 +17,6 @@ spring: primary-command: mycommand ---- -For example if you have a command `mycommand` with option `arg` -it had to be executed with ` mycommand --arg hi`, but with above -setting it can be executed with ` --arg hi`. +For example, if you have a command `mycommand` with option `arg` +that is expected to be executed with ` mycommand --arg hi` in a multi-command app, +then with the above configuration it can be executed with ` --arg hi`. diff --git a/spring-shell-docs/modules/ROOT/pages/customization/styling.adoc b/spring-shell-docs/modules/ROOT/pages/customization/styling.adoc index 242f3964d..ed4b7d942 100644 --- a/spring-shell-docs/modules/ROOT/pages/customization/styling.adoc +++ b/spring-shell-docs/modules/ROOT/pages/customization/styling.adoc @@ -4,37 +4,35 @@ ifndef::snippets[:snippets: ../../../../src/test/java/org/springframework/shell/docs] Current terminal implementations are rich in features and can usually show -something else that just plain text. For example a text can be styled to be +something else that just plain text. For example, a text can be styled to be _bold_ or have different colors. It's also common for terminals to be able -to show various characters from an unicode table like emoji's which are usually +to show various characters from a unicode table like emoji's which are usually used to make shell output more pretty. -Spring Shell supports these via it's theming framework which contains two parts, -firstly _styling_ can be used to change text type and secondly _figures_ how -some characters are shown. These two are then combined together as a _theme_. +Spring Shell supports these via it's theming framework which contains two parts. +Firstly, _styling_ can be used to change text type and secondly, _figures_ are used +to customize how characters are shown. These two parts are then combined as a _theme_. -More about _theming_ internals, see xref:appendices/techintro/theming.adoc[Theming]. +For more detail about _theming_ internals, refer to see xref:appendices/techintro/theming.adoc[Theming]. -NOTE: Default theme is named `default` but can be change using property -`spring.shell.theme.name`. Other built-in theme named `dump` uses -no styling for colors and tries to not use any special figures. +NOTE: Default theme is named `default` but can be changed using the property +`spring.shell.theme.name`. There is also another built-in theme named `dump` +that uses no styling for colors and tries to not use any special figures. -Modify existing style by overriding settings. +You can modify existing styles and figures by overriding the default settings: [source, java, indent=0] ---- include::{snippets}/ThemingSnippets.java[tag=custom-style-class] ---- -Modify existing figures by overriding settings. - [source, java, indent=0] ---- include::{snippets}/ThemingSnippets.java[tag=custom-figure-class] ---- -To create a new theme, create a `ThemeSettings` and provide your own _style_ -and _figure_ implementations. +You can also create a new theme, by creating a `ThemeSettings` and provide your own _style_ +and _figure_ implementations: [source, java, indent=0] ---- diff --git a/spring-shell-docs/modules/ROOT/pages/execution.adoc b/spring-shell-docs/modules/ROOT/pages/execution.adoc index da91e10fd..b238d4033 100644 --- a/spring-shell-docs/modules/ROOT/pages/execution.adoc +++ b/spring-shell-docs/modules/ROOT/pages/execution.adoc @@ -60,5 +60,5 @@ default boolean run(String[] args) throws Exception { } ---- -IMPORTANT: This will the main api going forward and other existing methods taking boot's -`ApplicationArguments` has been deprecated and will be removed in future. +IMPORTANT: This will be the main api going forward and other existing methods taking boot's +`ApplicationArguments` have been deprecated and will be removed in a future release. diff --git a/spring-shell-docs/modules/ROOT/pages/getting-started.adoc b/spring-shell-docs/modules/ROOT/pages/getting-started.adoc index f4b7bc014..82d302af0 100644 --- a/spring-shell-docs/modules/ROOT/pages/getting-started.adoc +++ b/spring-shell-docs/modules/ROOT/pages/getting-started.adoc @@ -66,6 +66,18 @@ dependencyManagement { } ---- +IMPORTANT: Versions up to `3.2.x` had all runners enabled by default, starting from `3.3.x` +only `NonInteractiveShellRunner` is enabled by default. This means you need to enable +`InteractiveShellRunner` to get REPL. + +[source, yaml] +---- +spring: + shell: + interactive: + enabled: true +---- + CAUTION: Given that Spring Shell starts the REPL (Read-Eval-Print-Loop) because this dependency is present, you need to either skip tests when you build (`-DskipTests`) throughout this tutorial or remove the sample integration test that was generated diff --git a/spring-shell-docs/modules/ROOT/pages/testing/index.adoc b/spring-shell-docs/modules/ROOT/pages/testing/index.adoc index 9f9bd7ffa..5fa3a24f5 100644 --- a/spring-shell-docs/modules/ROOT/pages/testing/index.adoc +++ b/spring-shell-docs/modules/ROOT/pages/testing/index.adoc @@ -4,15 +4,14 @@ ifndef::snippets[:snippets: ../../../../src/test/java/org/springframework/shell/docs] -Testing cli application is difficult due to various reasons: +Testing CLI application is difficult due to various reasons: - There are differences between OS's. -- Within OS there may be different shell implementations in use. -- What goes into a shell and comes out from a shell my be totally - different what you see in shell itself due to control characters. -- Shell may feel syncronous but most likely it is not meaning when - someting is written into it, you can't assume next update in - in it is not final. +- Within the same OS there may be different shell implementations in use. +- What goes into a shell and comes out from a shell may be totally + different from what you see in the shell itself due to invisible control characters. +- The shell may feel synchronous but most likely it is not, meaning when + something is written into it, you can't assume next update in it is not final. NOTE: Testing support is currently under development and will be unstable for various parts. diff --git a/spring-shell-docs/modules/ROOT/pages/testing/settings.adoc b/spring-shell-docs/modules/ROOT/pages/testing/settings.adoc index ae6e37c0e..1c9437a48 100644 --- a/spring-shell-docs/modules/ROOT/pages/testing/settings.adoc +++ b/spring-shell-docs/modules/ROOT/pages/testing/settings.adoc @@ -3,11 +3,11 @@ ifndef::snippets[:snippets: ../../../../src/test/java/org/springframework/shell/docs] -Built in emulation uses terminal width 80 and height 24 on default. -Changing dimensions is useful if output would span into multiple -lines and you don't want to handle those cases in a tests. +Built-in emulation uses terminal width 80 and height 24 by default. +Changing dimensions is useful if the output would span multiple lines +and you don't want to handle those cases in a tests. -These can be changed using properties `spring.shell.test.terminal-width` +These settings can be changed using properties `spring.shell.test.terminal-width` or `spring.shell.test.terminal-height`. [source, java, indent=0] diff --git a/spring-shell-docs/modules/ROOT/pages/tui/events/eventloop.adoc b/spring-shell-docs/modules/ROOT/pages/tui/events/eventloop.adoc index d281c9cb7..c5b78f9c1 100644 --- a/spring-shell-docs/modules/ROOT/pages/tui/events/eventloop.adoc +++ b/spring-shell-docs/modules/ROOT/pages/tui/events/eventloop.adoc @@ -6,16 +6,16 @@ ifndef::snippets[:snippets: ../../../../../src/test/java/org/springframework/she `EventLoop` is a central place where all eventing will be orchestrated for a lifecycle of a component. Orchestration is usually needed around timings -of redraws and and component state updates. +of redraws and component state updates. -Everything in an event loop is represented as a Spring Message. +Everything in an event loop is represented as a Spring Message: [source, java, indent=0] ---- include::{snippets}/EventLoopSnippets.java[tag=plainevents] ---- -Selecting key events use a build-in filtering method _keyEvents()_. +Selecting key events use a built-in filtering method _keyEvents()_. [source, java, indent=0] ---- diff --git a/spring-shell-docs/modules/ROOT/pages/tui/events/index.adoc b/spring-shell-docs/modules/ROOT/pages/tui/events/index.adoc index 01a249394..52920787b 100644 --- a/spring-shell-docs/modules/ROOT/pages/tui/events/index.adoc +++ b/spring-shell-docs/modules/ROOT/pages/tui/events/index.adoc @@ -4,4 +4,4 @@ ifndef::snippets[:snippets: ../../../../test/java/org/springframework/shell/docs] -This section contains information about eventing. +This section contains information about event handling. diff --git a/spring-shell-docs/modules/ROOT/pages/tui/events/key.adoc b/spring-shell-docs/modules/ROOT/pages/tui/events/key.adoc index 6bb7ff193..4258b9d6b 100644 --- a/spring-shell-docs/modules/ROOT/pages/tui/events/key.adoc +++ b/spring-shell-docs/modules/ROOT/pages/tui/events/key.adoc @@ -6,16 +6,16 @@ ifndef::snippets[:snippets: ../../../../../src/test/java/org/springframework/she Views have their own default bindings which can be changed. -You can subscribe into all key events: +You can subscribe to all key events: [source, java, indent=0] ---- include::{snippets}/KeyHandlingSnippets.java[tag=sample] ---- -`KeyEvent` is a record containing info about a binding coming out +`KeyEvent` is a record containing information about a binding coming out from a terminal. Some views allow you to register hot keys which are processed before -normal key handling. More about this can be found from +normal key handling. More about this can be found in xref:appendices/tui/viewdev.adoc#register-bindings[Register Bindings]. diff --git a/spring-shell-docs/modules/ROOT/pages/tui/events/mouse.adoc b/spring-shell-docs/modules/ROOT/pages/tui/events/mouse.adoc index b9d2ea4f3..de83add7e 100644 --- a/spring-shell-docs/modules/ROOT/pages/tui/events/mouse.adoc +++ b/spring-shell-docs/modules/ROOT/pages/tui/events/mouse.adoc @@ -4,12 +4,12 @@ ifndef::snippets[:snippets: ../../../../../src/test/java/org/springframework/shell/docs] -You can subscribe into all mouse events: +You can subscribe to all mouse events: [source, java, indent=0] ---- include::{snippets}/MouseHandlingSnippets.java[tag=sample] ---- -`MouseEvent` is a record wrapping _x_ and _Y_ coordinates and +`MouseEvent` is a record wrapping _X_ and _Y_ coordinates and `org.jline.terminal.MouseEvent` from JLine library. diff --git a/spring-shell-docs/modules/ROOT/pages/tui/index.adoc b/spring-shell-docs/modules/ROOT/pages/tui/index.adoc index 33a2d3753..8d4530988 100644 --- a/spring-shell-docs/modules/ROOT/pages/tui/index.adoc +++ b/spring-shell-docs/modules/ROOT/pages/tui/index.adoc @@ -2,13 +2,13 @@ = Terminal UI :page-section-summary-toc: 1 -NOTE: Feature is experimental and subject to breaking changes until foundation +NOTE: This feature is experimental and is subject to breaking changes until foundation and related concepts around framework are getting more stable. _Terminal UI Framework_ is a toolkit to build rich console apps. This section is for those using existing features as is. If you're planning to go deeper possibly -creating your own components xref:appendices/tui/index.adoc[Terminal UI Appendix] -provides more detailed documentation. +by creating your own components, then check xref:appendices/tui/index.adoc[Terminal UI Appendix] +for more detailed documentation. TIP: xref:appendices/tui/catalog.adoc[Catalog Sample App] is a good place to study a real application. diff --git a/spring-shell-docs/modules/ROOT/pages/tui/intro/index.adoc b/spring-shell-docs/modules/ROOT/pages/tui/intro/index.adoc index 37d0d3f95..aa295d9a8 100644 --- a/spring-shell-docs/modules/ROOT/pages/tui/intro/index.adoc +++ b/spring-shell-docs/modules/ROOT/pages/tui/intro/index.adoc @@ -4,11 +4,11 @@ ifndef::snippets[:snippets: ../../../../../src/test/java/org/springframework/shell/docs] -Lets start with a simple app which prints "hello world" in a view. +Let's start with a simple app which prints "hello world" in a view. [source, java, indent=0] ---- include::{snippets}/TerminalUiSnippets.java[tag=introsample] ---- There is not much to see here other than `TerminalUI` is a class handling -all logic aroung views and uses `View` as it's root view. +all logic around views and uses `View` as it's root view. diff --git a/spring-shell-docs/modules/ROOT/pages/tui/intro/terminalui.adoc b/spring-shell-docs/modules/ROOT/pages/tui/intro/terminalui.adoc index 1a54aeff1..086b734d2 100644 --- a/spring-shell-docs/modules/ROOT/pages/tui/intro/terminalui.adoc +++ b/spring-shell-docs/modules/ROOT/pages/tui/intro/terminalui.adoc @@ -6,8 +6,8 @@ ifndef::snippets[:snippets: ../../../../../src/test/java/org/springframework/she `TerminalUI` is a main implementation to drive ui execution logic. == Create TerminalUI -You can build `TerminalUI` manually but recommended way is to use `TerminalUIBuilder` -build is autoconfigured for you and will set needed services. +You can build `TerminalUI` manually but the recommended way is to use `TerminalUIBuilder` +which is autoconfigured for you and will set needed services. [source, java, indent=0] ---- @@ -34,8 +34,8 @@ include::{snippets}/TerminalUiSnippets.java[tag=uirun] == Exiting App -If you want to exit from an app using normal _CTRL-Q_ key combination listen -events and request _interrupt_. +If you want to exit from an app using normal _CTRL-Q_ key combination, then +you need to register a listener for events and request to _interrupt_ the execution. [source, java, indent=0] ---- diff --git a/spring-shell-docs/modules/ROOT/pages/tui/views/app.adoc b/spring-shell-docs/modules/ROOT/pages/tui/views/app.adoc index 91e6811cf..055613e03 100644 --- a/spring-shell-docs/modules/ROOT/pages/tui/views/app.adoc +++ b/spring-shell-docs/modules/ROOT/pages/tui/views/app.adoc @@ -4,9 +4,9 @@ ifndef::snippets[:snippets: ../../../../../src/test/java/org/springframework/shell/docs] _AppView_ is a base implementation providing functionality to draw opinionated _application view_. -Inherits xref:tui/views/box.adoc[]. +This view inherits from xref:tui/views/box.adoc[]. -Generic idea is to have menu and status views which typically are xref:tui/views/menubar.adoc[] and +The generic idea is to have menu and status views which typically are xref:tui/views/menubar.adoc[] and xref:tui/views/statusbar.adoc[] respectively. Main content view is then whatever user want to show in it. diff --git a/spring-shell-docs/modules/ROOT/pages/tui/views/box.adoc b/spring-shell-docs/modules/ROOT/pages/tui/views/box.adoc index a00611d4d..ff888d511 100644 --- a/spring-shell-docs/modules/ROOT/pages/tui/views/box.adoc +++ b/spring-shell-docs/modules/ROOT/pages/tui/views/box.adoc @@ -5,7 +5,7 @@ ifndef::snippets[:snippets: ../../../../../src/test/java/org/springframework/shell/docs] _BoxView_ is a base implementation providing functionality to draw into a -bounded _Rectancle_. Only direct use of it is its `drawFunction` which +bounded _Rectangle_. Only direct use of it is its `drawFunction` which allows to do simple things without implementing a full custom `View`. @@ -15,10 +15,10 @@ include::{snippets}/BoxViewSnippets.java[tag=sample] ---- == Customisation -_BoxView_ as mostly being a base class contains some useful features -like if it should draw a border and what what are its paddings. +_BoxView_ is mostly being a base class that contains some useful features +like if it should draw a border and what are its paddings. Border can have a title and its color and focused color can be -defined. It's also possible to explicitely set background color +defined. It's also possible to explicitly set a background color which will override one from styling. == Default Bindings diff --git a/spring-shell-docs/modules/ROOT/pages/tui/views/button.adoc b/spring-shell-docs/modules/ROOT/pages/tui/views/button.adoc index 73db8f641..0cacd035f 100644 --- a/spring-shell-docs/modules/ROOT/pages/tui/views/button.adoc +++ b/spring-shell-docs/modules/ROOT/pages/tui/views/button.adoc @@ -5,7 +5,7 @@ ifndef::snippets[:snippets: ../../../../../src/test/java/org/springframework/shell/docs] _ButtonView_ is a base implementation providing functionality to draw a button. -Inherits xref:tui/views/box.adoc[]. +_ButtonView_ inherits from xref:tui/views/box.adoc[]. [source, text] ---- @@ -23,7 +23,7 @@ include::{snippets}/ButtonViewSnippets.java[tag=sample] == Default Bindings -Default _key bindigs_ are: +Default _key bindings_ are: .Key |=== @@ -34,7 +34,7 @@ Default _key bindigs_ are: |=== -Default _mouse bindigs_ are: +Default _mouse bindings_ are: .Mouse |=== diff --git a/spring-shell-docs/modules/ROOT/pages/tui/views/dialog.adoc b/spring-shell-docs/modules/ROOT/pages/tui/views/dialog.adoc index 241b96923..8ad74e7c6 100644 --- a/spring-shell-docs/modules/ROOT/pages/tui/views/dialog.adoc +++ b/spring-shell-docs/modules/ROOT/pages/tui/views/dialog.adoc @@ -5,7 +5,7 @@ ifndef::snippets[:snippets: ../../../../../src/test/java/org/springframework/shell/docs] _DialogView_ is a base implementation providing functionality to draw a dialog. -Inherits xref:tui/views/box.adoc[]. +_DialogView_ inherits from xref:tui/views/box.adoc[]. [source, text] ---- diff --git a/spring-shell-docs/modules/ROOT/pages/tui/views/grid.adoc b/spring-shell-docs/modules/ROOT/pages/tui/views/grid.adoc index 89b3a693c..108f1c3cb 100644 --- a/spring-shell-docs/modules/ROOT/pages/tui/views/grid.adoc +++ b/spring-shell-docs/modules/ROOT/pages/tui/views/grid.adoc @@ -4,7 +4,7 @@ ifndef::snippets[:snippets: ../../../../../src/test/java/org/springframework/shell/docs] _GridView_ is a special type of view and its purpose is to layout other views -using a grid layout algorithms. Inherits xref:tui/views/box.adoc[]. +using a grid layout algorithms. _GridView_ inherits from xref:tui/views/box.adoc[]. [source, java, indent=0] ---- diff --git a/spring-shell-docs/modules/ROOT/pages/tui/views/index.adoc b/spring-shell-docs/modules/ROOT/pages/tui/views/index.adoc index 9f245114e..3525d24a9 100644 --- a/spring-shell-docs/modules/ROOT/pages/tui/views/index.adoc +++ b/spring-shell-docs/modules/ROOT/pages/tui/views/index.adoc @@ -4,4 +4,4 @@ ifndef::snippets[:snippets: ../../../../test/java/org/springframework/shell/docs] -Framework provides a build-in views which are documented below. +Spring Shell provides a built-in views which are documented below. diff --git a/spring-shell-docs/modules/ROOT/pages/tui/views/input.adoc b/spring-shell-docs/modules/ROOT/pages/tui/views/input.adoc index b68377b7a..ac41a086e 100644 --- a/spring-shell-docs/modules/ROOT/pages/tui/views/input.adoc +++ b/spring-shell-docs/modules/ROOT/pages/tui/views/input.adoc @@ -4,7 +4,7 @@ ifndef::snippets[:snippets: ../../../../../src/test/java/org/springframework/shell/docs] _InputView_ is a base implementation providing functionality to draw and modify -text in a bounded _Rectancle_. +text in a bounded _Rectangle_. [source, java, indent=0] ---- @@ -32,7 +32,7 @@ Default _view commands_ are: |=== -Default _key bindigs_ are: +Default _key bindings_ are: .Key |=== diff --git a/spring-shell-docs/modules/ROOT/pages/tui/views/list.adoc b/spring-shell-docs/modules/ROOT/pages/tui/views/list.adoc index 0d531a5ab..51a567530 100644 --- a/spring-shell-docs/modules/ROOT/pages/tui/views/list.adoc +++ b/spring-shell-docs/modules/ROOT/pages/tui/views/list.adoc @@ -5,10 +5,10 @@ ifndef::snippets[:snippets: ../../../../../src/test/java/org/springframework/shell/docs] _ListView_ is a base implementation providing functionality to draw a list of -_items_. Inherits xref:tui/views/box.adoc[]. +_items_. _ListView_ inherits from xref:tui/views/box.adoc[]. _ListView_ is typed as its _item_ and can take any object. Further _item_ -processing happens in a _CellFactory_. For conveniance there is a support +processing happens in a _CellFactory_. For convenience, there is a support for generic higher level list feature showing checked states as normal _check_ and _radio_ types. Essentially what you can have is a list of items which are shown as is, shown where any items can have a checked @@ -63,7 +63,7 @@ Default _view commands_ are: |=== -Default _key bindigs_ are: +Default _key bindings_ are: .Key |=== @@ -83,7 +83,7 @@ Default _key bindigs_ are: |=== -Default _mouse bindigs_ are: +Default _mouse bindings_ are: .Mouse |=== diff --git a/spring-shell-docs/modules/ROOT/pages/tui/views/menu.adoc b/spring-shell-docs/modules/ROOT/pages/tui/views/menu.adoc index cbab5534a..c17521a39 100644 --- a/spring-shell-docs/modules/ROOT/pages/tui/views/menu.adoc +++ b/spring-shell-docs/modules/ROOT/pages/tui/views/menu.adoc @@ -5,10 +5,10 @@ ifndef::snippets[:snippets: ../../../../../src/test/java/org/springframework/shell/docs] _MenuView_ is a base implementation providing functionality to draw a menu. -Inherits xref:tui/views/box.adoc[]. +_MenuView_ inherits from xref:tui/views/box.adoc[]. == Default Bindings -Default _key bindigs_ are: +Default _key bindings_ are: .Key |=== @@ -25,7 +25,7 @@ Default _key bindigs_ are: |=== -Default _mouse bindigs_ are: +Default _mouse bindings_ are: .Mouse |=== diff --git a/spring-shell-docs/modules/ROOT/pages/tui/views/menubar.adoc b/spring-shell-docs/modules/ROOT/pages/tui/views/menubar.adoc index 22c443349..3a25c422e 100644 --- a/spring-shell-docs/modules/ROOT/pages/tui/views/menubar.adoc +++ b/spring-shell-docs/modules/ROOT/pages/tui/views/menubar.adoc @@ -5,7 +5,7 @@ ifndef::snippets[:snippets: ../../../../../src/test/java/org/springframework/shell/docs] _MenuBarView_ is a base implementation providing functionality to draw a menu bar. -Inherits xref:tui/views/box.adoc[]. +_MenuBarView_ inherits from xref:tui/views/box.adoc[]. [source, text] ---- @@ -24,7 +24,7 @@ include::{snippets}/MenuBarViewSnippets.java[tag=snippet1] ---- == Default Bindings -Default _key bindigs_ are: +Default _key bindings_ are: .Key |=== @@ -38,7 +38,7 @@ Default _key bindigs_ are: |=== -Default _mouse bindigs_ are: +Default _mouse bindings_ are: .Mouse |=== diff --git a/spring-shell-docs/modules/ROOT/pages/tui/views/progress.adoc b/spring-shell-docs/modules/ROOT/pages/tui/views/progress.adoc index a1d2c2429..40fb891a2 100644 --- a/spring-shell-docs/modules/ROOT/pages/tui/views/progress.adoc +++ b/spring-shell-docs/modules/ROOT/pages/tui/views/progress.adoc @@ -4,25 +4,25 @@ ifndef::snippets[:snippets: ../../../../../src/test/java/org/springframework/shell/docs] -_ProgressView_ is a base implementation providing functionality to draw a progress info. -Inherits xref:tui/views/box.adoc[]. +_ProgressView_ is a base implementation providing functionality to draw a progress information. +_ProgressView_ inherits from xref:tui/views/box.adoc[]. -_ProgressView_ draws its content using concepts described below +_ProgressView_ draws its content using concepts described below: * _ProgressState_ contains various info about a runtime state -** _tickStart_ Lower bound of tick value -** _tickEnd_ Upper bound of tick value -** _tickValue_ Current tick value -** _running_ Running state, either true or false -** _startTime_ Start time in millis when progress was started -** _updateTime_ Last known time in millis when progress has updated +** _tickStart_: Lower bound of tick value +** _tickEnd_: Upper bound of tick value +** _tickValue_: Current tick value +** _running_: Running state, either true or false +** _startTime_: Start time in millis when progress was started +** _updateTime_: Last known time in millis when progress has updated * _ProgressContext_ is a context used with _ProgressViewItem_ -** _description_ The description given to progress -** _state_ The _ProgressState_ -** _view_ The owning _ProgressView_ -** _spinner_ The _Spinner_ representation used with _ProgressView_ +** _description_: The description given to progress +** _state_: The _ProgressState_ +** _view_: The owning _ProgressView_ +** _spinner_: The _Spinner_ representation used with _ProgressView_ ** Other methods to help with item drawing -* _ProgressViewItem_ is a representation of a cell used in _ProgressView_ +* _ProgressViewItem_: is a representation of a cell used in _ProgressView_ There are few build-in items namely `text`, `spinner` and `percent`. @@ -42,7 +42,7 @@ include::example$tui-progress-1.cast[] == Customisation -Here's some examples for various customisations: +Here are some examples for various customisations: [source, java, indent=0] ---- diff --git a/spring-shell-docs/modules/ROOT/pages/tui/views/statusbar.adoc b/spring-shell-docs/modules/ROOT/pages/tui/views/statusbar.adoc index bee090bbb..0d6657154 100644 --- a/spring-shell-docs/modules/ROOT/pages/tui/views/statusbar.adoc +++ b/spring-shell-docs/modules/ROOT/pages/tui/views/statusbar.adoc @@ -4,7 +4,7 @@ ifndef::snippets[:snippets: ../../../../../src/test/java/org/springframework/shell/docs] _StatusBarView_ is a base implementation providing functionality to draw a status bar. -Inherits xref:tui/views/box.adoc[]. +_StatusBarView_ inherits from xref:tui/views/box.adoc[]. [source, text] ---- @@ -20,15 +20,15 @@ You can create a simple status bar with an item: include::{snippets}/StatusBarViewSnippets.java[tag=simple] ---- -Constructor can take array form which allows to lay out simple -item definitions in a _dsl_ style. +The constructor can take array form which allows to lay out simple +item definitions in a _dsl_ style: [source, java, indent=0] ---- include::{snippets}/StatusBarViewSnippets.java[tag=viaarray] ---- -Items support runnable actions which generally as executed when +Items support runnable actions which generally are executed when item is selected. It can also get attached to a hot key. [source, java, indent=0] diff --git a/spring-shell-docs/spring-shell-docs.gradle b/spring-shell-docs/spring-shell-docs.gradle index 036b0ec0b..e47f5c8ea 100644 --- a/spring-shell-docs/spring-shell-docs.gradle +++ b/spring-shell-docs/spring-shell-docs.gradle @@ -11,26 +11,28 @@ dependencies { implementation project(':spring-shell-starters:spring-shell-starter') implementation project(':spring-shell-starters:spring-shell-starter-test') implementation project(':spring-shell-samples:spring-shell-sample-catalog') - testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation('org.springframework.boot:spring-boot-test') + testImplementation('org.junit.jupiter:junit-jupiter-engine') + testImplementation('org.junit.jupiter:junit-jupiter-params') + testImplementation('org.junit.platform:junit-platform-launcher') + testImplementation("org.mockito:mockito-junit-jupiter") + testImplementation('org.assertj:assertj-core') testImplementation 'org.awaitility:awaitility' } -node { - version = '16.15.0' -} - antora { - version = '3.2.0-alpha.2' + version = '3.2.0-alpha.6' if (project.hasProperty('antoraLocalBarePlaybook') && antoraLocalBarePlaybook.toBoolean()) { playbook = 'local-bare-antora-playbook.yml' } options = [clean: true, fetch: !project.gradle.startParameter.offline, stacktrace: true] dependencies = [ '@antora/atlas-extension': '1.0.0-alpha.1', - '@antora/collector-extension': '1.0.0-alpha.3', - '@asciidoctor/tabs': '1.0.0-beta.3', - '@springio/antora-extensions': '1.8.1', - '@springio/asciidoctor-extensions': '1.0.0-alpha.8', + '@antora/collector-extension': '1.0.0-alpha.7', + '@asciidoctor/tabs': '1.0.0-beta.6', + '@springio/antora-extensions': '1.14.2', + '@springio/asciidoctor-extensions': '1.0.0-alpha.12', + 'asciinema-player': '3.7.1' ] } diff --git a/spring-shell-management/spring-shell-management.gradle b/spring-shell-management/spring-shell-management.gradle index 72ffb830d..084a39ac9 100644 --- a/spring-shell-management/spring-shell-management.gradle +++ b/spring-shell-management/spring-shell-management.gradle @@ -22,6 +22,5 @@ dependencies { api "org.antlr:ST4:$st4Version" api "commons-io:commons-io:$commonsIoVersion" api "com.google.jimfs:jimfs:$jimfsVersion" - api "com.google.code.findbugs:jsr305:$findbugsVersion" } } diff --git a/spring-shell-samples/spring-shell-sample-catalog/spring-shell-sample-catalog.gradle b/spring-shell-samples/spring-shell-sample-catalog/spring-shell-sample-catalog.gradle index 4a1e5dfcb..6efaa4d97 100644 --- a/spring-shell-samples/spring-shell-sample-catalog/spring-shell-sample-catalog.gradle +++ b/spring-shell-samples/spring-shell-sample-catalog/spring-shell-sample-catalog.gradle @@ -8,6 +8,11 @@ dependencies { management platform(project(":spring-shell-management")) implementation project(':spring-shell-starters:spring-shell-starter-jna') testImplementation project(':spring-shell-starters:spring-shell-starter-test') - testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation('org.springframework.boot:spring-boot-test') + testImplementation('org.junit.jupiter:junit-jupiter-engine') + testImplementation('org.junit.jupiter:junit-jupiter-params') + testImplementation('org.junit.platform:junit-platform-launcher') + testImplementation("org.mockito:mockito-junit-jupiter") + testImplementation('org.assertj:assertj-core') testImplementation 'org.awaitility:awaitility' } diff --git a/spring-shell-samples/spring-shell-sample-commands/spring-shell-sample-commands.gradle b/spring-shell-samples/spring-shell-sample-commands/spring-shell-sample-commands.gradle index c1397b87a..c478f50cd 100644 --- a/spring-shell-samples/spring-shell-sample-commands/spring-shell-sample-commands.gradle +++ b/spring-shell-samples/spring-shell-sample-commands/spring-shell-sample-commands.gradle @@ -8,6 +8,11 @@ dependencies { management platform(project(":spring-shell-management")) implementation project(':spring-shell-starters:spring-shell-starter-jna') testImplementation project(':spring-shell-starters:spring-shell-starter-test') - testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation('org.springframework.boot:spring-boot-test') + testImplementation('org.junit.jupiter:junit-jupiter-engine') + testImplementation('org.junit.jupiter:junit-jupiter-params') + testImplementation('org.junit.platform:junit-platform-launcher') + testImplementation("org.mockito:mockito-junit-jupiter") + testImplementation('org.assertj:assertj-core') testImplementation 'org.awaitility:awaitility' } diff --git a/spring-shell-samples/spring-shell-sample-e2e/spring-shell-sample-e2e.gradle b/spring-shell-samples/spring-shell-sample-e2e/spring-shell-sample-e2e.gradle index c0975acc4..83b3ab2f8 100644 --- a/spring-shell-samples/spring-shell-sample-e2e/spring-shell-sample-e2e.gradle +++ b/spring-shell-samples/spring-shell-sample-e2e/spring-shell-sample-e2e.gradle @@ -8,6 +8,11 @@ dependencies { management platform(project(":spring-shell-management")) implementation project(':spring-shell-starters:spring-shell-starter-jna') testImplementation project(':spring-shell-starters:spring-shell-starter-test') - testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation('org.springframework.boot:spring-boot-test') + testImplementation('org.junit.jupiter:junit-jupiter-engine') + testImplementation('org.junit.jupiter:junit-jupiter-params') + testImplementation('org.junit.platform:junit-platform-launcher') + testImplementation("org.mockito:mockito-junit-jupiter") + testImplementation('org.assertj:assertj-core') testImplementation 'org.awaitility:awaitility' } diff --git a/spring-shell-samples/spring-shell-sample-ffm/spring-shell-sample-ffm.gradle b/spring-shell-samples/spring-shell-sample-ffm/spring-shell-sample-ffm.gradle new file mode 100644 index 000000000..1c880bfbd --- /dev/null +++ b/spring-shell-samples/spring-shell-sample-ffm/spring-shell-sample-ffm.gradle @@ -0,0 +1,25 @@ +plugins { + id 'org.springframework.shell.sample' + id 'org.springframework.shell.toolchain' +} + +description = 'Spring Shell Sample FFM' + +tasks.named("bootJar") { + manifest { + attributes 'Enable-Native-Access': 'ALL-UNNAMED' + } +} + +dependencies { + management platform(project(":spring-shell-management")) + implementation project(':spring-shell-starters:spring-shell-starter-ffm') + testImplementation project(':spring-shell-starters:spring-shell-starter-test') + testImplementation('org.springframework.boot:spring-boot-test') + testImplementation('org.junit.jupiter:junit-jupiter-engine') + testImplementation('org.junit.jupiter:junit-jupiter-params') + testImplementation('org.junit.platform:junit-platform-launcher') + testImplementation("org.mockito:mockito-junit-jupiter") + testImplementation('org.assertj:assertj-core') + testImplementation 'org.awaitility:awaitility' +} diff --git a/spring-shell-samples/spring-shell-sample-ffm/src/main/java/org/springframework/shell/samples/ffm/SpringShellApplication.java b/spring-shell-samples/spring-shell-sample-ffm/src/main/java/org/springframework/shell/samples/ffm/SpringShellApplication.java new file mode 100644 index 000000000..29d8941cd --- /dev/null +++ b/spring-shell-samples/spring-shell-sample-ffm/src/main/java/org/springframework/shell/samples/ffm/SpringShellApplication.java @@ -0,0 +1,33 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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. + */ +package org.springframework.shell.samples.ffm; + +import org.springframework.boot.Banner.Mode; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.shell.command.annotation.CommandScan; + +@SpringBootApplication +@CommandScan +public class SpringShellApplication { + + public static void main(String[] args) throws Exception { + SpringApplication application = new SpringApplication(SpringShellApplication.class); + application.setBannerMode(Mode.OFF); + application.run(args); + } + +} diff --git a/spring-shell-samples/spring-shell-sample-ffm/src/main/resources/application.yml b/spring-shell-samples/spring-shell-sample-ffm/src/main/resources/application.yml new file mode 100644 index 000000000..8b75a2cca --- /dev/null +++ b/spring-shell-samples/spring-shell-sample-ffm/src/main/resources/application.yml @@ -0,0 +1,18 @@ +spring: + main: + banner-mode: off + shell: + interactive: + enabled: true +## disable console logging +logging: + pattern: + console: +## log debug from a cli + # file: + # name: shell-ffm.log + # level: + # root: debug + # org: + # springframework: + # shell: debug diff --git a/spring-shell-samples/spring-shell-sample-ffm/src/test/java/org/springframework/shell/samples/ffm/SpringShellApplicationTests.java b/spring-shell-samples/spring-shell-sample-ffm/src/test/java/org/springframework/shell/samples/ffm/SpringShellApplicationTests.java new file mode 100644 index 000000000..58bf6d8b7 --- /dev/null +++ b/spring-shell-samples/spring-shell-sample-ffm/src/test/java/org/springframework/shell/samples/ffm/SpringShellApplicationTests.java @@ -0,0 +1,20 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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. + */ +package org.springframework.shell.samples.ffm; + +public class SpringShellApplicationTests { + +} diff --git a/spring-shell-standard-commands/spring-shell-standard-commands.gradle b/spring-shell-standard-commands/spring-shell-standard-commands.gradle index eea116586..f70d0029a 100644 --- a/spring-shell-standard-commands/spring-shell-standard-commands.gradle +++ b/spring-shell-standard-commands/spring-shell-standard-commands.gradle @@ -8,5 +8,10 @@ dependencies { management platform(project(":spring-shell-management")) implementation project(':spring-shell-core') implementation project(':spring-shell-standard') - testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation('org.springframework.boot:spring-boot-test') + testImplementation('org.junit.jupiter:junit-jupiter-engine') + testImplementation('org.junit.jupiter:junit-jupiter-params') + testImplementation('org.junit.platform:junit-platform-launcher') + testImplementation("org.mockito:mockito-junit-jupiter") + testImplementation('org.assertj:assertj-core') } diff --git a/spring-shell-standard/spring-shell-standard.gradle b/spring-shell-standard/spring-shell-standard.gradle index c471dbd29..4ec55e41b 100644 --- a/spring-shell-standard/spring-shell-standard.gradle +++ b/spring-shell-standard/spring-shell-standard.gradle @@ -7,6 +7,10 @@ description = 'Spring Shell Standard' dependencies { management platform(project(":spring-shell-management")) implementation project(':spring-shell-core') - compileOnly 'com.google.code.findbugs:jsr305' - testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation('org.springframework.boot:spring-boot-test') + testImplementation('org.junit.jupiter:junit-jupiter-engine') + testImplementation('org.junit.jupiter:junit-jupiter-params') + testImplementation('org.junit.platform:junit-platform-launcher') + testImplementation("org.mockito:mockito-junit-jupiter") + testImplementation('org.assertj:assertj-core') } diff --git a/spring-shell-starters/spring-shell-starter-ffm/spring-shell-starter-ffm.gradle b/spring-shell-starters/spring-shell-starter-ffm/spring-shell-starter-ffm.gradle new file mode 100644 index 000000000..177b9aa3d --- /dev/null +++ b/spring-shell-starters/spring-shell-starter-ffm/spring-shell-starter-ffm.gradle @@ -0,0 +1,12 @@ +plugins { + id 'org.springframework.shell.starter' + id 'org.springframework.shell.toolchain' +} + +description = 'Spring Shell Starter FFM' + +dependencies { + management platform(project(':spring-shell-management')) + api(project(':spring-shell-starters:spring-shell-starter')) + implementation 'org.jline:jline-terminal-ffm' +} diff --git a/spring-shell-table/spring-shell-table.gradle b/spring-shell-table/spring-shell-table.gradle index 1e172096d..6d50dfa93 100644 --- a/spring-shell-table/spring-shell-table.gradle +++ b/spring-shell-table/spring-shell-table.gradle @@ -8,6 +8,10 @@ dependencies { management platform(project(":spring-shell-management")) api('org.springframework:spring-core') api('org.springframework:spring-context') - compileOnly 'com.google.code.findbugs:jsr305' - testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation('org.springframework.boot:spring-boot-test') + testImplementation('org.junit.jupiter:junit-jupiter-engine') + testImplementation('org.junit.jupiter:junit-jupiter-params') + testImplementation('org.junit.platform:junit-platform-launcher') + testImplementation("org.mockito:mockito-junit-jupiter") + testImplementation('org.assertj:assertj-core') } diff --git a/spring-shell-test-autoconfigure/spring-shell-test-autoconfigure.gradle b/spring-shell-test-autoconfigure/spring-shell-test-autoconfigure.gradle index c740156ae..e13f37871 100644 --- a/spring-shell-test-autoconfigure/spring-shell-test-autoconfigure.gradle +++ b/spring-shell-test-autoconfigure/spring-shell-test-autoconfigure.gradle @@ -13,9 +13,10 @@ dependencies { implementation 'org.springframework:spring-test' implementation 'org.springframework.boot:spring-boot-autoconfigure' implementation 'org.springframework.boot:spring-boot-test-autoconfigure' - implementation 'org.springframework.boot:spring-boot-starter-test' - optional 'org.assertj:assertj-core' - optional 'org.junit.jupiter:junit-jupiter-api' + implementation 'org.springframework.boot:spring-boot-test' + implementation 'org.junit.jupiter:junit-jupiter-engine' + implementation 'org.junit.platform:junit-platform-launcher' + testImplementation 'org.assertj:assertj-core' testImplementation 'org.awaitility:awaitility' annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' } diff --git a/spring-shell-test/spring-shell-test.gradle b/spring-shell-test/spring-shell-test.gradle index bde8d398a..0a8093627 100644 --- a/spring-shell-test/spring-shell-test.gradle +++ b/spring-shell-test/spring-shell-test.gradle @@ -9,6 +9,11 @@ dependencies { implementation project(':spring-shell-core') optional 'org.assertj:assertj-core' optional 'org.junit.jupiter:junit-jupiter-api' - testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation('org.springframework.boot:spring-boot-test') + testImplementation('org.junit.jupiter:junit-jupiter-engine') + testImplementation('org.junit.jupiter:junit-jupiter-params') + testImplementation('org.junit.platform:junit-platform-launcher') + testImplementation("org.mockito:mockito-junit-jupiter") + testImplementation('org.assertj:assertj-core') testImplementation 'org.awaitility:awaitility' }