From 9830fafe1a5cc971aa8fb50eea89234270d55a42 Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Wed, 24 Jan 2024 15:38:22 +0000 Subject: [PATCH] Use CommandContext as method parameter - With annotation(@Command) model it's now possible to just add CommandContext and it will get resolved and doesn't cause it to appear as an option. - Fixes #779 --- .../CommandRegistrationFactoryBean.java | 7 +- .../shell/command/AbstractCommandTests.java | 13 +++- .../shell/command/CommandExecutionTests.java | 77 ++++++++++++++++++- .../modules/ROOT/pages/commands/writing.adoc | 12 ++- .../shell/docs/WritingSnippets.java | 17 +++- .../shell/samples/e2e/WriteCommands.java | 19 ++++- 6 files changed, 137 insertions(+), 8 deletions(-) diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/CommandRegistrationFactoryBean.java b/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/CommandRegistrationFactoryBean.java index 7cefcdf9..5426a457 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/CommandRegistrationFactoryBean.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/CommandRegistrationFactoryBean.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. @@ -40,6 +40,7 @@ import org.springframework.messaging.handler.invocation.InvocableHandlerMethod; import org.springframework.shell.Availability; import org.springframework.shell.AvailabilityProvider; import org.springframework.shell.Utils; +import org.springframework.shell.command.CommandContext; import org.springframework.shell.command.CommandExceptionResolver; import org.springframework.shell.command.CommandHandlingResult; import org.springframework.shell.command.CommandRegistration; @@ -322,6 +323,10 @@ class CommandRegistrationFactoryBean implements FactoryBean mp.initParameterNameDiscovery(new DefaultParameterNameDiscoverer()); String longName = mp.getParameterName(); Class parameterType = mp.getParameterType(); + // skip known types which are coming in via parameter resolvers + if (ClassUtils.isAssignable(parameterType, CommandContext.class)) { + return; + } if (longName != null) { log.debug("Using mp='{}' longName='{}' parameterType='{}'", mp, longName, parameterType); OptionSpec optionSpec = builder.withOption(); diff --git a/spring-shell-core/src/test/java/org/springframework/shell/command/AbstractCommandTests.java b/spring-shell-core/src/test/java/org/springframework/shell/command/AbstractCommandTests.java index c87f3c80..5dc15e30 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/command/AbstractCommandTests.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/command/AbstractCommandTests.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. @@ -43,6 +43,10 @@ public abstract class AbstractCommandTests { public int method1Count; public CommandContext method1Ctx; + public int method1Mixed1Count; + public String method1Mixed1Arg1; + public CommandContext method1Mixed1Ctx; + public String method1Mixed1Arg2; public int method2Count; public int method3Count; public int method4Count; @@ -68,6 +72,13 @@ public abstract class AbstractCommandTests { method1Count++; } + public void method1Mixed1(String arg1, CommandContext ctx, String arg2) { + method1Mixed1Arg1 = arg1; + method1Mixed1Ctx = ctx; + method1Mixed1Arg2 = arg2; + method1Mixed1Count++; + } + public String method2() { method2Count++; return "hi"; diff --git a/spring-shell-core/src/test/java/org/springframework/shell/command/CommandExecutionTests.java b/spring-shell-core/src/test/java/org/springframework/shell/command/CommandExecutionTests.java index 18aae901..abec3a2e 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/command/CommandExecutionTests.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/command/CommandExecutionTests.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. @@ -106,6 +106,81 @@ public class CommandExecutionTests extends AbstractCommandTests { assertThat(pojo1.method1Ctx).isNotNull(); } + @Test + public void testMixedWithCtx1() { + CommandRegistration r1 = CommandRegistration.builder() + .command("command1") + .description("help") + .withOption() + .longNames("arg1") + .description("some arg1") + .and() + .withOption() + .longNames("arg2") + .description("some arg1") + .and() + .withTarget() + .method(pojo1, "method1Mixed1") + .and() + .build(); + commandCatalog.register(r1); + execution.evaluate(new String[] { "command1" }); + assertThat(pojo1.method1Mixed1Count).isEqualTo(1); + assertThat(pojo1.method1Mixed1Arg1).isNull(); + assertThat(pojo1.method1Mixed1Ctx).isNotNull(); + assertThat(pojo1.method1Mixed1Arg2).isNull(); + } + + @Test + public void testMixedWithCtx2() { + CommandRegistration r1 = CommandRegistration.builder() + .command("command1") + .description("help") + .withOption() + .longNames("arg1") + .description("some arg1") + .and() + .withOption() + .longNames("arg2") + .description("some arg1") + .and() + .withTarget() + .method(pojo1, "method1Mixed1") + .and() + .build(); + commandCatalog.register(r1); + execution.evaluate(new String[] { "command1", "--arg1", "myarg1value" }); + assertThat(pojo1.method1Mixed1Count).isEqualTo(1); + assertThat(pojo1.method1Mixed1Arg1).isEqualTo("myarg1value"); + assertThat(pojo1.method1Mixed1Ctx).isNotNull(); + assertThat(pojo1.method1Mixed1Arg2).isNull(); + } + + @Test + public void testMixedWithCtx3() { + CommandRegistration r1 = CommandRegistration.builder() + .command("command1") + .description("help") + .withOption() + .longNames("arg1") + .description("some arg1") + .and() + .withOption() + .longNames("arg2") + .description("some arg1") + .and() + .withTarget() + .method(pojo1, "method1Mixed1") + .and() + .build(); + commandCatalog.register(r1); + execution.evaluate(new String[] { "command1", "--arg1", "myarg1value", "--arg2", "myarg2value" }); + assertThat(pojo1.method1Mixed1Count).isEqualTo(1); + assertThat(pojo1.method1Mixed1Arg1).isEqualTo("myarg1value"); + assertThat(pojo1.method1Mixed1Ctx).isNotNull(); + assertThat(pojo1.method1Mixed1Arg2).isEqualTo("myarg2value"); + } + @Test public void testMethodArgWithoutValue() { CommandRegistration r1 = CommandRegistration.builder() diff --git a/spring-shell-docs/modules/ROOT/pages/commands/writing.adoc b/spring-shell-docs/modules/ROOT/pages/commands/writing.adoc index e19ae311..61b58e5e 100644 --- a/spring-shell-docs/modules/ROOT/pages/commands/writing.adoc +++ b/spring-shell-docs/modules/ROOT/pages/commands/writing.adoc @@ -1,7 +1,7 @@ [[writing]] = Writing -ifndef::snippets[:snippets: ../../test/java/org/springframework/shell/docs] +ifndef::snippets[:snippets: ../../../../src/test/java/org/springframework/shell/docs] When something needs to get written into your console you can always use JDK's `System.out` which then goes directly into JDK's own streams. @@ -17,9 +17,17 @@ to return anything given `CommandContext` contains reference to include::{snippets}/WritingSnippets.java[tag=reg-terminal-writer] ---- -It's possible to autowire `Terminal` to get access to its writer. +If using `@Command` you can get access to `CommandContext` and get +`Terminal` from there. [source, java, indent=0] ---- include::{snippets}/WritingSnippets.java[tag=anno-terminal-writer] ---- + +It's possible to autowire `Terminal` to get access to its writer. + +[source, java, indent=0] +---- +include::{snippets}/WritingSnippets.java[tag=legacyanno-terminal-writer] +---- diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/WritingSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/WritingSnippets.java index 9ba34c07..dc2709d4 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/WritingSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/WritingSnippets.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. @@ -18,14 +18,16 @@ package org.springframework.shell.docs; import org.jline.terminal.Terminal; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.shell.command.CommandContext; import org.springframework.shell.command.CommandRegistration; +import org.springframework.shell.command.annotation.Command; import org.springframework.shell.standard.ShellMethod; class WritingSnippets { class Dump1 { - // tag::anno-terminal-writer[] + // tag::legacyanno-terminal-writer[] @Autowired Terminal terminal; @@ -34,6 +36,17 @@ class WritingSnippets { terminal.writer().println("hi"); terminal.writer().flush(); } + // end::legacyanno-terminal-writer[] + } + + class Dump2 { + + // tag::anno-terminal-writer[] + @Command + public void example(CommandContext ctx) { + ctx.getTerminal().writer().println("hi"); + ctx.getTerminal().writer().flush(); + } // end::anno-terminal-writer[] } diff --git a/spring-shell-samples/spring-shell-sample-e2e/src/main/java/org/springframework/shell/samples/e2e/WriteCommands.java b/spring-shell-samples/spring-shell-sample-e2e/src/main/java/org/springframework/shell/samples/e2e/WriteCommands.java index a3901d43..93ab4eb0 100644 --- a/spring-shell-samples/spring-shell-sample-e2e/src/main/java/org/springframework/shell/samples/e2e/WriteCommands.java +++ b/spring-shell-samples/spring-shell-sample-e2e/src/main/java/org/springframework/shell/samples/e2e/WriteCommands.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. @@ -19,7 +19,9 @@ import org.jline.terminal.Terminal; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; +import org.springframework.shell.command.CommandContext; import org.springframework.shell.command.CommandRegistration; +import org.springframework.shell.command.annotation.Command; import org.springframework.shell.standard.ShellComponent; import org.springframework.shell.standard.ShellMethod; import org.springframework.stereotype.Component; @@ -44,6 +46,21 @@ public class WriteCommands { } } + @Command(command = BaseE2ECommands.ANNO, group = BaseE2ECommands.GROUP) + public static class Annotation extends BaseE2ECommands { + + @Command(command = "write-terminalwriter") + public void writeTerminalWriter(CommandContext ctx) { + ctx.getTerminal().writer().println("hi"); + ctx.getTerminal().writer().flush(); + } + + @Command(command = "write-systemout") + public void writeSystemOut() { + System.out.println("hi"); + } + } + @Component public static class Registration extends BaseE2ECommands {