You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2022/11/15 02:35:05 UTC
[groovy] branch GROOVY_4_0_X updated: GROOVY-10822: Groovysh DocCommand improvements
This is an automated email from the ASF dual-hosted git repository.
paulk pushed a commit to branch GROOVY_4_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/GROOVY_4_0_X by this push:
new 6932584b7b GROOVY-10822: Groovysh DocCommand improvements
6932584b7b is described below
commit 6932584b7b9f678ac7cb780bfbd1c7587eba78f7
Author: Paul King <pa...@asert.com.au>
AuthorDate: Sun Nov 13 23:54:47 2022 +1000
GROOVY-10822: Groovysh DocCommand improvements
---
.../groovy/groovysh/commands/DocCommand.groovy | 68 ++++++++++++++--------
.../groovy/groovysh/commands/DocCommand.properties | 2 +-
.../groovy-groovysh/src/spec/doc/groovysh.adoc | 61 +++++++++++++++++--
.../groovy/groovysh/commands/DocCommandTest.groovy | 15 +++--
4 files changed, 111 insertions(+), 35 deletions(-)
diff --git a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/commands/DocCommand.groovy b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/commands/DocCommand.groovy
index ecfaa9cbab..2f1bfb337a 100644
--- a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/commands/DocCommand.groovy
+++ b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/commands/DocCommand.groovy
@@ -33,6 +33,7 @@ class DocCommand extends CommandSupport {
private static final String ENV_BROWSER = 'BROWSER'
private static final String ENV_BROWSER_GROOVYSH = 'GROOVYSH_BROWSER'
+ private static final List<String> PRIMITIVES = ['boolean', 'byte', 'short', 'char', 'int', 'long', 'float', 'double']
private static final int TIMEOUT_CONN = 5 * 1000 // ms
private static final int TIMEOUT_READ = 5 * 1000 // ms
@@ -75,12 +76,17 @@ class DocCommand extends CommandSupport {
doc(args[0])
return
}
+ if (args?.size() == 2) {
+ doc(args[1], args[0])
+ return
+ }
fail(messages.format('error.unexpected_args', args ? args.join(' ') : 'no arguments'))
}
- void doc(String className) {
+ void doc(String className, String module = null) {
def normalizedClassName = normalizeClassName(className)
- def urls = urlsFor(normalizedClassName)
+ def normalizedModule = normalizeClassName(module ?: '')
+ def urls = urlsFor(normalizedClassName, normalizedModule)
if (urls.empty) {
fail("Documentation for \"${normalizedClassName}\" could not be found.")
}
@@ -93,7 +99,7 @@ class DocCommand extends CommandSupport {
}
protected String normalizeClassName(String className) {
- className.replace('"', '').replace("'", '')
+ className.replace('"', '').replace("'", '').replace('[', '%5B').replace(']', '%5D')
}
protected void browse(List urls) {
@@ -134,53 +140,65 @@ class DocCommand extends CommandSupport {
}
}
- protected List urlsFor(String className) {
+ protected List urlsFor(String className, String module = '') {
String groovyVersion = GroovySystem.version
- def path = className.replace('.', '/') + '.html'
+ String path = className.replace('.', '/') + '.html'
def urls = []
- if (className.matches(/^(groovy|org\.codehaus\.groovy|)\..+/)) {
- def url = new URL("http://docs.groovy-lang.org/$groovyVersion/html/gapi/$path")
- if (sendHEADRequest(url)) {
+ if (!module && className.matches(/^(groovy|org\.codehaus\.groovy|org\.apache\.groovy|)\..+/)) {
+ def url = new URL("https://docs.groovy-lang.org/$groovyVersion/html/gapi/$path")
+ if (sendHEADRequest(url, path)) {
urls << url
}
- } else {
- // Don't specify package names to not depend on a specific version of Java SE.
- // Java SE includes non-java(x) packages such as org.w3m.*, org.omg.*. org.xml.* for now
- // and new packages might be added in the future.
- def url = new URL("http://docs.oracle.com/javase/${simpleVersion()}/docs/api/$path")
- if (sendHEADRequest(url)) {
+ }
+ // Don't specify package names to not depend on a specific version of Java SE.
+ // Java SE includes non-java(x) packages such as org.w3m.*, org.omg.*. org.xml.* for now
+ // and new packages might be added in the future.
+ def url = new URL("https://docs.oracle.com/${versionPrefix(module)}/$path")
+ if (sendHEADRequest(url, path)) {
+ urls << url
+ } else if (!module) {
+ // if no module specified, fall back to JDK8 if java.base url wasn't found
+ url = new URL("https://docs.oracle.com/javase/8/docs/api/$path")
+ if (sendHEADRequest(url, path)) {
urls << url
- url = new URL("http://docs.groovy-lang.org/$groovyVersion/html/groovy-jdk/$path")
- if (sendHEADRequest(url)) {
- urls << url
- }
}
}
+ // make accessing enhancements for e.g. int[] or double[][] easier
+ if (PRIMITIVES.any{path.startsWith(it) }) {
+ path = "primitives-and-primitive-arrays/$path"
+ }
+ url = new URL("https://docs.groovy-lang.org/$groovyVersion/html/groovy-jdk/$path")
+ if (sendHEADRequest(url, path)) {
+ urls << url
+ }
urls
}
- private static simpleVersion() {
+ private static versionPrefix(String module) {
String javaVersion = System.getProperty('java.version')
if (javaVersion.startsWith('1.')) {
- javaVersion.split(/\./)[1]
+ 'javase/' + javaVersion.split(/\./)[1] + '/docs/api'
} else {
// java 9 and above
- javaVersion.replaceAll(/-.*/, '').split(/\./)[0]
+ def mod = module ?: 'java.base'
+ 'en/java/javase/' + javaVersion.replaceAll(/-.*/, '').split(/\./)[0] + "/docs/api/$mod"
}
}
- protected boolean sendHEADRequest(URL url) {
+ protected boolean sendHEADRequest(URL url, String path = null) {
try {
HttpURLConnection conn = url.openConnection() as HttpURLConnection
conn.requestMethod = 'HEAD'
conn.connectTimeout = TIMEOUT_CONN
conn.readTimeout = TIMEOUT_READ
conn.instanceFollowRedirects = true
-
- return conn.responseCode == 200
-
+ def code = conn.responseCode
+ // if not found, redirects to search page, which we don't count as successful
+ // if no path given (legacy calls from third parties), treat all redirects as suspicious
+ boolean successfulRedirect = path ? conn.URL.toString().endsWith(path) : url.toString() == conn.URL.toString()
+ return code == 200 && successfulRedirect
} catch (IOException e) {
fail "Sending a HEAD request to $url failed (${e}). Please check your network settings."
}
diff --git a/subprojects/groovy-groovysh/src/main/resources/org/apache/groovy/groovysh/commands/DocCommand.properties b/subprojects/groovy-groovysh/src/main/resources/org/apache/groovy/groovysh/commands/DocCommand.properties
index a8498c4bc3..b8ea877c53 100644
--- a/subprojects/groovy-groovysh/src/main/resources/org/apache/groovy/groovysh/commands/DocCommand.properties
+++ b/subprojects/groovy-groovysh/src/main/resources/org/apache/groovy/groovysh/commands/DocCommand.properties
@@ -17,5 +17,5 @@
# under the License.
#
command.description=Open a browser window displaying the doc for the argument
-command.usage=[<class>]
+command.usage=[<module>] <class>
command.help=Opens a browser window displaying the doc for the argument.
diff --git a/subprojects/groovy-groovysh/src/spec/doc/groovysh.adoc b/subprojects/groovy-groovysh/src/spec/doc/groovysh.adoc
index 200412d6c1..a9acc27b8d 100644
--- a/subprojects/groovy-groovysh/src/spec/doc/groovysh.adoc
+++ b/subprojects/groovy-groovysh/src/spec/doc/groovysh.adoc
@@ -395,19 +395,72 @@ Create an alias.
[[GroovyShell-doc]]
===== `doc`
-Opens a browser with documentation for the provided class. For example:
+Opens a browser with documentation for the provided class.
+For example, we can get both the Javadoc and GDK enhancements doc for `java.util.List` (shown running on JDK17):
+
+[subs=attributes]
----
groovy:000> :doc java.util.List
-http://docs.oracle.com/javase/7/docs/api/java/util/List.html
-http://docs.groovy-lang.org/2.4.2-SNAPSHOT/html/groovy-jdk/java/util/List.html
+https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/List.html
+https://docs.groovy-lang.org/{groovy-full-version}/html/groovy-jdk/java/util/List.html
----
-will open two windows (or tabs, depending on your browser):
+This will print the documentation URLs found and open two windows (or tabs, depending on your browser):
* one for the JDK documentation
* one for the GDK documentation
+By default, for Java classes, the `java.base` module is assumed. You can specify an optional module
+for other cases (shown running on JDK17):
+
+----
+groovy:000> :doc java.scripting javax.script.ScriptContext
+https://docs.oracle.com/en/java/javase/17/docs/api/java.scripting/javax/script/ScriptContext.html
+----
+
+For backwards compatibility, if no module is specified when searching for Java classes, and no class is found in the `java.base` module, an additional attempt is made to find documentation for the class in the JDK8 (pre-module) Javadoc:
+
+----
+groovy:000> :doc javax.script.ScriptContext
+https://docs.oracle.com/javase/8/docs/api/javax/script/ScriptContext.html
+----
+
+To get the Groovydoc for `groovy.ant.AntBuilder` and `groovy.xml.XmlSlurper`:
+
+[subs=attributes]
+----
+groovy:000> :doc groovy.ant.AntBuilder
+https://docs.groovy-lang.org/{groovy-full-version}/html/gapi/groovy/ant/AntBuilder.html
+groovy:000> :doc groovy.xml.XmlSlurper
+https://docs.groovy-lang.org/{groovy-full-version}/html/gapi/groovy/xml/XmlSlurper.html
+----
+
+To get both the Groovydoc and GDK enhancements doc for `groovy.lang.Closure` and `groovy.sql.GroovyResultSet`:
+
+[subs=attributes]
+----
+groovy:000> :doc groovy.lang.Closure
+https://docs.groovy-lang.org/{groovy-full-version}/html/gapi/groovy/lang/Closure.html
+https://docs.groovy-lang.org/{groovy-full-version}/html/groovy-jdk/groovy/lang/Closure.html
+groovy:000> :doc groovy.sql.GroovyResultSet
+https://docs.groovy-lang.org/{groovy-full-version}/html/gapi/groovy/sql/GroovyResultSet.html
+https://docs.groovy-lang.org/{groovy-full-version}/html/groovy-jdk/groovy/sql/GroovyResultSet.html
+----
+
+Documentation is also available for the GDK enhancements to primitive arrays and arrays of arrays:
+
+[subs=attributes]
+----
+groovy:000> :doc int[]
+https://docs.groovy-lang.org/{groovy-full-version}/html/groovy-jdk/primitives-and-primitive-arrays/int%5B%5D.html
+groovy:000> :doc double[][]
+https://docs.groovy-lang.org/{groovy-full-version}/html/groovy-jdk/primitives-and-primitive-arrays/double%5B%5D%5B%5D.html
+----
+
+NOTE: In contexts where opening a browser may not be desirable, e.g. on a CI server,
+this command can be disabled by setting the `groovysh.disableDocCommand` system property to `true`.
+
[[GroovyShell-set]]
===== `set`
diff --git a/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/commands/DocCommandTest.groovy b/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/commands/DocCommandTest.groovy
index fa9f2c6ad6..1ce13d17c9 100644
--- a/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/commands/DocCommandTest.groovy
+++ b/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/commands/DocCommandTest.groovy
@@ -36,10 +36,15 @@ class DocCommandTest extends CommandTestSupport {
void testUrlsForJavaOnlyClass() {
def command = docCommandWithSendHEADRequestReturnValueOf { !it.host.contains('docs.groovy-lang.org') }
+ // no module specified
def urls = command.urlsFor('org.ietf.jgss.GSSContext')
+ assert urls ==
+ [new URL("https://docs.oracle.com/javase/8/docs/api/org/ietf/jgss/GSSContext.html")]
+ // explicit module given
+ urls = command.urlsFor('org.ietf.jgss.GSSContext', 'java.security.jgss')
assert urls ==
- [new URL("http://docs.oracle.com/javase/${simpleJavaVersion()}/docs/api/org/ietf/jgss/GSSContext.html")]
+ [new URL("https://docs.oracle.com/en/java/javase/${simpleJavaVersion()}/docs/api/java.security.jgss/org/ietf/jgss/GSSContext.html")]
}
void testUrlsForJavaClass() {
@@ -48,17 +53,17 @@ class DocCommandTest extends CommandTestSupport {
def urls = command.urlsFor('java.util.List')
assert urls ==
- [new URL("http://docs.oracle.com/javase/${simpleJavaVersion()}/docs/api/java/util/List.html"),
- new URL("http://docs.groovy-lang.org/$GroovySystem.version/html/groovy-jdk/java/util/List.html")]
+ [new URL("https://docs.oracle.com/en/java/javase/${simpleJavaVersion()}/docs/api/java.base/java/util/List.html"),
+ new URL("https://docs.groovy-lang.org/$GroovySystem.version/html/groovy-jdk/java/util/List.html")]
}
void testUrlsForGroovyClass() {
def command = docCommandWithSendHEADRequestReturnValueOf { true }
- def urls = command.urlsFor('groovy.Dummy')
+ def urls = command.urlsFor('groovy.console.TextNode')
assert urls ==
- [new URL("http://docs.groovy-lang.org/$GroovySystem.version/html/gapi/groovy/Dummy.html")]
+ [new URL("https://docs.groovy-lang.org/$GroovySystem.version/html/gapi/groovy/console/TextNode.html")]
}
void testUrlsForWithUnknownClass() {