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

commit a94a90e3c39b36573dc01da83c3c41d28bab3594
Author: Paul King <pa...@asert.com.au>
AuthorDate: Wed Aug 20 21:15:07 2025 +1000

    GROOVY-8162: Update Groovysh to JLine3 (further /doc improvements)
---
 .../groovy/org/apache/groovy/groovysh/Main.groovy  |   8 +-
 .../groovy/groovysh/jline/GroovyCommands.groovy    | 107 +++++++++++++++++++++
 .../groovysh/jline/GroovyConsoleEngine.groovy      |   2 +-
 .../apache/groovy/groovysh/util/DocFinder.groovy   |  25 ++++-
 4 files changed, 135 insertions(+), 7 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 3ca95a99fd..ffaa9a217c 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
@@ -363,7 +363,9 @@ class Main {
             // ScriptEngine and command registries
             GroovyEngine scriptEngine = new GroovyEngine()
             scriptEngine.put('ROOT', rootURL.toString())
-            scriptEngine.put('CONSOLE_OPTIONS', [:])
+            if (!scriptEngine.hasVariable('CONSOLE_OPTIONS')) {
+                scriptEngine.put('CONSOLE_OPTIONS', [:])
+            }
             def interpreterMode = 
Boolean.parseBoolean(System.getProperty("groovysh.interpreterMode", "true"))
             scriptEngine.put('GROOVYSH_OPTIONS', [interpreterMode: 
interpreterMode])
             Printer printer = new DefaultPrinter(scriptEngine, configPath)
@@ -436,9 +438,9 @@ class Main {
             KeyMap<Binding> keyMap = reader.keyMaps.get("main")
             keyMap.bind(new Reference(Widgets.TAILTIP_TOGGLE), KeyMap.alt("s"))
             keyMap.bind(new Reference(Widgets.AUTOSUGGEST_TOGGLE), 
KeyMap.alt("v"))
-            def init = configPath.getUserConfig('groovysh_init')
+            def init = configPath.getUserConfig('groovysh_init.groovy')
             if (init) {
-                systemRegistry.setConsoleOption() // 
initialize(configPath.getUserConfig('groovysh_init').toFile())
+                systemRegistry.initialize(init.toFile())
             }
 
             if (options.q) {
diff --git 
a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/jline/GroovyCommands.groovy
 
b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/jline/GroovyCommands.groovy
index a4f23ca4a6..1a6f630c22 100644
--- 
a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/jline/GroovyCommands.groovy
+++ 
b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/jline/GroovyCommands.groovy
@@ -43,6 +43,7 @@ import org.jline.reader.impl.completer.NullCompleter
 import org.jline.reader.impl.completer.StringsCompleter
 import org.jline.utils.AttributedString
 
+import java.awt.Desktop
 import java.awt.event.ActionListener
 import java.lang.reflect.Method
 import java.nio.charset.Charset
@@ -64,6 +65,7 @@ class GroovyCommands extends JlineCommandRegistry implements 
CommandRegistry {
     private final Map<String, Tuple4<Function, Function, Function, 
List<String>>> commands = [
         '/inspect'     : new Tuple4<>(this::inspect, this::inspectCompleter, 
this::inspectCmdDesc, ['display/browse object info on terminal/object 
browser']),
         '/console'     : new Tuple4<>(this::console, this::defCompleter, 
this::defCmdDesc, ['launch Groovy console']),
+        '/doc'         : new Tuple4<>(this::doc, this::defCompleter, 
this::defCmdDesc, ['display documentation']),
         '/grab'        : new Tuple4<>(this::grab, this::grabCompleter, 
this::grabCmdDesc, ['add maven repository dependencies to classpath']),
         '/classloader' : new Tuple4<>(this::classLoader, 
this::classloaderCompleter, this::classLoaderCmdDesc, ['display/manage Groovy 
classLoader data']),
         '/imports'     : new Tuple4<>(this::importsCommand, 
this::importsCompleter, this::nameDeleteCmdDesc, ['show/delete import 
statements']),
@@ -231,6 +233,111 @@ class GroovyCommands extends JlineCommandRegistry 
implements CommandRegistry {
         loadFile(engine, workDir.get().resolve(arg).toFile(), merge)
     }
 
+    private static final String VAR_CONSOLE_OPTIONS = "CONSOLE_OPTIONS"
+
+    private Map<String, Object> consoleOption(String key) {
+        def opts = engine.hasVariable(VAR_CONSOLE_OPTIONS)
+            ? (Map<String, Object>) engine.get(VAR_CONSOLE_OPTIONS)
+            : new HashMap<>()
+        opts[key]
+    }
+
+    def doc(CommandInput input) {
+        def usage = new String[]{
+            "/doc -  open document on browser",
+            "Usage: /doc [OBJECT]",
+            "  -? --help                       Displays command help"
+        }
+        try {
+            parseOptions(usage, input.xargs());
+            if (input.xargs().length == 0) {
+                return null
+            }
+            if (!Desktop.isDesktopSupported()) {
+                throw new IllegalStateException("Desktop is not supported!")
+            }
+            Map<String, Object> docs
+            try {
+                docs = consoleOption("docs")
+            } catch (Exception e) {
+                Exception exception = new IllegalStateException("Bad documents 
configuration!")
+                exception.addSuppressed(e)
+                throw exception
+            }
+            if (docs == null) {
+                throw new IllegalStateException("No documents configuration!")
+            }
+            boolean done = false
+            Object arg = input.xargs()[0]
+            if (arg instanceof String) {
+                def addresses = []
+                addresses += docs.get(input.args()[0])
+                addresses.each { address ->
+                    if (address != null) {
+                        done = true
+                        if (urlExists(address)) {
+                            Desktop.getDesktop().browse(new URI(address))
+                        } else {
+                            throw new IllegalArgumentException("Document not 
found: " + address)
+                        }
+                    }
+                }
+            }
+            if (!done) {
+                String name
+                if (arg instanceof String && ((String) 
arg).matches("([a-z]+\\.)+[A-Z][a-zA-Z]+")) {
+                    name = (String) arg
+                } else {
+                    name = arg.getClass().getCanonicalName()
+                }
+                name = name.replaceAll("\\.", "/") + ".html"
+                Object doc = null
+                for (Map.Entry<String, Object> entry : docs.entrySet()) {
+                    if (name.matches(entry.getKey())) {
+                        doc = entry.getValue()
+                        break
+                    }
+                }
+                if (doc == null) {
+                    throw new IllegalArgumentException("No document 
configuration for " + name)
+                }
+                String url = name
+                if (doc instanceof Collection) {
+                    for (Object o : (Collection<?>) doc) {
+                        url = o + name
+                        if (urlExists(url)) {
+                            Desktop.getDesktop().browse(new URI(url))
+                            done = true
+                        }
+                    }
+                } else {
+                    url = doc + name
+                    if (urlExists(url)) {
+                        Desktop.getDesktop().browse(new URI(url))
+                        done = true
+                    }
+                }
+                if (!done) {
+                    throw new IllegalArgumentException("Document not found: " 
+ url)
+                }
+            }
+        } catch (Exception e) {
+            saveException(e)
+        }
+        return null
+    }
+
+    private boolean urlExists(String weburl) {
+        try {
+            URL url = URI.create(weburl).toURL()
+            HttpURLConnection huc = (HttpURLConnection) url.openConnection()
+            huc.setRequestMethod("HEAD")
+            return huc.getResponseCode() == HttpURLConnection.HTTP_OK
+        } catch (Exception ignore) {
+            return false
+        }
+    }
+
     def slurpcmd(CommandInput input) {
         checkArgCount(input, [0, 1, 2, 3, 4])
         if (maybePrintHelp(input, '/slurp')) return
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 15cad9ebb1..4c1f938888 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
@@ -31,7 +31,7 @@ class GroovyConsoleEngine extends ConsoleEngineImpl {
     private final Printer printer
 
     GroovyConsoleEngine(ScriptEngine engine, Printer printer, Supplier<Path> 
workDir, ConfigurationPath configPath, LineReader reader) {
-        super(Command.values().toSet() - Command.SLURP, engine, printer, 
workDir, configPath)
+        super(Command.values().toSet() - Command.SLURP - Command.DOC, engine, 
printer, workDir, configPath)
         this.printer = printer
         setLineReader(reader)
         commandNames().each{ name -> rename(Command."${name.toUpperCase()}", 
"/$name") }
diff --git 
a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/util/DocFinder.groovy
 
b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/util/DocFinder.groovy
index 83fb135a9d..c77a560a41 100644
--- 
a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/util/DocFinder.groovy
+++ 
b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/util/DocFinder.groovy
@@ -18,11 +18,25 @@
  */
 package org.apache.groovy.groovysh.util
 
-// TODO previous version had browseWithAWT as potential fallback if no native 
browser found.
-// It also provided a list of URLs including GDK links. It would be nice to 
give those as options (or fire them all off).
 class DocFinder extends HashMap<String, Object> {
+    private static boolean exists(String url) {
+        try {
+            (url.toURL().openConnection() as HttpURLConnection).with {
+                requestMethod = 'HEAD'
+                connectTimeout = 5000
+                readTimeout = 5000
+                return responseCode in 200..399
+            }
+        } catch (Exception e) {
+            return false
+        }
+    }
+
     @Override
     Object get(Object key) {
+        if (containsKey(key)) {
+            return super.get(key)
+        }
         def groovyVersion = GroovySystem.getVersion()
         Class clazz = Eval.me("${key}")
         def name = clazz.name
@@ -32,6 +46,11 @@ class DocFinder extends HashMap<String, Object> {
         }
         def majorVersion = System.getProperty('java.version').tokenize('.')[0]
         def module = clazz.module?.name ?: 'java.base'
-        
"https://docs.oracle.com/en/java/javase/${majorVersion}/docs/api/${module}/${path}".toString()
+        def result = 
["https://docs.oracle.com/en/java/javase/${majorVersion}/docs/api/${module}/${path}".toString()]
+        def gdk = "https://docs.groovy-lang.org/latest/html/groovy-jdk/${path}";
+        if (exists(gdk)) {
+            result << gdk.toString()
+        }
+        result
     }
 }

Reply via email to