├── .gitattributes
├── .gitignore
├── .mvn
└── wrapper
│ ├── maven-wrapper.jar
│ └── maven-wrapper.properties
├── LICENSE
├── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
├── resources
└── console-screenshot.png
└── src
├── main
├── java
│ └── com
│ │ └── github
│ │ └── gaborbata
│ │ └── console
│ │ ├── GroovyWebConsoleApplication.java
│ │ └── controller
│ │ └── GroovyConsoleController.java
└── resources
│ ├── application.properties
│ └── public
│ └── console
│ ├── css
│ ├── ambiance.css
│ ├── codemirror.css
│ └── console.css
│ ├── examples
│ ├── get-environment-info.groovy
│ ├── is-it-friday.groovy
│ └── list-spring-beans.groovy
│ ├── images
│ └── console.png
│ ├── index.html
│ └── js
│ ├── codemirror.js
│ ├── console.js
│ ├── groovy.js
│ ├── javascript.js
│ ├── jquery-3.3.1.min.js
│ ├── jquery.blockui.js
│ ├── lz-string.js
│ └── matchbrackets.js
└── test
└── java
└── com
└── github
└── gaborbata
└── console
├── configuration
└── JGivenConfig.java
└── controller
├── GroovyConsoleControllerTest.java
└── stage
└── GroovyConsoleControllerStage.java
/.gitattributes:
--------------------------------------------------------------------------------
1 | * linguist-vendored=false
2 | src/main/resources/**/* linguist-vendored
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | !.mvn/wrapper/maven-wrapper.jar
3 |
4 | ### STS ###
5 | .apt_generated
6 | .classpath
7 | .factorypath
8 | .project
9 | .settings
10 | .springBeans
11 |
12 | ### IntelliJ IDEA ###
13 | .idea
14 | *.iws
15 | *.iml
16 | *.ipr
17 |
18 | ### NetBeans ###
19 | nbproject/private/
20 | build/
21 | nbbuild/
22 | dist/
23 | nbdist/
24 | .nb-gradle/
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gaborbata/groovy-web-console/32e6ea4686605472f90e3045f13f2e2fc80b2692/.mvn/wrapper/maven-wrapper.jar
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.2/apache-maven-3.5.2-bin.zip
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Gabor Bata
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Groovy Web Console
2 |
3 | Overview
4 | --------
5 | This application is intended for embedding a Groovy console in a Spring Boot web application
6 | which then could access the application context (for testing purposes).
7 |
8 | 
9 |
10 | How to compile
11 | --------------
12 |
13 | mvnw clean package
14 |
15 | Usage
16 | -----
17 | Java 17 or later is required to start the application.
18 |
19 | * Start server: `mvnw spring-boot:run` or `java -jar target/groovy-web-console-1.0.0-SNAPSHOT.jar`
20 | * Access the console: `http://localhost:8080/console`
21 |
--------------------------------------------------------------------------------
/mvnw:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # ----------------------------------------------------------------------------
3 | # Licensed to the Apache Software Foundation (ASF) under one
4 | # or more contributor license agreements. See the NOTICE file
5 | # distributed with this work for additional information
6 | # regarding copyright ownership. The ASF licenses this file
7 | # to you under the Apache License, Version 2.0 (the
8 | # "License"); you may not use this file except in compliance
9 | # with the License. You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing,
14 | # software distributed under the License is distributed on an
15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | # KIND, either express or implied. See the License for the
17 | # specific language governing permissions and limitations
18 | # under the License.
19 | # ----------------------------------------------------------------------------
20 |
21 | # ----------------------------------------------------------------------------
22 | # Maven2 Start Up Batch script
23 | #
24 | # Required ENV vars:
25 | # ------------------
26 | # JAVA_HOME - location of a JDK home dir
27 | #
28 | # Optional ENV vars
29 | # -----------------
30 | # M2_HOME - location of maven2's installed home dir
31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven
32 | # e.g. to debug Maven itself, use
33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files
35 | # ----------------------------------------------------------------------------
36 |
37 | if [ -z "$MAVEN_SKIP_RC" ] ; then
38 |
39 | if [ -f /etc/mavenrc ] ; then
40 | . /etc/mavenrc
41 | fi
42 |
43 | if [ -f "$HOME/.mavenrc" ] ; then
44 | . "$HOME/.mavenrc"
45 | fi
46 |
47 | fi
48 |
49 | # OS specific support. $var _must_ be set to either true or false.
50 | cygwin=false;
51 | darwin=false;
52 | mingw=false
53 | case "`uname`" in
54 | CYGWIN*) cygwin=true ;;
55 | MINGW*) mingw=true;;
56 | Darwin*) darwin=true
57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
59 | if [ -z "$JAVA_HOME" ]; then
60 | if [ -x "/usr/libexec/java_home" ]; then
61 | export JAVA_HOME="`/usr/libexec/java_home`"
62 | else
63 | export JAVA_HOME="/Library/Java/Home"
64 | fi
65 | fi
66 | ;;
67 | esac
68 |
69 | if [ -z "$JAVA_HOME" ] ; then
70 | if [ -r /etc/gentoo-release ] ; then
71 | JAVA_HOME=`java-config --jre-home`
72 | fi
73 | fi
74 |
75 | if [ -z "$M2_HOME" ] ; then
76 | ## resolve links - $0 may be a link to maven's home
77 | PRG="$0"
78 |
79 | # need this for relative symlinks
80 | while [ -h "$PRG" ] ; do
81 | ls=`ls -ld "$PRG"`
82 | link=`expr "$ls" : '.*-> \(.*\)$'`
83 | if expr "$link" : '/.*' > /dev/null; then
84 | PRG="$link"
85 | else
86 | PRG="`dirname "$PRG"`/$link"
87 | fi
88 | done
89 |
90 | saveddir=`pwd`
91 |
92 | M2_HOME=`dirname "$PRG"`/..
93 |
94 | # make it fully qualified
95 | M2_HOME=`cd "$M2_HOME" && pwd`
96 |
97 | cd "$saveddir"
98 | # echo Using m2 at $M2_HOME
99 | fi
100 |
101 | # For Cygwin, ensure paths are in UNIX format before anything is touched
102 | if $cygwin ; then
103 | [ -n "$M2_HOME" ] &&
104 | M2_HOME=`cygpath --unix "$M2_HOME"`
105 | [ -n "$JAVA_HOME" ] &&
106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
107 | [ -n "$CLASSPATH" ] &&
108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
109 | fi
110 |
111 | # For Migwn, ensure paths are in UNIX format before anything is touched
112 | if $mingw ; then
113 | [ -n "$M2_HOME" ] &&
114 | M2_HOME="`(cd "$M2_HOME"; pwd)`"
115 | [ -n "$JAVA_HOME" ] &&
116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
117 | # TODO classpath?
118 | fi
119 |
120 | if [ -z "$JAVA_HOME" ]; then
121 | javaExecutable="`which javac`"
122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
123 | # readlink(1) is not available as standard on Solaris 10.
124 | readLink=`which readlink`
125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
126 | if $darwin ; then
127 | javaHome="`dirname \"$javaExecutable\"`"
128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
129 | else
130 | javaExecutable="`readlink -f \"$javaExecutable\"`"
131 | fi
132 | javaHome="`dirname \"$javaExecutable\"`"
133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'`
134 | JAVA_HOME="$javaHome"
135 | export JAVA_HOME
136 | fi
137 | fi
138 | fi
139 |
140 | if [ -z "$JAVACMD" ] ; then
141 | if [ -n "$JAVA_HOME" ] ; then
142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
143 | # IBM's JDK on AIX uses strange locations for the executables
144 | JAVACMD="$JAVA_HOME/jre/sh/java"
145 | else
146 | JAVACMD="$JAVA_HOME/bin/java"
147 | fi
148 | else
149 | JAVACMD="`which java`"
150 | fi
151 | fi
152 |
153 | if [ ! -x "$JAVACMD" ] ; then
154 | echo "Error: JAVA_HOME is not defined correctly." >&2
155 | echo " We cannot execute $JAVACMD" >&2
156 | exit 1
157 | fi
158 |
159 | if [ -z "$JAVA_HOME" ] ; then
160 | echo "Warning: JAVA_HOME environment variable is not set."
161 | fi
162 |
163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
164 |
165 | # traverses directory structure from process work directory to filesystem root
166 | # first directory with .mvn subdirectory is considered project base directory
167 | find_maven_basedir() {
168 |
169 | if [ -z "$1" ]
170 | then
171 | echo "Path not specified to find_maven_basedir"
172 | return 1
173 | fi
174 |
175 | basedir="$1"
176 | wdir="$1"
177 | while [ "$wdir" != '/' ] ; do
178 | if [ -d "$wdir"/.mvn ] ; then
179 | basedir=$wdir
180 | break
181 | fi
182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc)
183 | if [ -d "${wdir}" ]; then
184 | wdir=`cd "$wdir/.."; pwd`
185 | fi
186 | # end of workaround
187 | done
188 | echo "${basedir}"
189 | }
190 |
191 | # concatenates all lines of a file
192 | concat_lines() {
193 | if [ -f "$1" ]; then
194 | echo "$(tr -s '\n' ' ' < "$1")"
195 | fi
196 | }
197 |
198 | BASE_DIR=`find_maven_basedir "$(pwd)"`
199 | if [ -z "$BASE_DIR" ]; then
200 | exit 1;
201 | fi
202 |
203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
204 | echo $MAVEN_PROJECTBASEDIR
205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
206 |
207 | # For Cygwin, switch paths to Windows format before running java
208 | if $cygwin; then
209 | [ -n "$M2_HOME" ] &&
210 | M2_HOME=`cygpath --path --windows "$M2_HOME"`
211 | [ -n "$JAVA_HOME" ] &&
212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
213 | [ -n "$CLASSPATH" ] &&
214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
215 | [ -n "$MAVEN_PROJECTBASEDIR" ] &&
216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
217 | fi
218 |
219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
220 |
221 | exec "$JAVACMD" \
222 | $MAVEN_OPTS \
223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
226 |
--------------------------------------------------------------------------------
/mvnw.cmd:
--------------------------------------------------------------------------------
1 | @REM ----------------------------------------------------------------------------
2 | @REM Licensed to the Apache Software Foundation (ASF) under one
3 | @REM or more contributor license agreements. See the NOTICE file
4 | @REM distributed with this work for additional information
5 | @REM regarding copyright ownership. The ASF licenses this file
6 | @REM to you under the Apache License, Version 2.0 (the
7 | @REM "License"); you may not use this file except in compliance
8 | @REM with the License. You may obtain a copy of the License at
9 | @REM
10 | @REM http://www.apache.org/licenses/LICENSE-2.0
11 | @REM
12 | @REM Unless required by applicable law or agreed to in writing,
13 | @REM software distributed under the License is distributed on an
14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | @REM KIND, either express or implied. See the License for the
16 | @REM specific language governing permissions and limitations
17 | @REM under the License.
18 | @REM ----------------------------------------------------------------------------
19 |
20 | @REM ----------------------------------------------------------------------------
21 | @REM Maven2 Start Up Batch script
22 | @REM
23 | @REM Required ENV vars:
24 | @REM JAVA_HOME - location of a JDK home dir
25 | @REM
26 | @REM Optional ENV vars
27 | @REM M2_HOME - location of maven2's installed home dir
28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
31 | @REM e.g. to debug Maven itself, use
32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
34 | @REM ----------------------------------------------------------------------------
35 |
36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
37 | @echo off
38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
40 |
41 | @REM set %HOME% to equivalent of $HOME
42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
43 |
44 | @REM Execute a user defined script before this one
45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending
47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
49 | :skipRcPre
50 |
51 | @setlocal
52 |
53 | set ERROR_CODE=0
54 |
55 | @REM To isolate internal variables from possible post scripts, we use another setlocal
56 | @setlocal
57 |
58 | @REM ==== START VALIDATION ====
59 | if not "%JAVA_HOME%" == "" goto OkJHome
60 |
61 | echo.
62 | echo Error: JAVA_HOME not found in your environment. >&2
63 | echo Please set the JAVA_HOME variable in your environment to match the >&2
64 | echo location of your Java installation. >&2
65 | echo.
66 | goto error
67 |
68 | :OkJHome
69 | if exist "%JAVA_HOME%\bin\java.exe" goto init
70 |
71 | echo.
72 | echo Error: JAVA_HOME is set to an invalid directory. >&2
73 | echo JAVA_HOME = "%JAVA_HOME%" >&2
74 | echo Please set the JAVA_HOME variable in your environment to match the >&2
75 | echo location of your Java installation. >&2
76 | echo.
77 | goto error
78 |
79 | @REM ==== END VALIDATION ====
80 |
81 | :init
82 |
83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
84 | @REM Fallback to current working directory if not found.
85 |
86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
88 |
89 | set EXEC_DIR=%CD%
90 | set WDIR=%EXEC_DIR%
91 | :findBaseDir
92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound
93 | cd ..
94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound
95 | set WDIR=%CD%
96 | goto findBaseDir
97 |
98 | :baseDirFound
99 | set MAVEN_PROJECTBASEDIR=%WDIR%
100 | cd "%EXEC_DIR%"
101 | goto endDetectBaseDir
102 |
103 | :baseDirNotFound
104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
105 | cd "%EXEC_DIR%"
106 |
107 | :endDetectBaseDir
108 |
109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
110 |
111 | @setlocal EnableExtensions EnableDelayedExpansion
112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
114 |
115 | :endReadAdditionalConfig
116 |
117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
118 |
119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
121 |
122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
123 | if ERRORLEVEL 1 goto error
124 | goto end
125 |
126 | :error
127 | set ERROR_CODE=1
128 |
129 | :end
130 | @endlocal & set ERROR_CODE=%ERROR_CODE%
131 |
132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending
134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
136 | :skipRcPost
137 |
138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause
140 |
141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
142 |
143 | exit /B %ERROR_CODE%
144 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | com.github.gaborbata
6 | groovy-web-console
7 | 1.0.0-SNAPSHOT
8 | jar
9 |
10 | groovy-web-console
11 | Groovy web console
12 |
13 |
14 | org.springframework.boot
15 | spring-boot-starter-parent
16 | 3.0.1
17 |
18 |
19 |
20 |
21 | UTF-8
22 | UTF-8
23 | 17
24 | 4.0.7
25 | 1.2.4
26 | 0.8.8
27 | 3.0.1
28 |
29 |
30 |
31 |
32 | org.springframework.boot
33 | spring-boot-starter-web
34 |
35 |
36 |
37 | org.apache.groovy
38 | groovy-all
39 | ${groovy.version}
40 | pom
41 |
42 |
43 |
44 | org.springframework.boot
45 | spring-boot-starter-test
46 | test
47 |
48 |
49 | com.tngtech.jgiven
50 | jgiven-junit5
51 | ${jgiven.version}
52 | test
53 |
54 |
55 | com.tngtech.jgiven
56 | jgiven-spring-junit5
57 | ${jgiven.version}
58 | test
59 |
60 |
61 |
62 |
63 |
64 |
65 | org.springframework.boot
66 | spring-boot-maven-plugin
67 |
68 |
69 | org.jacoco
70 | jacoco-maven-plugin
71 | ${jacoco.version}
72 |
73 |
74 |
75 | prepare-agent
76 |
77 |
78 |
79 | jacoco-report
80 | prepare-package
81 |
82 | report
83 |
84 |
85 |
86 |
87 |
88 | com.tngtech.jgiven
89 | jgiven-maven-plugin
90 | ${jgiven.version}
91 |
92 |
93 | jgiven-report
94 | prepare-package
95 |
96 | report
97 |
98 |
99 |
100 |
101 | html
102 |
103 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/resources/console-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gaborbata/groovy-web-console/32e6ea4686605472f90e3045f13f2e2fc80b2692/resources/console-screenshot.png
--------------------------------------------------------------------------------
/src/main/java/com/github/gaborbata/console/GroovyWebConsoleApplication.java:
--------------------------------------------------------------------------------
1 | package com.github.gaborbata.console;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
6 |
7 | @SpringBootApplication
8 | @ConditionalOnProperty(name = "groovy-web-console.application-enabled", havingValue = "true")
9 | public class GroovyWebConsoleApplication {
10 |
11 | public static void main(String[] args) {
12 | SpringApplication.run(GroovyWebConsoleApplication.class, args);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/github/gaborbata/console/controller/GroovyConsoleController.java:
--------------------------------------------------------------------------------
1 | package com.github.gaborbata.console.controller;
2 |
3 | import com.fasterxml.jackson.annotation.JsonInclude;
4 | import groovy.lang.Binding;
5 | import groovy.lang.GroovyShell;
6 | import groovy.transform.TimedInterrupt;
7 | import org.codehaus.groovy.control.CompilerConfiguration;
8 | import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer;
9 | import org.codehaus.groovy.control.customizers.SecureASTCustomizer;
10 | import org.springframework.beans.BeansException;
11 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
12 | import org.springframework.context.ApplicationContext;
13 | import org.springframework.context.ApplicationContextAware;
14 | import org.springframework.stereotype.Controller;
15 | import org.springframework.util.StringUtils;
16 | import org.springframework.web.bind.annotation.RequestMapping;
17 | import org.springframework.web.bind.annotation.RequestMethod;
18 | import org.springframework.web.bind.annotation.RequestParam;
19 | import org.springframework.web.bind.annotation.ResponseBody;
20 |
21 | import java.io.ByteArrayOutputStream;
22 | import java.io.OutputStream;
23 | import java.io.PrintStream;
24 | import java.util.List;
25 | import java.util.concurrent.CompletableFuture;
26 | import java.util.stream.Collectors;
27 | import java.util.stream.Stream;
28 |
29 | import static java.util.Collections.singletonMap;
30 |
31 | /**
32 | * Controller for evaluating scripts from groovy console.
33 | */
34 | @Controller
35 | @RequestMapping("/console")
36 | @ConditionalOnProperty(name = "groovy-web-console.enabled", havingValue = "true", matchIfMissing = true)
37 | public class GroovyConsoleController implements ApplicationContextAware {
38 |
39 | private static final long SCRIPT_TIMEOUT_IN_SECONDS = 5;
40 | private static final List RECEIVERS_BLACK_LIST = Stream.of(System.class, Thread.class)
41 | .map(Class::getName)
42 | .collect(Collectors.toList());
43 |
44 | private ApplicationContext applicationContext;
45 |
46 | /**
47 | * Redirects to groovy console index page.
48 | *
49 | * @return the redirect view of groovy console index page
50 | */
51 | @RequestMapping(method = RequestMethod.GET)
52 | public String index() {
53 | return "redirect:/console/index.html";
54 | }
55 |
56 | /**
57 | * Executes the given groovy script
58 | *
59 | * @param script the groovy script
60 | * @return the result object
61 | */
62 | @RequestMapping(value = "/groovy", method = {RequestMethod.GET, RequestMethod.POST})
63 | @ResponseBody
64 | public CompletableFuture execute(@RequestParam String script) {
65 | return CompletableFuture.supplyAsync(() -> {
66 | var out = new ByteArrayOutputStream();
67 | var groovyShell = createGroovyShell(out);
68 | var result = groovyShell.evaluate(script);
69 | return ScriptResult.create(result, out.toString());
70 | }).exceptionally(ScriptResult::create);
71 | }
72 |
73 | @Override
74 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
75 | this.applicationContext = applicationContext;
76 | }
77 |
78 | private GroovyShell createGroovyShell(OutputStream outputStream) {
79 | var configuration = createCompilerConfiguration();
80 | var binding = createBinding(outputStream);
81 | return new GroovyShell(binding, configuration);
82 | }
83 |
84 | private Binding createBinding(OutputStream outputStream) {
85 | var binding = new Binding();
86 | binding.setVariable("applicationContext", applicationContext);
87 | binding.setProperty("out", new PrintStream(outputStream, true));
88 | return binding;
89 | }
90 |
91 | private CompilerConfiguration createCompilerConfiguration() {
92 | var timedCustomizer = new ASTTransformationCustomizer(singletonMap("value", SCRIPT_TIMEOUT_IN_SECONDS), TimedInterrupt.class);
93 | var secureCustomizer = new SecureASTCustomizer();
94 | secureCustomizer.setReceiversBlackList(RECEIVERS_BLACK_LIST);
95 | var configuration = new CompilerConfiguration();
96 | configuration.addCompilationCustomizers(secureCustomizer, timedCustomizer);
97 | return configuration;
98 | }
99 |
100 | @JsonInclude(JsonInclude.Include.NON_NULL)
101 | private static final class ScriptResult {
102 |
103 | private String[] output;
104 | private Object result;
105 |
106 | private ScriptResult() {
107 | }
108 |
109 | public String[] getOutput() {
110 | return output;
111 | }
112 |
113 | public Object getResult() {
114 | return result;
115 | }
116 |
117 | private static ScriptResult create(Throwable throwable) {
118 | var message = throwable.getMessage() == null ? throwable.getClass().getName() : throwable.getMessage();
119 | return create(null, message);
120 | }
121 |
122 | private static ScriptResult create(Object result, String output) {
123 | var scriptletResult = new ScriptResult();
124 | scriptletResult.result = result;
125 | if (StringUtils.hasLength(output)) {
126 | scriptletResult.output = output.split(System.lineSeparator());
127 | }
128 | return scriptletResult;
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | spring.application.name: Groovy Web Console
2 | groovy-web-console.application-enabled=true
3 |
--------------------------------------------------------------------------------
/src/main/resources/public/console/css/ambiance.css:
--------------------------------------------------------------------------------
1 | /* ambiance theme for codemirror */
2 |
3 | /* Color scheme */
4 |
5 | .cm-s-ambiance .cm-header { color: blue; }
6 | .cm-s-ambiance .cm-quote { color: #24C2C7; }
7 |
8 | .cm-s-ambiance .cm-keyword { color: #cda869; }
9 | .cm-s-ambiance .cm-atom { color: #CF7EA9; }
10 | .cm-s-ambiance .cm-number { color: #78CF8A; }
11 | .cm-s-ambiance .cm-def { color: #aac6e3; }
12 | .cm-s-ambiance .cm-variable { color: #ffb795; }
13 | .cm-s-ambiance .cm-variable-2 { color: #eed1b3; }
14 | .cm-s-ambiance .cm-variable-3, .cm-s-ambiance .cm-type { color: #faded3; }
15 | .cm-s-ambiance .cm-property { color: #eed1b3; }
16 | .cm-s-ambiance .cm-operator { color: #fa8d6a; }
17 | .cm-s-ambiance .cm-comment { color: #555; font-style:italic; }
18 | .cm-s-ambiance .cm-string { color: #8f9d6a; }
19 | .cm-s-ambiance .cm-string-2 { color: #9d937c; }
20 | .cm-s-ambiance .cm-meta { color: #D2A8A1; }
21 | .cm-s-ambiance .cm-qualifier { color: yellow; }
22 | .cm-s-ambiance .cm-builtin { color: #9999cc; }
23 | .cm-s-ambiance .cm-bracket { color: #24C2C7; }
24 | .cm-s-ambiance .cm-tag { color: #fee4ff; }
25 | .cm-s-ambiance .cm-attribute { color: #9B859D; }
26 | .cm-s-ambiance .cm-hr { color: pink; }
27 | .cm-s-ambiance .cm-link { color: #F4C20B; }
28 | .cm-s-ambiance .cm-special { color: #FF9D00; }
29 | .cm-s-ambiance .cm-error { color: #AF2018; }
30 |
31 | .cm-s-ambiance .CodeMirror-matchingbracket { color: #0f0; }
32 | .cm-s-ambiance .CodeMirror-nonmatchingbracket { color: #f22; }
33 |
34 | .cm-s-ambiance div.CodeMirror-selected { background: rgba(255, 255, 255, 0.15); }
35 | .cm-s-ambiance.CodeMirror-focused div.CodeMirror-selected { background: rgba(255, 255, 255, 0.10); }
36 | .cm-s-ambiance .CodeMirror-line::selection, .cm-s-ambiance .CodeMirror-line > span::selection, .cm-s-ambiance .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); }
37 | .cm-s-ambiance .CodeMirror-line::-moz-selection, .cm-s-ambiance .CodeMirror-line > span::-moz-selection, .cm-s-ambiance .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); }
38 |
39 | /* Editor styling */
40 |
41 | .cm-s-ambiance.CodeMirror {
42 | line-height: 1.40em;
43 | color: #E6E1DC;
44 | background-color: #202020;
45 | -webkit-box-shadow: inset 0 0 10px black;
46 | -moz-box-shadow: inset 0 0 10px black;
47 | box-shadow: inset 0 0 10px black;
48 | }
49 |
50 | .cm-s-ambiance .CodeMirror-gutters {
51 | background: #3D3D3D;
52 | border-right: 1px solid #4D4D4D;
53 | box-shadow: 0 10px 20px black;
54 | }
55 |
56 | .cm-s-ambiance .CodeMirror-linenumber {
57 | text-shadow: 0px 1px 1px #4d4d4d;
58 | color: #111;
59 | padding: 0 5px;
60 | }
61 |
62 | .cm-s-ambiance .CodeMirror-guttermarker { color: #aaa; }
63 | .cm-s-ambiance .CodeMirror-guttermarker-subtle { color: #111; }
64 |
65 | .cm-s-ambiance .CodeMirror-cursor { border-left: 1px solid #7991E8; }
66 |
67 | .cm-s-ambiance .CodeMirror-activeline-background {
68 | background: none repeat scroll 0% 0% rgba(255, 255, 255, 0.031);
69 | }
70 |
71 | .cm-s-ambiance.CodeMirror,
72 | .cm-s-ambiance .CodeMirror-gutters {
73 | background-image: url("data:image/png;base64,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");
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/resources/public/console/css/codemirror.css:
--------------------------------------------------------------------------------
1 | /* BASICS */
2 |
3 | .CodeMirror {
4 | /* Set height, width, borders, and global font properties here */
5 | font-family: monospace;
6 | height: 300px;
7 | color: black;
8 | direction: ltr;
9 | }
10 |
11 | /* PADDING */
12 |
13 | .CodeMirror-lines {
14 | padding: 4px 0; /* Vertical padding around content */
15 | }
16 | .CodeMirror pre {
17 | padding: 0 4px; /* Horizontal padding of content */
18 | }
19 |
20 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
21 | background-color: white; /* The little square between H and V scrollbars */
22 | }
23 |
24 | /* GUTTER */
25 |
26 | .CodeMirror-gutters {
27 | border-right: 1px solid #ddd;
28 | background-color: #f7f7f7;
29 | white-space: nowrap;
30 | }
31 | .CodeMirror-linenumbers {}
32 | .CodeMirror-linenumber {
33 | padding: 0 3px 0 5px;
34 | min-width: 20px;
35 | text-align: right;
36 | color: #999;
37 | white-space: nowrap;
38 | }
39 |
40 | .CodeMirror-guttermarker { color: black; }
41 | .CodeMirror-guttermarker-subtle { color: #999; }
42 |
43 | /* CURSOR */
44 |
45 | .CodeMirror-cursor {
46 | border-left: 1px solid black;
47 | border-right: none;
48 | width: 0;
49 | }
50 | /* Shown when moving in bi-directional text */
51 | .CodeMirror div.CodeMirror-secondarycursor {
52 | border-left: 1px solid silver;
53 | }
54 | .cm-fat-cursor .CodeMirror-cursor {
55 | width: auto;
56 | border: 0 !important;
57 | background: #7e7;
58 | }
59 | .cm-fat-cursor div.CodeMirror-cursors {
60 | z-index: 1;
61 | }
62 | .cm-fat-cursor-mark {
63 | background-color: rgba(20, 255, 20, 0.5);
64 | -webkit-animation: blink 1.06s steps(1) infinite;
65 | -moz-animation: blink 1.06s steps(1) infinite;
66 | animation: blink 1.06s steps(1) infinite;
67 | }
68 | .cm-animate-fat-cursor {
69 | width: auto;
70 | border: 0;
71 | -webkit-animation: blink 1.06s steps(1) infinite;
72 | -moz-animation: blink 1.06s steps(1) infinite;
73 | animation: blink 1.06s steps(1) infinite;
74 | background-color: #7e7;
75 | }
76 | @-moz-keyframes blink {
77 | 0% {}
78 | 50% { background-color: transparent; }
79 | 100% {}
80 | }
81 | @-webkit-keyframes blink {
82 | 0% {}
83 | 50% { background-color: transparent; }
84 | 100% {}
85 | }
86 | @keyframes blink {
87 | 0% {}
88 | 50% { background-color: transparent; }
89 | 100% {}
90 | }
91 |
92 | /* Can style cursor different in overwrite (non-insert) mode */
93 | .CodeMirror-overwrite .CodeMirror-cursor {}
94 |
95 | .cm-tab { display: inline-block; text-decoration: inherit; }
96 |
97 | .CodeMirror-rulers {
98 | position: absolute;
99 | left: 0; right: 0; top: -50px; bottom: -20px;
100 | overflow: hidden;
101 | }
102 | .CodeMirror-ruler {
103 | border-left: 1px solid #ccc;
104 | top: 0; bottom: 0;
105 | position: absolute;
106 | }
107 |
108 | /* DEFAULT THEME */
109 |
110 | .cm-s-default .cm-header {color: blue;}
111 | .cm-s-default .cm-quote {color: #090;}
112 | .cm-negative {color: #d44;}
113 | .cm-positive {color: #292;}
114 | .cm-header, .cm-strong {font-weight: bold;}
115 | .cm-em {font-style: italic;}
116 | .cm-link {text-decoration: underline;}
117 | .cm-strikethrough {text-decoration: line-through;}
118 |
119 | .cm-s-default .cm-keyword {color: #708;}
120 | .cm-s-default .cm-atom {color: #219;}
121 | .cm-s-default .cm-number {color: #164;}
122 | .cm-s-default .cm-def {color: #00f;}
123 | .cm-s-default .cm-variable,
124 | .cm-s-default .cm-punctuation,
125 | .cm-s-default .cm-property,
126 | .cm-s-default .cm-operator {}
127 | .cm-s-default .cm-variable-2 {color: #05a;}
128 | .cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
129 | .cm-s-default .cm-comment {color: #a50;}
130 | .cm-s-default .cm-string {color: #a11;}
131 | .cm-s-default .cm-string-2 {color: #f50;}
132 | .cm-s-default .cm-meta {color: #555;}
133 | .cm-s-default .cm-qualifier {color: #555;}
134 | .cm-s-default .cm-builtin {color: #30a;}
135 | .cm-s-default .cm-bracket {color: #997;}
136 | .cm-s-default .cm-tag {color: #170;}
137 | .cm-s-default .cm-attribute {color: #00c;}
138 | .cm-s-default .cm-hr {color: #999;}
139 | .cm-s-default .cm-link {color: #00c;}
140 |
141 | .cm-s-default .cm-error {color: #f00;}
142 | .cm-invalidchar {color: #f00;}
143 |
144 | .CodeMirror-composing { border-bottom: 2px solid; }
145 |
146 | /* Default styles for common addons */
147 |
148 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
149 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
150 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
151 | .CodeMirror-activeline-background {background: #e8f2ff;}
152 |
153 | /* STOP */
154 |
155 | /* The rest of this file contains styles related to the mechanics of
156 | the editor. You probably shouldn't touch them. */
157 |
158 | .CodeMirror {
159 | position: relative;
160 | overflow: hidden;
161 | background: white;
162 | }
163 |
164 | .CodeMirror-scroll {
165 | overflow: scroll !important; /* Things will break if this is overridden */
166 | /* 30px is the magic margin used to hide the element's real scrollbars */
167 | /* See overflow: hidden in .CodeMirror */
168 | margin-bottom: -30px; margin-right: -30px;
169 | padding-bottom: 30px;
170 | height: 100%;
171 | outline: none; /* Prevent dragging from highlighting the element */
172 | position: relative;
173 | }
174 | .CodeMirror-sizer {
175 | position: relative;
176 | border-right: 30px solid transparent;
177 | }
178 |
179 | /* The fake, visible scrollbars. Used to force redraw during scrolling
180 | before actual scrolling happens, thus preventing shaking and
181 | flickering artifacts. */
182 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
183 | position: absolute;
184 | z-index: 6;
185 | display: none;
186 | }
187 | .CodeMirror-vscrollbar {
188 | right: 0; top: 0;
189 | overflow-x: hidden;
190 | overflow-y: scroll;
191 | }
192 | .CodeMirror-hscrollbar {
193 | bottom: 0; left: 0;
194 | overflow-y: hidden;
195 | overflow-x: scroll;
196 | }
197 | .CodeMirror-scrollbar-filler {
198 | right: 0; bottom: 0;
199 | }
200 | .CodeMirror-gutter-filler {
201 | left: 0; bottom: 0;
202 | }
203 |
204 | .CodeMirror-gutters {
205 | position: absolute; left: 0; top: 0;
206 | min-height: 100%;
207 | z-index: 3;
208 | }
209 | .CodeMirror-gutter {
210 | white-space: normal;
211 | height: 100%;
212 | display: inline-block;
213 | vertical-align: top;
214 | margin-bottom: -30px;
215 | }
216 | .CodeMirror-gutter-wrapper {
217 | position: absolute;
218 | z-index: 4;
219 | background: none !important;
220 | border: none !important;
221 | }
222 | .CodeMirror-gutter-background {
223 | position: absolute;
224 | top: 0; bottom: 0;
225 | z-index: 4;
226 | }
227 | .CodeMirror-gutter-elt {
228 | position: absolute;
229 | cursor: default;
230 | z-index: 4;
231 | }
232 | .CodeMirror-gutter-wrapper ::selection { background-color: transparent }
233 | .CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
234 |
235 | .CodeMirror-lines {
236 | cursor: text;
237 | min-height: 1px; /* prevents collapsing before first draw */
238 | }
239 | .CodeMirror pre {
240 | /* Reset some styles that the rest of the page might have set */
241 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
242 | border-width: 0;
243 | background: transparent;
244 | font-family: inherit;
245 | font-size: inherit;
246 | margin: 0;
247 | white-space: pre;
248 | word-wrap: normal;
249 | line-height: inherit;
250 | color: inherit;
251 | z-index: 2;
252 | position: relative;
253 | overflow: visible;
254 | -webkit-tap-highlight-color: transparent;
255 | -webkit-font-variant-ligatures: contextual;
256 | font-variant-ligatures: contextual;
257 | }
258 | .CodeMirror-wrap pre {
259 | word-wrap: break-word;
260 | white-space: pre-wrap;
261 | word-break: normal;
262 | }
263 |
264 | .CodeMirror-linebackground {
265 | position: absolute;
266 | left: 0; right: 0; top: 0; bottom: 0;
267 | z-index: 0;
268 | }
269 |
270 | .CodeMirror-linewidget {
271 | position: relative;
272 | z-index: 2;
273 | padding: 0.1px; /* Force widget margins to stay inside of the container */
274 | }
275 |
276 | .CodeMirror-widget {}
277 |
278 | .CodeMirror-rtl pre { direction: rtl; }
279 |
280 | .CodeMirror-code {
281 | outline: none;
282 | }
283 |
284 | /* Force content-box sizing for the elements where we expect it */
285 | .CodeMirror-scroll,
286 | .CodeMirror-sizer,
287 | .CodeMirror-gutter,
288 | .CodeMirror-gutters,
289 | .CodeMirror-linenumber {
290 | -moz-box-sizing: content-box;
291 | box-sizing: content-box;
292 | }
293 |
294 | .CodeMirror-measure {
295 | position: absolute;
296 | width: 100%;
297 | height: 0;
298 | overflow: hidden;
299 | visibility: hidden;
300 | }
301 |
302 | .CodeMirror-cursor {
303 | position: absolute;
304 | pointer-events: none;
305 | }
306 | .CodeMirror-measure pre { position: static; }
307 |
308 | div.CodeMirror-cursors {
309 | visibility: hidden;
310 | position: relative;
311 | z-index: 3;
312 | }
313 | div.CodeMirror-dragcursors {
314 | visibility: visible;
315 | }
316 |
317 | .CodeMirror-focused div.CodeMirror-cursors {
318 | visibility: visible;
319 | }
320 |
321 | .CodeMirror-selected { background: #d9d9d9; }
322 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
323 | .CodeMirror-crosshair { cursor: crosshair; }
324 | .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
325 | .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
326 |
327 | .cm-searching {
328 | background-color: #ffa;
329 | background-color: rgba(255, 255, 0, .4);
330 | }
331 |
332 | /* Used to force a border model for a node */
333 | .cm-force-border { padding-right: .1px; }
334 |
335 | @media print {
336 | /* Hide the cursor when printing */
337 | .CodeMirror div.CodeMirror-cursors {
338 | visibility: hidden;
339 | }
340 | }
341 |
342 | /* See issue #2901 */
343 | .cm-tab-wrap-hack:after { content: ''; }
344 |
345 | /* Help users use markselection to safely style text background */
346 | span.CodeMirror-selectedtext { background: none; }
347 |
--------------------------------------------------------------------------------
/src/main/resources/public/console/css/console.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #4d4d4d;
3 | margin: 0;
4 | padding: 0;
5 | }
6 |
7 | .CodeMirror {
8 | font-size: 14px;
9 | }
10 |
11 | .blockUI {
12 | font-family: sans-serif;
13 | }
14 |
15 | .input-code-title img {
16 | position: absolute;
17 | top: 1px;
18 | }
19 |
20 | .input-code-title .title {
21 | margin-left: 40px;
22 | }
23 |
24 | .input-code-title button {
25 | background-color: #5f5f5f;
26 | font-family: sans-serif;
27 | font-size: 14px;
28 | height: 22px;
29 | color: #fff;
30 | border: 1px solid #ddd;
31 | margin-left: 20px;
32 | }
33 |
34 | .input-code-title .pulled-right {
35 | float: right;
36 | }
37 |
38 | .input-code-title a {
39 | color: #fff;
40 | }
41 |
42 | .input-code-title select {
43 | background-color: #5f5f5f;
44 | font-family: sans-serif;
45 | font-size: 14px;
46 | height: 22px;
47 | color: #fff;
48 | border: 1px solid #ddd;
49 | margin-right: 20px;
50 | }
51 |
52 | .input-code-title, .result-code-title {
53 | background-color: #4d4d4d;
54 | border-top: 1px solid #000;
55 | border-bottom: 1px solid #000;
56 | font-family: sans-serif;
57 | font-size: 14px;
58 | color: #fff;
59 | padding: 6px;
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/resources/public/console/examples/get-environment-info.groovy:
--------------------------------------------------------------------------------
1 | // Get Spring Boot environment information
2 | def environment = applicationContext.getBean("environment")
3 | println "Active profiles: ${environment.getActiveProfiles()}"
4 | println "Application name: ${environment.getProperty('spring.application.name')}"
5 |
--------------------------------------------------------------------------------
/src/main/resources/public/console/examples/is-it-friday.groovy:
--------------------------------------------------------------------------------
1 | // checks if groovy console works
2 | def calendar = Calendar.getInstance()
3 | def isFriday = calendar.get(Calendar.DAY_OF_WEEK) == Calendar.FRIDAY
4 | println "Is it friday? ${isFriday ? 'Yes' : 'No'}"
5 |
--------------------------------------------------------------------------------
/src/main/resources/public/console/examples/list-spring-beans.groovy:
--------------------------------------------------------------------------------
1 | // list all spring beans
2 | applicationContext.getBeanDefinitionNames()
3 |
--------------------------------------------------------------------------------
/src/main/resources/public/console/images/console.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gaborbata/groovy-web-console/32e6ea4686605472f90e3045f13f2e2fc80b2692/src/main/resources/public/console/images/console.png
--------------------------------------------------------------------------------
/src/main/resources/public/console/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Groovy Web Console
6 |
7 |
8 |
9 |
10 |
11 |
12 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/main/resources/public/console/js/console.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var $location = $(location);
3 | var $window = $(window);
4 | var $exampleSelector = $("#input-code-example-select");
5 |
6 | function hashChangeEventHandler() {
7 | var hash = $location.attr('hash');
8 | if (hash) {
9 | var decompressed = LZString.decompressFromEncodedURIComponent(hash.substring(1));
10 | if (decompressed) {
11 | inputEditor.getDoc().setValue(decompressed);
12 | }
13 | $exampleSelector.val("");
14 | } else {
15 | $exampleSelector.val("/console/examples/get-environment-info.groovy");
16 | }
17 | $exampleSelector.trigger("change");
18 | }
19 |
20 | var inputEditor = CodeMirror.fromTextArea(document.getElementById("input-code"), {
21 | mode: "text/x-groovy",
22 | lineNumbers: true,
23 | theme: "ambiance",
24 | matchBrackets: true,
25 | indentUnit: 2
26 | });
27 |
28 | var resultEditor = CodeMirror.fromTextArea(document.getElementById("result-code"), {
29 | mode: "application/x-json",
30 | lineNumbers: true,
31 | theme: "ambiance",
32 | matchBrackets: true,
33 | indentUnit: 2,
34 | readOnly: true
35 | });
36 |
37 | $("#send-button").on("click", function() {
38 | $.blockUI();
39 | $.ajax({
40 | url: "/console/groovy",
41 | type: "POST",
42 | data: { script: inputEditor.getValue() }
43 | }).done(function(data) {
44 | resultEditor.getDoc().setValue(JSON.stringify(data, null, 2));
45 | }).fail(function() {
46 | resultEditor.getDoc().setValue("Failed to send request.");
47 | }).always(function() {
48 | $.unblockUI();
49 | });
50 | });
51 |
52 | $exampleSelector.on("change", function() {
53 | var selectedScript = $(this).val();
54 | if (selectedScript) {
55 | $.blockUI();
56 | $.ajax({
57 | url: selectedScript,
58 | mimeType: "text/x-groovy"
59 | }).done(function(data) {
60 | inputEditor.getDoc().setValue(data);
61 | }).fail(function() {
62 | resultEditor.getDoc().setValue("Failed to load example.");
63 | }).always(function() {
64 | $.unblockUI();
65 | });
66 | }
67 | });
68 |
69 | $("#permalink-button").on("click", function() {
70 | var href = $location.attr('href').replace(/#.*/g, '');
71 | resultEditor.getDoc().setValue(href + '#' + LZString.compressToEncodedURIComponent(inputEditor.getValue()));
72 | });
73 |
74 | $window.on('hashchange', hashChangeEventHandler);
75 | $window.trigger('hashchange');
76 | })();
77 |
--------------------------------------------------------------------------------
/src/main/resources/public/console/js/groovy.js:
--------------------------------------------------------------------------------
1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 | // Distributed under an MIT license: http://codemirror.net/LICENSE
3 |
4 | (function(mod) {
5 | if (typeof exports == "object" && typeof module == "object") // CommonJS
6 | mod(require("../../lib/codemirror"));
7 | else if (typeof define == "function" && define.amd) // AMD
8 | define(["../../lib/codemirror"], mod);
9 | else // Plain browser env
10 | mod(CodeMirror);
11 | })(function(CodeMirror) {
12 | "use strict";
13 |
14 | CodeMirror.defineMode("groovy", function(config) {
15 | function words(str) {
16 | var obj = {}, words = str.split(" ");
17 | for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
18 | return obj;
19 | }
20 | var keywords = words(
21 | "abstract as assert boolean break byte case catch char class const continue def default " +
22 | "do double else enum extends final finally float for goto if implements import in " +
23 | "instanceof int interface long native new package private protected public return " +
24 | "short static strictfp super switch synchronized threadsafe throw throws trait transient " +
25 | "try void volatile while");
26 | var blockKeywords = words("catch class def do else enum finally for if interface switch trait try while");
27 | var standaloneKeywords = words("return break continue");
28 | var atoms = words("null true false this");
29 |
30 | var curPunc;
31 | function tokenBase(stream, state) {
32 | var ch = stream.next();
33 | if (ch == '"' || ch == "'") {
34 | return startString(ch, stream, state);
35 | }
36 | if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
37 | curPunc = ch;
38 | return null;
39 | }
40 | if (/\d/.test(ch)) {
41 | stream.eatWhile(/[\w\.]/);
42 | if (stream.eat(/eE/)) { stream.eat(/\+\-/); stream.eatWhile(/\d/); }
43 | return "number";
44 | }
45 | if (ch == "/") {
46 | if (stream.eat("*")) {
47 | state.tokenize.push(tokenComment);
48 | return tokenComment(stream, state);
49 | }
50 | if (stream.eat("/")) {
51 | stream.skipToEnd();
52 | return "comment";
53 | }
54 | if (expectExpression(state.lastToken, false)) {
55 | return startString(ch, stream, state);
56 | }
57 | }
58 | if (ch == "-" && stream.eat(">")) {
59 | curPunc = "->";
60 | return null;
61 | }
62 | if (/[+\-*&%=<>!?|\/~]/.test(ch)) {
63 | stream.eatWhile(/[+\-*&%=<>|~]/);
64 | return "operator";
65 | }
66 | stream.eatWhile(/[\w\$_]/);
67 | if (ch == "@") { stream.eatWhile(/[\w\$_\.]/); return "meta"; }
68 | if (state.lastToken == ".") return "property";
69 | if (stream.eat(":")) { curPunc = "proplabel"; return "property"; }
70 | var cur = stream.current();
71 | if (atoms.propertyIsEnumerable(cur)) { return "atom"; }
72 | if (keywords.propertyIsEnumerable(cur)) {
73 | if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
74 | else if (standaloneKeywords.propertyIsEnumerable(cur)) curPunc = "standalone";
75 | return "keyword";
76 | }
77 | return "variable";
78 | }
79 | tokenBase.isBase = true;
80 |
81 | function startString(quote, stream, state) {
82 | var tripleQuoted = false;
83 | if (quote != "/" && stream.eat(quote)) {
84 | if (stream.eat(quote)) tripleQuoted = true;
85 | else return "string";
86 | }
87 | function t(stream, state) {
88 | var escaped = false, next, end = !tripleQuoted;
89 | while ((next = stream.next()) != null) {
90 | if (next == quote && !escaped) {
91 | if (!tripleQuoted) { break; }
92 | if (stream.match(quote + quote)) { end = true; break; }
93 | }
94 | if (quote == '"' && next == "$" && !escaped && stream.eat("{")) {
95 | state.tokenize.push(tokenBaseUntilBrace());
96 | return "string";
97 | }
98 | escaped = !escaped && next == "\\";
99 | }
100 | if (end) state.tokenize.pop();
101 | return "string";
102 | }
103 | state.tokenize.push(t);
104 | return t(stream, state);
105 | }
106 |
107 | function tokenBaseUntilBrace() {
108 | var depth = 1;
109 | function t(stream, state) {
110 | if (stream.peek() == "}") {
111 | depth--;
112 | if (depth == 0) {
113 | state.tokenize.pop();
114 | return state.tokenize[state.tokenize.length-1](stream, state);
115 | }
116 | } else if (stream.peek() == "{") {
117 | depth++;
118 | }
119 | return tokenBase(stream, state);
120 | }
121 | t.isBase = true;
122 | return t;
123 | }
124 |
125 | function tokenComment(stream, state) {
126 | var maybeEnd = false, ch;
127 | while (ch = stream.next()) {
128 | if (ch == "/" && maybeEnd) {
129 | state.tokenize.pop();
130 | break;
131 | }
132 | maybeEnd = (ch == "*");
133 | }
134 | return "comment";
135 | }
136 |
137 | function expectExpression(last, newline) {
138 | return !last || last == "operator" || last == "->" || /[\.\[\{\(,;:]/.test(last) ||
139 | last == "newstatement" || last == "keyword" || last == "proplabel" ||
140 | (last == "standalone" && !newline);
141 | }
142 |
143 | function Context(indented, column, type, align, prev) {
144 | this.indented = indented;
145 | this.column = column;
146 | this.type = type;
147 | this.align = align;
148 | this.prev = prev;
149 | }
150 | function pushContext(state, col, type) {
151 | return state.context = new Context(state.indented, col, type, null, state.context);
152 | }
153 | function popContext(state) {
154 | var t = state.context.type;
155 | if (t == ")" || t == "]" || t == "}")
156 | state.indented = state.context.indented;
157 | return state.context = state.context.prev;
158 | }
159 |
160 | // Interface
161 |
162 | return {
163 | startState: function(basecolumn) {
164 | return {
165 | tokenize: [tokenBase],
166 | context: new Context((basecolumn || 0) - config.indentUnit, 0, "top", false),
167 | indented: 0,
168 | startOfLine: true,
169 | lastToken: null
170 | };
171 | },
172 |
173 | token: function(stream, state) {
174 | var ctx = state.context;
175 | if (stream.sol()) {
176 | if (ctx.align == null) ctx.align = false;
177 | state.indented = stream.indentation();
178 | state.startOfLine = true;
179 | // Automatic semicolon insertion
180 | if (ctx.type == "statement" && !expectExpression(state.lastToken, true)) {
181 | popContext(state); ctx = state.context;
182 | }
183 | }
184 | if (stream.eatSpace()) return null;
185 | curPunc = null;
186 | var style = state.tokenize[state.tokenize.length-1](stream, state);
187 | if (style == "comment") return style;
188 | if (ctx.align == null) ctx.align = true;
189 |
190 | if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state);
191 | // Handle indentation for {x -> \n ... }
192 | else if (curPunc == "->" && ctx.type == "statement" && ctx.prev.type == "}") {
193 | popContext(state);
194 | state.context.align = false;
195 | }
196 | else if (curPunc == "{") pushContext(state, stream.column(), "}");
197 | else if (curPunc == "[") pushContext(state, stream.column(), "]");
198 | else if (curPunc == "(") pushContext(state, stream.column(), ")");
199 | else if (curPunc == "}") {
200 | while (ctx.type == "statement") ctx = popContext(state);
201 | if (ctx.type == "}") ctx = popContext(state);
202 | while (ctx.type == "statement") ctx = popContext(state);
203 | }
204 | else if (curPunc == ctx.type) popContext(state);
205 | else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement"))
206 | pushContext(state, stream.column(), "statement");
207 | state.startOfLine = false;
208 | state.lastToken = curPunc || style;
209 | return style;
210 | },
211 |
212 | indent: function(state, textAfter) {
213 | if (!state.tokenize[state.tokenize.length-1].isBase) return CodeMirror.Pass;
214 | var firstChar = textAfter && textAfter.charAt(0), ctx = state.context;
215 | if (ctx.type == "statement" && !expectExpression(state.lastToken, true)) ctx = ctx.prev;
216 | var closing = firstChar == ctx.type;
217 | if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : config.indentUnit);
218 | else if (ctx.align) return ctx.column + (closing ? 0 : 1);
219 | else return ctx.indented + (closing ? 0 : config.indentUnit);
220 | },
221 |
222 | electricChars: "{}",
223 | closeBrackets: {triples: "'\""},
224 | fold: "brace"
225 | };
226 | });
227 |
228 | CodeMirror.defineMIME("text/x-groovy", "groovy");
229 |
230 | });
231 |
--------------------------------------------------------------------------------
/src/main/resources/public/console/js/javascript.js:
--------------------------------------------------------------------------------
1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 | // Distributed under an MIT license: http://codemirror.net/LICENSE
3 |
4 | (function(mod) {
5 | if (typeof exports == "object" && typeof module == "object") // CommonJS
6 | mod(require("../../lib/codemirror"));
7 | else if (typeof define == "function" && define.amd) // AMD
8 | define(["../../lib/codemirror"], mod);
9 | else // Plain browser env
10 | mod(CodeMirror);
11 | })(function(CodeMirror) {
12 | "use strict";
13 |
14 | CodeMirror.defineMode("javascript", function(config, parserConfig) {
15 | var indentUnit = config.indentUnit;
16 | var statementIndent = parserConfig.statementIndent;
17 | var jsonldMode = parserConfig.jsonld;
18 | var jsonMode = parserConfig.json || jsonldMode;
19 | var isTS = parserConfig.typescript;
20 | var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
21 |
22 | // Tokenizer
23 |
24 | var keywords = function(){
25 | function kw(type) {return {type: type, style: "keyword"};}
26 | var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"), D = kw("keyword d");
27 | var operator = kw("operator"), atom = {type: "atom", style: "atom"};
28 |
29 | return {
30 | "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
31 | "return": D, "break": D, "continue": D, "new": kw("new"), "delete": C, "void": C, "throw": C,
32 | "debugger": kw("debugger"), "var": kw("var"), "const": kw("var"), "let": kw("var"),
33 | "function": kw("function"), "catch": kw("catch"),
34 | "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
35 | "in": operator, "typeof": operator, "instanceof": operator,
36 | "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
37 | "this": kw("this"), "class": kw("class"), "super": kw("atom"),
38 | "yield": C, "export": kw("export"), "import": kw("import"), "extends": C,
39 | "await": C
40 | };
41 | }();
42 |
43 | var isOperatorChar = /[+\-*&%=<>!?|~^@]/;
44 | var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;
45 |
46 | function readRegexp(stream) {
47 | var escaped = false, next, inSet = false;
48 | while ((next = stream.next()) != null) {
49 | if (!escaped) {
50 | if (next == "/" && !inSet) return;
51 | if (next == "[") inSet = true;
52 | else if (inSet && next == "]") inSet = false;
53 | }
54 | escaped = !escaped && next == "\\";
55 | }
56 | }
57 |
58 | // Used as scratch variables to communicate multiple values without
59 | // consing up tons of objects.
60 | var type, content;
61 | function ret(tp, style, cont) {
62 | type = tp; content = cont;
63 | return style;
64 | }
65 | function tokenBase(stream, state) {
66 | var ch = stream.next();
67 | if (ch == '"' || ch == "'") {
68 | state.tokenize = tokenString(ch);
69 | return state.tokenize(stream, state);
70 | } else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) {
71 | return ret("number", "number");
72 | } else if (ch == "." && stream.match("..")) {
73 | return ret("spread", "meta");
74 | } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
75 | return ret(ch);
76 | } else if (ch == "=" && stream.eat(">")) {
77 | return ret("=>", "operator");
78 | } else if (ch == "0" && stream.eat(/x/i)) {
79 | stream.eatWhile(/[\da-f]/i);
80 | return ret("number", "number");
81 | } else if (ch == "0" && stream.eat(/o/i)) {
82 | stream.eatWhile(/[0-7]/i);
83 | return ret("number", "number");
84 | } else if (ch == "0" && stream.eat(/b/i)) {
85 | stream.eatWhile(/[01]/i);
86 | return ret("number", "number");
87 | } else if (/\d/.test(ch)) {
88 | stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
89 | return ret("number", "number");
90 | } else if (ch == "/") {
91 | if (stream.eat("*")) {
92 | state.tokenize = tokenComment;
93 | return tokenComment(stream, state);
94 | } else if (stream.eat("/")) {
95 | stream.skipToEnd();
96 | return ret("comment", "comment");
97 | } else if (expressionAllowed(stream, state, 1)) {
98 | readRegexp(stream);
99 | stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/);
100 | return ret("regexp", "string-2");
101 | } else {
102 | stream.eat("=");
103 | return ret("operator", "operator", stream.current());
104 | }
105 | } else if (ch == "`") {
106 | state.tokenize = tokenQuasi;
107 | return tokenQuasi(stream, state);
108 | } else if (ch == "#") {
109 | stream.skipToEnd();
110 | return ret("error", "error");
111 | } else if (isOperatorChar.test(ch)) {
112 | if (ch != ">" || !state.lexical || state.lexical.type != ">") {
113 | if (stream.eat("=")) {
114 | if (ch == "!" || ch == "=") stream.eat("=")
115 | } else if (/[<>*+\-]/.test(ch)) {
116 | stream.eat(ch)
117 | if (ch == ">") stream.eat(ch)
118 | }
119 | }
120 | return ret("operator", "operator", stream.current());
121 | } else if (wordRE.test(ch)) {
122 | stream.eatWhile(wordRE);
123 | var word = stream.current()
124 | if (state.lastType != ".") {
125 | if (keywords.propertyIsEnumerable(word)) {
126 | var kw = keywords[word]
127 | return ret(kw.type, kw.style, word)
128 | }
129 | if (word == "async" && stream.match(/^(\s|\/\*.*?\*\/)*[\(\w]/, false))
130 | return ret("async", "keyword", word)
131 | }
132 | return ret("variable", "variable", word)
133 | }
134 | }
135 |
136 | function tokenString(quote) {
137 | return function(stream, state) {
138 | var escaped = false, next;
139 | if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){
140 | state.tokenize = tokenBase;
141 | return ret("jsonld-keyword", "meta");
142 | }
143 | while ((next = stream.next()) != null) {
144 | if (next == quote && !escaped) break;
145 | escaped = !escaped && next == "\\";
146 | }
147 | if (!escaped) state.tokenize = tokenBase;
148 | return ret("string", "string");
149 | };
150 | }
151 |
152 | function tokenComment(stream, state) {
153 | var maybeEnd = false, ch;
154 | while (ch = stream.next()) {
155 | if (ch == "/" && maybeEnd) {
156 | state.tokenize = tokenBase;
157 | break;
158 | }
159 | maybeEnd = (ch == "*");
160 | }
161 | return ret("comment", "comment");
162 | }
163 |
164 | function tokenQuasi(stream, state) {
165 | var escaped = false, next;
166 | while ((next = stream.next()) != null) {
167 | if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) {
168 | state.tokenize = tokenBase;
169 | break;
170 | }
171 | escaped = !escaped && next == "\\";
172 | }
173 | return ret("quasi", "string-2", stream.current());
174 | }
175 |
176 | var brackets = "([{}])";
177 | // This is a crude lookahead trick to try and notice that we're
178 | // parsing the argument patterns for a fat-arrow function before we
179 | // actually hit the arrow token. It only works if the arrow is on
180 | // the same line as the arguments and there's no strange noise
181 | // (comments) in between. Fallback is to only notice when we hit the
182 | // arrow, and not declare the arguments as locals for the arrow
183 | // body.
184 | function findFatArrow(stream, state) {
185 | if (state.fatArrowAt) state.fatArrowAt = null;
186 | var arrow = stream.string.indexOf("=>", stream.start);
187 | if (arrow < 0) return;
188 |
189 | if (isTS) { // Try to skip TypeScript return type declarations after the arguments
190 | var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(stream.string.slice(stream.start, arrow))
191 | if (m) arrow = m.index
192 | }
193 |
194 | var depth = 0, sawSomething = false;
195 | for (var pos = arrow - 1; pos >= 0; --pos) {
196 | var ch = stream.string.charAt(pos);
197 | var bracket = brackets.indexOf(ch);
198 | if (bracket >= 0 && bracket < 3) {
199 | if (!depth) { ++pos; break; }
200 | if (--depth == 0) { if (ch == "(") sawSomething = true; break; }
201 | } else if (bracket >= 3 && bracket < 6) {
202 | ++depth;
203 | } else if (wordRE.test(ch)) {
204 | sawSomething = true;
205 | } else if (/["'\/]/.test(ch)) {
206 | return;
207 | } else if (sawSomething && !depth) {
208 | ++pos;
209 | break;
210 | }
211 | }
212 | if (sawSomething && !depth) state.fatArrowAt = pos;
213 | }
214 |
215 | // Parser
216 |
217 | var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true};
218 |
219 | function JSLexical(indented, column, type, align, prev, info) {
220 | this.indented = indented;
221 | this.column = column;
222 | this.type = type;
223 | this.prev = prev;
224 | this.info = info;
225 | if (align != null) this.align = align;
226 | }
227 |
228 | function inScope(state, varname) {
229 | for (var v = state.localVars; v; v = v.next)
230 | if (v.name == varname) return true;
231 | for (var cx = state.context; cx; cx = cx.prev) {
232 | for (var v = cx.vars; v; v = v.next)
233 | if (v.name == varname) return true;
234 | }
235 | }
236 |
237 | function parseJS(state, style, type, content, stream) {
238 | var cc = state.cc;
239 | // Communicate our context to the combinators.
240 | // (Less wasteful than consing up a hundred closures on every call.)
241 | cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;
242 |
243 | if (!state.lexical.hasOwnProperty("align"))
244 | state.lexical.align = true;
245 |
246 | while(true) {
247 | var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
248 | if (combinator(type, content)) {
249 | while(cc.length && cc[cc.length - 1].lex)
250 | cc.pop()();
251 | if (cx.marked) return cx.marked;
252 | if (type == "variable" && inScope(state, content)) return "variable-2";
253 | return style;
254 | }
255 | }
256 | }
257 |
258 | // Combinator utils
259 |
260 | var cx = {state: null, column: null, marked: null, cc: null};
261 | function pass() {
262 | for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
263 | }
264 | function cont() {
265 | pass.apply(null, arguments);
266 | return true;
267 | }
268 | function register(varname) {
269 | function inList(list) {
270 | for (var v = list; v; v = v.next)
271 | if (v.name == varname) return true;
272 | return false;
273 | }
274 | var state = cx.state;
275 | cx.marked = "def";
276 | if (state.context) {
277 | if (inList(state.localVars)) return;
278 | state.localVars = {name: varname, next: state.localVars};
279 | } else {
280 | if (inList(state.globalVars)) return;
281 | if (parserConfig.globalVars)
282 | state.globalVars = {name: varname, next: state.globalVars};
283 | }
284 | }
285 |
286 | function isModifier(name) {
287 | return name == "public" || name == "private" || name == "protected" || name == "abstract" || name == "readonly"
288 | }
289 |
290 | // Combinators
291 |
292 | var defaultVars = {name: "this", next: {name: "arguments"}};
293 | function pushcontext() {
294 | cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
295 | cx.state.localVars = defaultVars;
296 | }
297 | function popcontext() {
298 | cx.state.localVars = cx.state.context.vars;
299 | cx.state.context = cx.state.context.prev;
300 | }
301 | function pushlex(type, info) {
302 | var result = function() {
303 | var state = cx.state, indent = state.indented;
304 | if (state.lexical.type == "stat") indent = state.lexical.indented;
305 | else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev)
306 | indent = outer.indented;
307 | state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
308 | };
309 | result.lex = true;
310 | return result;
311 | }
312 | function poplex() {
313 | var state = cx.state;
314 | if (state.lexical.prev) {
315 | if (state.lexical.type == ")")
316 | state.indented = state.lexical.indented;
317 | state.lexical = state.lexical.prev;
318 | }
319 | }
320 | poplex.lex = true;
321 |
322 | function expect(wanted) {
323 | function exp(type) {
324 | if (type == wanted) return cont();
325 | else if (wanted == ";") return pass();
326 | else return cont(exp);
327 | };
328 | return exp;
329 | }
330 |
331 | function statement(type, value) {
332 | if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex);
333 | if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex);
334 | if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
335 | if (type == "keyword d") return cx.stream.match(/^\s*$/, false) ? cont() : cont(pushlex("stat"), maybeexpression, expect(";"), poplex);
336 | if (type == "debugger") return cont(expect(";"));
337 | if (type == "{") return cont(pushlex("}"), block, poplex);
338 | if (type == ";") return cont();
339 | if (type == "if") {
340 | if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
341 | cx.state.cc.pop()();
342 | return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse);
343 | }
344 | if (type == "function") return cont(functiondef);
345 | if (type == "for") return cont(pushlex("form"), forspec, statement, poplex);
346 | if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), className, poplex); }
347 | if (type == "variable") {
348 | if (isTS && value == "type") {
349 | cx.marked = "keyword"
350 | return cont(typeexpr, expect("operator"), typeexpr, expect(";"));
351 | } else if (isTS && value == "declare") {
352 | cx.marked = "keyword"
353 | return cont(statement)
354 | } else if (isTS && (value == "module" || value == "enum") && cx.stream.match(/^\s*\w/, false)) {
355 | cx.marked = "keyword"
356 | return cont(pushlex("form"), pattern, expect("{"), pushlex("}"), block, poplex, poplex)
357 | } else if (isTS && value == "namespace") {
358 | cx.marked = "keyword"
359 | return cont(pushlex("form"), expression, block, poplex)
360 | } else {
361 | return cont(pushlex("stat"), maybelabel);
362 | }
363 | }
364 | if (type == "switch") return cont(pushlex("form"), parenExpr, expect("{"), pushlex("}", "switch"),
365 | block, poplex, poplex);
366 | if (type == "case") return cont(expression, expect(":"));
367 | if (type == "default") return cont(expect(":"));
368 | if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
369 | statement, poplex, popcontext);
370 | if (type == "export") return cont(pushlex("stat"), afterExport, poplex);
371 | if (type == "import") return cont(pushlex("stat"), afterImport, poplex);
372 | if (type == "async") return cont(statement)
373 | if (value == "@") return cont(expression, statement)
374 | return pass(pushlex("stat"), expression, expect(";"), poplex);
375 | }
376 | function expression(type, value) {
377 | return expressionInner(type, value, false);
378 | }
379 | function expressionNoComma(type, value) {
380 | return expressionInner(type, value, true);
381 | }
382 | function parenExpr(type) {
383 | if (type != "(") return pass()
384 | return cont(pushlex(")"), expression, expect(")"), poplex)
385 | }
386 | function expressionInner(type, value, noComma) {
387 | if (cx.state.fatArrowAt == cx.stream.start) {
388 | var body = noComma ? arrowBodyNoComma : arrowBody;
389 | if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, expect("=>"), body, popcontext);
390 | else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
391 | }
392 |
393 | var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
394 | if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
395 | if (type == "function") return cont(functiondef, maybeop);
396 | if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), classExpression, poplex); }
397 | if (type == "keyword c" || type == "async") return cont(noComma ? expressionNoComma : expression);
398 | if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
399 | if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
400 | if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
401 | if (type == "{") return contCommasep(objprop, "}", null, maybeop);
402 | if (type == "quasi") return pass(quasi, maybeop);
403 | if (type == "new") return cont(maybeTarget(noComma));
404 | return cont();
405 | }
406 | function maybeexpression(type) {
407 | if (type.match(/[;\}\)\],]/)) return pass();
408 | return pass(expression);
409 | }
410 |
411 | function maybeoperatorComma(type, value) {
412 | if (type == ",") return cont(expression);
413 | return maybeoperatorNoComma(type, value, false);
414 | }
415 | function maybeoperatorNoComma(type, value, noComma) {
416 | var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
417 | var expr = noComma == false ? expression : expressionNoComma;
418 | if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
419 | if (type == "operator") {
420 | if (/\+\+|--/.test(value) || isTS && value == "!") return cont(me);
421 | if (isTS && value == "<" && cx.stream.match(/^([^>]|<.*?>)*>\s*\(/, false))
422 | return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, me);
423 | if (value == "?") return cont(expression, expect(":"), expr);
424 | return cont(expr);
425 | }
426 | if (type == "quasi") { return pass(quasi, me); }
427 | if (type == ";") return;
428 | if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
429 | if (type == ".") return cont(property, me);
430 | if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
431 | if (isTS && value == "as") { cx.marked = "keyword"; return cont(typeexpr, me) }
432 | if (type == "regexp") {
433 | cx.state.lastType = cx.marked = "operator"
434 | cx.stream.backUp(cx.stream.pos - cx.stream.start - 1)
435 | return cont(expr)
436 | }
437 | }
438 | function quasi(type, value) {
439 | if (type != "quasi") return pass();
440 | if (value.slice(value.length - 2) != "${") return cont(quasi);
441 | return cont(expression, continueQuasi);
442 | }
443 | function continueQuasi(type) {
444 | if (type == "}") {
445 | cx.marked = "string-2";
446 | cx.state.tokenize = tokenQuasi;
447 | return cont(quasi);
448 | }
449 | }
450 | function arrowBody(type) {
451 | findFatArrow(cx.stream, cx.state);
452 | return pass(type == "{" ? statement : expression);
453 | }
454 | function arrowBodyNoComma(type) {
455 | findFatArrow(cx.stream, cx.state);
456 | return pass(type == "{" ? statement : expressionNoComma);
457 | }
458 | function maybeTarget(noComma) {
459 | return function(type) {
460 | if (type == ".") return cont(noComma ? targetNoComma : target);
461 | else if (type == "variable" && isTS) return cont(maybeTypeArgs, noComma ? maybeoperatorNoComma : maybeoperatorComma)
462 | else return pass(noComma ? expressionNoComma : expression);
463 | };
464 | }
465 | function target(_, value) {
466 | if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); }
467 | }
468 | function targetNoComma(_, value) {
469 | if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); }
470 | }
471 | function maybelabel(type) {
472 | if (type == ":") return cont(poplex, statement);
473 | return pass(maybeoperatorComma, expect(";"), poplex);
474 | }
475 | function property(type) {
476 | if (type == "variable") {cx.marked = "property"; return cont();}
477 | }
478 | function objprop(type, value) {
479 | if (type == "async") {
480 | cx.marked = "property";
481 | return cont(objprop);
482 | } else if (type == "variable" || cx.style == "keyword") {
483 | cx.marked = "property";
484 | if (value == "get" || value == "set") return cont(getterSetter);
485 | var m // Work around fat-arrow-detection complication for detecting typescript typed arrow params
486 | if (isTS && cx.state.fatArrowAt == cx.stream.start && (m = cx.stream.match(/^\s*:\s*/, false)))
487 | cx.state.fatArrowAt = cx.stream.pos + m[0].length
488 | return cont(afterprop);
489 | } else if (type == "number" || type == "string") {
490 | cx.marked = jsonldMode ? "property" : (cx.style + " property");
491 | return cont(afterprop);
492 | } else if (type == "jsonld-keyword") {
493 | return cont(afterprop);
494 | } else if (isTS && isModifier(value)) {
495 | cx.marked = "keyword"
496 | return cont(objprop)
497 | } else if (type == "[") {
498 | return cont(expression, maybetype, expect("]"), afterprop);
499 | } else if (type == "spread") {
500 | return cont(expressionNoComma, afterprop);
501 | } else if (value == "*") {
502 | cx.marked = "keyword";
503 | return cont(objprop);
504 | } else if (type == ":") {
505 | return pass(afterprop)
506 | }
507 | }
508 | function getterSetter(type) {
509 | if (type != "variable") return pass(afterprop);
510 | cx.marked = "property";
511 | return cont(functiondef);
512 | }
513 | function afterprop(type) {
514 | if (type == ":") return cont(expressionNoComma);
515 | if (type == "(") return pass(functiondef);
516 | }
517 | function commasep(what, end, sep) {
518 | function proceed(type, value) {
519 | if (sep ? sep.indexOf(type) > -1 : type == ",") {
520 | var lex = cx.state.lexical;
521 | if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
522 | return cont(function(type, value) {
523 | if (type == end || value == end) return pass()
524 | return pass(what)
525 | }, proceed);
526 | }
527 | if (type == end || value == end) return cont();
528 | return cont(expect(end));
529 | }
530 | return function(type, value) {
531 | if (type == end || value == end) return cont();
532 | return pass(what, proceed);
533 | };
534 | }
535 | function contCommasep(what, end, info) {
536 | for (var i = 3; i < arguments.length; i++)
537 | cx.cc.push(arguments[i]);
538 | return cont(pushlex(end, info), commasep(what, end), poplex);
539 | }
540 | function block(type) {
541 | if (type == "}") return cont();
542 | return pass(statement, block);
543 | }
544 | function maybetype(type, value) {
545 | if (isTS) {
546 | if (type == ":") return cont(typeexpr);
547 | if (value == "?") return cont(maybetype);
548 | }
549 | }
550 | function mayberettype(type) {
551 | if (isTS && type == ":") {
552 | if (cx.stream.match(/^\s*\w+\s+is\b/, false)) return cont(expression, isKW, typeexpr)
553 | else return cont(typeexpr)
554 | }
555 | }
556 | function isKW(_, value) {
557 | if (value == "is") {
558 | cx.marked = "keyword"
559 | return cont()
560 | }
561 | }
562 | function typeexpr(type, value) {
563 | if (type == "variable" || value == "void") {
564 | if (value == "keyof") {
565 | cx.marked = "keyword"
566 | return cont(typeexpr)
567 | } else {
568 | cx.marked = "type"
569 | return cont(afterType)
570 | }
571 | }
572 | if (type == "string" || type == "number" || type == "atom") return cont(afterType);
573 | if (type == "[") return cont(pushlex("]"), commasep(typeexpr, "]", ","), poplex, afterType)
574 | if (type == "{") return cont(pushlex("}"), commasep(typeprop, "}", ",;"), poplex, afterType)
575 | if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType)
576 | }
577 | function maybeReturnType(type) {
578 | if (type == "=>") return cont(typeexpr)
579 | }
580 | function typeprop(type, value) {
581 | if (type == "variable" || cx.style == "keyword") {
582 | cx.marked = "property"
583 | return cont(typeprop)
584 | } else if (value == "?") {
585 | return cont(typeprop)
586 | } else if (type == ":") {
587 | return cont(typeexpr)
588 | } else if (type == "[") {
589 | return cont(expression, maybetype, expect("]"), typeprop)
590 | }
591 | }
592 | function typearg(type) {
593 | if (type == "variable") return cont(typearg)
594 | else if (type == ":") return cont(typeexpr)
595 | }
596 | function afterType(type, value) {
597 | if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
598 | if (value == "|" || type == ".") return cont(typeexpr)
599 | if (type == "[") return cont(expect("]"), afterType)
600 | if (value == "extends" || value == "implements") { cx.marked = "keyword"; return cont(typeexpr) }
601 | }
602 | function maybeTypeArgs(_, value) {
603 | if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
604 | }
605 | function typeparam() {
606 | return pass(typeexpr, maybeTypeDefault)
607 | }
608 | function maybeTypeDefault(_, value) {
609 | if (value == "=") return cont(typeexpr)
610 | }
611 | function vardef() {
612 | return pass(pattern, maybetype, maybeAssign, vardefCont);
613 | }
614 | function pattern(type, value) {
615 | if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(pattern) }
616 | if (type == "variable") { register(value); return cont(); }
617 | if (type == "spread") return cont(pattern);
618 | if (type == "[") return contCommasep(pattern, "]");
619 | if (type == "{") return contCommasep(proppattern, "}");
620 | }
621 | function proppattern(type, value) {
622 | if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
623 | register(value);
624 | return cont(maybeAssign);
625 | }
626 | if (type == "variable") cx.marked = "property";
627 | if (type == "spread") return cont(pattern);
628 | if (type == "}") return pass();
629 | return cont(expect(":"), pattern, maybeAssign);
630 | }
631 | function maybeAssign(_type, value) {
632 | if (value == "=") return cont(expressionNoComma);
633 | }
634 | function vardefCont(type) {
635 | if (type == ",") return cont(vardef);
636 | }
637 | function maybeelse(type, value) {
638 | if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
639 | }
640 | function forspec(type) {
641 | if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex);
642 | }
643 | function forspec1(type) {
644 | if (type == "var") return cont(vardef, expect(";"), forspec2);
645 | if (type == ";") return cont(forspec2);
646 | if (type == "variable") return cont(formaybeinof);
647 | return pass(expression, expect(";"), forspec2);
648 | }
649 | function formaybeinof(_type, value) {
650 | if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
651 | return cont(maybeoperatorComma, forspec2);
652 | }
653 | function forspec2(type, value) {
654 | if (type == ";") return cont(forspec3);
655 | if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
656 | return pass(expression, expect(";"), forspec3);
657 | }
658 | function forspec3(type) {
659 | if (type != ")") cont(expression);
660 | }
661 | function functiondef(type, value) {
662 | if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
663 | if (type == "variable") {register(value); return cont(functiondef);}
664 | if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, statement, popcontext);
665 | if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondef)
666 | }
667 | function funarg(type, value) {
668 | if (value == "@") cont(expression, funarg)
669 | if (type == "spread") return cont(funarg);
670 | if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(funarg); }
671 | return pass(pattern, maybetype, maybeAssign);
672 | }
673 | function classExpression(type, value) {
674 | // Class expressions may have an optional name.
675 | if (type == "variable") return className(type, value);
676 | return classNameAfter(type, value);
677 | }
678 | function className(type, value) {
679 | if (type == "variable") {register(value); return cont(classNameAfter);}
680 | }
681 | function classNameAfter(type, value) {
682 | if (value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, classNameAfter)
683 | if (value == "extends" || value == "implements" || (isTS && type == ","))
684 | return cont(isTS ? typeexpr : expression, classNameAfter);
685 | if (type == "{") return cont(pushlex("}"), classBody, poplex);
686 | }
687 | function classBody(type, value) {
688 | if (type == "async" ||
689 | (type == "variable" &&
690 | (value == "static" || value == "get" || value == "set" || (isTS && isModifier(value))) &&
691 | cx.stream.match(/^\s+[\w$\xa1-\uffff]/, false))) {
692 | cx.marked = "keyword";
693 | return cont(classBody);
694 | }
695 | if (type == "variable" || cx.style == "keyword") {
696 | cx.marked = "property";
697 | return cont(isTS ? classfield : functiondef, classBody);
698 | }
699 | if (type == "[")
700 | return cont(expression, maybetype, expect("]"), isTS ? classfield : functiondef, classBody)
701 | if (value == "*") {
702 | cx.marked = "keyword";
703 | return cont(classBody);
704 | }
705 | if (type == ";") return cont(classBody);
706 | if (type == "}") return cont();
707 | if (value == "@") return cont(expression, classBody)
708 | }
709 | function classfield(type, value) {
710 | if (value == "?") return cont(classfield)
711 | if (type == ":") return cont(typeexpr, maybeAssign)
712 | if (value == "=") return cont(expressionNoComma)
713 | return pass(functiondef)
714 | }
715 | function afterExport(type, value) {
716 | if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
717 | if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
718 | if (type == "{") return cont(commasep(exportField, "}"), maybeFrom, expect(";"));
719 | return pass(statement);
720 | }
721 | function exportField(type, value) {
722 | if (value == "as") { cx.marked = "keyword"; return cont(expect("variable")); }
723 | if (type == "variable") return pass(expressionNoComma, exportField);
724 | }
725 | function afterImport(type) {
726 | if (type == "string") return cont();
727 | return pass(importSpec, maybeMoreImports, maybeFrom);
728 | }
729 | function importSpec(type, value) {
730 | if (type == "{") return contCommasep(importSpec, "}");
731 | if (type == "variable") register(value);
732 | if (value == "*") cx.marked = "keyword";
733 | return cont(maybeAs);
734 | }
735 | function maybeMoreImports(type) {
736 | if (type == ",") return cont(importSpec, maybeMoreImports)
737 | }
738 | function maybeAs(_type, value) {
739 | if (value == "as") { cx.marked = "keyword"; return cont(importSpec); }
740 | }
741 | function maybeFrom(_type, value) {
742 | if (value == "from") { cx.marked = "keyword"; return cont(expression); }
743 | }
744 | function arrayLiteral(type) {
745 | if (type == "]") return cont();
746 | return pass(commasep(expressionNoComma, "]"));
747 | }
748 |
749 | function isContinuedStatement(state, textAfter) {
750 | return state.lastType == "operator" || state.lastType == "," ||
751 | isOperatorChar.test(textAfter.charAt(0)) ||
752 | /[,.]/.test(textAfter.charAt(0));
753 | }
754 |
755 | function expressionAllowed(stream, state, backUp) {
756 | return state.tokenize == tokenBase &&
757 | /^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
758 | (state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))
759 | }
760 |
761 | // Interface
762 |
763 | return {
764 | startState: function(basecolumn) {
765 | var state = {
766 | tokenize: tokenBase,
767 | lastType: "sof",
768 | cc: [],
769 | lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
770 | localVars: parserConfig.localVars,
771 | context: parserConfig.localVars && {vars: parserConfig.localVars},
772 | indented: basecolumn || 0
773 | };
774 | if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
775 | state.globalVars = parserConfig.globalVars;
776 | return state;
777 | },
778 |
779 | token: function(stream, state) {
780 | if (stream.sol()) {
781 | if (!state.lexical.hasOwnProperty("align"))
782 | state.lexical.align = false;
783 | state.indented = stream.indentation();
784 | findFatArrow(stream, state);
785 | }
786 | if (state.tokenize != tokenComment && stream.eatSpace()) return null;
787 | var style = state.tokenize(stream, state);
788 | if (type == "comment") return style;
789 | state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
790 | return parseJS(state, style, type, content, stream);
791 | },
792 |
793 | indent: function(state, textAfter) {
794 | if (state.tokenize == tokenComment) return CodeMirror.Pass;
795 | if (state.tokenize != tokenBase) return 0;
796 | var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top
797 | // Kludge to prevent 'maybelse' from blocking lexical scope pops
798 | if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
799 | var c = state.cc[i];
800 | if (c == poplex) lexical = lexical.prev;
801 | else if (c != maybeelse) break;
802 | }
803 | while ((lexical.type == "stat" || lexical.type == "form") &&
804 | (firstChar == "}" || ((top = state.cc[state.cc.length - 1]) &&
805 | (top == maybeoperatorComma || top == maybeoperatorNoComma) &&
806 | !/^[,\.=+\-*:?[\(]/.test(textAfter))))
807 | lexical = lexical.prev;
808 | if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
809 | lexical = lexical.prev;
810 | var type = lexical.type, closing = firstChar == type;
811 |
812 | if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0);
813 | else if (type == "form" && firstChar == "{") return lexical.indented;
814 | else if (type == "form") return lexical.indented + indentUnit;
815 | else if (type == "stat")
816 | return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0);
817 | else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
818 | return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
819 | else if (lexical.align) return lexical.column + (closing ? 0 : 1);
820 | else return lexical.indented + (closing ? 0 : indentUnit);
821 | },
822 |
823 | electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
824 | blockCommentStart: jsonMode ? null : "/*",
825 | blockCommentEnd: jsonMode ? null : "*/",
826 | blockCommentContinue: jsonMode ? null : " * ",
827 | lineComment: jsonMode ? null : "//",
828 | fold: "brace",
829 | closeBrackets: "()[]{}''\"\"``",
830 |
831 | helperType: jsonMode ? "json" : "javascript",
832 | jsonldMode: jsonldMode,
833 | jsonMode: jsonMode,
834 |
835 | expressionAllowed: expressionAllowed,
836 |
837 | skipExpression: function(state) {
838 | var top = state.cc[state.cc.length - 1]
839 | if (top == expression || top == expressionNoComma) state.cc.pop()
840 | }
841 | };
842 | });
843 |
844 | CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/);
845 |
846 | CodeMirror.defineMIME("text/javascript", "javascript");
847 | CodeMirror.defineMIME("text/ecmascript", "javascript");
848 | CodeMirror.defineMIME("application/javascript", "javascript");
849 | CodeMirror.defineMIME("application/x-javascript", "javascript");
850 | CodeMirror.defineMIME("application/ecmascript", "javascript");
851 | CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
852 | CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
853 | CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true});
854 | CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
855 | CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
856 |
857 | });
858 |
--------------------------------------------------------------------------------
/src/main/resources/public/console/js/jquery.blockui.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * jQuery blockUI plugin
3 | * Version 2.70.0-2014.11.23
4 | * Requires jQuery v1.7 or later
5 | *
6 | * Examples at: http://malsup.com/jquery/block/
7 | * Copyright (c) 2007-2013 M. Alsup
8 | * Dual licensed under the MIT and GPL licenses:
9 | * http://www.opensource.org/licenses/mit-license.php
10 | * http://www.gnu.org/licenses/gpl.html
11 | *
12 | * Thanks to Amir-Hossein Sobhi for some excellent contributions!
13 | */
14 |
15 | ;(function() {
16 | /*jshint eqeqeq:false curly:false latedef:false */
17 | "use strict";
18 |
19 | function setup($) {
20 | $.fn._fadeIn = $.fn.fadeIn;
21 |
22 | var noOp = $.noop || function() {};
23 |
24 | // this bit is to ensure we don't call setExpression when we shouldn't (with extra muscle to handle
25 | // confusing userAgent strings on Vista)
26 | var msie = /MSIE/.test(navigator.userAgent);
27 | var ie6 = /MSIE 6.0/.test(navigator.userAgent) && ! /MSIE 8.0/.test(navigator.userAgent);
28 | var mode = document.documentMode || 0;
29 | var setExpr = $.isFunction( document.createElement('div').style.setExpression );
30 |
31 | // global $ methods for blocking/unblocking the entire page
32 | $.blockUI = function(opts) { install(window, opts); };
33 | $.unblockUI = function(opts) { remove(window, opts); };
34 |
35 | // convenience method for quick growl-like notifications (http://www.google.com/search?q=growl)
36 | $.growlUI = function(title, message, timeout, onClose) {
37 | var $m = $('');
38 | if (title) $m.append('
'+title+'
');
39 | if (message) $m.append('
'+message+'
');
40 | if (timeout === undefined) timeout = 3000;
41 |
42 | // Added by konapun: Set timeout to 30 seconds if this growl is moused over, like normal toast notifications
43 | var callBlock = function(opts) {
44 | opts = opts || {};
45 |
46 | $.blockUI({
47 | message: $m,
48 | fadeIn : typeof opts.fadeIn !== 'undefined' ? opts.fadeIn : 700,
49 | fadeOut: typeof opts.fadeOut !== 'undefined' ? opts.fadeOut : 1000,
50 | timeout: typeof opts.timeout !== 'undefined' ? opts.timeout : timeout,
51 | centerY: false,
52 | showOverlay: false,
53 | onUnblock: onClose,
54 | css: $.blockUI.defaults.growlCSS
55 | });
56 | };
57 |
58 | callBlock();
59 | var nonmousedOpacity = $m.css('opacity');
60 | $m.mouseover(function() {
61 | callBlock({
62 | fadeIn: 0,
63 | timeout: 30000
64 | });
65 |
66 | var displayBlock = $('.blockMsg');
67 | displayBlock.stop(); // cancel fadeout if it has started
68 | displayBlock.fadeTo(300, 1); // make it easier to read the message by removing transparency
69 | }).mouseout(function() {
70 | $('.blockMsg').fadeOut(1000);
71 | });
72 | // End konapun additions
73 | };
74 |
75 | // plugin method for blocking element content
76 | $.fn.block = function(opts) {
77 | if ( this[0] === window ) {
78 | $.blockUI( opts );
79 | return this;
80 | }
81 | var fullOpts = $.extend({}, $.blockUI.defaults, opts || {});
82 | this.each(function() {
83 | var $el = $(this);
84 | if (fullOpts.ignoreIfBlocked && $el.data('blockUI.isBlocked'))
85 | return;
86 | $el.unblock({ fadeOut: 0 });
87 | });
88 |
89 | return this.each(function() {
90 | if ($.css(this,'position') == 'static') {
91 | this.style.position = 'relative';
92 | $(this).data('blockUI.static', true);
93 | }
94 | this.style.zoom = 1; // force 'hasLayout' in ie
95 | install(this, opts);
96 | });
97 | };
98 |
99 | // plugin method for unblocking element content
100 | $.fn.unblock = function(opts) {
101 | if ( this[0] === window ) {
102 | $.unblockUI( opts );
103 | return this;
104 | }
105 | return this.each(function() {
106 | remove(this, opts);
107 | });
108 | };
109 |
110 | $.blockUI.version = 2.70; // 2nd generation blocking at no extra cost!
111 |
112 | // override these in your code to change the default behavior and style
113 | $.blockUI.defaults = {
114 | // message displayed when blocking (use null for no message)
115 | message: '
Please wait...
',
116 |
117 | title: null, // title string; only used when theme == true
118 | draggable: true, // only used when theme == true (requires jquery-ui.js to be loaded)
119 |
120 | theme: false, // set to true to use with jQuery UI themes
121 |
122 | // styles for the message when blocking; if you wish to disable
123 | // these and use an external stylesheet then do this in your code:
124 | // $.blockUI.defaults.css = {};
125 | css: {
126 | padding: 0,
127 | margin: 0,
128 | width: '30%',
129 | top: '40%',
130 | left: '35%',
131 | textAlign: 'center',
132 | color: '#000',
133 | border: '3px solid #aaa',
134 | backgroundColor:'#fff',
135 | cursor: 'wait'
136 | },
137 |
138 | // minimal style set used when themes are used
139 | themedCSS: {
140 | width: '30%',
141 | top: '40%',
142 | left: '35%'
143 | },
144 |
145 | // styles for the overlay
146 | overlayCSS: {
147 | backgroundColor: '#000',
148 | opacity: 0.6,
149 | cursor: 'wait'
150 | },
151 |
152 | // style to replace wait cursor before unblocking to correct issue
153 | // of lingering wait cursor
154 | cursorReset: 'default',
155 |
156 | // styles applied when using $.growlUI
157 | growlCSS: {
158 | width: '350px',
159 | top: '10px',
160 | left: '',
161 | right: '10px',
162 | border: 'none',
163 | padding: '5px',
164 | opacity: 0.6,
165 | cursor: 'default',
166 | color: '#fff',
167 | backgroundColor: '#000',
168 | '-webkit-border-radius':'10px',
169 | '-moz-border-radius': '10px',
170 | 'border-radius': '10px'
171 | },
172 |
173 | // IE issues: 'about:blank' fails on HTTPS and javascript:false is s-l-o-w
174 | // (hat tip to Jorge H. N. de Vasconcelos)
175 | /*jshint scripturl:true */
176 | iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank',
177 |
178 | // force usage of iframe in non-IE browsers (handy for blocking applets)
179 | forceIframe: false,
180 |
181 | // z-index for the blocking overlay
182 | baseZ: 1000,
183 |
184 | // set these to true to have the message automatically centered
185 | centerX: true, // <-- only effects element blocking (page block controlled via css above)
186 | centerY: true,
187 |
188 | // allow body element to be stetched in ie6; this makes blocking look better
189 | // on "short" pages. disable if you wish to prevent changes to the body height
190 | allowBodyStretch: true,
191 |
192 | // enable if you want key and mouse events to be disabled for content that is blocked
193 | bindEvents: true,
194 |
195 | // be default blockUI will supress tab navigation from leaving blocking content
196 | // (if bindEvents is true)
197 | constrainTabKey: true,
198 |
199 | // fadeIn time in millis; set to 0 to disable fadeIn on block
200 | fadeIn: 200,
201 |
202 | // fadeOut time in millis; set to 0 to disable fadeOut on unblock
203 | fadeOut: 400,
204 |
205 | // time in millis to wait before auto-unblocking; set to 0 to disable auto-unblock
206 | timeout: 0,
207 |
208 | // disable if you don't want to show the overlay
209 | showOverlay: true,
210 |
211 | // if true, focus will be placed in the first available input field when
212 | // page blocking
213 | focusInput: true,
214 |
215 | // elements that can receive focus
216 | focusableElements: ':input:enabled:visible',
217 |
218 | // suppresses the use of overlay styles on FF/Linux (due to performance issues with opacity)
219 | // no longer needed in 2012
220 | // applyPlatformOpacityRules: true,
221 |
222 | // callback method invoked when fadeIn has completed and blocking message is visible
223 | onBlock: null,
224 |
225 | // callback method invoked when unblocking has completed; the callback is
226 | // passed the element that has been unblocked (which is the window object for page
227 | // blocks) and the options that were passed to the unblock call:
228 | // onUnblock(element, options)
229 | onUnblock: null,
230 |
231 | // callback method invoked when the overlay area is clicked.
232 | // setting this will turn the cursor to a pointer, otherwise cursor defined in overlayCss will be used.
233 | onOverlayClick: null,
234 |
235 | // don't ask; if you really must know: http://groups.google.com/group/jquery-en/browse_thread/thread/36640a8730503595/2f6a79a77a78e493#2f6a79a77a78e493
236 | quirksmodeOffsetHack: 4,
237 |
238 | // class name of the message block
239 | blockMsgClass: 'blockMsg',
240 |
241 | // if it is already blocked, then ignore it (don't unblock and reblock)
242 | ignoreIfBlocked: false
243 | };
244 |
245 | // private data and functions follow...
246 |
247 | var pageBlock = null;
248 | var pageBlockEls = [];
249 |
250 | function install(el, opts) {
251 | var css, themedCSS;
252 | var full = (el == window);
253 | var msg = (opts && opts.message !== undefined ? opts.message : undefined);
254 | opts = $.extend({}, $.blockUI.defaults, opts || {});
255 |
256 | if (opts.ignoreIfBlocked && $(el).data('blockUI.isBlocked'))
257 | return;
258 |
259 | opts.overlayCSS = $.extend({}, $.blockUI.defaults.overlayCSS, opts.overlayCSS || {});
260 | css = $.extend({}, $.blockUI.defaults.css, opts.css || {});
261 | if (opts.onOverlayClick)
262 | opts.overlayCSS.cursor = 'pointer';
263 |
264 | themedCSS = $.extend({}, $.blockUI.defaults.themedCSS, opts.themedCSS || {});
265 | msg = msg === undefined ? opts.message : msg;
266 |
267 | // remove the current block (if there is one)
268 | if (full && pageBlock)
269 | remove(window, {fadeOut:0});
270 |
271 | // if an existing element is being used as the blocking content then we capture
272 | // its current place in the DOM (and current display style) so we can restore
273 | // it when we unblock
274 | if (msg && typeof msg != 'string' && (msg.parentNode || msg.jquery)) {
275 | var node = msg.jquery ? msg[0] : msg;
276 | var data = {};
277 | $(el).data('blockUI.history', data);
278 | data.el = node;
279 | data.parent = node.parentNode;
280 | data.display = node.style.display;
281 | data.position = node.style.position;
282 | if (data.parent)
283 | data.parent.removeChild(node);
284 | }
285 |
286 | $(el).data('blockUI.onUnblock', opts.onUnblock);
287 | var z = opts.baseZ;
288 |
289 | // blockUI uses 3 layers for blocking, for simplicity they are all used on every platform;
290 | // layer1 is the iframe layer which is used to supress bleed through of underlying content
291 | // layer2 is the overlay layer which has opacity and a wait cursor (by default)
292 | // layer3 is the message content that is displayed while blocking
293 | var lyr1, lyr2, lyr3, s;
294 | if (msie || opts.forceIframe)
295 | lyr1 = $('');
296 | else
297 | lyr1 = $('');
298 |
299 | if (opts.theme)
300 | lyr2 = $('');
301 | else
302 | lyr2 = $('');
303 |
304 | if (opts.theme && full) {
305 | s = '
';
306 | if ( opts.title ) {
307 | s += '
'+(opts.title || ' ')+'
';
308 | }
309 | s += '';
310 | s += '
';
311 | }
312 | else if (opts.theme) {
313 | s = '
';
314 | if ( opts.title ) {
315 | s += '
'+(opts.title || ' ')+'
';
316 | }
317 | s += '';
318 | s += '
';
319 | }
320 | else if (full) {
321 | s = '';
322 | }
323 | else {
324 | s = '';
325 | }
326 | lyr3 = $(s);
327 |
328 | // if we have a message, style it
329 | if (msg) {
330 | if (opts.theme) {
331 | lyr3.css(themedCSS);
332 | lyr3.addClass('ui-widget-content');
333 | }
334 | else
335 | lyr3.css(css);
336 | }
337 |
338 | // style the overlay
339 | if (!opts.theme /*&& (!opts.applyPlatformOpacityRules)*/)
340 | lyr2.css(opts.overlayCSS);
341 | lyr2.css('position', full ? 'fixed' : 'absolute');
342 |
343 | // make iframe layer transparent in IE
344 | if (msie || opts.forceIframe)
345 | lyr1.css('opacity',0.0);
346 |
347 | //$([lyr1[0],lyr2[0],lyr3[0]]).appendTo(full ? 'body' : el);
348 | var layers = [lyr1,lyr2,lyr3], $par = full ? $('body') : $(el);
349 | $.each(layers, function() {
350 | this.appendTo($par);
351 | });
352 |
353 | if (opts.theme && opts.draggable && $.fn.draggable) {
354 | lyr3.draggable({
355 | handle: '.ui-dialog-titlebar',
356 | cancel: 'li'
357 | });
358 | }
359 |
360 | // ie7 must use absolute positioning in quirks mode and to account for activex issues (when scrolling)
361 | var expr = setExpr && (!$.support.boxModel || $('object,embed', full ? null : el).length > 0);
362 | if (ie6 || expr) {
363 | // give body 100% height
364 | if (full && opts.allowBodyStretch && $.support.boxModel)
365 | $('html,body').css('height','100%');
366 |
367 | // fix ie6 issue when blocked element has a border width
368 | if ((ie6 || !$.support.boxModel) && !full) {
369 | var t = sz(el,'borderTopWidth'), l = sz(el,'borderLeftWidth');
370 | var fixT = t ? '(0 - '+t+')' : 0;
371 | var fixL = l ? '(0 - '+l+')' : 0;
372 | }
373 |
374 | // simulate fixed position
375 | $.each(layers, function(i,o) {
376 | var s = o[0].style;
377 | s.position = 'absolute';
378 | if (i < 2) {
379 | if (full)
380 | s.setExpression('height','Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.support.boxModel?0:'+opts.quirksmodeOffsetHack+') + "px"');
381 | else
382 | s.setExpression('height','this.parentNode.offsetHeight + "px"');
383 | if (full)
384 | s.setExpression('width','jQuery.support.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"');
385 | else
386 | s.setExpression('width','this.parentNode.offsetWidth + "px"');
387 | if (fixL) s.setExpression('left', fixL);
388 | if (fixT) s.setExpression('top', fixT);
389 | }
390 | else if (opts.centerY) {
391 | if (full) s.setExpression('top','(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"');
392 | s.marginTop = 0;
393 | }
394 | else if (!opts.centerY && full) {
395 | var top = (opts.css && opts.css.top) ? parseInt(opts.css.top, 10) : 0;
396 | var expression = '((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"';
397 | s.setExpression('top',expression);
398 | }
399 | });
400 | }
401 |
402 | // show the message
403 | if (msg) {
404 | if (opts.theme)
405 | lyr3.find('.ui-widget-content').append(msg);
406 | else
407 | lyr3.append(msg);
408 | if (msg.jquery || msg.nodeType)
409 | $(msg).show();
410 | }
411 |
412 | if ((msie || opts.forceIframe) && opts.showOverlay)
413 | lyr1.show(); // opacity is zero
414 | if (opts.fadeIn) {
415 | var cb = opts.onBlock ? opts.onBlock : noOp;
416 | var cb1 = (opts.showOverlay && !msg) ? cb : noOp;
417 | var cb2 = msg ? cb : noOp;
418 | if (opts.showOverlay)
419 | lyr2._fadeIn(opts.fadeIn, cb1);
420 | if (msg)
421 | lyr3._fadeIn(opts.fadeIn, cb2);
422 | }
423 | else {
424 | if (opts.showOverlay)
425 | lyr2.show();
426 | if (msg)
427 | lyr3.show();
428 | if (opts.onBlock)
429 | opts.onBlock.bind(lyr3)();
430 | }
431 |
432 | // bind key and mouse events
433 | bind(1, el, opts);
434 |
435 | if (full) {
436 | pageBlock = lyr3[0];
437 | pageBlockEls = $(opts.focusableElements,pageBlock);
438 | if (opts.focusInput)
439 | setTimeout(focus, 20);
440 | }
441 | else
442 | center(lyr3[0], opts.centerX, opts.centerY);
443 |
444 | if (opts.timeout) {
445 | // auto-unblock
446 | var to = setTimeout(function() {
447 | if (full)
448 | $.unblockUI(opts);
449 | else
450 | $(el).unblock(opts);
451 | }, opts.timeout);
452 | $(el).data('blockUI.timeout', to);
453 | }
454 | }
455 |
456 | // remove the block
457 | function remove(el, opts) {
458 | var count;
459 | var full = (el == window);
460 | var $el = $(el);
461 | var data = $el.data('blockUI.history');
462 | var to = $el.data('blockUI.timeout');
463 | if (to) {
464 | clearTimeout(to);
465 | $el.removeData('blockUI.timeout');
466 | }
467 | opts = $.extend({}, $.blockUI.defaults, opts || {});
468 | bind(0, el, opts); // unbind events
469 |
470 | if (opts.onUnblock === null) {
471 | opts.onUnblock = $el.data('blockUI.onUnblock');
472 | $el.removeData('blockUI.onUnblock');
473 | }
474 |
475 | var els;
476 | if (full) // crazy selector to handle odd field errors in ie6/7
477 | els = $('body').children().filter('.blockUI').add('body > .blockUI');
478 | else
479 | els = $el.find('>.blockUI');
480 |
481 | // fix cursor issue
482 | if ( opts.cursorReset ) {
483 | if ( els.length > 1 )
484 | els[1].style.cursor = opts.cursorReset;
485 | if ( els.length > 2 )
486 | els[2].style.cursor = opts.cursorReset;
487 | }
488 |
489 | if (full)
490 | pageBlock = pageBlockEls = null;
491 |
492 | if (opts.fadeOut) {
493 | count = els.length;
494 | els.stop().fadeOut(opts.fadeOut, function() {
495 | if ( --count === 0)
496 | reset(els,data,opts,el);
497 | });
498 | }
499 | else
500 | reset(els, data, opts, el);
501 | }
502 |
503 | // move blocking element back into the DOM where it started
504 | function reset(els,data,opts,el) {
505 | var $el = $(el);
506 | if ( $el.data('blockUI.isBlocked') )
507 | return;
508 |
509 | els.each(function(i,o) {
510 | // remove via DOM calls so we don't lose event handlers
511 | if (this.parentNode)
512 | this.parentNode.removeChild(this);
513 | });
514 |
515 | if (data && data.el) {
516 | data.el.style.display = data.display;
517 | data.el.style.position = data.position;
518 | data.el.style.cursor = 'default'; // #59
519 | if (data.parent)
520 | data.parent.appendChild(data.el);
521 | $el.removeData('blockUI.history');
522 | }
523 |
524 | if ($el.data('blockUI.static')) {
525 | $el.css('position', 'static'); // #22
526 | }
527 |
528 | if (typeof opts.onUnblock == 'function')
529 | opts.onUnblock(el,opts);
530 |
531 | // fix issue in Safari 6 where block artifacts remain until reflow
532 | var body = $(document.body), w = body.width(), cssW = body[0].style.width;
533 | body.width(w-1).width(w);
534 | body[0].style.width = cssW;
535 | }
536 |
537 | // bind/unbind the handler
538 | function bind(b, el, opts) {
539 | var full = el == window, $el = $(el);
540 |
541 | // don't bother unbinding if there is nothing to unbind
542 | if (!b && (full && !pageBlock || !full && !$el.data('blockUI.isBlocked')))
543 | return;
544 |
545 | $el.data('blockUI.isBlocked', b);
546 |
547 | // don't bind events when overlay is not in use or if bindEvents is false
548 | if (!full || !opts.bindEvents || (b && !opts.showOverlay))
549 | return;
550 |
551 | // bind anchors and inputs for mouse and key events
552 | var events = 'mousedown mouseup keydown keypress keyup touchstart touchend touchmove';
553 | if (b)
554 | $(document).bind(events, opts, handler);
555 | else
556 | $(document).unbind(events, handler);
557 |
558 | // former impl...
559 | // var $e = $('a,:input');
560 | // b ? $e.bind(events, opts, handler) : $e.unbind(events, handler);
561 | }
562 |
563 | // event handler to suppress keyboard/mouse events when blocking
564 | function handler(e) {
565 | // allow tab navigation (conditionally)
566 | if (e.type === 'keydown' && e.keyCode && e.keyCode == 9) {
567 | if (pageBlock && e.data.constrainTabKey) {
568 | var els = pageBlockEls;
569 | var fwd = !e.shiftKey && e.target === els[els.length-1];
570 | var back = e.shiftKey && e.target === els[0];
571 | if (fwd || back) {
572 | setTimeout(function(){focus(back);},10);
573 | return false;
574 | }
575 | }
576 | }
577 | var opts = e.data;
578 | var target = $(e.target);
579 | if (target.hasClass('blockOverlay') && opts.onOverlayClick)
580 | opts.onOverlayClick(e);
581 |
582 | // allow events within the message content
583 | if (target.parents('div.' + opts.blockMsgClass).length > 0)
584 | return true;
585 |
586 | // allow events for content that is not being blocked
587 | return target.parents().children().filter('div.blockUI').length === 0;
588 | }
589 |
590 | function focus(back) {
591 | if (!pageBlockEls)
592 | return;
593 | var e = pageBlockEls[back===true ? pageBlockEls.length-1 : 0];
594 | if (e)
595 | e.focus();
596 | }
597 |
598 | function center(el, x, y) {
599 | var p = el.parentNode, s = el.style;
600 | var l = ((p.offsetWidth - el.offsetWidth)/2) - sz(p,'borderLeftWidth');
601 | var t = ((p.offsetHeight - el.offsetHeight)/2) - sz(p,'borderTopWidth');
602 | if (x) s.left = l > 0 ? (l+'px') : '0';
603 | if (y) s.top = t > 0 ? (t+'px') : '0';
604 | }
605 |
606 | function sz(el, p) {
607 | return parseInt($.css(el,p),10)||0;
608 | }
609 | }
610 |
611 | /*global define:true */
612 | if (typeof define === 'function' && define.amd && define.amd.jQuery) {
613 | define(['jquery'], setup);
614 | } else {
615 | setup(jQuery);
616 | }
617 |
618 | })();
619 |
--------------------------------------------------------------------------------
/src/main/resources/public/console/js/lz-string.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2013 Pieroxy
2 | // This work is free. You can redistribute it and/or modify it
3 | // under the terms of the WTFPL, Version 2
4 | // For more information see LICENSE.txt or http://www.wtfpl.net/
5 | //
6 | // For more information, the home page:
7 | // http://pieroxy.net/blog/pages/lz-string/testing.html
8 | //
9 | // LZ-based compression algorithm, version 1.4.4
10 | var LZString = (function() {
11 |
12 | // private property
13 | var f = String.fromCharCode;
14 | var keyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
15 | var keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$";
16 | var baseReverseDic = {};
17 |
18 | function getBaseValue(alphabet, character) {
19 | if (!baseReverseDic[alphabet]) {
20 | baseReverseDic[alphabet] = {};
21 | for (var i=0 ; i>> 8;
66 | buf[i*2+1] = current_value % 256;
67 | }
68 | return buf;
69 | },
70 |
71 | //decompress from uint8array (UCS-2 big endian format)
72 | decompressFromUint8Array:function (compressed) {
73 | if (compressed===null || compressed===undefined){
74 | return LZString.decompress(compressed);
75 | } else {
76 | var buf=new Array(compressed.length/2); // 2 bytes per character
77 | for (var i=0, TotalLen=buf.length; i> 1;
159 | }
160 | } else {
161 | value = 1;
162 | for (i=0 ; i> 1;
184 | }
185 | }
186 | context_enlargeIn--;
187 | if (context_enlargeIn == 0) {
188 | context_enlargeIn = Math.pow(2, context_numBits);
189 | context_numBits++;
190 | }
191 | delete context_dictionaryToCreate[context_w];
192 | } else {
193 | value = context_dictionary[context_w];
194 | for (i=0 ; i> 1;
204 | }
205 |
206 |
207 | }
208 | context_enlargeIn--;
209 | if (context_enlargeIn == 0) {
210 | context_enlargeIn = Math.pow(2, context_numBits);
211 | context_numBits++;
212 | }
213 | // Add wc to the dictionary.
214 | context_dictionary[context_wc] = context_dictSize++;
215 | context_w = String(context_c);
216 | }
217 | }
218 |
219 | // Output the code for w.
220 | if (context_w !== "") {
221 | if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate,context_w)) {
222 | if (context_w.charCodeAt(0)<256) {
223 | for (i=0 ; i> 1;
244 | }
245 | } else {
246 | value = 1;
247 | for (i=0 ; i> 1;
269 | }
270 | }
271 | context_enlargeIn--;
272 | if (context_enlargeIn == 0) {
273 | context_enlargeIn = Math.pow(2, context_numBits);
274 | context_numBits++;
275 | }
276 | delete context_dictionaryToCreate[context_w];
277 | } else {
278 | value = context_dictionary[context_w];
279 | for (i=0 ; i> 1;
289 | }
290 |
291 |
292 | }
293 | context_enlargeIn--;
294 | if (context_enlargeIn == 0) {
295 | context_enlargeIn = Math.pow(2, context_numBits);
296 | context_numBits++;
297 | }
298 | }
299 |
300 | // Mark the end of the stream
301 | value = 2;
302 | for (i=0 ; i> 1;
312 | }
313 |
314 | // Flush the last char
315 | while (true) {
316 | context_data_val = (context_data_val << 1);
317 | if (context_data_position == bitsPerChar-1) {
318 | context_data.push(getCharFromInt(context_data_val));
319 | break;
320 | }
321 | else context_data_position++;
322 | }
323 | return context_data.join('');
324 | },
325 |
326 | decompress: function (compressed) {
327 | if (compressed == null) return "";
328 | if (compressed == "") return null;
329 | return LZString._decompress(compressed.length, 32768, function(index) { return compressed.charCodeAt(index); });
330 | },
331 |
332 | _decompress: function (length, resetValue, getNextValue) {
333 | var dictionary = [],
334 | next,
335 | enlargeIn = 4,
336 | dictSize = 4,
337 | numBits = 3,
338 | entry = "",
339 | result = [],
340 | i,
341 | w,
342 | bits, resb, maxpower, power,
343 | c,
344 | data = {val:getNextValue(0), position:resetValue, index:1};
345 |
346 | for (i = 0; i < 3; i += 1) {
347 | dictionary[i] = i;
348 | }
349 |
350 | bits = 0;
351 | maxpower = Math.pow(2,2);
352 | power=1;
353 | while (power!=maxpower) {
354 | resb = data.val & data.position;
355 | data.position >>= 1;
356 | if (data.position == 0) {
357 | data.position = resetValue;
358 | data.val = getNextValue(data.index++);
359 | }
360 | bits |= (resb>0 ? 1 : 0) * power;
361 | power <<= 1;
362 | }
363 |
364 | switch (next = bits) {
365 | case 0:
366 | bits = 0;
367 | maxpower = Math.pow(2,8);
368 | power=1;
369 | while (power!=maxpower) {
370 | resb = data.val & data.position;
371 | data.position >>= 1;
372 | if (data.position == 0) {
373 | data.position = resetValue;
374 | data.val = getNextValue(data.index++);
375 | }
376 | bits |= (resb>0 ? 1 : 0) * power;
377 | power <<= 1;
378 | }
379 | c = f(bits);
380 | break;
381 | case 1:
382 | bits = 0;
383 | maxpower = Math.pow(2,16);
384 | power=1;
385 | while (power!=maxpower) {
386 | resb = data.val & data.position;
387 | data.position >>= 1;
388 | if (data.position == 0) {
389 | data.position = resetValue;
390 | data.val = getNextValue(data.index++);
391 | }
392 | bits |= (resb>0 ? 1 : 0) * power;
393 | power <<= 1;
394 | }
395 | c = f(bits);
396 | break;
397 | case 2:
398 | return "";
399 | }
400 | dictionary[3] = c;
401 | w = c;
402 | result.push(c);
403 | while (true) {
404 | if (data.index > length) {
405 | return "";
406 | }
407 |
408 | bits = 0;
409 | maxpower = Math.pow(2,numBits);
410 | power=1;
411 | while (power!=maxpower) {
412 | resb = data.val & data.position;
413 | data.position >>= 1;
414 | if (data.position == 0) {
415 | data.position = resetValue;
416 | data.val = getNextValue(data.index++);
417 | }
418 | bits |= (resb>0 ? 1 : 0) * power;
419 | power <<= 1;
420 | }
421 |
422 | switch (c = bits) {
423 | case 0:
424 | bits = 0;
425 | maxpower = Math.pow(2,8);
426 | power=1;
427 | while (power!=maxpower) {
428 | resb = data.val & data.position;
429 | data.position >>= 1;
430 | if (data.position == 0) {
431 | data.position = resetValue;
432 | data.val = getNextValue(data.index++);
433 | }
434 | bits |= (resb>0 ? 1 : 0) * power;
435 | power <<= 1;
436 | }
437 |
438 | dictionary[dictSize++] = f(bits);
439 | c = dictSize-1;
440 | enlargeIn--;
441 | break;
442 | case 1:
443 | bits = 0;
444 | maxpower = Math.pow(2,16);
445 | power=1;
446 | while (power!=maxpower) {
447 | resb = data.val & data.position;
448 | data.position >>= 1;
449 | if (data.position == 0) {
450 | data.position = resetValue;
451 | data.val = getNextValue(data.index++);
452 | }
453 | bits |= (resb>0 ? 1 : 0) * power;
454 | power <<= 1;
455 | }
456 | dictionary[dictSize++] = f(bits);
457 | c = dictSize-1;
458 | enlargeIn--;
459 | break;
460 | case 2:
461 | return result.join('');
462 | }
463 |
464 | if (enlargeIn == 0) {
465 | enlargeIn = Math.pow(2, numBits);
466 | numBits++;
467 | }
468 |
469 | if (dictionary[c]) {
470 | entry = dictionary[c];
471 | } else {
472 | if (c === dictSize) {
473 | entry = w + w.charAt(0);
474 | } else {
475 | return null;
476 | }
477 | }
478 | result.push(entry);
479 |
480 | // Add w+entry[0] to the dictionary.
481 | dictionary[dictSize++] = w + entry.charAt(0);
482 | enlargeIn--;
483 |
484 | w = entry;
485 |
486 | if (enlargeIn == 0) {
487 | enlargeIn = Math.pow(2, numBits);
488 | numBits++;
489 | }
490 |
491 | }
492 | }
493 | };
494 | return LZString;
495 | })();
496 |
497 | if (typeof define === 'function' && define.amd) {
498 | define(function () { return LZString; });
499 | } else if( typeof module !== 'undefined' && module != null ) {
500 | module.exports = LZString
501 | } else if( typeof angular !== 'undefined' && angular != null ) {
502 | angular.module('LZString', [])
503 | .factory('LZString', function () {
504 | return LZString;
505 | });
506 | }
507 |
--------------------------------------------------------------------------------
/src/main/resources/public/console/js/matchbrackets.js:
--------------------------------------------------------------------------------
1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 | // Distributed under an MIT license: http://codemirror.net/LICENSE
3 |
4 | (function(mod) {
5 | if (typeof exports == "object" && typeof module == "object") // CommonJS
6 | mod(require("../../lib/codemirror"));
7 | else if (typeof define == "function" && define.amd) // AMD
8 | define(["../../lib/codemirror"], mod);
9 | else // Plain browser env
10 | mod(CodeMirror);
11 | })(function(CodeMirror) {
12 | var ie_lt8 = /MSIE \d/.test(navigator.userAgent) &&
13 | (document.documentMode == null || document.documentMode < 8);
14 |
15 | var Pos = CodeMirror.Pos;
16 |
17 | var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
18 |
19 | function findMatchingBracket(cm, where, config) {
20 | var line = cm.getLineHandle(where.line), pos = where.ch - 1;
21 | var afterCursor = config && config.afterCursor
22 | if (afterCursor == null)
23 | afterCursor = /(^| )cm-fat-cursor($| )/.test(cm.getWrapperElement().className)
24 |
25 | // A cursor is defined as between two characters, but in in vim command mode
26 | // (i.e. not insert mode), the cursor is visually represented as a
27 | // highlighted box on top of the 2nd character. Otherwise, we allow matches
28 | // from before or after the cursor.
29 | var match = (!afterCursor && pos >= 0 && matching[line.text.charAt(pos)]) ||
30 | matching[line.text.charAt(++pos)];
31 | if (!match) return null;
32 | var dir = match.charAt(1) == ">" ? 1 : -1;
33 | if (config && config.strict && (dir > 0) != (pos == where.ch)) return null;
34 | var style = cm.getTokenTypeAt(Pos(where.line, pos + 1));
35 |
36 | var found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style || null, config);
37 | if (found == null) return null;
38 | return {from: Pos(where.line, pos), to: found && found.pos,
39 | match: found && found.ch == match.charAt(0), forward: dir > 0};
40 | }
41 |
42 | // bracketRegex is used to specify which type of bracket to scan
43 | // should be a regexp, e.g. /[[\]]/
44 | //
45 | // Note: If "where" is on an open bracket, then this bracket is ignored.
46 | //
47 | // Returns false when no bracket was found, null when it reached
48 | // maxScanLines and gave up
49 | function scanForBracket(cm, where, dir, style, config) {
50 | var maxScanLen = (config && config.maxScanLineLength) || 10000;
51 | var maxScanLines = (config && config.maxScanLines) || 1000;
52 |
53 | var stack = [];
54 | var re = config && config.bracketRegex ? config.bracketRegex : /[(){}[\]]/;
55 | var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1)
56 | : Math.max(cm.firstLine() - 1, where.line - maxScanLines);
57 | for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) {
58 | var line = cm.getLine(lineNo);
59 | if (!line) continue;
60 | var pos = dir > 0 ? 0 : line.length - 1, end = dir > 0 ? line.length : -1;
61 | if (line.length > maxScanLen) continue;
62 | if (lineNo == where.line) pos = where.ch - (dir < 0 ? 1 : 0);
63 | for (; pos != end; pos += dir) {
64 | var ch = line.charAt(pos);
65 | if (re.test(ch) && (style === undefined || cm.getTokenTypeAt(Pos(lineNo, pos + 1)) == style)) {
66 | var match = matching[ch];
67 | if ((match.charAt(1) == ">") == (dir > 0)) stack.push(ch);
68 | else if (!stack.length) return {pos: Pos(lineNo, pos), ch: ch};
69 | else stack.pop();
70 | }
71 | }
72 | }
73 | return lineNo - dir == (dir > 0 ? cm.lastLine() : cm.firstLine()) ? false : null;
74 | }
75 |
76 | function matchBrackets(cm, autoclear, config) {
77 | // Disable brace matching in long lines, since it'll cause hugely slow updates
78 | var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000;
79 | var marks = [], ranges = cm.listSelections();
80 | for (var i = 0; i < ranges.length; i++) {
81 | var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, config);
82 | if (match && cm.getLine(match.from.line).length <= maxHighlightLen) {
83 | var style = match.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
84 | marks.push(cm.markText(match.from, Pos(match.from.line, match.from.ch + 1), {className: style}));
85 | if (match.to && cm.getLine(match.to.line).length <= maxHighlightLen)
86 | marks.push(cm.markText(match.to, Pos(match.to.line, match.to.ch + 1), {className: style}));
87 | }
88 | }
89 |
90 | if (marks.length) {
91 | // Kludge to work around the IE bug from issue #1193, where text
92 | // input stops going to the textare whever this fires.
93 | if (ie_lt8 && cm.state.focused) cm.focus();
94 |
95 | var clear = function() {
96 | cm.operation(function() {
97 | for (var i = 0; i < marks.length; i++) marks[i].clear();
98 | });
99 | };
100 | if (autoclear) setTimeout(clear, 800);
101 | else return clear;
102 | }
103 | }
104 |
105 | var currentlyHighlighted = null;
106 | function doMatchBrackets(cm) {
107 | cm.operation(function() {
108 | if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;}
109 | currentlyHighlighted = matchBrackets(cm, false, cm.state.matchBrackets);
110 | });
111 | }
112 |
113 | CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) {
114 | if (old && old != CodeMirror.Init) {
115 | cm.off("cursorActivity", doMatchBrackets);
116 | if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;}
117 | }
118 | if (val) {
119 | cm.state.matchBrackets = typeof val == "object" ? val : {};
120 | cm.on("cursorActivity", doMatchBrackets);
121 | }
122 | });
123 |
124 | CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);});
125 | CodeMirror.defineExtension("findMatchingBracket", function(pos, config, oldConfig){
126 | // Backwards-compatibility kludge
127 | if (oldConfig || typeof config == "boolean") {
128 | if (!oldConfig) {
129 | config = config ? {strict: true} : null
130 | } else {
131 | oldConfig.strict = config
132 | config = oldConfig
133 | }
134 | }
135 | return findMatchingBracket(this, pos, config)
136 | });
137 | CodeMirror.defineExtension("scanForBracket", function(pos, dir, style, config){
138 | return scanForBracket(this, pos, dir, style, config);
139 | });
140 | });
141 |
--------------------------------------------------------------------------------
/src/test/java/com/github/gaborbata/console/configuration/JGivenConfig.java:
--------------------------------------------------------------------------------
1 | package com.github.gaborbata.console.configuration;
2 |
3 | import com.tngtech.jgiven.config.AbstractJGivenConfiguration;
4 | import com.tngtech.jgiven.integration.spring.EnableJGiven;
5 | import org.springframework.context.annotation.ComponentScan;
6 | import org.springframework.context.annotation.Configuration;
7 | import org.springframework.http.HttpStatus;
8 |
9 | import static java.lang.String.format;
10 |
11 | /**
12 | * Configuration class for JGiven tests.
13 | */
14 | @Configuration
15 | @EnableJGiven
16 | @ComponentScan("com.github.gaborbata.console")
17 | public class JGivenConfig extends AbstractJGivenConfiguration {
18 |
19 | @Override
20 | public void configure() {
21 | setFormatter(HttpStatus.class, (status, annotations) -> format("%s (%d)", status.getReasonPhrase(), status.value()));
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/test/java/com/github/gaborbata/console/controller/GroovyConsoleControllerTest.java:
--------------------------------------------------------------------------------
1 | package com.github.gaborbata.console.controller;
2 |
3 | import com.github.gaborbata.console.configuration.JGivenConfig;
4 | import com.github.gaborbata.console.controller.stage.GroovyConsoleControllerStage;
5 | import com.tngtech.jgiven.integration.spring.junit5.SimpleSpringScenarioTest;
6 | import com.tngtech.jgiven.junit5.JGivenExtension;
7 | import org.junit.jupiter.api.Test;
8 | import org.junit.jupiter.api.extension.ExtendWith;
9 | import org.springframework.boot.test.context.SpringBootTest;
10 | import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
11 | import org.springframework.http.HttpStatus;
12 |
13 | @ExtendWith(JGivenExtension.class)
14 | @SpringBootTest(classes = {JGivenConfig.class}, webEnvironment = WebEnvironment.NONE)
15 | public class GroovyConsoleControllerTest extends SimpleSpringScenarioTest {
16 |
17 | private static final String GROOVY_CONSOLE_ENDPOINT = "/console/groovy";
18 |
19 | @Test
20 | public void should_include_script_return_result_in_the_result_object() throws Exception {
21 | given().an_endpoint(GROOVY_CONSOLE_ENDPOINT)
22 | .and().a_groovy_script_$script("2 + 3");
23 | when().post_request_is_received();
24 | then().the_status_is(HttpStatus.OK)
25 | .and().the_content_is("{'result': 5}");
26 | }
27 |
28 | @Test
29 | public void should_include_script_output_in_the_result_object() throws Exception {
30 | given().an_endpoint(GROOVY_CONSOLE_ENDPOINT)
31 | .and().a_groovy_script_$script("println 2 + 3");
32 | when().post_request_is_received();
33 | then().the_status_is(HttpStatus.OK)
34 | .and().the_content_is("{'output': ['5']}");
35 | }
36 |
37 | @Test
38 | public void should_include_both_output_and_result_object_in_the_result() throws Exception {
39 | given().an_endpoint(GROOVY_CONSOLE_ENDPOINT)
40 | .and().a_groovy_script_$script("println('test'); 2 + 3");
41 | when().post_request_is_received();
42 | then().the_status_is(HttpStatus.OK)
43 | .and().the_content_is("{'output': ['test'], 'result': 5}");
44 | }
45 |
46 | @Test
47 | public void should_respond_with_bad_request_when_script_is_missing() throws Exception {
48 | given().an_endpoint(GROOVY_CONSOLE_ENDPOINT)
49 | .and().a_groovy_script_$script(null);
50 | when().post_request_is_received();
51 | then().the_status_is(HttpStatus.BAD_REQUEST);
52 | }
53 |
54 | @Test
55 | public void should_include_spring_application_context_in_bound_variables() throws Exception {
56 | given().an_endpoint(GROOVY_CONSOLE_ENDPOINT)
57 | .and().a_groovy_script_$script("applicationContext != null");
58 | when().post_request_is_received();
59 | then().the_status_is(HttpStatus.OK)
60 | .and().the_content_is("{'result': true}");
61 | }
62 |
63 | @Test
64 | public void should_include_exception_message_in_the_result() throws Exception {
65 | given().an_endpoint(GROOVY_CONSOLE_ENDPOINT)
66 | .and().a_groovy_script_$script("throw new RuntimeException('test')");
67 | when().post_request_is_received();
68 | then().the_status_is(HttpStatus.OK)
69 | .and().the_content_is("{'output': ['java.lang.RuntimeException: test']}");
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/test/java/com/github/gaborbata/console/controller/stage/GroovyConsoleControllerStage.java:
--------------------------------------------------------------------------------
1 | package com.github.gaborbata.console.controller.stage;
2 |
3 | import com.github.gaborbata.console.controller.GroovyConsoleController;
4 | import com.tngtech.jgiven.Stage;
5 | import com.tngtech.jgiven.annotation.BeforeStage;
6 | import com.tngtech.jgiven.annotation.Quoted;
7 | import com.tngtech.jgiven.integration.spring.JGivenStage;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.http.HttpStatus;
10 | import org.springframework.test.web.servlet.MockMvc;
11 | import org.springframework.test.web.servlet.ResultActions;
12 | import org.springframework.test.web.servlet.setup.MockMvcBuilders;
13 |
14 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
15 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
16 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
17 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
18 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
19 |
20 | @JGivenStage
21 | public class GroovyConsoleControllerStage extends Stage {
22 |
23 | @Autowired
24 | private GroovyConsoleController groovyConsoleController;
25 |
26 | private MockMvc mockMvc;
27 |
28 | private String uri;
29 | private String script;
30 | private ResultActions resultActions;
31 |
32 | @BeforeStage
33 | public void setup() {
34 | mockMvc = MockMvcBuilders.standaloneSetup(groovyConsoleController).build();
35 | }
36 |
37 | public GroovyConsoleControllerStage an_endpoint(@Quoted String uri) {
38 | this.uri = uri;
39 | return this;
40 | }
41 |
42 | public GroovyConsoleControllerStage a_groovy_script_$script(@Quoted String script) {
43 | this.script = script;
44 | return this;
45 | }
46 |
47 | public GroovyConsoleControllerStage post_request_is_received() throws Exception {
48 | ResultActions resultActions = mockMvc.perform(post(uri).contentType("text/x-groovy").param("script", script));
49 | if (script == null) {
50 | this.resultActions = resultActions;
51 | } else {
52 | this.resultActions = mockMvc.perform(asyncDispatch(resultActions.andExpect(request().asyncStarted()).andReturn()));
53 | }
54 | return this;
55 | }
56 |
57 | public GroovyConsoleControllerStage the_status_is(HttpStatus status) throws Exception {
58 | resultActions.andExpect(status().is(status.value()));
59 | return this;
60 | }
61 |
62 | public GroovyConsoleControllerStage the_content_is(String content) throws Exception {
63 | resultActions.andExpect(content().json(content));
64 | return this;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------