This is an automated email from the ASF dual-hosted git repository.

paulk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/master by this push:
     new 91e166d513 GROOVY-8162: Update Groovysh to JLine3 (add support for 
additional builtin posix commands)
91e166d513 is described below

commit 91e166d513268d838f9e56649695969adf6880e5
Author: Paul King <[email protected]>
AuthorDate: Mon Aug 11 19:26:04 2025 +1000

    GROOVY-8162: Update Groovysh to JLine3 (add support for additional builtin 
posix commands)
---
 .../groovy/org/apache/groovy/groovysh/Main.groovy  | 167 ++++++++-------------
 .../groovy/groovysh/jline/GroovyBuiltins.groovy    |   4 +-
 .../groovysh/jline/GroovyConsoleEngine.groovy      |   4 +-
 .../groovysh/commands/ConsoleTestSupport.groovy    |   4 +-
 4 files changed, 67 insertions(+), 112 deletions(-)

diff --git 
a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/Main.groovy
 
b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/Main.groovy
index 12306156e9..6ed2dc62a8 100644
--- 
a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/Main.groovy
+++ 
b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/Main.groovy
@@ -80,29 +80,21 @@ import static org.jline.jansi.AnsiRenderer.render
 class Main {
     private static final MessageSource messages = new MessageSource(Main)
     public static final String INTERPRETER_MODE_PREFERENCE_KEY = 
'interpreterMode'
+    private static POSIX_FILE_CMDS = ['/tail', '/ls', '/head', '/grep', '/wc', 
'/sort', '/cat']
 
     @SuppressWarnings("resource")
     protected static class ExtraConsoleCommands extends JlineCommandRegistry 
implements CommandRegistry {
-        private LineReader reader
+        private final LineReader reader
         private final Supplier<Path> workDir
         private final Map<String, Object> variables
-        private PosixCommandsRegistry posix
+        private final PosixCommandsRegistry posix
+        private final Map<String, String[]> usage = [:]
 
-        ExtraConsoleCommands(Supplier<Path> workDir, Map<String, Object> 
variables) {
+        ExtraConsoleCommands(Supplier<Path> workDir, Map<String, Object> 
variables, LineReader reader) {
             super()
             this.workDir = workDir
             this.variables = variables
-            registerCommands([
-                '/tail' : new CommandMethods((Function) this::tail, 
this::optFileCompleter),
-                '/head' : new CommandMethods((Function) this::head, 
this::optFileCompleter),
-                '/ls'   : new CommandMethods((Function) this::ls, 
this::optFileCompleter),
-                '/clear': new CommandMethods((Function) this::clear, 
this::defaultCompleter),
-                '/echo' : new CommandMethods((Function) this::echo, 
this::defaultCompleter),
-                "/!"    : new CommandMethods((Function) this::shell, 
this::defaultCompleter)
-            ])
-        }
-
-        private void init() {
+            this.reader = reader
             def terminal = reader.terminal
             posix = new PosixCommandsRegistry(
                 terminal.input(),
@@ -111,9 +103,26 @@ class Main {
                 workDir.get(),
                 terminal,
                 variables::get)
-            posix.register('/tail', PosixCommands::tail)
-            posix.register('/head', PosixCommands::head)
-            posix.register('/ls', PosixCommands::ls)
+            def cmds = [
+                '/clear': new CommandMethods((Function) this::clear, 
this::defaultCompleter),
+                '/echo' : new CommandMethods((Function) this::echo, 
this::defaultCompleter),
+                "/!"    : new CommandMethods((Function) this::shell, 
this::defaultCompleter)
+            ]
+            POSIX_FILE_CMDS.each { String cmd ->
+                String orig = cmd[1..-1]
+                usage[cmd] = adjustUsage(orig, cmd)
+                posix.register(cmd, PosixCommands::"$orig")
+                cmds.put(cmd, new CommandMethods((Function) this::posix, 
this::optFileCompleter))
+            }
+            registerCommands(cmds)
+        }
+
+        private String[] adjustUsage(String from, String to) {
+            try {
+                posix.execute(from, [from, '--help'] as String[])
+            } catch (Options.HelpException e) {
+                e.message.readLines()*.replaceAll("$from ", "$to ") as String[]
+            }
         }
 
         @Override
@@ -121,10 +130,6 @@ class Main {
             'Console Commands'
         }
 
-        void setLineReader(LineReader reader) {
-            this.reader = reader
-        }
-
         private Terminal terminal() {
             return reader?.terminal
         }
@@ -133,63 +138,10 @@ class Main {
             [new ArgumentCompleter(NullCompleter.INSTANCE, new 
Completers.OptionCompleter(new Completers.FilesCompleter(workDir), 
this::commandOptions, 1))]
         }
 
-        private void tail(CommandInput input) {
-            def usage = new String[]{
-                "/tail - display last lines of files",
-                "Usage: /tail [-f] [-q] [-c # | -n #] [file ...]",
-                "  -? --help                    Show help",
-                "  -f --follow                  Do not stop at end of file",
-                "  -F --FOLLOW                  Follow and check for file 
renaming or rotation",
-                "  -n --lines=LINES             Number of lines to print",
-                "  -c --bytes=BYTES             Number of bytes to print",
-            }
+        private void posix(CommandInput input) {
             try {
-                parseOptions(usage, input.args())
-                posix.execute('/tail', input.args())
-            } catch (Exception e) {
-                saveException(e)
-            }
-        }
-
-        private void head(CommandInput input) {
-            def usage = new String[]{
-                "/head - display first lines of files",
-                "Usage: /head [-n lines | -c bytes] [file ...]",
-                "  -? --help                    Show help",
-                "  -n --lines=LINES             Print line counts",
-                "  -c --bytes=BYTES             Print byte counts"
-            }
-            try {
-                parseOptions(usage, input.args())
-                posix.execute('/head', input.args())
-            } catch (Exception e) {
-                saveException(e)
-            }
-        }
-
-        private void ls(CommandInput input) {
-            def usage = new String[]{
-                "/ls - list files",
-                "Usage: /ls [OPTIONS] [PATTERNS...]",
-                "  -? --help                show help",
-                "  -1                       list one entry per line",
-                "  -C                       multi-column output",
-                "     --color=WHEN          colorize the output, may be 
`always', `never' or `auto'",
-                "  -a                       list entries starting with .",
-                "  -F                       append file type indicators",
-                "  -m                       comma separated",
-                "  -l                       long listing",
-                "  -S                       sort by size",
-                "  -f                       output is not sorted",
-                "  -r                       reverse sort order",
-                "  -t                       sort by modification time",
-                "  -x                       sort horizontally",
-                "  -L                       list referenced file for links",
-                "  -h                       print sizes in human readable form"
-            }
-            try {
-                parseOptions(usage, input.args())
-                posix.execute('/ls', input.args())
+                parseOptions(usage[input.command()], input.args())
+                posix.execute(input.command(), [input.command(), 
*input.args()] as String[])
             } catch (Exception e) {
                 saveException(e)
             }
@@ -335,15 +287,7 @@ class Main {
             def interpreterMode = 
Boolean.parseBoolean(System.getProperty("groovysh.interpreterMode", "true"))
             scriptEngine.put('GROOVYSH_OPTIONS', [interpreterMode: 
interpreterMode])
             Printer printer = new DefaultPrinter(scriptEngine, configPath)
-            ConsoleEngine consoleEngine = new 
GroovyConsoleEngine(scriptEngine, printer, workDir, configPath)
-            consoleEngine.setConsoleOption('docs', new DocFinder())
 
-            CommandRegistry builtins = new GroovyBuiltins(scriptEngine, 
workDir, configPath, (String fun) ->
-                new ConsoleEngine.WidgetCreator(consoleEngine, fun)
-            )
-            def extra = new ExtraConsoleCommands(workDir, 
scriptEngine.variables)
-
-            // Command line highlighter
             scriptEngine.put(GroovyEngine.NANORC_VALUE, rootURL.toString())
             Path jnanorc = root.resolve('jnanorc')
             def commandHighlighter = SyntaxHighlighter.build(jnanorc, 
"COMMAND")
@@ -351,6 +295,33 @@ class Main {
             def groovyHighlighter = SyntaxHighlighter.build(jnanorc, "Groovy")
 
             CommandRegistry groovy = new GroovyCommands(scriptEngine, workDir, 
printer, groovyHighlighter)
+
+            LineReader reader = LineReaderBuilder.builder()
+                .terminal(terminal)
+                .parser(parser)
+                .variable(LineReader.SECONDARY_PROMPT_PATTERN, "%M%P > ")
+                .variable(LineReader.INDENTATION, 2)
+                .variable(LineReader.LIST_MAX, 100)
+                .variable(LineReader.HISTORY_FILE, 
configPath.getUserConfig('groovysh_history', true))
+                .option(Option.INSERT_BRACKET, true)
+                .option(Option.EMPTY_WORD_OPTIONS, false)
+                .option(Option.USE_FORWARD_SLASH, true)
+                .option(Option.DISABLE_EVENT_EXPANSION, true)
+                .build()
+            if (OSUtils.IS_WINDOWS) {
+                reader.setVariable(
+                    LineReader.BLINK_MATCHING_PAREN, 0) // if enabled cursor 
remains in begin parenthesis (gitbash)
+            }
+
+            def extra = new ExtraConsoleCommands(workDir, 
scriptEngine.variables, reader)
+
+            ConsoleEngine consoleEngine = new 
GroovyConsoleEngine(scriptEngine, printer, workDir, configPath, reader)
+            consoleEngine.setConsoleOption('docs', new DocFinder())
+
+            CommandRegistry builtins = new GroovyBuiltins(scriptEngine, 
workDir, configPath, reader, (String fun) ->
+                new ConsoleEngine.WidgetCreator(consoleEngine, fun)
+            )
+
             GroovySystemRegistry systemRegistry = new 
GroovySystemRegistry(parser, terminal, workDir, configPath).tap {
                 groupCommandsInHelp(false)
                 setCommandRegistries(extra, consoleEngine, builtins, groovy)
@@ -367,34 +338,14 @@ class Main {
                 if (!OSUtils.IS_WINDOWS) {
                     setSpecificHighlighter("/!", 
SyntaxHighlighter.build(jnanorc, "SH-REPL"))
                 }
-                addFileHighlight('/nano', '/less', '/slurp', '/load', '/save', 
'/head', '/tail', '/ls')
+                addFileHighlight('/nano', '/less', '/slurp', '/load', '/save', 
*POSIX_FILE_CMDS)
                 addFileHighlight('/classloader', null, ['-a', '--add'])
                 addExternalHighlighterRefresh(printer::refresh)
                 addExternalHighlighterRefresh(scriptEngine::refresh)
             }
 
-            // LineReader
-            LineReader reader = LineReaderBuilder.builder()
-                .terminal(terminal)
-                .completer(systemRegistry.completer())
-                .parser(parser)
-                .highlighter(highlighter)
-                .variable(LineReader.SECONDARY_PROMPT_PATTERN, "%M%P > ")
-                .variable(LineReader.INDENTATION, 2)
-                .variable(LineReader.LIST_MAX, 100)
-                .variable(LineReader.HISTORY_FILE, 
configPath.getUserConfig('groovysh_history', true))
-                .option(Option.INSERT_BRACKET, true)
-                .option(Option.EMPTY_WORD_OPTIONS, false)
-                .option(Option.USE_FORWARD_SLASH, true)
-                .option(Option.DISABLE_EVENT_EXPANSION, true)
-                .build()
-            if (OSUtils.IS_WINDOWS) {
-                reader.setVariable(
-                    LineReader.BLINK_MATCHING_PAREN, 0) // if enabled cursor 
remains in begin parenthesis (gitbash)
-            }
-
-            [consoleEngine, builtins, extra]*.setLineReader(reader)
-            extra.init()
+            reader.highlighter = highlighter
+            reader.completer = systemRegistry.completer()
 
             // widgets and console initialization
             new TailTipWidgets(reader, systemRegistry::commandDescription, 5, 
TipType.COMPLETER)
diff --git 
a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/jline/GroovyBuiltins.groovy
 
b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/jline/GroovyBuiltins.groovy
index b634448ba2..f75659c0b0 100644
--- 
a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/jline/GroovyBuiltins.groovy
+++ 
b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/jline/GroovyBuiltins.groovy
@@ -26,6 +26,7 @@ import org.jline.builtins.Options
 import org.jline.console.CommandInput
 import org.jline.console.CommandMethods
 import org.jline.console.impl.Builtins
+import org.jline.reader.LineReader
 import org.jline.reader.Widget
 
 import java.nio.file.Path
@@ -37,7 +38,7 @@ class GroovyBuiltins extends Builtins {
     private final Supplier<Path> workDir
     private final GroovyEngine engine
 
-    GroovyBuiltins(GroovyEngine engine, Supplier<Path> workDir, 
ConfigurationPath configPath, Function<String, Widget> widgetCreator) {
+    GroovyBuiltins(GroovyEngine engine, Supplier<Path> workDir, 
ConfigurationPath configPath, LineReader reader, Function<String, Widget> 
widgetCreator) {
         super(workDir, configPath, widgetCreator)
         this.workDir = workDir
         this.configPath = configPath
@@ -53,6 +54,7 @@ class GroovyBuiltins extends Builtins {
         def commandName = commandNames().collectEntries{ name ->
             [Command."${name.toUpperCase()}", '/' + name]
         }
+        setLineReader(reader)
         registerCommands(commandName, commandExecute)
     }
 
diff --git 
a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/jline/GroovyConsoleEngine.groovy
 
b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/jline/GroovyConsoleEngine.groovy
index df37e643c7..15cad9ebb1 100644
--- 
a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/jline/GroovyConsoleEngine.groovy
+++ 
b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/jline/GroovyConsoleEngine.groovy
@@ -22,6 +22,7 @@ import org.jline.builtins.ConfigurationPath
 import org.jline.console.Printer
 import org.jline.console.ScriptEngine
 import org.jline.console.impl.ConsoleEngineImpl
+import org.jline.reader.LineReader
 
 import java.nio.file.Path
 import java.util.function.Supplier
@@ -29,9 +30,10 @@ import java.util.function.Supplier
 class GroovyConsoleEngine extends ConsoleEngineImpl {
     private final Printer printer
 
-    GroovyConsoleEngine(ScriptEngine engine, Printer printer, Supplier<Path> 
workDir, ConfigurationPath configPath) {
+    GroovyConsoleEngine(ScriptEngine engine, Printer printer, Supplier<Path> 
workDir, ConfigurationPath configPath, LineReader reader) {
         super(Command.values().toSet() - Command.SLURP, engine, printer, 
workDir, configPath)
         this.printer = printer
+        setLineReader(reader)
         commandNames().each{ name -> rename(Command."${name.toUpperCase()}", 
"/$name") }
     }
 
diff --git 
a/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/commands/ConsoleTestSupport.groovy
 
b/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/commands/ConsoleTestSupport.groovy
index ac93d75776..5e21f0df7a 100644
--- 
a/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/commands/ConsoleTestSupport.groovy
+++ 
b/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/commands/ConsoleTestSupport.groovy
@@ -47,7 +47,7 @@ abstract class ConsoleTestSupport extends GroovyTestCase {
     protected DummyPrinter printer = new DummyPrinter(configPath)
     private highlighter = SyntaxHighlighter.build(root, "DUMMY")
     protected CommandRegistry groovy = new GroovyCommands(engine, null, 
printer, highlighter)
-    protected ConsoleEngine console = new GroovyConsoleEngine(engine, printer, 
null, configPath)
+    protected ConsoleEngine console
     protected CommandRegistry.CommandSession session = new 
CommandRegistry.CommandSession()
     protected LineReader reader
 
@@ -55,7 +55,7 @@ abstract class ConsoleTestSupport extends GroovyTestCase {
     void setUp() {
         super.setUp()
         reader = LineReaderBuilder.builder().parser(new 
DefaultParser(regexCommand: /\/?[a-zA-Z!]+\S*/)).build()
-        console.lineReader = reader
+        console = new GroovyConsoleEngine(engine, printer, null, configPath, 
reader)
     }
 
     static class DummyPrinter extends DefaultPrinter {

Reply via email to