gemmellr commented on code in PR #4565:
URL: https://github.com/apache/activemq-artemis/pull/4565#discussion_r1276148633


##########
pom.xml:
##########
@@ -586,17 +587,23 @@
             <!-- License: Apache 2.0 -->
          </dependency>
          <dependency>
-            <groupId>com.github.rvesse</groupId>
-            <artifactId>airline</artifactId>
-            <version>${airline.version}</version>
-            <exclusions>
-               <exclusion>
-                  <groupId>com.github.rvesse</groupId>
-                  <artifactId>airline-backcompat-javaxinject</artifactId>
-               </exclusion>
-            </exclusions>
+            <groupId>info.picocli</groupId>
+            <artifactId>picocli</artifactId>
+            <version>${picocli.version}</version>
             <!-- License: Apache 2.0 -->
          </dependency>
+         <dependency>
+            <groupId>info.picocli</groupId>
+            <artifactId>picocli-shell-jline3</artifactId>
+            <version>${picocli.version}</version>
+            <!-- License: Apache 2.0 -->
+         </dependency>
+         <dependency>
+            <groupId>org.jline</groupId>
+            <artifactId>jline</artifactId>
+            <version>${jline.version}</version>
+            <!-- License: BSD 3-Clause -->
+         </dependency>

Review Comment:
   Should update the binary assembly LICENCE file to cover this addition since 
it isnt ALv2.



##########
artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java:
##########
@@ -36,11 +34,12 @@
 import org.apache.activemq.artemis.nativo.jlibaio.LibaioContext;
 import org.apache.activemq.artemis.nativo.jlibaio.LibaioFile;
 import org.apache.activemq.artemis.utils.FileUtil;
+import picocli.CommandLine;
 
 /**
  * CLI action that creates a broker instance directory.
  */
-@Command(name = "create", description = "Create a new broker instance.")
[email protected](name = "create", description = "Create a new broker 
instance.")

Review Comment:
   Do these actually need to be prefixed? Wouldnt it just work the same as 
before if you simply switched the imports and left the types unchanged where 
the name is the same? If so the lines would then be shorter/nicer and the diff 
would be much simpler (just adding the s to make 'name' become 'names').
   
   Same for most of the other files.



##########
artemis-cli/src/main/java/org/apache/activemq/artemis/cli/Artemis.java:
##########
@@ -194,62 +186,102 @@ public static Object execute(boolean inputEnabled, 
boolean useSystemOut, File ar
     * Useful on test cases
     */
    private static Object internalExecute(File artemisHome, File 
artemisInstance, File etcFolder, String[] args) throws Exception {
-      return internalExecute(artemisHome, artemisInstance, etcFolder, args, 
ActionContext.system());
+      return internalExecute(artemisHome, artemisInstance, etcFolder, args, 
new ActionContext());
    }
 
    public static Object internalExecute(File artemisHome, File 
artemisInstance, File etcFolder, String[] args, ActionContext context) throws 
Exception {
-      Action action = builder(artemisInstance).build().parse(args);
-      action.setHomeValues(artemisHome, artemisInstance, etcFolder);
+      boolean isInstance = artemisInstance != null || 
System.getProperty("artemis.instance") != null;
+      CommandLine commandLine = buildCommand(isInstance, !isInstance);
+
+      Object userObject = parseAction(commandLine, args);
+      if (userObject instanceof Action) {
+         Action action = (Action) userObject;
+         action.setHomeValues(artemisHome, artemisInstance, etcFolder);
+         if (action.isVerbose()) {
+            context.out.print("Executing " + action.getClass().getName() + " 
");
+            for (String arg : args) {
+               context.out.print(arg + " ");
+            }
+            context.out.println();
+            context.out.println("Home::" + action.getBrokerHome() + ", 
Instance::" + action.getBrokerInstance());
+         }
 
-      if (action.isVerbose()) {
-         context.out.print("Executing " + action.getClass().getName() + " ");
-         for (String arg : args) {
-            context.out.print(arg + " ");
+         return action.execute(context);
+      } else {
+         if (userObject instanceof Runnable) {
+            ((Runnable) userObject).run();
+         }
+      }
+      return null;
+   }
+
+   /*
+    Pico-cli traditionally would execute user objects that implement Runnable.
+    However as we used airline before, we needed parse for the proper action.
+    This method here is parsing the arg and find the proper user object in the 
hierarchy of sub-commands
+    and return it to the caller.
+    */
+   private static Object parseAction(CommandLine line, String[] args) {
+      CommandLine.ParseResult parseResult = line.parseArgs(args);
+      if (parseResult != null) {
+         for (; parseResult.hasSubcommand(); parseResult = 
parseResult.subcommand()) {

Review Comment:
   While instead of for?



##########
artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Upgrade.java:
##########
@@ -106,8 +106,16 @@ public Object run(ActionContext context) throws Exception {
          throw new IOException(etcFolder + " does not exist for etc");
       }
 
+      StringBuffer javaOption = new StringBuffer();

Review Comment:
   StringBuilder?
   
   Name could be more obvious than the singular of the original plural list, 
e.g javaOptionsArgs...or javaOptionsString (and build the value here rather 
than later at the point it is passed).



##########
artemis-distribution/src/main/resources/bin/artemis:
##########
@@ -91,6 +91,9 @@ if $cygwin ; then
 fi
 
 exec "$JAVACMD" $JAVA_ARGS $ARTEMIS_CLUSTER_PROPS \
+    --add-opens java.base/java.lang=ALL-UNNAMED \
+    --add-opens java.base/java.io=ALL-UNNAMED \
+    --add-opens java.base/java.util=ALL-UNNAMED \

Review Comment:
   Ick. Maybe add a comment to say it is for the CLI stuff.
   Have you tried it on JDK17 and 21EA to check nothing different is needed 
there than on 11?



##########
docs/user-manual/en/using-server.md:
##########
@@ -50,6 +50,30 @@ The following highlights some important folders on the 
distribution:
 
 - `user-manual` - The user manual is placed under the web folder.
 
+## Artemis Shell
+
+You can activate the artemis shell by typing:
+
+```shell
+./artemis shell
+```
+
+This will start a JLine3 terminal with auto-completion:

Review Comment:
   Sentence fragment, seems to end mid:
   
   ---
   Also, this drop-in about the shell usage also doesnt really fit with the 
rest of the existing information on the page below that all assumes you are 
**not** using it. It could do with some elaboration / description linking and 
contrasting the different usages so the two routes are clear, and some new user 
actually following this page linearly isnt met with a sequence that essentially 
says 'do foo' followed by 'do this incompatible thing that requires you didnt 
do the earlier thing we already said'.



##########
artemis-cli/src/main/java/org/apache/activemq/artemis/cli/Artemis.java:
##########
@@ -194,62 +186,102 @@ public static Object execute(boolean inputEnabled, 
boolean useSystemOut, File ar
     * Useful on test cases
     */
    private static Object internalExecute(File artemisHome, File 
artemisInstance, File etcFolder, String[] args) throws Exception {
-      return internalExecute(artemisHome, artemisInstance, etcFolder, args, 
ActionContext.system());
+      return internalExecute(artemisHome, artemisInstance, etcFolder, args, 
new ActionContext());
    }
 
    public static Object internalExecute(File artemisHome, File 
artemisInstance, File etcFolder, String[] args, ActionContext context) throws 
Exception {
-      Action action = builder(artemisInstance).build().parse(args);
-      action.setHomeValues(artemisHome, artemisInstance, etcFolder);
+      boolean isInstance = artemisInstance != null || 
System.getProperty("artemis.instance") != null;
+      CommandLine commandLine = buildCommand(isInstance, !isInstance);
+
+      Object userObject = parseAction(commandLine, args);
+      if (userObject instanceof Action) {
+         Action action = (Action) userObject;
+         action.setHomeValues(artemisHome, artemisInstance, etcFolder);
+         if (action.isVerbose()) {
+            context.out.print("Executing " + action.getClass().getName() + " 
");
+            for (String arg : args) {
+               context.out.print(arg + " ");
+            }
+            context.out.println();
+            context.out.println("Home::" + action.getBrokerHome() + ", 
Instance::" + action.getBrokerInstance());
+         }
 
-      if (action.isVerbose()) {
-         context.out.print("Executing " + action.getClass().getName() + " ");
-         for (String arg : args) {
-            context.out.print(arg + " ");
+         return action.execute(context);
+      } else {
+         if (userObject instanceof Runnable) {
+            ((Runnable) userObject).run();
+         }

Review Comment:
   Maybe do something if it isnt? Should it ever get here otherwise?



##########
artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java:
##########
@@ -712,11 +711,15 @@ public Object run(ActionContext context) throws Exception 
{
       File logFolder = createDirectory("log", directory);
       File oomeDumpFile = new File(logFolder, "oom_dump.hprof");
 
-      if (javaOptions == null || javaOptions.length() == 0) {
-         javaOptions = "";
+      StringBuffer javaOption = new StringBuffer();

Review Comment:
   Same comment as other file. Same impl too...maybe add a helper.



##########
artemis-cli/src/main/java/org/apache/activemq/artemis/cli/Artemis.java:
##########
@@ -194,62 +186,102 @@ public static Object execute(boolean inputEnabled, 
boolean useSystemOut, File ar
     * Useful on test cases
     */
    private static Object internalExecute(File artemisHome, File 
artemisInstance, File etcFolder, String[] args) throws Exception {
-      return internalExecute(artemisHome, artemisInstance, etcFolder, args, 
ActionContext.system());
+      return internalExecute(artemisHome, artemisInstance, etcFolder, args, 
new ActionContext());
    }
 
    public static Object internalExecute(File artemisHome, File 
artemisInstance, File etcFolder, String[] args, ActionContext context) throws 
Exception {
-      Action action = builder(artemisInstance).build().parse(args);
-      action.setHomeValues(artemisHome, artemisInstance, etcFolder);
+      boolean isInstance = artemisInstance != null || 
System.getProperty("artemis.instance") != null;
+      CommandLine commandLine = buildCommand(isInstance, !isInstance);
+
+      Object userObject = parseAction(commandLine, args);
+      if (userObject instanceof Action) {
+         Action action = (Action) userObject;
+         action.setHomeValues(artemisHome, artemisInstance, etcFolder);
+         if (action.isVerbose()) {
+            context.out.print("Executing " + action.getClass().getName() + " 
");
+            for (String arg : args) {
+               context.out.print(arg + " ");
+            }
+            context.out.println();
+            context.out.println("Home::" + action.getBrokerHome() + ", 
Instance::" + action.getBrokerInstance());
+         }
 
-      if (action.isVerbose()) {
-         context.out.print("Executing " + action.getClass().getName() + " ");
-         for (String arg : args) {
-            context.out.print(arg + " ");
+         return action.execute(context);
+      } else {
+         if (userObject instanceof Runnable) {
+            ((Runnable) userObject).run();
+         }
+      }
+      return null;
+   }
+
+   /*
+    Pico-cli traditionally would execute user objects that implement Runnable.
+    However as we used airline before, we needed parse for the proper action.
+    This method here is parsing the arg and find the proper user object in the 
hierarchy of sub-commands
+    and return it to the caller.
+    */
+   private static Object parseAction(CommandLine line, String[] args) {
+      CommandLine.ParseResult parseResult = line.parseArgs(args);
+      if (parseResult != null) {
+         for (; parseResult.hasSubcommand(); parseResult = 
parseResult.subcommand()) {
          }
-         context.out.println();
-         context.out.println("Home::" + action.getBrokerHome() + ", 
Instance::" + action.getBrokerInstance());
       }
+      if (parseResult == null) {
+         throw new RuntimeException("Cannot match arg::" + 
Arrays.toString(args));
+      }
+      return parseResult.commandSpec().userObject();
+   }
+
+   public static CommandLine buildCommand(boolean includeInstanceCommands, 
boolean includeHomeCommands) {
+      return buildCommand(includeInstanceCommands, includeHomeCommands, true);
 
-      action.checkOptions(args);
-      return action.execute(context);
    }
 
-   private static CliBuilder<Action> builder(File artemisInstance) {
-      String instance = artemisInstance != null ? 
artemisInstance.getAbsolutePath() : System.getProperty("artemis.instance");
-      CliBuilder<Action> builder = 
Cli.<Action>builder("artemis").withDescription("ActiveMQ Artemis Command Line").
-         
withCommand(HelpAction.class).withCommand(Producer.class).withCommand(Transfer.class).withCommand(Consumer.class).
-         
withCommand(Browse.class).withCommand(Mask.class).withCommand(PrintVersion.class).withDefaultCommand(HelpAction.class);
-
-      builder.withGroup("perf").withDescription("Perf tools group (example 
./artemis perf client)")
-         .withDefaultCommand(PerfClientCommand.class)
-         .withCommands(PerfProducerCommand.class, PerfConsumerCommand.class, 
PerfClientCommand.class);
-
-      builder.withGroup("check").withDescription("Check tools group 
(node|queue) (example ./artemis check node)").
-         withDefaultCommand(HelpCheck.class).withCommands(NodeCheck.class, 
QueueCheck.class);
-
-      builder.withGroup("queue").withDescription("Queue tools group 
(create|delete|update|stat|purge) (example ./artemis queue create)").
-         withDefaultCommand(HelpQueue.class).withCommands(CreateQueue.class, 
DeleteQueue.class, UpdateQueue.class, StatQueue.class, PurgeQueue.class);
-
-      builder.withGroup("address").withDescription("Address tools group 
(create|delete|update|show) (example ./artemis address create)").
-         
withDefaultCommand(HelpAddress.class).withCommands(CreateAddress.class, 
DeleteAddress.class, UpdateAddress.class, ShowAddress.class);
-
-      if (instance != null) {
-         builder.withGroup("activation")
-            .withDescription("activation tools group (sync) (example ./artemis 
activation list)")
-            .withDefaultCommand(ActivationSequenceList.class)
-            .withCommands(ActivationSequenceList.class, 
ActivationSequenceSet.class);
-         builder.withGroup("data").withDescription("data tools group 
(print|imp|exp|encode|decode|compact|recover) (example ./artemis data print)").
-            
withDefaultCommand(HelpData.class).withCommands(RecoverMessages.class, 
PrintData.class, XmlDataExporter.class, XmlDataImporter.class, 
DecodeJournal.class, EncodeJournal.class, CompactJournal.class);
-         builder.withGroup("user").withDescription("default file-based user 
management (add|rm|list|reset) (example ./artemis user list)").
-                 
withDefaultCommand(HelpUser.class).withCommands(ListUser.class, AddUser.class, 
RemoveUser.class, ResetUser.class);
-         builder = builder.withCommands(Run.class, Stop.class, Kill.class, 
PerfJournal.class);
-      } else {
-         builder.withGroup("data").withDescription("data tools group 
(print|recover) (example ./artemis data print)").
-            
withDefaultCommand(HelpData.class).withCommands(RecoverMessages.class, 
PrintData.class);
-         builder = builder.withCommands(Create.class, Upgrade.class);
+   public static CommandLine buildCommand(boolean includeInstanceCommands, 
boolean includeHomeCommands, boolean includeShell) {
+      Artemis artemis = new Artemis();
+
+      CommandLine commandLine = new CommandLine(artemis);
+      artemis.setCommandLine(commandLine);
+
+      HelpAction help = new HelpAction();
+      help.setCommandLine(commandLine);
+      commandLine.addSubcommand(help);
+
+      commandLine.addSubcommand(new AutoCompletion());
+
+      if (includeShell) {
+         commandLine.addSubcommand(new Shell(commandLine));
+      }
+
+      commandLine.addSubcommand(new Producer()).addSubcommand(new 
Transfer()).addSubcommand(new Consumer()).addSubcommand(new 
Browse()).addSubcommand(new Mask()).addSubcommand(new PrintVersion());
+
+      commandLine.addSubcommand(new PerfGroup(commandLine));
+      commandLine.addSubcommand(new CheckGroup(commandLine));
+      commandLine.addSubcommand(new QueueGroup(commandLine));
+      commandLine.addSubcommand(new AddressGroup(commandLine));
+
+      if (includeInstanceCommands) {
+         commandLine.addSubcommand(new ActivationGroup(commandLine));
+         commandLine.addSubcommand(new DataGroup(commandLine));
+         commandLine.addSubcommand(new UserGroup(commandLine));
+
+         commandLine.addSubcommand(new Run());
+         commandLine.addSubcommand(new Stop());
+         commandLine.addSubcommand(new Kill());
+         commandLine.addSubcommand(new PerfJournal());
+      }
+
+      if (includeHomeCommands) {
+         if (!includeInstanceCommands) {
+            // Data is already preent in InstanceCommands

Review Comment:
   typo: preent



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to