├── .ceylon
├── bootstrap
│ ├── ceylon-bootstrap.jar
│ └── ceylon-bootstrap.properties
├── config
├── gyokuro.format
└── ide-config
├── .classpath
├── .gitignore
├── .idea
├── .name
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── misc.xml
├── modules.xml
└── vcs.xml
├── .project
├── .settings
├── org.eclipse.core.resources.prefs
└── org.eclipse.jdt.core.prefs
├── .travis.yml
├── LICENSE
├── README.md
├── ceylonb
├── ceylonb.bat
├── demos-assets
├── css
│ └── main.css
├── gson
│ └── index.html
├── index.html
├── js
│ └── main.js
├── mustache
│ └── hello.mustache
├── rythm
│ └── hello.rythm
└── thymeleaf
│ └── hello.xhtml
├── demos
└── gyokuro
│ └── demo
│ ├── ceylonhtml
│ ├── module.ceylon
│ ├── package.ceylon
│ └── run.ceylon
│ ├── gson
│ ├── module.ceylon
│ ├── package.ceylon
│ └── run.ceylon
│ ├── mustache
│ ├── module.ceylon
│ ├── package.ceylon
│ └── run.ceylon
│ ├── report
│ ├── module.ceylon
│ ├── package.ceylon
│ └── run.ceylon
│ ├── rest
│ ├── controllers.ceylon
│ ├── module.ceylon
│ ├── package.ceylon
│ └── run.ceylon
│ ├── rythm
│ ├── module.ceylon
│ ├── package.ceylon
│ └── run.ceylon
│ ├── spring
│ ├── module.ceylon
│ ├── package.ceylon
│ └── run.ceylon
│ └── thymeleaf
│ ├── module.ceylon
│ ├── package.ceylon
│ └── run.ceylon
├── gyokuro.iml
├── overrides.xml
├── resource
└── com
│ └── github
│ └── bjansen
│ └── gyokuro
│ └── report
│ └── style.css
├── source
└── net
│ └── gyokuro
│ ├── core
│ ├── Flash.ceylon
│ ├── annotations.ceylon
│ ├── application.ceylon
│ ├── functions.ceylon
│ ├── http
│ │ ├── methods.ceylon
│ │ └── package.ceylon
│ ├── internal
│ │ ├── AnnotationScanner.ceylon
│ │ ├── DefaultFlash.ceylon
│ │ ├── RequestWrapper.ceylon
│ │ ├── converters.ceylon
│ │ ├── dispatcher.ceylon
│ │ ├── package.ceylon
│ │ └── router.ceylon
│ ├── mimeparse.ceylon
│ ├── module.ceylon
│ ├── package.ceylon
│ └── websocket.ceylon
│ ├── report
│ ├── GyokuroApiGenerator.ceylon
│ ├── module.ceylon
│ ├── package.ceylon
│ └── run.ceylon
│ ├── transform
│ ├── api
│ │ ├── Transformer.ceylon
│ │ ├── module.ceylon
│ │ └── package.ceylon
│ └── gson
│ │ ├── GsonTransformer.ceylon
│ │ ├── module.ceylon
│ │ └── package.ceylon
│ └── view
│ ├── api
│ ├── module.ceylon
│ ├── package.ceylon
│ └── templates.ceylon
│ ├── ceylonhtml
│ ├── CeylonHtmlRenderer.ceylon
│ ├── module.ceylon
│ └── package.ceylon
│ ├── mustache
│ ├── MustacheRenderer.ceylon
│ ├── module.ceylon
│ └── package.ceylon
│ ├── pebble
│ ├── PebbleRenderer.ceylon
│ ├── module.ceylon
│ └── package.ceylon
│ ├── rythm
│ ├── RythmRenderer.ceylon
│ ├── module.ceylon
│ └── package.ceylon
│ └── thymeleaf
│ ├── ThymeleafRenderer.ceylon
│ ├── module.ceylon
│ └── package.ceylon
└── test-source
└── test
└── net
└── gyokuro
└── core
├── filtersTest.ceylon
├── internal
├── AnnotationScannerTest.ceylon
├── RequestDispatcherTest.ceylon
├── package.ceylon
└── testdata
│ ├── AnnotatedClass.ceylon
│ ├── ListBinding.ceylon
│ ├── ParameterBinding.ceylon
│ └── package.ceylon
├── mimeParseTest.ceylon
├── module.ceylon
└── package.ceylon
/.ceylon/bootstrap/ceylon-bootstrap.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bjansen/gyokuro/a3f0db80f7029ae350bc97e33eaf7882a9182d00/.ceylon/bootstrap/ceylon-bootstrap.jar
--------------------------------------------------------------------------------
/.ceylon/bootstrap/ceylon-bootstrap.properties:
--------------------------------------------------------------------------------
1 | #Generated by 'ceylon bootstrap'
2 | #Tue Jan 10 20:17:50 CET 2017
3 | distribution=https\://ceylon-lang.org/download/dist/1_3_1
4 |
--------------------------------------------------------------------------------
/.ceylon/config:
--------------------------------------------------------------------------------
1 |
2 | [defaults]
3 | encoding=UTF-8
4 | offline=false
5 | flatclasspath=false
6 | autoexportmavendependencies=false
7 | overrides=overrides.xml
8 |
9 | [compiler]
10 | source=source
11 | source=demos
12 | source=test-source
13 |
14 | [formattool]
15 | profile=gyokuro
16 |
17 | [runtool]
18 | module=gyokuro.demo.rest
19 |
20 |
--------------------------------------------------------------------------------
/.ceylon/gyokuro.format:
--------------------------------------------------------------------------------
1 |
2 | [formatter]
3 | lineBreakStrategy=default
4 | maxLineLength=unlimited
5 | lineBreaksBetweenImportElements=1..1
6 | elseOnOwnLine=false
7 | lineBreaksBeforeMultiComment=0..3
8 | indentBeforeTypeInfo=2
9 | spaceOptionalAroundOperatorLevel=3
10 | spaceAfterValueIteratorOpeningParenthesis=false
11 | indentationAfterSpecifierExpressionStart=addIndentBefore
12 | lineBreaksBeforeSingleComment=0..3
13 | spaceAroundSatisfiesOf=true
14 | spaceBeforeParamListClosingParen=false
15 | inlineAnnotations=abstract actual annotation default final formal late native optional sealed shared variable controller
16 | lineBreaksInTypeParameterList=0..1
17 | spaceAroundImportAliasEqualsSign=false
18 | spaceBeforeAnnotationPositionalArgumentList=false
19 | spaceAfterTypeParamListComma=true
20 | spaceAfterParamListOpeningParen=false
21 | spaceBeforeValueIteratorClosingParenthesis=false
22 | spaceAfterParamListClosingParen=true
23 | indentMode=4 spaces
24 | braceOnOwnLine=false
25 | spaceBeforeMethodOrClassPositionalArgumentList=false
26 | lineBreaksAfterSingleComment=0..3
27 | spaceAfterSequenceEnumerationOpeningBrace=true
28 | spaceBeforeParamListOpeningParen=false
29 | spaceAfterTypeArgListComma=false
30 | spaceAroundTypeParamListEqualsSign=false
31 | failFast=true
32 | lineBreaksAfterLineComment=1..3
33 | lineBreaksBeforeLineComment=0..3
34 | spaceAfterControlStructureKeyword=true
35 | lineBreak=lf
36 | spaceBeforeSequenceEnumerationClosingBrace=true
37 | indentBlankLines=true
38 | lineBreaksAfterMultiComment=0..3
39 |
--------------------------------------------------------------------------------
/.ceylon/ide-config:
--------------------------------------------------------------------------------
1 |
2 | [project]
3 | compile-jvm=true
4 | compile-js=false
5 | system-repository=
6 |
--------------------------------------------------------------------------------
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.exploded/
2 | /classes/
3 | config.old
4 | modules
5 | /.idea/libraries/
6 | /.idea/workspace.xml
7 | /.idea/shelf
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | gyokuro
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
19 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | General
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | gyokuro
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 | com.redhat.ceylon.eclipse.ui.ceylonBuilder
15 |
16 |
17 | explodeModules
18 | true
19 |
20 |
21 |
22 |
23 |
24 | com.redhat.ceylon.eclipse.ui.ceylonNature
25 | org.eclipse.jdt.core.javanature
26 |
27 |
28 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.core.resources.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | encoding/=UTF-8
3 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
5 | org.eclipse.jdt.core.compiler.compliance=1.7
6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate
7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate
8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate
9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
11 | org.eclipse.jdt.core.compiler.source=1.7
12 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: java
3 | jdk:
4 | - openjdk7
5 | install:
6 | - wget --quiet --output-document=/tmp/ceylon.zip $CEYLON
7 | - unzip /tmp/ceylon.zip
8 | - export PATH=$PATH:$PWD/ceylon-1.3.3/bin/
9 | before_script:
10 | - ceylon compile
11 | script:
12 | - ceylon test $TEST_MODULE
13 | env:
14 | global:
15 | - CEYLON="https://downloads.ceylon-lang.org/cli/ceylon-1.3.3.zip"
16 | - TEST_MODULE="test.net.gyokuro.core"
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Bastien Jansen
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 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # gyokuro [](https://travis-ci.org/bjansen/gyokuro) [](https://gitter.im/bjansen/gyokuro)
2 |
3 | A web framework written in Ceylon, which allows:
4 |
5 | * routing GET/POST requests to simple `(Request, Response)` handlers
6 | * creating annotated controllers containing more powerful handlers
7 | * serving static assets (HTML, CSS, JS, ...) from a directory
8 |
9 | gyokuro is based on the [Ceylon SDK](http://github.com/ceylon/ceylon-sdk),
10 | and uses `ceylon.net`'s server API.
11 |
12 | ## Creating a simple webapp
13 |
14 | Create a new Ceylon module:
15 |
16 | ```ceylon
17 | module gyokuro.demo.rest "1.0.0" {
18 | import net.gyokuro.core "0.2";
19 | import ceylon.net "1.3.1";
20 | }
21 | ```
22 |
23 | Add a runnable top level function that bootstraps a gyokuro application:
24 |
25 | ```ceylon
26 | import net.gyokuro.core {
27 | Application,
28 | get,
29 | post,
30 | serve
31 | }
32 |
33 | "Run an HTTP server listening on port 8080, that will react to requests on /hello.
34 | Static assets will be served from the `assets` directory."
35 | shared void run() {
36 |
37 | // React to GET/POST requests using a basic handler
38 | get("/hello", void (Request request, Response response) {
39 | response.writeString("Hello yourself!");
40 | });
41 |
42 | // Shorter syntax that lets Ceylon infer types and lets gyokuro
43 | // write the response
44 | post("/hello", (request, response) => "You're the POST master!");
45 |
46 | value app = Application {
47 | assets = serve("assets");
48 | };
49 |
50 | app.run();
51 | }
52 | ```
53 |
54 | ## Binding parameters
55 |
56 | In addition to basic handlers, gyokuro allows you to bind GET/POST data
57 | directly to function parameters, and return an object that represents your response:
58 |
59 | ```ceylon
60 | shared void run() {
61 | // ...
62 | post("/hello", `postHandler`);
63 | // ...
64 | }
65 |
66 | "Advanced handlers have more flexible parameters, you're
67 | not limited to `Request` and `Response`, you can bind
68 | GET/POST values directly to handler parameters!
69 | The returned value will be written to the response."
70 | String postHandler(Float float, Integer? optionalInt, String who = "world") {
71 | // `float` is required, `optionalInt` is optional and
72 | // `who` will be defaulted to "world" if it's not in POST data.
73 | return "Hello, " + who + "!\n";
74 | }
75 | ```
76 |
77 | GET/POST values are mapped by name and automatically converted to the correct type.
78 | Note that optional types and default values are also supported!
79 |
80 | ## Using annotated controllers
81 |
82 | In addition to `get` and `post` functions, gyokuro supports annotated controllers.
83 | Using annotations, you can easily group related handlers in a same controller.
84 |
85 | Let's see how it works on a simple example:
86 |
87 | ```ceylon
88 | shared void run() {
89 |
90 | value app = Application {
91 | // You can use REST-style annotated controllers like this:
92 | controllers = bind(`package gyokuro.demo.rest`, "/rest");
93 | };
94 |
95 | app.run();
96 | }
97 | ```
98 |
99 | The package `gyokuro.demo.rest` will be scanned for classes annotated with `controller`.
100 | Each function annotated with `route` will be mapped to the corresponding path. For example:
101 |
102 | ```ceylon
103 | import ceylon.net.http.server {
104 | Response
105 | }
106 | import net.gyokuro.core {
107 | controller,
108 | route
109 | }
110 |
111 | route("duck")
112 | controller class SimpleRestController() {
113 |
114 | route("talk")
115 | shared void makeDuckTalk(Response resp) {
116 | resp.writeString("Quack world!");
117 | }
118 | }
119 | ```
120 |
121 | Will be mapped to `http://localhost:8080/rest/duck/talk`.
122 |
123 | ## Want to learn more?
124 |
125 | See the [complete documentation](http://bjansen.github.io/gyokuro/doc/0.2/) for more info.
126 |
127 | You can find examples in the [demos directory](https://github.com/bjansen/gyokuro/tree/master/demos/).
128 |
129 |
--------------------------------------------------------------------------------
/ceylonb:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # resolve links - $0 may be a softlink
4 | PRG="$0"
5 | while [ -h "$PRG" ]; do
6 | ls="$(ls -ld "$PRG")"
7 | link="${ls##*-> }" # remove largest prefix: yields link target (behind ->)
8 | if [ "$link" != "${link#/}" ]; then # remove prefix / if present
9 | # path was absolute
10 | PRG="$link"
11 | else
12 | # was not
13 | PRG="$(dirname "$PRG")/$link"
14 | fi
15 | done
16 |
17 | DIR="$(dirname "$PRG")"
18 |
19 | # Check if we should use a distribution bootstrap
20 | if [ -f "$DIR/.ceylon/bootstrap/ceylon-bootstrap.properties" ] && [ -f "$DIR/.ceylon/bootstrap/ceylon-bootstrap.jar" ]; then
21 | # Using bootstrap
22 | LIB="$DIR/.ceylon/bootstrap"
23 | else
24 | # Normal execution
25 | CEYLON_HOME="$DIR/.."
26 | LIB="$CEYLON_HOME/lib"
27 |
28 | if [ "$1" = "--show-home" ]; then
29 | echo "$CEYLON_HOME"
30 | exit
31 | fi
32 | fi
33 |
34 | if [ -z "$JAVA_HOME" ]; then
35 | JAVA="java"
36 | else
37 | JAVA="$JAVA_HOME/bin/java"
38 | fi
39 |
40 | # Make sure we have java installed
41 | if ! hash java 2>&-
42 | then
43 | echo >&2 "Java not found, you must install Java in order to compile and run Ceylon programs"
44 | echo >&2 "Go to http://www.java.com/getjava/ to download the latest version of Java"
45 | exit 1
46 | fi
47 |
48 | #JAVA_DEBUG_OPTS="-Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=y"
49 |
50 | if [ "$PRESERVE_JAVA_OPTS" != "true" ]; then
51 | PREPEND_JAVA_OPTS="$JAVA_DEBUG_OPTS"
52 | if [ -n "$COLUMNS" ]; then
53 | CEYL_COLS="$COLUMNS"
54 | elif stty size 2>/dev/null >/dev/null; then
55 | CEYL_COLS="$(stty size 2>/dev/null | cut -d' ' -f2)"
56 | else
57 | CEYL_COLS="$(tput 2>/dev/null cols)"
58 | fi
59 | PREPEND_JAVA_OPTS="$PREPEND_JAVA_OPTS -Dcom.redhat.ceylon.common.tool.terminal.width=$CEYL_COLS"
60 | PREPEND_JAVA_OPTS="$PREPEND_JAVA_OPTS -Dcom.redhat.ceylon.common.tool.progname=$(basename "$PRG")"
61 | fi
62 | for arg; do
63 | case $arg in
64 | --java=*) JAVA_OPTS="$JAVA_OPTS ${arg#--java=}";;
65 | [!-]*) break;;
66 | esac
67 | done
68 | JAVA_OPTS="$PREPEND_JAVA_OPTS $JAVA_OPTS"
69 |
70 | BOOTSTRAP="$LIB/ceylon-bootstrap.jar"
71 |
72 | # Check for cygwin, convert bootstrap path to Windows format
73 | case "`uname`" in
74 | CYGWIN*) [ -n "$LIB" ] && BOOTSTRAP=`cygpath -w "$BOOTSTRAP"`
75 | esac
76 |
77 | exec "$JAVA" \
78 | $JAVA_OPTS \
79 | -jar "$BOOTSTRAP" \
80 | "$@"
81 |
82 |
--------------------------------------------------------------------------------
/ceylonb.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | setlocal ENABLEDELAYEDEXPANSION
3 |
4 | :: Check if we should use a distribution bootstrap
5 | pushd "%~dp0"
6 | set "DIR=%CD%"
7 | popd
8 | if NOT exist "%DIR%\.ceylon\bootstrap\ceylon-bootstrap.properties" (
9 | goto :normal
10 | )
11 | if NOT exist "%DIR%\.ceylon\bootstrap\ceylon-bootstrap.jar" (
12 | goto :normal
13 | )
14 |
15 | :: Using bootstrap
16 | set "LIB=%DIR%\.ceylon\bootstrap"
17 |
18 | goto :endbs
19 |
20 | :normal
21 |
22 | :: Normal execution
23 |
24 | :: Find CEYLON_HOME
25 | pushd "%~dp0.."
26 | set "CEYLON_HOME=%CD%"
27 | popd
28 | set "LIB=%CEYLON_HOME%\lib"
29 |
30 | if "%~1" == "--show-home" (
31 | @echo %CEYLON_HOME%
32 | exit /b 1
33 | )
34 |
35 | :endbs
36 |
37 | :: Find Java
38 |
39 | :: Only check the registry if JAVA_HOME is not already set
40 | IF NOT "%JAVA_HOME%" == "" (
41 | goto :javaend
42 | )
43 |
44 | :: Find Java in the registry
45 | set "KEY_NAME=HKLM\SOFTWARE\JavaSoft\Java Runtime Environment"
46 | set "KEY_NAME2=HKLM\SOFTWARE\Wow6432Node\JavaSoft\Java Runtime Environment"
47 |
48 | :: get the current version
49 | FOR /F "usebackq skip=2 tokens=3" %%A IN (`REG QUERY "%KEY_NAME%" /v CurrentVersion 2^>nul`) DO (
50 | set "ValueValue=%%A"
51 | )
52 |
53 | if "%ValueValue%" NEQ "" (
54 | set "JAVA_CURRENT=%KEY_NAME%\%ValueValue%"
55 | ) else (
56 | rem Try again for 64bit systems
57 |
58 | FOR /F "usebackq skip=2 tokens=3" %%A IN (`REG QUERY "%KEY_NAME2%" /v CurrentVersion 2^>nul`) DO (
59 | set "JAVA_CURRENT=%KEY_NAME2%\%%A"
60 | )
61 | )
62 |
63 | if "%ValueValue%" NEQ "" (
64 | set "JAVA_CURRENT=%KEY_NAME%\%ValueValue%"
65 | ) else (
66 | rem Try again for 64bit systems from a 32-bit process
67 |
68 | FOR /F "usebackq skip=2 tokens=3" %%A IN (`REG QUERY "%KEY_NAME%" /v CurrentVersion /reg:64 2^>nul`) DO (
69 | set "JAVA_CURRENT=%KEY_NAME%\%%A"
70 | )
71 | )
72 |
73 | if "%JAVA_CURRENT%" == "" (
74 | @echo Java not found, you must install Java in order to compile and run Ceylon programs
75 | @echo Go to http://www.java.com/getjava/ to download the latest version of Java
76 | exit /b 1
77 | )
78 |
79 | :: get the javahome
80 | FOR /F "usebackq skip=2 tokens=3*" %%A IN (`REG QUERY "%JAVA_CURRENT%" /v JavaHome 2^>nul`) DO (
81 | set "JAVA_HOME=%%A %%B"
82 | )
83 |
84 | if "%JAVA_HOME%" EQU "" (
85 | rem Try again for 64bit systems from a 32-bit process
86 | FOR /F "usebackq skip=2 tokens=3*" %%A IN (`REG QUERY "%JAVA_CURRENT%" /v JavaHome /reg:64 2^>nul`) DO (
87 | set "JAVA_HOME=%%A %%B"
88 | )
89 | )
90 |
91 | :javaend
92 |
93 | set "JAVA=%JAVA_HOME%\bin\java.exe"
94 |
95 | :: Check that Java executable actually exists
96 | if not exist "%JAVA%" (
97 | @echo "Cannot find java.exe at %JAVA%, check that your JAVA_HOME variable is pointing to the right place"
98 | exit /b 1
99 | )
100 |
101 | rem set JAVA_DEBUG_OPTS="-Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=y"
102 |
103 | if NOT "%PRESERVE_JAVA_OPTS%" == "true" (
104 | set PREPEND_JAVA_OPTS=%JAVA_DEBUG_OPTS%
105 | rem Other java opts go here
106 | )
107 |
108 | rem Find any --java options and add their values to JAVA_OPTS
109 | for %%x in (%*) do (
110 | set ARG=%%~x
111 | if "!ARG:~0,7!" EQU "--java=" (
112 | set OPT=!ARG:~7!
113 | set "JAVA_OPTS=!JAVA_OPTS! !OPT!"
114 | ) else if "!ARG!" EQU "--java" (
115 | @echo Error: use --java options with an equal sign and quotes, eg: "--java=-Xmx500m"
116 | exit /b 1
117 | ) else if "!ARG:~0,1!" NEQ "-" (
118 | goto :breakloop
119 | )
120 | )
121 | :breakloop
122 |
123 | set "JAVA_OPTS=%PREPEND_JAVA_OPTS% %JAVA_OPTS%"
124 |
125 | "%JAVA%" ^
126 | %JAVA_OPTS% ^
127 | -jar "%LIB%\ceylon-bootstrap.jar" ^
128 | %*
129 |
130 | endlocal
131 |
132 | if %errorlevel% neq 0 exit /B %errorlevel%
133 |
--------------------------------------------------------------------------------
/demos-assets/css/main.css:
--------------------------------------------------------------------------------
1 | h1 {
2 | color: #222;
3 | }
4 |
5 | body {
6 | background-color: #EEE;
7 | }
--------------------------------------------------------------------------------
/demos-assets/gson/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Gson demo
5 |
6 | Serialization
7 |
17 |
18 |
19 |
20 |
21 |
22 | Deserialization
23 |
24 | Name:
25 | Salary:
26 |
27 |
28 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/demos-assets/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Coin
4 |
5 |
6 |
7 |
8 |
9 | It works!
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/demos-assets/js/main.js:
--------------------------------------------------------------------------------
1 | getDataFromServer = function() {
2 | // Output manually written in Response
3 | sendRequest('/rest/duck/talk', function(xhr) {
4 | document.getElementById('response').innerHTML = 'Server said "' + xhr.responseText + '"';
5 | });
6 |
7 | // Object automatically serialized to JSON and written in Response
8 | sendRequest('/rest/duck/actions', function(xhr) {
9 | document.getElementById('response2').innerHTML = 'A duck can ' + xhr.responseText;
10 | });
11 | }
12 |
13 | sendRequest = function(url, callback) {
14 | var xhr = new XMLHttpRequest();
15 | xhr.open('GET', url, true);
16 | xhr.onload = function (e) {
17 | if (xhr.readyState === 4) {
18 | if (xhr.status === 200) {
19 | callback(xhr);
20 | } else {
21 | console.error(xhr.statusText);
22 | }
23 | }
24 | };
25 |
26 | xhr.onerror = function (e) {
27 | console.error(xhr.statusText);
28 | };
29 |
30 | xhr.send(null);
31 | }
--------------------------------------------------------------------------------
/demos-assets/mustache/hello.mustache:
--------------------------------------------------------------------------------
1 | Hello, {{who}}!
--------------------------------------------------------------------------------
/demos-assets/rythm/hello.rythm:
--------------------------------------------------------------------------------
1 | Hello, @who!
--------------------------------------------------------------------------------
/demos-assets/thymeleaf/hello.xhtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 | Thymeleaf demo
8 |
9 |
10 |
11 |
12 |
13 | Hello, John Doe!
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/demos/gyokuro/demo/ceylonhtml/module.ceylon:
--------------------------------------------------------------------------------
1 | native("jvm")
2 | module gyokuro.demo.ceylonhtml "0.4-SNAPSHOT" {
3 | import net.gyokuro.view.ceylonhtml "0.4-SNAPSHOT";
4 | }
5 |
--------------------------------------------------------------------------------
/demos/gyokuro/demo/ceylonhtml/package.ceylon:
--------------------------------------------------------------------------------
1 | shared package gyokuro.demo.ceylonhtml;
2 |
--------------------------------------------------------------------------------
/demos/gyokuro/demo/ceylonhtml/run.ceylon:
--------------------------------------------------------------------------------
1 | import ceylon.html {
2 | Html,
3 | Head,
4 | Title,
5 | Body,
6 | Div
7 | }
8 |
9 | import net.gyokuro.core {
10 | render,
11 | Application,
12 | get
13 | }
14 | import net.gyokuro.view.ceylonhtml {
15 | CeylonHtmlRenderer,
16 | HtmlTemplate
17 | }
18 |
19 | "Run the module `gyokuro.demo.ceylonhtml`."
20 | shared void run() {
21 | get("/hello", `hello`);
22 |
23 | Application {
24 | renderer = CeylonHtmlRenderer();
25 | }.run();
26 | }
27 |
28 | HtmlTemplate hello() {
29 | value html = Html {
30 | Head {
31 | Title { "Hello world" }
32 | },
33 | Body {
34 | Div {
35 | clazz = "mycls";
36 | children = {
37 | "Hello from Ceylon HTML!"
38 | };
39 | }
40 | }
41 | };
42 |
43 | return render(html);
44 | }
45 |
--------------------------------------------------------------------------------
/demos/gyokuro/demo/gson/module.ceylon:
--------------------------------------------------------------------------------
1 | native("jvm")
2 | module gyokuro.demo.gson "0.4-SNAPSHOT" {
3 | import net.gyokuro.core "0.4-SNAPSHOT";
4 | import net.gyokuro.transform.gson "0.4-SNAPSHOT";
5 | import ceylon.logging "1.3.4-SNAPSHOT";
6 | }
7 |
--------------------------------------------------------------------------------
/demos/gyokuro/demo/gson/package.ceylon:
--------------------------------------------------------------------------------
1 | shared package gyokuro.demo.gson;
2 |
--------------------------------------------------------------------------------
/demos/gyokuro/demo/gson/run.ceylon:
--------------------------------------------------------------------------------
1 | import net.gyokuro.core {
2 | get,
3 | Application,
4 | serve,
5 | post
6 | }
7 | import net.gyokuro.transform.gson {
8 | GsonTransformer
9 | }
10 | import ceylon.logging {
11 | defaultPriority,
12 | trace,
13 | writeSimpleLog,
14 | addLogWriter
15 | }
16 |
17 | "Run the module `gyokuro.demo.gson`."
18 | shared void run() {
19 | get("/bob", (req, resp) => Employee("Bob", 75049.4));
20 | post("/save", `saveEmp`);
21 |
22 | addLogWriter(writeSimpleLog);
23 | defaultPriority = trace;
24 |
25 | Application {
26 | transformers = [GsonTransformer()];
27 | assets = serve("demos-assets/gson");
28 | }.run();
29 | }
30 |
31 | class Employee(name, salary) {
32 | shared String name;
33 | shared Float salary;
34 | }
35 |
36 | String saveEmp(Employee emp) => "Saved ``emp.name`` in DB";
--------------------------------------------------------------------------------
/demos/gyokuro/demo/mustache/module.ceylon:
--------------------------------------------------------------------------------
1 | native("jvm")
2 | module gyokuro.demo.mustache "0.4-SNAPSHOT" {
3 | import net.gyokuro.core "0.4-SNAPSHOT";
4 | import net.gyokuro.view.mustache "0.4-SNAPSHOT";
5 | }
6 |
--------------------------------------------------------------------------------
/demos/gyokuro/demo/mustache/package.ceylon:
--------------------------------------------------------------------------------
1 | shared package gyokuro.demo.mustache;
2 |
--------------------------------------------------------------------------------
/demos/gyokuro/demo/mustache/run.ceylon:
--------------------------------------------------------------------------------
1 | import net.gyokuro.core {
2 | get,
3 | Application,
4 | Template,
5 | render
6 | }
7 | import net.gyokuro.view.mustache {
8 | MustacheRenderer
9 | }
10 |
11 | "Run the module `gyokuro.demo.mustache`."
12 | shared void run() {
13 |
14 | get("/hello", `hello`);
15 |
16 | Application {
17 | renderer = MustacheRenderer("demos-assets/mustache/", ".mustache");
18 | }.run();
19 | }
20 |
21 | Template hello() => render("hello", map {"who" -> "World"});
--------------------------------------------------------------------------------
/demos/gyokuro/demo/report/module.ceylon:
--------------------------------------------------------------------------------
1 | native("jvm")
2 | module gyokuro.demo.report "0.4-SNAPSHOT" {
3 | import net.gyokuro.report "0.4-SNAPSHOT";
4 | import gyokuro.demo.rest "1.0.0";
5 | }
6 |
--------------------------------------------------------------------------------
/demos/gyokuro/demo/report/package.ceylon:
--------------------------------------------------------------------------------
1 | shared package gyokuro.demo.report;
2 |
--------------------------------------------------------------------------------
/demos/gyokuro/demo/report/run.ceylon:
--------------------------------------------------------------------------------
1 | import ceylon.file {
2 | parsePath,
3 | Directory,
4 | Nil
5 | }
6 |
7 | import net.gyokuro.report {
8 | GyokuroApiGenerator
9 | }
10 | "Run the module `gyokuro.demo.report`."
11 | shared void run() {
12 | value output = "modules/reports/gyokuro/";
13 | value path =
14 | let (p = parsePath(output).resource)
15 | if (is Directory p) then p
16 | else if (is Nil p) then p.createDirectory(true)
17 | else null;
18 |
19 | if (exists path) {
20 | GyokuroApiGenerator(`package gyokuro.demo.rest`, path).run();
21 | } else {
22 | print("Can't access directory " + output);
23 | }
24 | }
--------------------------------------------------------------------------------
/demos/gyokuro/demo/rest/controllers.ceylon:
--------------------------------------------------------------------------------
1 | import ceylon.http.server {
2 | Response,
3 | Request
4 | }
5 |
6 | import net.gyokuro.core {
7 | controller,
8 | route,
9 | halt
10 | }
11 |
12 | route("duck")
13 | controller class SimpleRestController() {
14 |
15 | "Make the duck talk!"
16 | route("talk")
17 | shared void makeDuckTalk(Response resp, Request req) {
18 | resp.writeString("Quack world!");
19 | }
20 |
21 | "Lists all the things a duck can do."
22 | route("actions")
23 | shared String[] listThingsDucksCanDo() {
24 | return ["fly", "quack", "eat", "dive"];
25 | }
26 |
27 | "Tries to find a duck."
28 | suppressWarnings("expressionTypeNothing")
29 | route("find")
30 | shared String findDuck(Integer id) {
31 | // If we can't find the duck in DB,
32 | // return a 404 response.
33 | return daoFind(id) else halt(404, "Duck not found");
34 | }
35 |
36 | String? daoFind(Integer id) => null;
37 | }
38 |
--------------------------------------------------------------------------------
/demos/gyokuro/demo/rest/module.ceylon:
--------------------------------------------------------------------------------
1 | native("jvm")
2 | module gyokuro.demo.rest "1.0.0" {
3 | import net.gyokuro.core "0.4-SNAPSHOT";
4 | import ceylon.logging "1.3.4-SNAPSHOT";
5 | }
6 |
--------------------------------------------------------------------------------
/demos/gyokuro/demo/rest/package.ceylon:
--------------------------------------------------------------------------------
1 | "Demo for annotated controllers."
2 | shared package gyokuro.demo.rest;
3 |
--------------------------------------------------------------------------------
/demos/gyokuro/demo/rest/run.ceylon:
--------------------------------------------------------------------------------
1 | import ceylon.logging {
2 | addLogWriter,
3 | defaultPriority,
4 | trace,
5 | writeSimpleLog
6 | }
7 | import ceylon.http.server {
8 | Request,
9 | Response
10 | }
11 |
12 | import net.gyokuro.core {
13 | Application,
14 | get,
15 | post,
16 | Template,
17 | render,
18 | serve,
19 | bind,
20 | websocket
21 | }
22 | import net.gyokuro.view.api {
23 | TemplateRenderer
24 | }
25 |
26 | shared void run() {
27 |
28 | addLogWriter(writeSimpleLog);
29 | defaultPriority = trace;
30 |
31 | // React to GET/POST requests using a basic handler
32 | get("/hello", void(Request request, Response response) {
33 | response.writeString("Hello yourself!");
34 | });
35 |
36 | // You can also use more advanced handlers
37 | post("/hello", `postHandler`);
38 |
39 | // And render templates
40 | get("/render", `renderingHandler`);
41 |
42 | // WebSockets are also supported
43 | websocket("/ws", (channel, text) => channel.sendText("Hello, ``text``!"));
44 |
45 | websocket("/chat", (channel, text) {
46 | for (peer in channel.peerConnections) {
47 | peer.sendText(text);
48 | }
49 | });
50 |
51 | value app = Application {
52 | // You can also use annotated controllers, if you're
53 | // a nostalgic Java developer ;-)
54 | controllers = bind(`package gyokuro.demo.rest`, "/rest");
55 |
56 | // And serve static assets
57 | assets = serve("demos-assets");
58 |
59 | // You can use any template engine you want
60 | renderer = object satisfies TemplateRenderer<> {
61 |
62 | // this is a dummy template renderer
63 | shared actual String render(String templateName, Map context,
64 | Request req, Response resp) {
65 |
66 | variable value result = templateName;
67 | for (key->val in context) {
68 | if (exists val) {
69 | result = result.replace(key, val.string);
70 | }
71 | }
72 | return result;
73 | }
74 | };
75 | };
76 |
77 | // By default, the server will be started on 0.0.0.0:8080
78 | app.run();
79 | }
80 |
81 | "Advanced handlers have more flexible parameters, you're
82 | not limited to `Request` and `Response`, you can bind
83 | GET/POST values directly to handler parameters!"
84 | String postHandler(String who = "world") {
85 | // `who` will get its value from POST data, and will
86 | // be defaulted to "world".
87 | return "Hello, " + who + "!\n";
88 | }
89 |
90 | Template renderingHandler() {
91 | return render("foobar", map { "bar"->"baz" });
92 | }
93 |
--------------------------------------------------------------------------------
/demos/gyokuro/demo/rythm/module.ceylon:
--------------------------------------------------------------------------------
1 | native("jvm")
2 | module gyokuro.demo.rythm "0.4-SNAPSHOT" {
3 | import net.gyokuro.core "0.4-SNAPSHOT";
4 | import net.gyokuro.view.rythm "0.4-SNAPSHOT";
5 | }
6 |
--------------------------------------------------------------------------------
/demos/gyokuro/demo/rythm/package.ceylon:
--------------------------------------------------------------------------------
1 | shared package gyokuro.demo.rythm;
2 |
--------------------------------------------------------------------------------
/demos/gyokuro/demo/rythm/run.ceylon:
--------------------------------------------------------------------------------
1 | import net.gyokuro.core {
2 | render,
3 | Application,
4 | Template,
5 | get
6 | }
7 | import net.gyokuro.view.rythm {
8 | RythmRenderer
9 | }
10 |
11 | "Run the module `gyokuro.demo.rythm`."
12 | shared void run() {
13 |
14 | get("/hello", `hello`);
15 |
16 | Application {
17 | renderer = RythmRenderer("demos-assets/rythm/", ".rythm");
18 | }.run();
19 | }
20 |
21 | Template hello() => render("hello", map { "who"->"World" });
22 |
--------------------------------------------------------------------------------
/demos/gyokuro/demo/spring/module.ceylon:
--------------------------------------------------------------------------------
1 | "Shows how to inject Spring beans in gyokuro controllers."
2 | native ("jvm")
3 | module gyokuro.demo.spring "0.4-SNAPSHOT" {
4 | import ceylon.logging "1.3.4-SNAPSHOT";
5 |
6 | import net.gyokuro.core "0.4-SNAPSHOT";
7 |
8 | import maven:"org.springframework:spring-core" "4.3.5.RELEASE";
9 | import maven:"org.springframework:spring-beans" "4.3.5.RELEASE";
10 | import maven:"org.springframework:spring-context" "4.3.5.RELEASE";
11 | import maven:"commons-logging:commons-logging" "1.2";
12 | }
13 |
--------------------------------------------------------------------------------
/demos/gyokuro/demo/spring/package.ceylon:
--------------------------------------------------------------------------------
1 | shared package gyokuro.demo.spring;
2 |
--------------------------------------------------------------------------------
/demos/gyokuro/demo/spring/run.ceylon:
--------------------------------------------------------------------------------
1 | import ceylon.http.server {
2 | Response
3 | }
4 | import ceylon.logging {
5 | trace,
6 | addLogWriter,
7 | writeSimpleLog,
8 | defaultPriority
9 | }
10 |
11 | import java.lang {
12 | Types
13 | }
14 |
15 | import net.gyokuro.core {
16 | controller,
17 | route,
18 | Application,
19 | bind,
20 | ControllerAnnotation
21 | }
22 |
23 | import org.springframework.beans.factory.annotation {
24 | autowired
25 | }
26 | import org.springframework.context.annotation {
27 | AnnotationConfigApplicationContext
28 | }
29 | import org.springframework.stereotype {
30 | component
31 | }
32 |
33 | shared void run() {
34 | addLogWriter(writeSimpleLog);
35 | defaultPriority = trace;
36 |
37 | print("Scanning current package for Spring-annotated classes");
38 | value springContext = AnnotationConfigApplicationContext(`package`.qualifiedName);
39 |
40 | print("Starting gyokuro application");
41 |
42 | value controllerAnnotation = Types.classForAnnotationType();
43 | value controllers = [*springContext.getBeansWithAnnotation(controllerAnnotation).values()];
44 |
45 | Application {
46 | // We provide our own controller instances instead of letting gyokuro scan a package
47 | controllers = bind(controllers);
48 | }.run();
49 | }
50 |
51 | "A gyokuro [[controller]] that will be instantiated by Spring."
52 | component controller class MyController() {
53 |
54 | "Could also be injected in the parameter list:
55 |
56 | class MyController(autowired IService service)
57 | "
58 | late autowired IService service;
59 |
60 | route ("/hello")
61 | shared void hello(Response resp, String who = "world") {
62 | resp.writeString(service.greet(who));
63 | }
64 | }
65 |
66 | interface IService {
67 | shared formal String greet(String who) ;
68 | }
69 |
70 | "A Spring bean defining a simple service."
71 | component class Service() satisfies IService {
72 | greet(String who) => "Hello, ``who`` from a Spring service!";
73 | }
74 |
--------------------------------------------------------------------------------
/demos/gyokuro/demo/thymeleaf/module.ceylon:
--------------------------------------------------------------------------------
1 | native("jvm")
2 | module gyokuro.demo.thymeleaf "0.4-SNAPSHOT" {
3 | import net.gyokuro.core "0.4-SNAPSHOT";
4 | import net.gyokuro.view.thymeleaf "0.4-SNAPSHOT";
5 | import ceylon.logging "1.3.4-SNAPSHOT";
6 | }
7 |
--------------------------------------------------------------------------------
/demos/gyokuro/demo/thymeleaf/package.ceylon:
--------------------------------------------------------------------------------
1 | shared package gyokuro.demo.thymeleaf;
2 |
--------------------------------------------------------------------------------
/demos/gyokuro/demo/thymeleaf/run.ceylon:
--------------------------------------------------------------------------------
1 | import net.gyokuro.core {
2 | render,
3 | Application,
4 | Template,
5 | get
6 | }
7 | import net.gyokuro.view.thymeleaf {
8 | ThymeleafRenderer
9 | }
10 | import ceylon.logging {
11 | defaultPriority,
12 | debug,
13 | addLogWriter,
14 | writeSimpleLog
15 | }
16 |
17 | "Run the module `gyokuro.demo.thymeleaf`."
18 | shared void run() {
19 | defaultPriority = debug;
20 | addLogWriter(writeSimpleLog);
21 |
22 | get("/hello", `hello`);
23 |
24 | Application {
25 | renderer = ThymeleafRenderer("demos-assets/thymeleaf/", ".xhtml");
26 | }.run();
27 | }
28 |
29 | Template hello() => render("hello", map { "who"->"World" });
30 |
--------------------------------------------------------------------------------
/gyokuro.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Do not edit, modify .config/ide-config instead
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/overrides.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/resource/com/github/bjansen/gyokuro/report/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #EEE;
3 | width: 1024px;
4 | margin: auto;
5 | font-family: Verdana, Geneva, Tahoma, sans-serif;
6 | font-size: 0.9em;
7 | }
8 |
9 | .route-header {
10 | display: flex;
11 | cursor: pointer;
12 | }
13 |
14 | .route-header > :first-child {
15 | border-radius: 2px;
16 | padding: 5px;
17 | color: #EEE;
18 | min-width: 45px;
19 | text-align: center;
20 | }
21 |
22 | .route-header > :nth-child(2) {
23 | padding: 5px 15px;
24 | }
25 |
26 | .route-header > :nth-child(3) {
27 | padding: 5px 10px;
28 | text-align: right;
29 | flex: 1;
30 | }
31 |
32 | div[class^="method-"] {
33 | margin-bottom: 20px;
34 | }
35 |
36 | /* GET */
37 | .method-get {
38 | background-color: #e7f6ec;
39 | border: 1px solid #c3e8d1;
40 | }
41 | .method-get .route-params {
42 | border-top: 1px solid #c3e8d1;
43 | }
44 |
45 | .method-get .route-header > :first-child {
46 | background-color: #10a54a;
47 | }
48 |
49 | .method-get .route-header > :nth-child(3) {
50 | color: #10a54a;
51 | }
52 |
53 | /* POST */
54 | .method-post {
55 | background-color: #e7f0f7;
56 | border: 1px solid #c3d9ec;
57 | }
58 |
59 | .method-post .route-params {
60 | border-top: 1px solid #c3d9ec;
61 | }
62 |
63 | .method-post .route-header > :first-child {
64 | background-color: #0f6ab4;
65 | }
66 |
67 | .method-post .route-header > :nth-child(3) {
68 | color: #0f6ab4;
69 | }
70 |
71 | .collapsed {
72 | display: none;
73 | }
74 |
75 | /* Parameters */
76 | .route-params {
77 | border-top: 1px solid rgba(0, 0, 0, 0.5);
78 | padding: 10px;
79 | }
80 | .route-params h2 {
81 | font-size: 1.1em;
82 | }
83 |
84 | .route-params table {
85 | width: 100%;
86 | border-collapse:collapse;
87 | text-align: left;
88 | }
89 |
90 | .route-params table th {
91 | color: #666;
92 | font-weight: normal;
93 | border-bottom: 1px solid #CCC;
94 | }
95 |
96 | .route-params table td {
97 | padding: 2px 5px;
98 | }
--------------------------------------------------------------------------------
/source/net/gyokuro/core/Flash.ceylon:
--------------------------------------------------------------------------------
1 | import ceylon.http.server {
2 | Session
3 | }
4 |
5 | import net.gyokuro.core.internal {
6 | DefaultFlash
7 | }
8 |
9 | "A holder for special messages stored in the session,
10 | meant to be used exactly once. Flash messages are removed
11 | from the session as soon as they are accessed."
12 | shared interface Flash {
13 | "Adds a flash object to the session."
14 | shared formal void add(String key, Object val);
15 |
16 | "Gets a flash object if it exists, and removes it
17 | immediately from the session."
18 | shared formal Object? get(String key);
19 |
20 | "Gets a flash object if it exists, without removing
21 | it from the session."
22 | shared formal Object? peek(String key);
23 | }
24 |
25 | "Creates a new instance of a [[Flash]]. You shouldn't have
26 | to use this function directly unless you are creating a custom
27 | [[net.gyokuro.view.api::TemplateRenderer]].
28 | If you want to access a `Flash` instance from a handler,
29 | use parameters injection instead:
30 |
31 | route(\"/login\")
32 | shared void login(Flash flash) {
33 | if (loginOk()) {
34 | flash.add(\"info\", \"You have been logged in\");
35 | redirect(\"/\");
36 | }
37 | }
38 | "
39 | shared Flash newFlash(Session session)
40 | => DefaultFlash(session);
41 |
--------------------------------------------------------------------------------
/source/net/gyokuro/core/annotations.ceylon:
--------------------------------------------------------------------------------
1 | import ceylon.language.meta.declaration {
2 | FunctionDeclaration,
3 | ClassDeclaration,
4 | ValueDeclaration
5 | }
6 | import ceylon.http.common {
7 | AbstractMethod,
8 | get,
9 | post
10 | }
11 |
12 | "Declares a partial path associated to a class or a function.
13 | Routes declared on a class will be concatenated with its member
14 | routes.
15 | For example, the following code will result in a route `/foo/bar`:
16 |
17 | route(\"foo\")
18 | controller class MyController() {
19 | route(\"bar\")
20 | void hello() { }
21 | }
22 | "
23 | shared annotation RouteAnnotation route(String path,
24 | {AbstractMethod+} methods = { get, post }) => RouteAnnotation(path, methods);
25 |
26 | "The annotation class for the [[route]] annotation."
27 | shared final annotation class RouteAnnotation(path, methods)
28 | satisfies OptionalAnnotation {
29 |
30 | shared String path;
31 | shared {AbstractMethod+} methods;
32 | }
33 |
34 | "Declares a class or an object as a controller, allowing routes to be scanned."
35 | see(`function route`)
36 | shared annotation ControllerAnnotation controller() => ControllerAnnotation();
37 |
38 | "The annotation class for the [[controller]] annotation."
39 | shared final annotation class ControllerAnnotation()
40 | satisfies OptionalAnnotation {
41 | }
42 |
43 | "Declares that a handler parameter should be retrieved from the current
44 | HTTP session instead of GET/POST data. If no value can be retrieved from
45 | the session, a 400 response will be sent back to the client."
46 | shared annotation SessionAnnotation session() => SessionAnnotation();
47 |
48 | "The annotation class for the [[session]] annotation."
49 | shared final annotation class SessionAnnotation()
50 | satisfies OptionalAnnotation {
51 | }
52 |
--------------------------------------------------------------------------------
/source/net/gyokuro/core/application.ceylon:
--------------------------------------------------------------------------------
1 | import ceylon.collection {
2 | ArrayList
3 | }
4 | import ceylon.http.common {
5 | post,
6 | get,
7 | Method
8 | }
9 | import ceylon.http.server {
10 | Options,
11 | Request,
12 | newServer,
13 | Response,
14 | Server,
15 | startsWith,
16 | AsynchronousEndpoint,
17 | HttpEndpoint,
18 | Status
19 | }
20 | import ceylon.http.server.endpoints {
21 | serveStaticFile,
22 | RepositoryEndpoint
23 | }
24 | import ceylon.http.server.websocket {
25 | WebSocketEndpoint
26 | }
27 | import ceylon.io {
28 | SocketAddress
29 | }
30 | import ceylon.language.meta.declaration {
31 | Package
32 | }
33 |
34 | import net.gyokuro.core.internal {
35 | RequestDispatcher,
36 | router
37 | }
38 | import net.gyokuro.transform.api {
39 | Transformer
40 | }
41 | import net.gyokuro.view.api {
42 | TemplateRenderer
43 | }
44 |
45 | "A web server application that can route requests to handler functions
46 | or annotated controllers, and serve static assets."
47 | shared class Application(
48 | "The address or hostname on which the HTTP server will be bound."
49 | shared String address = "0.0.0.0",
50 | "The port on which the server will listen."
51 | shared Integer port = 8080,
52 | "Additional controllers in which [[route]]s will be scanned, that will be
53 | associated to the given context root.
54 |
55 | If a package is provided, gyokuro will look for classes and objects annotated
56 | with the [[controller]] annotation and instantiate them automatically.
57 |
58 | If a stream of [[Object]]s is provided, gyokuro will look for existing instances
59 | annotated with [[controller]].
60 |
61 | See also the [[bind]] function."
62 | [String, Package|{Object*}]? controllers = null,
63 | "A tuple [filesystem folder, context root] used to serve static assets.
64 | See the [[serve]] function."
65 | [String, String]? assets = null,
66 | "A context root used to serve modules."
67 | String? modulesPath = null,
68 | "Additional (chained) filters run before each request."
69 | Filter[] filters = [],
70 | "A template renderer"
71 | TemplateRenderer? renderer = null,
72 | "Transformers that can serialize to responses and deserialize from request bodies."
73 | Transformer[] transformers = []) {
74 |
75 | variable Server? server = null;
76 | variable Boolean stopped = false;
77 |
78 | "A filter applied to each incoming request before it is dispatched to
79 | its matching handler. Multiple filters can be chained, and returning
80 | [[false]] will stop the chain. In this case, the filter returning `false`
81 | should modify the [[Response]] such as it can be returned to the client."
82 | shared alias Filter => Anything(Request, Response, Anything(Request, Response));
83 |
84 | "Starts the web application."
85 | shared void run(Anything(Status) statusListener = noop) {
86 | if (stopped) {
87 | return;
88 | }
89 | value endpoints = ArrayList();
90 |
91 | endpoints.add(RequestDispatcher(controllers, filter, renderer, transformers).endpoint());
92 | if (exists modulesPath) {
93 | endpoints.add(RepositoryEndpoint(modulesPath));
94 | }
95 |
96 | if (exists assets) {
97 | value assetsEndpoint = AsynchronousEndpoint(startsWith(assets[1]),
98 | serveRoot(assets),
99 | { get, post, special });
100 |
101 | endpoints.add(assetsEndpoint);
102 | }
103 |
104 | for (path -> handler in router.webSocketHandlers) {
105 | WebSocketHandler wsHandler;
106 |
107 | if (is WebSocketHandler handler) {
108 | wsHandler = handler;
109 | } else {
110 | wsHandler = object extends WebSocketHandler() {
111 | onText = handler;
112 | };
113 | }
114 |
115 | endpoints.add(WebSocketEndpoint {
116 | path = startsWith(path);
117 | onOpen = wsHandler.onOpen;
118 | onClose = wsHandler.onClose;
119 | onError = wsHandler.onError;
120 | onText = wsHandler.onText;
121 | onBinary = wsHandler.onBinary;
122 | });
123 | }
124 |
125 | value s = server = newServer(endpoints);
126 | s.addListener(statusListener);
127 | s.start(SocketAddress(address, port), Options());
128 | server = null;
129 | }
130 |
131 | "Stops the web application, if started, and inhibits any further attempts to start it."
132 | shared void stop() {
133 | stopped = true;
134 | if (exists s = server) {
135 | s.stop();
136 | }
137 | }
138 |
139 | object special satisfies Method {
140 | string => "BREW";
141 | hash => string.hash;
142 | shared actual Boolean equals(Object that) {
143 | if (is Method that) {
144 | return that.string == string;
145 | }
146 | return false;
147 | }
148 | }
149 |
150 | "Runs the first element in the filter chain. Each filter has the responsibility to
151 | run the next filter in the chain."
152 | void filter(Request req, Response resp, Anything(Request, Response) last) {
153 | void lastFilter(Request req, Response resp, Anything(Request, Response) next) {
154 | last(req, resp);
155 | }
156 |
157 | void next(Filter[] filters, Request req, Response resp) {
158 | if (exists filter = filters.first) {
159 | filter(req, resp, (newReq, newResp) {
160 | next(filters.rest, newReq, newResp);
161 | });
162 | }
163 | }
164 |
165 | value ourFilters = filters.withTrailing(lastFilter);
166 | next(ourFilters, req, resp);
167 | }
168 |
169 | void serveRoot([String, String] conf)(Request req, Response resp, void complete()) {
170 | filter(req, resp, (req, resp) {
171 | if (req.method == special) {
172 | resp.status = 418;
173 | resp.writeString("418 - I'm a teapot");
174 | } else {
175 | value root = conf[1];
176 | value assetsPath = conf[0];
177 | value file = root.empty then req.path else req.path[root.size...];
178 | serveStaticFile(assetsPath, (req) => req.path.equals("/") then "/index.html" else file)(req, resp, complete);
179 | }
180 | });
181 | }
182 | }
183 |
184 | "Tells gyokuro to bind [[controllers|controller]] scanned in [[pkgToScan]] to the given
185 | [[context]] root. This function is meant to be used for [[Application.controllers]].
186 |
187 | value app = Application {
188 | controllers = bind(\"rest\", `package com.myapp.controllers`);
189 | };
190 |
191 | "
192 | shared [String, Package|{Object*}] bind(Package|{Object*} pkgToScan, String context = "/") {
193 | return [context, pkgToScan];
194 | }
195 |
196 | "Tells gyokuro to serve static assets located in the filesystem folder [[path]] under the
197 | given [[context]] root. For example, all routes starting with `/public` will serve files
198 | located in `./assets`:
199 |
200 | value app = Application {
201 | assets = serve(\"assets\", \"/public\");
202 | };
203 | "
204 | shared [String, String] serve(String path, String context = "") {
205 | return [path, context];
206 | }
207 |
--------------------------------------------------------------------------------
/source/net/gyokuro/core/functions.ceylon:
--------------------------------------------------------------------------------
1 | import ceylon.buffer.charset {
2 | Charset,
3 | utf8
4 | }
5 | import ceylon.http.common {
6 | getMethod=get,
7 | postMethod=post,
8 | optionsMethod=options,
9 | deleteMethod=delete,
10 | connectMethod=connect,
11 | traceMethod=trace,
12 | putMethod=put,
13 | headMethod=head,
14 | sdkContentType=contentType
15 | }
16 | import ceylon.http.server {
17 | Request,
18 | Response
19 | }
20 | import ceylon.language.meta.model {
21 | Function
22 | }
23 |
24 | import net.gyokuro.core.internal {
25 | router,
26 | HaltException,
27 | RedirectException
28 | }
29 | import net.gyokuro.core.http {
30 | patchMethod=patch
31 | }
32 | import net.gyokuro.view.api {
33 | TemplateRenderer
34 | }
35 |
36 | "A function capable of handling a request."
37 | shared alias Handler => Function|Callable;
38 |
39 | "Declares a new GET route for the given [[path]] and [[handler]]."
40 | shared void get(String path, Handler handler)
41 | given Params satisfies Anything[]
42 | => router.registerRoute(path, { getMethod }, handler);
43 |
44 | "Declares a new POST route for the given [[path]] and [[handler]]."
45 | shared void post(String path, Handler handler)
46 | given Params satisfies Anything[]
47 | => router.registerRoute(path, { postMethod }, handler);
48 |
49 | "Declares a new OPTIONS route for the given [[path]] and [[handler]]."
50 | shared void options(String path, Handler handler)
51 | given Params satisfies Anything[]
52 | => router.registerRoute(path, { optionsMethod }, handler);
53 |
54 | "Declares a new DELET route for the given [[path]] and [[handler]]."
55 | shared void delete(String path, Handler handler)
56 | given Params satisfies Anything[]
57 | => router.registerRoute(path, { deleteMethod }, handler);
58 |
59 | "Declares a new CONNECT route for the given [[path]] and [[handler]]."
60 | shared void connect(String path, Handler handler)
61 | given Params satisfies Anything[]
62 | => router.registerRoute(path, { connectMethod }, handler);
63 |
64 | "Declares a new TRACE route for the given [[path]] and [[handler]]."
65 | shared void trace(String path, Handler handler)
66 | given Params satisfies Anything[]
67 | => router.registerRoute(path, { traceMethod }, handler);
68 |
69 | "Declares a new PUT route for the given [[path]] and [[handler]]."
70 | shared void put(String path, Handler handler)
71 | given Params satisfies Anything[]
72 | => router.registerRoute(path, { putMethod }, handler);
73 |
74 | "Declares a new HEAD route for the given [[path]] and [[handler]]."
75 | shared void head(String path, Handler handler)
76 | given Params satisfies Anything[]
77 | => router.registerRoute(path, { headMethod }, handler);
78 |
79 | "Declares a new PATCH route for the given [[path]] and [[handler]]."
80 | shared void patch(String path, Handler handler)
81 | given Params satisfies Anything[]
82 | => router.registerRoute(path, { patchMethod }, handler);
83 |
84 | "Interrupts the current handler immediately, resulting in an HTTP
85 | response with code [[errorCode]] and a body equal to [[message]].
86 |
87 | This can be used for example to indicate that something was
88 | not found in the database:
89 |
90 | shared void findAuthor(Integer authorId) {
91 | value author = authorDao.findById(authorId)
92 | else halt(404, \"Author not found\");
93 | }
94 | "
95 | shared Nothing halt(Integer errorCode, String? message = null) {
96 | throw HaltException(errorCode, message);
97 | }
98 |
99 | "Interrupts the current handler immediately, and asks the client
100 | browser to redirect to the specified [[url]].
101 |
102 | shared void login(String username, String password) {
103 | if (exists user = ...) {
104 | session.put(\"user\", user);
105 | redirect(\"/\");
106 | }
107 | ...
108 | }
109 | "
110 | shared Nothing redirect(String url, Integer redirectCode = 303) {
111 | throw RedirectException(url, redirectCode);
112 | }
113 |
114 | "A template that can be called by a [[TemplateRenderer]]."
115 | shared alias AnyTemplate => Anything(TemplateRenderer, Request, Response);
116 | shared alias Template => AnyTemplate;
117 |
118 | "Renders a template that will be returned as the response body."
119 | shared void render(
120 | "The template name"
121 | T template,
122 | "A map of things to pass to the template."
123 | Map context = emptyMap,
124 | "The content type to be used in the response."
125 | String contentType = "text/html",
126 | "The charset to be used in the response."
127 | Charset charset = utf8)
128 | (TemplateRenderer renderer, Request request, Response response) {
129 |
130 | value result = renderer.render(template, context, request, response);
131 |
132 | response.addHeader(sdkContentType(contentType, charset));
133 | response.writeString(result);
134 | }
135 |
136 | "Clears every registered route."
137 | shared void clearRoutes() {
138 | router.clear();
139 | }
140 |
--------------------------------------------------------------------------------
/source/net/gyokuro/core/http/methods.ceylon:
--------------------------------------------------------------------------------
1 | import ceylon.http.common {
2 | Method
3 | }
4 |
5 | "Workaround until the SDK contains it."
6 | shared object patch satisfies Method {
7 | string => "PATCH";
8 | hash => string.hash;
9 | equals(Object that) =>
10 | if (is Method that)
11 | then that.string == this.string
12 | else false;
13 | }
14 |
--------------------------------------------------------------------------------
/source/net/gyokuro/core/http/package.ceylon:
--------------------------------------------------------------------------------
1 | "Default documentation for package `net.gyokuro.core.http`."
2 | shared package net.gyokuro.core.http;
3 |
--------------------------------------------------------------------------------
/source/net/gyokuro/core/internal/AnnotationScanner.ceylon:
--------------------------------------------------------------------------------
1 | import ceylon.http.common {
2 | AbstractMethod
3 | }
4 | import ceylon.language.meta {
5 | annotations,
6 | classDeclaration
7 | }
8 | import ceylon.language.meta.declaration {
9 | FunctionDeclaration,
10 | Package,
11 | ClassDeclaration,
12 | ValueDeclaration
13 | }
14 | import ceylon.logging {
15 | logger
16 | }
17 |
18 | import net.gyokuro.core {
19 | ControllerAnnotation,
20 | RouteAnnotation
21 | }
22 |
23 | shared object annotationScanner {
24 |
25 | value log = logger(`module`);
26 |
27 | shared alias Consumer => Anything(String, [Object, FunctionDeclaration], {AbstractMethod+});
28 |
29 | "Looks for controller definitions in the given [[controllers]].
30 | Scanned controllers and routes will be registered in the [[router]]
31 | for GET and POST methods."
32 | shared void scanControllers(String contextRoot, Package|{Object*} controllers,
33 | Consumer consumer = router.registerControllerRoute) {
34 |
35 | Anything>[] members;
36 |
37 | if (is Package controllers) {
38 | members = [ for (member in controllers.members())
39 | member -> null];
40 | log.trace("Scanning members in package ``controllers.name``");
41 | } else {
42 | members = [ for (o in controllers)
43 | classDeclaration(o) -> o ];
44 | log.trace("Scanning members in existing instances");
45 | }
46 |
47 | for (member -> possibleInstance in members) {
48 | if (exists controller = annotations(`ControllerAnnotation`, member)) {
49 | log.trace("Scanning member ``member.name``");
50 |
51 | String controllerRoute;
52 |
53 | if (exists route = annotations(`RouteAnnotation`, member)) {
54 | controllerRoute = buildPath(contextRoot, route.path);
55 | } else {
56 | controllerRoute = contextRoot;
57 | }
58 |
59 | value classDecl = if (is ClassDeclaration member)
60 | then member
61 | else member.objectClass;
62 |
63 | if (!exists classDecl) {
64 | log.warn("Skipped non-object value ``member.qualifiedName``");
65 | continue;
66 | }
67 |
68 | Object instance;
69 | if (exists possibleInstance) {
70 | instance = possibleInstance;
71 | } else if (is ClassDeclaration member) {
72 | instance = member.classApply