├── gradle.properties ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src ├── site │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.ttf │ │ └── glyphicons-halflings-regular.woff │ ├── css │ │ ├── prettify.css │ │ ├── base.css │ │ └── asciidoctor.css │ ├── js │ │ ├── html5shiv.min.js │ │ ├── prettify.js │ │ └── bootstrap.min.js │ └── index.html ├── main │ └── groovy │ │ └── io │ │ └── github │ │ └── httpbuilderng │ │ └── http │ │ ├── HttpPlugin.groovy │ │ ├── HttpLibrary.groovy │ │ ├── HttpExtension.groovy │ │ └── HttpTask.groovy ├── test │ └── groovy │ │ ├── io │ │ └── github │ │ │ └── httpbuilderng │ │ │ └── http │ │ │ ├── HttpLibrarySpec.groovy │ │ │ ├── HttpExtensionSpec.groovy │ │ │ ├── HttpTaskDeleteSpec.groovy │ │ │ ├── HttpTaskHeadSpec.groovy │ │ │ ├── HttpTaskGetSpec.groovy │ │ │ ├── HttpTaskPatchSpec.groovy │ │ │ ├── HttpTaskPutSpec.groovy │ │ │ └── HttpTaskPostSpec.groovy │ │ └── com │ │ └── stehno │ │ └── gradle │ │ └── testing │ │ └── GradleBuild.groovy └── docs │ └── asciidoc │ └── index.adoc ├── .travis.yml ├── .gitignore ├── publish.gradle ├── config └── license_header.txt ├── gradlew.bat ├── CODE_OF_CONDUCT.md ├── README.md ├── gradlew └── LICENSE.txt /gradle.properties: -------------------------------------------------------------------------------- 1 | httpBuilderVersion=1.0.3 -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'http-plugin' -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/http-builder-ng/gradle-http-plugin/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/site/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/http-builder-ng/gradle-http-plugin/HEAD/src/site/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /src/site/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/http-builder-ng/gradle-http-plugin/HEAD/src/site/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /src/site/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/http-builder-ng/gradle-http-plugin/HEAD/src/site/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | language: java 3 | install: true 4 | jdk: oraclejdk8 5 | env: 6 | matrix: 7 | - TERM=dumb 8 | before_script: 9 | - ./gradlew --version 10 | script: ./gradlew build 11 | after_success: 12 | - ./gradlew jacocoTestReport coveralls -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Nov 27 05:58:11 CST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-all.zip 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/** 2 | *.iws 3 | out/ 4 | .idea_modules/ 5 | atlassian-ide-plugin.xml 6 | com_crashlytics_export_strings.xml 7 | crashlytics.properties 8 | crashlytics-build.properties 9 | fabric.properties 10 | .gradle 11 | /build/ 12 | gradle-app.setting 13 | !gradle-wrapper.jar 14 | .gradletasknamecache 15 | -------------------------------------------------------------------------------- /publish.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.ajoberstar.git-publish' version '0.3.0' 3 | } 4 | 5 | gitPublish { 6 | repoUri = 'git@github.com:http-builder-ng/gradle-http-plugin.git' 7 | branch = 'gh-pages' 8 | 9 | contents { 10 | from(file('build/site')) { 11 | into '.' 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/site/css/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} 2 | -------------------------------------------------------------------------------- /config/license_header.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) ${year} HttpBuilder-NG Project 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /src/site/css/base.css: -------------------------------------------------------------------------------- 1 | /* Sticky footer styles 2 | -------------------------------------------------- */ 3 | 4 | html, 5 | body { 6 | height: 100%; 7 | /* The html and body elements cannot have any padding or margin. */ 8 | } 9 | 10 | /* Wrapper for page content to push down footer */ 11 | #wrap { 12 | min-height: 100%; 13 | height: auto !important; 14 | height: 100%; 15 | /* Negative indent footer by it's height */ 16 | margin: 0 auto -60px; 17 | } 18 | 19 | /* Set the fixed height of the footer here */ 20 | #push, 21 | #footer { 22 | height: 60px; 23 | } 24 | #footer { 25 | background-color: #f5f5f5; 26 | padding: 0; 27 | } 28 | 29 | /* Lastly, apply responsive CSS fixes as necessary */ 30 | @media (max-width: 767px) { 31 | #footer { 32 | margin-left: -20px; 33 | margin-right: -20px; 34 | padding-left: 20px; 35 | padding-right: 20px; 36 | } 37 | } 38 | 39 | /* Custom page CSS 40 | -------------------------------------------------- */ 41 | /* Not required for template or sticky footer method. */ 42 | 43 | #wrap > .container { 44 | padding-top: 60px; 45 | } 46 | .container .credit { 47 | margin: 20px 0; 48 | } 49 | 50 | code { 51 | font-size: 80% !important; 52 | } -------------------------------------------------------------------------------- /src/main/groovy/io/github/httpbuilderng/http/HttpPlugin.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 HttpBuilder-NG Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.httpbuilderng.http 17 | 18 | import groovy.transform.CompileStatic 19 | import org.gradle.api.Plugin 20 | import org.gradle.api.Project 21 | 22 | /** 23 | * Gradle Plugin providing ability to configure and execute HTTP requests using the HttpBuilder-NG client library. 24 | */ 25 | @CompileStatic 26 | class HttpPlugin implements Plugin { 27 | 28 | /** 29 | * Applies the configuration for the plugin. 30 | * 31 | * @param project a reference to the project 32 | */ 33 | @Override void apply(final Project project) { 34 | project.extensions.create('http', HttpExtension) as HttpExtension 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/groovy/io/github/httpbuilderng/http/HttpLibrarySpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 HttpBuilder-NG Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.httpbuilderng.http 17 | 18 | import spock.lang.Specification 19 | 20 | class HttpLibrarySpec extends Specification { 21 | 22 | def 'http library'() { 23 | expect: 24 | HttpLibrary.fromName(name) == result 25 | 26 | where: 27 | name || result 28 | 'core' || HttpLibrary.CORE 29 | 'apache' || HttpLibrary.APACHE 30 | 'okhttp' || HttpLibrary.OKHTTP 31 | 'CORE' || HttpLibrary.CORE 32 | 'APACHE' || HttpLibrary.APACHE 33 | 'OKHTTP' || HttpLibrary.OKHTTP 34 | } 35 | 36 | def 'invalid library'(){ 37 | when: 38 | HttpLibrary.fromName('blah') 39 | 40 | then: 41 | def ex = thrown(IllegalArgumentException) 42 | ex.message == 'Specified library (blah) is unknown.' 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/groovy/io/github/httpbuilderng/http/HttpExtensionSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 HttpBuilder-NG Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.httpbuilderng.http 17 | 18 | import spock.lang.Specification 19 | import spock.lang.Unroll 20 | 21 | class HttpExtensionSpec extends Specification { 22 | 23 | @Unroll 'usage #library'() { 24 | setup: 25 | HttpExtension extension = new HttpExtension( 26 | library: library 27 | ) 28 | extension.config { 29 | 'configuration' 30 | } 31 | 32 | expect: 33 | extension.library == result 34 | extension.config.call() == 'configuration' 35 | 36 | where: 37 | library || result 38 | HttpLibrary.CORE || HttpLibrary.CORE 39 | HttpLibrary.APACHE || HttpLibrary.APACHE 40 | HttpLibrary.OKHTTP || HttpLibrary.OKHTTP 41 | 'core' || HttpLibrary.CORE 42 | 'apache' || HttpLibrary.APACHE 43 | 'okhttp' || HttpLibrary.OKHTTP 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/groovy/io/github/httpbuilderng/http/HttpLibrary.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 HttpBuilder-NG Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.httpbuilderng.http 17 | 18 | import groovy.transform.CompileStatic 19 | import groovy.transform.TupleConstructor 20 | 21 | /** 22 | * Represents the available underlying HTTP client libraries (as supported by HttpBuilder-NG). 23 | */ 24 | @CompileStatic @TupleConstructor 25 | enum HttpLibrary { 26 | 27 | /** 28 | * The "core" library, using built-in Java HTTP client. 29 | */ 30 | CORE, 31 | 32 | /** 33 | * The "apache" library, using the Apache HttpComponents client library. 34 | */ 35 | APACHE, 36 | 37 | /** 38 | * The "okhttp" library, using the OkHttp client library. 39 | */ 40 | OKHTTP 41 | 42 | /** 43 | * Resolves the enum value from the name, which should be the same as the enum name, case insensitive. 44 | * 45 | * @param name the library name 46 | * @return the enum value representing that specified library 47 | */ 48 | static HttpLibrary fromName(final String name) { 49 | HttpLibrary library = values().find { it.name().equalsIgnoreCase(name) } 50 | if (library) { 51 | return library 52 | } else { 53 | throw new IllegalArgumentException("Specified library ($name) is unknown.") 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/site/js/html5shiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.2 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.2",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b)}(this,document); -------------------------------------------------------------------------------- /src/main/groovy/io/github/httpbuilderng/http/HttpExtension.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 HttpBuilder-NG Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.httpbuilderng.http 17 | 18 | import groovy.transform.CompileStatic 19 | import groovyx.net.http.HttpObjectConfig 20 | 21 | import java.util.function.Consumer 22 | 23 | /** 24 | * A Gradle Plugin DSL Extension used to configure the HTTP plugin. 25 | */ 26 | @CompileStatic 27 | class HttpExtension { 28 | 29 | private Object config 30 | private HttpLibrary library = HttpLibrary.CORE 31 | 32 | /** 33 | * Used to specify the HTTP client library with the HttpLibrary enum. If not specified, the CORE library is used. 34 | * 35 | * @param value the library to be used 36 | */ 37 | void setLibrary(final HttpLibrary value) { 38 | this.library = value 39 | } 40 | 41 | /** 42 | * Used to specify the HTTP client library with a String representing the HttpLibrary enum. If not specified, the CORE library is used. 43 | * 44 | * @param value the library to be used 45 | */ 46 | void setLibrary(final String value) { 47 | this.library = HttpLibrary.fromName(value) 48 | } 49 | 50 | /** 51 | * Retrieves the configured library value as the HttpLibrary enum. 52 | * 53 | * @return the configured library value 54 | */ 55 | HttpLibrary getLibrary() { 56 | library 57 | } 58 | 59 | /** 60 | * Provides a global/shared configuration to be used by all HTTP calls that do not provide their own "config" block. See the 61 | * HttpObjectConfig interface 62 | * in the HttpBuilder-NG JavaDocs for details about the specific configuration. 63 | * 64 | * @param closure the configuration closure 65 | */ 66 | void config(@DelegatesTo(HttpObjectConfig) final Closure closure) { 67 | config = closure 68 | } 69 | 70 | void config(final Consumer consumer){ 71 | config = consumer 72 | } 73 | 74 | protected Object getConfig() { 75 | config 76 | } 77 | } -------------------------------------------------------------------------------- /src/test/groovy/com/stehno/gradle/testing/GradleBuild.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 HttpBuilder-NG Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.stehno.gradle.testing 17 | 18 | import groovy.text.GStringTemplateEngine 19 | import groovy.text.TemplateEngine 20 | import groovy.transform.CompileStatic 21 | import org.gradle.testkit.runner.BuildResult 22 | import org.gradle.testkit.runner.BuildTask 23 | import org.gradle.testkit.runner.GradleRunner 24 | import org.gradle.testkit.runner.TaskOutcome 25 | import org.junit.rules.ExternalResource 26 | import org.junit.rules.TemporaryFolder 27 | 28 | // FIXME: this needs to be pulled out into its own project and used in my other plugins 29 | @CompileStatic 30 | class GradleBuild extends ExternalResource { 31 | 32 | String template 33 | 34 | @Delegate final TemporaryFolder folder = new TemporaryFolder() 35 | 36 | private final TemplateEngine templateEngine = new GStringTemplateEngine() 37 | 38 | @Override 39 | protected void before() throws Throwable { 40 | folder.create() 41 | } 42 | 43 | void buildFile(final Map config = [:]) { 44 | File buildFile = newFile('build.gradle') 45 | buildFile.text = (templateEngine.createTemplate(template).make(config: config) as String).stripIndent() 46 | } 47 | 48 | GradleRunner runner(final String line) { 49 | GradleRunner.create().withPluginClasspath().withDebug(true).withProjectDir(root).withArguments(line.split(' ')) 50 | } 51 | 52 | GradleRunner runner(final String... args) { 53 | GradleRunner.create().withPluginClasspath().withDebug(true).withProjectDir(root).withArguments(args) 54 | } 55 | 56 | GradleRunner runner(final List args) { 57 | GradleRunner.create().withPluginClasspath().withDebug(true).withProjectDir(root).withArguments(args) 58 | } 59 | 60 | static boolean totalSuccess(final BuildResult result) { 61 | result.tasks.every { BuildTask task -> task.outcome != TaskOutcome.FAILED } 62 | } 63 | 64 | static boolean textContainsLines(final String text, final Collection lines, final boolean trimmed = true) { 65 | lines.every { String line -> 66 | text.contains(trimmed ? line.trim() : line) 67 | } 68 | } 69 | 70 | static boolean textDoesNotContainLines(final String text, final Collection lines, final boolean trimmed = true) { 71 | lines.every { String line -> 72 | !text.contains(trimmed ? line.trim() : line) 73 | } 74 | } 75 | 76 | @Override 77 | protected void after() { 78 | folder.delete() 79 | } 80 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at chris@stehno.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gradle HTTP Plugin 2 | 3 | [![Build Status](https://travis-ci.org/http-builder-ng/gradle-http-plugin.svg?branch=master)](https://travis-ci.org/http-builder-ng/gradle-http-plugin) [![Coverage Status](https://coveralls.io/repos/github/http-builder-ng/gradle-http-plugin/badge.svg?branch=master)](https://coveralls.io/github/http-builder-ng/gradle-http-plugin?branch=master) 4 | 5 | > Dormant - The HttpBuilder-NG project is going dormant. Neither of us use the project any longer nor do we have the extra time to properly maintain it. Please feel free to fork it and move it forward, or contact us (with an issue) to discuss options for taking over the project. 6 | 7 | ## Quick Links 8 | 9 | * Site: https://http-builder-ng.github.io/gradle-http-plugin/ 10 | * Project: https://github.com/http-builder-ng/gradle-http-plugin 11 | * Issues: https://github.com/http-builder-ng/gradle-http-plugin/issues 12 | * GroovyDocs: https://http-builder-ng.github.io/gradle-http-plugin/docs/groovydoc/ 13 | * User Guide: https://http-builder-ng.github.io/gradle-http-plugin/asciidoc/html5/ 14 | 15 | ## Introduction 16 | 17 | A Gradle plugin providing the ability to define tasks to make HTTP requests using the HttpBuilder-NG client library. The resulting tasks have a clean 18 | DSL and will look something like the following: 19 | 20 | Groovy 21 | ```groovy 22 | task notify(type:HttpTask){ 23 | config { 24 | request.uri = 'http://something.com' 25 | } 26 | post { 27 | request.uri.path = '/notify' 28 | request.body = [event: 'activated'] 29 | response.success { 30 | println 'The event notification was successful' 31 | } 32 | } 33 | } 34 | ``` 35 | 36 | Kotlin 37 | ```kotlin 38 | tasks { 39 | val notify by registering(HttpTask::class) { 40 | config { 41 | it.request.setUri = "http://something.com" 42 | } 43 | post { 44 | it.request.uri.setPath("/notify") 45 | } 46 | response.success { fromServer, body -> 47 | println("The event notification was successful") 48 | } 49 | } 50 | } 51 | ``` 52 | 53 | ## Installing 54 | 55 | The plugin is available through the [Gradle Plugin Repository](https://plugins.gradle.org/plugin/io.github.http-builder-ng.http-plugin) and may be 56 | applied to your Gradle build with one of the following: 57 | 58 | ```groovy 59 | plugins { 60 | id "io.github.http-builder-ng.http-plugin" version "0.1.1" 61 | } 62 | ``` 63 | 64 | or 65 | 66 | ```groovy 67 | buildscript { 68 | repositories { 69 | maven { 70 | url "https://plugins.gradle.org/m2/" 71 | } 72 | } 73 | dependencies { 74 | classpath "gradle.plugin.io.github.http-builder-ng:http-plugin:0.1.1" 75 | } 76 | } 77 | 78 | apply plugin: "io.github.http-builder-ng.http-plugin" 79 | ``` 80 | 81 | ## Building 82 | 83 | The project is build with Gradle using the following command: 84 | 85 | ./gradlew clean build 86 | 87 | ## License 88 | 89 | The Gradle HTTP Plugin is licensed under the [Apache 2](http://www.apache.org/licenses/LICENSE-2.0) open source license. 90 | 91 | Copyright 2017 HttpBuilder-NG Project 92 | 93 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 94 | in compliance with the License. You may obtain a copy of the License at 95 | 96 | http://www.apache.org/licenses/LICENSE-2.0 97 | 98 | Unless required by applicable law or agreed to in writing, software distributed under the License 99 | is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 100 | or implied. See the License for the specific language governing permissions and limitations under 101 | the License. 102 | 103 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /src/test/groovy/io/github/httpbuilderng/http/HttpTaskDeleteSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 HttpBuilder-NG Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.httpbuilderng.http 17 | 18 | import com.stehno.ersatz.ErsatzServer 19 | import com.stehno.gradle.testing.GradleBuild 20 | import groovyx.net.http.HttpConfig 21 | import org.gradle.testkit.runner.BuildResult 22 | import org.junit.Rule 23 | import spock.lang.AutoCleanup 24 | import spock.lang.Specification 25 | import spock.lang.Unroll 26 | 27 | import java.util.function.Consumer 28 | 29 | import static GradleBuild.textContainsLines 30 | import static GradleBuild.totalSuccess 31 | 32 | class HttpTaskDeleteSpec extends Specification { 33 | 34 | @Rule GradleBuild gradle = new GradleBuild( 35 | template: ''' 36 | plugins { 37 | id 'io.github.http-builder-ng.http-plugin' 38 | } 39 | repositories { 40 | jcenter() 41 | } 42 | 43 | import groovyx.net.http.HttpConfig 44 | import java.util.function.Consumer 45 | 46 | ${config.globalConfig ?: ''} 47 | 48 | task makeRequest(type:io.github.httpbuilderng.http.HttpTask){ 49 | ${config.taskConfig ?: ''} 50 | } 51 | ''' 52 | ) 53 | 54 | @AutoCleanup(value = 'stop') private ErsatzServer ersatz = new ErsatzServer() 55 | 56 | def 'single DELETE request'() { 57 | setup: 58 | ersatz.expectations { 59 | delete('/notify').called(1).responds().code(204) 60 | } 61 | 62 | gradle.buildFile(taskConfig: """ 63 | config { 64 | request.uri = '${ersatz.httpUrl}' 65 | } 66 | delete { 67 | request.uri.path = '/notify' 68 | response.when(204) { 69 | println 'I have arrived!' 70 | } 71 | } 72 | """) 73 | 74 | when: 75 | BuildResult result = gradle.runner('makeRequest').build() 76 | 77 | then: 78 | totalSuccess result 79 | 80 | and: 81 | textContainsLines result.output, ['I have arrived!'] 82 | 83 | and: 84 | ersatz.verify() 85 | } 86 | 87 | def 'multiple DELETE requests'() { 88 | setup: 89 | ersatz.expectations { 90 | delete('/notify').called(3).responds().code(204) 91 | } 92 | 93 | gradle.buildFile(taskConfig: """ 94 | config { 95 | request.uri = '${ersatz.httpUrl}' 96 | response.when(204){ 97 | println 'I have arrived!' 98 | } 99 | } 100 | deleteAsync { 101 | request.uri.path = '/notify' 102 | } 103 | delete { 104 | request.uri.path = '/notify' 105 | } 106 | delete { 107 | request.uri.path = '/notify' 108 | } 109 | """) 110 | 111 | when: 112 | BuildResult result = gradle.runner('makeRequest').build() 113 | 114 | then: 115 | totalSuccess result 116 | 117 | and: 118 | textContainsLines result.output, ['I have arrived!'] 119 | 120 | and: 121 | ersatz.verify() 122 | } 123 | 124 | def 'multiple DELETE requests (consumer)'() { 125 | setup: 126 | ersatz.expectations { 127 | delete('/notify').called(2).responds().code(204) 128 | } 129 | 130 | gradle.buildFile(taskConfig: """ 131 | config { 132 | request.uri = '${ersatz.httpUrl}' 133 | response.when(204){ 134 | println 'I have arrived!' 135 | } 136 | } 137 | deleteAsync(new Consumer() { 138 | @Override void accept(HttpConfig cfg) { 139 | cfg.request.uri.path = '/notify' 140 | } 141 | }) 142 | delete(new Consumer() { 143 | @Override void accept(HttpConfig cfg) { 144 | cfg.request.uri.path = '/notify' 145 | } 146 | }) 147 | """) 148 | 149 | when: 150 | BuildResult result = gradle.runner('makeRequest').build() 151 | 152 | then: 153 | totalSuccess result 154 | 155 | and: 156 | textContainsLines result.output, ['I have arrived!'] 157 | 158 | and: 159 | ersatz.verify() 160 | } 161 | 162 | @Unroll 'single DELETE request (external config with #library)'() { 163 | setup: 164 | ersatz.expectations { 165 | delete('/notify').called(1).responds().code(204) 166 | } 167 | 168 | gradle.buildFile( 169 | globalConfig: """ 170 | http { 171 | library = $library 172 | config { 173 | request.uri = '${ersatz.httpUrl}' 174 | } 175 | } 176 | """, 177 | taskConfig: """ 178 | delete { 179 | request.uri.path = '/notify' 180 | response.when(204){ 181 | println 'I have arrived!' 182 | } 183 | } 184 | """) 185 | 186 | when: 187 | BuildResult result = gradle.runner('makeRequest').build() 188 | 189 | then: 190 | totalSuccess result 191 | 192 | and: 193 | textContainsLines result.output, ['I have arrived!'] 194 | 195 | and: 196 | ersatz.verify() 197 | 198 | where: 199 | library << [ 200 | "io.github.httpbuilderng.http.HttpLibrary.CORE", 201 | "io.github.httpbuilderng.http.HttpLibrary.APACHE", 202 | "io.github.httpbuilderng.http.HttpLibrary.OKHTTP", 203 | "'core'", 204 | "'apache'", 205 | "'okhttp'", 206 | ] 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/docs/asciidoc/index.adoc: -------------------------------------------------------------------------------- 1 | = Gradle HTTP Plugin User Guide 2 | Christopher J. Stehno 3 | v0.1.1, October 2017 4 | :toc: left 5 | :toclevels: 4 6 | 7 | == Introduction 8 | 9 | The Gradle HTTP Plugin provides a straight-forward means of configuring and executing HTTP requests from your Gradle build, using the 10 | https://http-builder-ng.github.io/http-builder-ng[HttpBuilder-NG] client library. 11 | 12 | == Getting Started 13 | 14 | In order to use the HTTP Plugin to create HTTP tasks, you first need to apply it to your build. The plugin is available via the common Gradle plugins 15 | repository as https://plugins.gradle.org/plugin/io.github.http-builder-ng.http-plugin[io.github.http-builder-ng.http-plugin]. You can then apply it 16 | to your build with by adding the following to your `build.gradle` file: 17 | 18 | [source,groovy] 19 | .build.gradle 20 | ---- 21 | plugins { 22 | id "io.github.http-builder-ng.http-plugin" version "0.1.1" 23 | } 24 | ---- 25 | 26 | Next, you will need to create a task of type `io.github.httpbuilderng.http.HttpTask` to preform your requests. The following is an example: 27 | 28 | [source,groovy] 29 | .build.gradle 30 | ---- 31 | task notify(type:HttpTask){ 32 | config { 33 | request.uri = 'http://something.com' 34 | } 35 | post { 36 | request.uri.path = '/notify' 37 | request.body = [event: 'activated'] 38 | response.success { 39 | println 'The event notification was successful' 40 | } 41 | } 42 | } 43 | ---- 44 | 45 | This `notify` task will send a POST request to http://something.com/notify with the given payload whenever the task is executed. 46 | 47 | == Configuration 48 | 49 | The HTTP Plugin is a thin Gradle configuration layer on top of the https://http-builder-ng.github.io/http-builder-ng/[HttpBuilder-NG] library, which 50 | means that most of the configuration delegates to classes defined in that project. You will want to familiarize yourself with the 51 | https://http-builder-ng.github.io/http-builder-ng/asciidoc/html5/#_configuration[HttpBuilder configuration objects]. 52 | 53 | === `http` Extension 54 | 55 | The plugin provides an `http` extension block used to configure the global default client configuration and the client library to be used. 56 | 57 | The *`library`* property accepts either an instance of the `HttpLibrary` enum (`CORE`, `APACHE`, or `OKHTTP`) or the case-insensitive string version of 58 | the enum name to configure which underlying HTTP client library is used to make the requests. The `CORE` library is used by default if not explicitly 59 | specified. 60 | 61 | The *`config(Closure)`* and *`config(Consumer)`* methods accept a `Closure` or `Consumer` which will be passed into 62 | the `HttpBuilder::configure` method if no specific configuration is applied in the task itself (via the `config` method on the task). 63 | 64 | The documentation for the `HttpObjectConfig` configuration interface is available in either the 65 | https://http-builder-ng.github.io/http-builder-ng/docs/javadoc/groovyx/net/http/HttpObjectConfig.html[JavaDocs] or 66 | https://http-builder-ng.github.io/http-builder-ng/asciidoc/html5/[User Guide] for the HttpBuilder-NG project. 67 | 68 | An example configuration would be similar to the following: 69 | 70 | [source,groovy] 71 | ---- 72 | http { 73 | library = 'apache' 74 | config { 75 | request.uri = 'http://localhost:1234' 76 | } 77 | } 78 | ---- 79 | 80 | === `HttpTask` 81 | 82 | The primary purpose of this plugin is to allow the simple creation of Gradle tasks to make HTTP requests. This is done by creating a task of type 83 | `io.github.httpbuilderng.http.HttpTask` and providing the desired configuration, such as: 84 | 85 | [source,groovy] 86 | ---- 87 | task hook(type:HttpTask){ 88 | config { 89 | request.uri = 'http://localhost:9876' 90 | request.body = [id:42] 91 | } 92 | post { 93 | request.uri.path = '/build-hook' 94 | response.success { 95 | lifecycle.info 'The external build hook was executed.' 96 | } 97 | } 98 | } 99 | ---- 100 | 101 | which would submit a POST to the http://localhost:9876/build-hook URL whenever the task is executed. 102 | 103 | There are six supported HTTP request methods: `GET`, `HEAD`, `POST`, `PUT`, `DELETE`, and `PATCH`. The `OPTIONS` and `TRACE` methods are intentionally 104 | omitted since they do not seem useful in this context (if you find them useful, please submit an issue to add support for them). The request method DSL 105 | is based directly on the https://http-builder-ng.github.io/http-builder-ng/docs/javadoc/groovyx/net/http/HttpConfig.html[HttpConfig] interface from 106 | the HttpBuilder-NG library and there is a configuration block in the task DSL for each supported method type. All configuration is provided as either 107 | a Groovy `Closure` or a `Consumer`. Lastly, each request method has an asynchronous version denoted by the request method name suffixed by 108 | `Async` (e.g. `postAsync`) similar to the interface provided by `HttpBuilder`. 109 | 110 | Each task can make one or more HTTP requests. Additional requests are configured and executed sequentially (unless async). An example of the above 111 | configuration with multiple requests would be: 112 | 113 | [source,groovy] 114 | ---- 115 | task hook(type:HttpTask){ 116 | config { 117 | request.uri = 'http://localhost:9876' 118 | request.body = [id:42] 119 | } 120 | post { 121 | request.uri.path = '/build-hook' 122 | response.success { 123 | lifecycle.info 'The external build hook was executed.' 124 | } 125 | } 126 | post { 127 | request.uri.path = '/notify' 128 | response.success { 129 | lifecyclt.info 'Notification sent.' 130 | } 131 | } 132 | } 133 | ---- 134 | 135 | The response values of the requests are not used or stored. If any interaction on response values is desired, the HttpBuilder 136 | https://http-builder-ng.github.io/http-builder-ng/asciidoc/html5/#_response[response handlers] must be used to perform the desired actions. 137 | 138 | TIP: See the https://docs.gradle.org/current/userguide/more_about_tasks.html[More About Tasks] section of the Gradle User Guide for more information about creating tasks. 139 | 140 | == License 141 | 142 | The Gradle HTTP Plugin is licensed under the http://www.apache.org/licenses/LICENSE-2.0[Apache 2] open source license. 143 | 144 | ---- 145 | Copyright 2017 HttpBuilder-NG Project 146 | 147 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 148 | 149 | http://www.apache.org/licenses/LICENSE-2.0 150 | 151 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 152 | ---- -------------------------------------------------------------------------------- /src/test/groovy/io/github/httpbuilderng/http/HttpTaskHeadSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 HttpBuilder-NG Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.httpbuilderng.http 17 | 18 | import com.stehno.ersatz.ErsatzServer 19 | import com.stehno.gradle.testing.GradleBuild 20 | import org.gradle.testkit.runner.BuildResult 21 | import org.junit.Rule 22 | import spock.lang.AutoCleanup 23 | import spock.lang.Specification 24 | import spock.lang.Unroll 25 | 26 | import static GradleBuild.textContainsLines 27 | import static GradleBuild.totalSuccess 28 | import static com.stehno.ersatz.ContentType.TEXT_PLAIN 29 | 30 | class HttpTaskHeadSpec extends Specification { 31 | 32 | @Rule GradleBuild gradle = new GradleBuild( 33 | template: ''' 34 | plugins { 35 | id 'io.github.http-builder-ng.http-plugin' 36 | } 37 | repositories { 38 | jcenter() 39 | } 40 | 41 | import groovyx.net.http.HttpConfig 42 | import java.util.function.Consumer 43 | 44 | ${config.globalConfig ?: ''} 45 | 46 | task makeRequest(type:io.github.httpbuilderng.http.HttpTask){ 47 | ${config.taskConfig ?: ''} 48 | } 49 | ''' 50 | ) 51 | 52 | @AutoCleanup(value = 'stop') private ErsatzServer ersatz = new ErsatzServer() 53 | 54 | def 'single HEAD request'() { 55 | setup: 56 | ersatz.expectations { 57 | head('/notify').called(1).responds().code(200) 58 | } 59 | 60 | gradle.buildFile(taskConfig: """ 61 | config { 62 | request.uri = '${ersatz.httpUrl}' 63 | } 64 | head { 65 | request.uri.path = '/notify' 66 | response.success { 67 | println 'I have arrived!' 68 | } 69 | } 70 | """) 71 | 72 | when: 73 | BuildResult result = gradle.runner('makeRequest').build() 74 | 75 | then: 76 | totalSuccess result 77 | 78 | and: 79 | textContainsLines result.output, ['I have arrived!'] 80 | 81 | and: 82 | ersatz.verify() 83 | } 84 | 85 | def 'multiple HEAD requests'() { 86 | setup: 87 | ersatz.expectations { 88 | head('/notify').called(1).responds().code(200) 89 | head('/other').called(1).responds().code(200) 90 | head('/third').called(1).responds().code(200) 91 | } 92 | 93 | gradle.buildFile(taskConfig: """ 94 | config { 95 | request.uri = '${ersatz.httpUrl}' 96 | } 97 | headAsync { 98 | request.uri.path = '/third' 99 | response.success { 100 | println 'I have arrived A!' 101 | } 102 | } 103 | head { 104 | request.uri.path = '/notify' 105 | response.success { 106 | println 'I have arrived B!' 107 | } 108 | } 109 | head { 110 | request.uri.path = '/other' 111 | response.success { 112 | println 'I have arrived C!' 113 | } 114 | } 115 | """) 116 | 117 | when: 118 | BuildResult result = gradle.runner('makeRequest').build() 119 | 120 | then: 121 | totalSuccess result 122 | 123 | and: 124 | textContainsLines result.output, ['I have arrived A!', 'I have arrived B!', 'I have arrived C!'] 125 | 126 | and: 127 | ersatz.verify() 128 | } 129 | 130 | def 'multiple HEAD requests (consumer)'() { 131 | setup: 132 | ersatz.expectations { 133 | head('/notify').called(2).responds().code(200) 134 | } 135 | 136 | gradle.buildFile(taskConfig: """ 137 | config { 138 | request.uri = '${ersatz.httpUrl}' 139 | response.success { 140 | println 'I have arrived!' 141 | } 142 | } 143 | headAsync(new Consumer() { 144 | @Override void accept(HttpConfig cfg) { 145 | cfg.request.uri.path = '/notify' 146 | } 147 | }) 148 | head(new Consumer() { 149 | @Override void accept(HttpConfig cfg) { 150 | cfg.request.uri.path = '/notify' 151 | } 152 | }) 153 | """) 154 | 155 | when: 156 | BuildResult result = gradle.runner('makeRequest').build() 157 | 158 | then: 159 | totalSuccess result 160 | 161 | and: 162 | textContainsLines result.output, ['I have arrived!'] 163 | 164 | and: 165 | ersatz.verify() 166 | } 167 | 168 | @Unroll 'single HEAD request (external config with #library)'() { 169 | setup: 170 | ersatz.expectations { 171 | head('/notify').called(1).responds().code(200) 172 | } 173 | 174 | gradle.buildFile( 175 | globalConfig: """ 176 | http { 177 | library = io.github.httpbuilderng.http.HttpLibrary.$library 178 | config { 179 | request.uri = '${ersatz.httpUrl}' 180 | } 181 | } 182 | """, 183 | taskConfig: """ 184 | head { 185 | request.uri.path = '/notify' 186 | response.success { 187 | println 'I have arrived!' 188 | } 189 | } 190 | """) 191 | 192 | when: 193 | BuildResult result = gradle.runner('makeRequest').build() 194 | 195 | then: 196 | totalSuccess result 197 | 198 | and: 199 | textContainsLines result.output, ['I have arrived!'] 200 | 201 | and: 202 | ersatz.verify() 203 | 204 | where: 205 | library << HttpLibrary.values()*.name() 206 | } 207 | 208 | @Unroll 'single HEAD request (external config with #library as string)'() { 209 | setup: 210 | ersatz.expectations { 211 | head('/notify').called(1).responds().code(200) 212 | } 213 | 214 | gradle.buildFile( 215 | globalConfig: """ 216 | http { 217 | library = '$library' 218 | config { 219 | request.uri = '${ersatz.httpUrl}' 220 | } 221 | } 222 | """, 223 | taskConfig: """ 224 | head { 225 | request.uri.path = '/notify' 226 | response.success { 227 | println 'I have arrived!' 228 | } 229 | } 230 | """) 231 | 232 | when: 233 | BuildResult result = gradle.runner('makeRequest').build() 234 | 235 | then: 236 | totalSuccess result 237 | 238 | and: 239 | textContainsLines result.output, ['I have arrived!'] 240 | 241 | and: 242 | ersatz.verify() 243 | 244 | where: 245 | library << HttpLibrary.values()*.name()*.toLowerCase() 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/main/groovy/io/github/httpbuilderng/http/HttpTask.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 HttpBuilder-NG Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.httpbuilderng.http 17 | 18 | import groovy.transform.CompileStatic 19 | import groovy.transform.TupleConstructor 20 | import groovyx.net.http.* 21 | import org.gradle.api.DefaultTask 22 | import org.gradle.api.tasks.Input 23 | import org.gradle.api.tasks.TaskAction 24 | 25 | import java.util.function.Consumer 26 | 27 | import static groovy.transform.TypeCheckingMode.SKIP 28 | 29 | /** 30 | * Gradle Task used to allow configuration and execution of HTTP requests using the HttpBuilder-NG library. 31 | * 32 | * The return values of the HTTP calls are not used in any manner. In order to process and handle responses, the 33 | * Response Handlers should be used. 34 | * 35 | * All HTTP request methods supported by HttpBuilder-NG are supported, except OPTIONS and TRACE. 36 | */ 37 | @CompileStatic @SuppressWarnings('GroovyUnusedDeclaration') 38 | class HttpTask extends DefaultTask { 39 | 40 | private Object config 41 | private final List requests = [] 42 | 43 | /** 44 | * Used to provide the client configuration for the task. This is required if the global configuration is not specified in the http 45 | * extension configuration. 46 | * 47 | * The configuration itself is based on the HttpObjectConfig from HttpBuilder-NG. 48 | * 49 | * @param closure the configuration closure 50 | */ 51 | @Input void config(@DelegatesTo(HttpObjectConfig) final Closure closure) { 52 | config = closure 53 | } 54 | 55 | /** 56 | * Used to provide the client configuration for the task. This is required if the global configuration is not specified in the http 57 | * extension configuration. 58 | * 59 | * The configuration itself is based on the HttpObjectConfig from HttpBuilder-NG. 60 | * 61 | * @param consumer the configuration consumer 62 | */ 63 | @Input void config(final Consumer consumer) { 64 | config = consumer 65 | } 66 | 67 | @Input void get(@DelegatesTo(HttpConfig) final Closure conf) { 68 | addRequestConfig 'get', conf 69 | } 70 | 71 | @Input void getAsync(@DelegatesTo(HttpConfig) final Closure conf) { 72 | addRequestConfig 'getAsync', conf 73 | } 74 | 75 | @Input void get(final Consumer conf) { 76 | addRequestConfig 'get', conf 77 | } 78 | 79 | @Input void getAsync(final Consumer conf) { 80 | addRequestConfig 'getAsync', conf 81 | } 82 | 83 | @Input void head(@DelegatesTo(HttpConfig) final Closure conf) { 84 | addRequestConfig 'head', conf 85 | } 86 | 87 | @Input void headAsync(@DelegatesTo(HttpConfig) final Closure conf) { 88 | addRequestConfig 'headAsync', conf 89 | } 90 | 91 | @Input void head(final Consumer conf) { 92 | addRequestConfig 'head', conf 93 | } 94 | 95 | @Input void headAsync(final Consumer conf) { 96 | addRequestConfig 'headAsync', conf 97 | } 98 | 99 | @Input void put(@DelegatesTo(HttpConfig) final Closure conf) { 100 | addRequestConfig 'put', conf 101 | } 102 | 103 | @Input void putAsync(@DelegatesTo(HttpConfig) final Closure conf) { 104 | addRequestConfig 'putAsync', conf 105 | } 106 | 107 | @Input void put(final Consumer conf) { 108 | addRequestConfig 'put', conf 109 | } 110 | 111 | @Input void putAsync(final Consumer conf) { 112 | addRequestConfig 'putAsync', conf 113 | } 114 | 115 | @Input void post(@DelegatesTo(HttpConfig) final Closure conf) { 116 | addRequestConfig 'post', conf 117 | } 118 | 119 | @Input void postAsync(@DelegatesTo(HttpConfig) final Closure conf) { 120 | addRequestConfig 'postAsync', conf 121 | } 122 | 123 | @Input void post(final Consumer conf) { 124 | addRequestConfig 'post', conf 125 | } 126 | 127 | @Input void postAsync(final Consumer conf) { 128 | addRequestConfig 'postAsync', conf 129 | } 130 | 131 | @Input void delete(@DelegatesTo(HttpConfig) final Closure conf) { 132 | addRequestConfig 'delete', conf 133 | } 134 | 135 | @Input void deleteAsync(@DelegatesTo(HttpConfig) final Closure conf) { 136 | addRequestConfig 'deleteAsync', conf 137 | } 138 | 139 | @Input void delete(final Consumer conf) { 140 | addRequestConfig 'delete', conf 141 | } 142 | 143 | @Input void deleteAsync(final Consumer conf) { 144 | addRequestConfig 'deleteAsync', conf 145 | } 146 | 147 | @Input void patch(@DelegatesTo(HttpConfig) final Closure conf) { 148 | addRequestConfig 'patch', conf 149 | } 150 | 151 | @Input void patchAsync(@DelegatesTo(HttpConfig) final Closure conf) { 152 | addRequestConfig 'patchAsync', conf 153 | } 154 | 155 | @Input void patch(final Consumer conf) { 156 | addRequestConfig 'patch', conf 157 | } 158 | 159 | @Input void patchAsync(final Consumer conf) { 160 | addRequestConfig 'patchAsync', conf 161 | } 162 | 163 | private void addRequestConfig(final String method, final Object conf) { 164 | requests << new RequestConfig(method, conf) 165 | } 166 | 167 | @TaskAction void http() { 168 | HttpExtension extension = project.extensions.findByType(HttpExtension) 169 | HttpBuilder builder = resolveHttpBuilder(extension) 170 | 171 | if (!requests) { 172 | throw new IllegalArgumentException('There are no requests configured.') 173 | } 174 | 175 | requests.each { RequestConfig rc -> 176 | executeRequest(builder, rc) 177 | } 178 | } 179 | 180 | @CompileStatic(SKIP) 181 | private void executeRequest(final HttpBuilder builder, final RequestConfig rc) { 182 | builder."${rc.method}"(rc.config instanceof Closure ? rc.config as Closure : rc.config as Consumer) 183 | } 184 | 185 | @CompileStatic(SKIP) 186 | private HttpBuilder resolveHttpBuilder(final HttpExtension extension) { 187 | switch (extension.library) { 188 | case HttpLibrary.CORE: 189 | return JavaHttpBuilder.configure(resolveConfig(extension)) 190 | case HttpLibrary.APACHE: 191 | return ApacheHttpBuilder.configure(resolveConfig(extension)) 192 | case HttpLibrary.OKHTTP: 193 | return OkHttpBuilder.configure(resolveConfig(extension)) 194 | default: 195 | throw new IllegalArgumentException("HttpLibrary (${extension.library}) is not supported.") 196 | } 197 | } 198 | 199 | private Object resolveConfig(final HttpExtension extension) { 200 | Object configObject = config ?: extension.config 201 | if (configObject) { 202 | return configObject 203 | } else { 204 | throw new IllegalArgumentException('A configuration closure or consumer must be provided either globally or by the task configuration.') 205 | } 206 | } 207 | 208 | @TupleConstructor 209 | private static class RequestConfig { 210 | 211 | final String method 212 | final Object config 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/test/groovy/io/github/httpbuilderng/http/HttpTaskGetSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 HttpBuilder-NG Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.httpbuilderng.http 17 | 18 | import com.stehno.ersatz.ErsatzServer 19 | import com.stehno.gradle.testing.GradleBuild 20 | import org.gradle.testkit.runner.BuildResult 21 | import org.junit.Rule 22 | import spock.lang.AutoCleanup 23 | import spock.lang.Specification 24 | import spock.lang.Unroll 25 | 26 | import static GradleBuild.textContainsLines 27 | import static GradleBuild.totalSuccess 28 | import static com.stehno.ersatz.ContentType.TEXT_PLAIN 29 | 30 | class HttpTaskGetSpec extends Specification { 31 | 32 | @Rule GradleBuild gradle = new GradleBuild( 33 | template: ''' 34 | plugins { 35 | id 'io.github.http-builder-ng.http-plugin' 36 | } 37 | repositories { 38 | jcenter() 39 | } 40 | 41 | import groovyx.net.http.HttpConfig 42 | import java.util.function.Consumer 43 | 44 | ${config.globalConfig ?: ''} 45 | 46 | task makeRequest(type:io.github.httpbuilderng.http.HttpTask){ 47 | ${config.taskConfig ?: ''} 48 | } 49 | ''' 50 | ) 51 | 52 | @AutoCleanup(value = 'stop') private ErsatzServer ersatz = new ErsatzServer() 53 | 54 | def 'single GET request'() { 55 | setup: 56 | ersatz.expectations { 57 | get('/notify').called(1).responder { 58 | content 'ok', TEXT_PLAIN 59 | } 60 | } 61 | 62 | gradle.buildFile(taskConfig: """ 63 | config { 64 | request.uri = '${ersatz.httpUrl}' 65 | } 66 | get { 67 | request.uri.path = '/notify' 68 | response.success { fs, obj -> 69 | println 'I received: ' + obj 70 | } 71 | } 72 | """) 73 | 74 | when: 75 | BuildResult result = gradle.runner('makeRequest').build() 76 | 77 | then: 78 | totalSuccess result 79 | 80 | and: 81 | textContainsLines result.output, ['I received: ok'] 82 | 83 | and: 84 | ersatz.verify() 85 | } 86 | 87 | def 'multiple GET requests'() { 88 | setup: 89 | ersatz.expectations { 90 | get('/notify').called(1).responder { 91 | content 'ok', TEXT_PLAIN 92 | } 93 | get('/other').called(1).responder { 94 | content 'good', TEXT_PLAIN 95 | } 96 | get('/third').called(1).responder { 97 | content 'bueno', TEXT_PLAIN 98 | } 99 | } 100 | 101 | gradle.buildFile(taskConfig: """ 102 | config { 103 | request.uri = '${ersatz.httpUrl}' 104 | response.success { fs, obj -> 105 | println 'I received: ' + obj 106 | } 107 | } 108 | getAsync { 109 | request.uri.path = '/third' 110 | } 111 | get { 112 | request.uri.path = '/notify' 113 | } 114 | get { 115 | request.uri.path = '/other' 116 | } 117 | """) 118 | 119 | when: 120 | BuildResult result = gradle.runner('makeRequest').build() 121 | 122 | then: 123 | totalSuccess result 124 | 125 | and: 126 | textContainsLines result.output, ['I received: ok', 'I received: good', 'I received: bueno'] 127 | 128 | and: 129 | ersatz.verify() 130 | } 131 | 132 | def 'multiple GET requests (consumer)'() { 133 | setup: 134 | ersatz.expectations { 135 | get('/notify').called(2).responder { 136 | content 'ok', TEXT_PLAIN 137 | } 138 | } 139 | 140 | gradle.buildFile(taskConfig: """ 141 | config { 142 | request.uri = '${ersatz.httpUrl}' 143 | response.success { fs, obj -> 144 | println 'I received: ' + obj 145 | } 146 | } 147 | getAsync(new Consumer() { 148 | @Override void accept(HttpConfig cfg) { 149 | cfg.request.uri.path = '/notify' 150 | } 151 | }) 152 | get(new Consumer() { 153 | @Override void accept(HttpConfig cfg) { 154 | cfg.request.uri.path = '/notify' 155 | } 156 | }) 157 | """) 158 | 159 | when: 160 | BuildResult result = gradle.runner('makeRequest').build() 161 | 162 | then: 163 | totalSuccess result 164 | 165 | and: 166 | textContainsLines result.output, ['I received: ok'] 167 | 168 | and: 169 | ersatz.verify() 170 | } 171 | 172 | @Unroll 'single GET request (external config with #library)'() { 173 | setup: 174 | ersatz.expectations { 175 | get('/notify').called(1).responder { 176 | content 'ok', TEXT_PLAIN 177 | } 178 | } 179 | 180 | gradle.buildFile( 181 | globalConfig: """ 182 | http { 183 | library = io.github.httpbuilderng.http.HttpLibrary.$library 184 | config { 185 | request.uri = '${ersatz.httpUrl}' 186 | } 187 | } 188 | """, 189 | taskConfig: """ 190 | get { 191 | request.uri.path = '/notify' 192 | response.success { fs, obj -> 193 | println 'I received: ' + obj 194 | } 195 | } 196 | """) 197 | 198 | when: 199 | BuildResult result = gradle.runner('makeRequest').build() 200 | 201 | then: 202 | totalSuccess result 203 | 204 | and: 205 | textContainsLines result.output, ['I received: ok'] 206 | 207 | and: 208 | ersatz.verify() 209 | 210 | where: 211 | library << HttpLibrary.values()*.name() 212 | } 213 | 214 | @Unroll 'single GET request (external config with #library as string)'() { 215 | setup: 216 | ersatz.expectations { 217 | get('/notify').called(1).responder { 218 | content 'ok', TEXT_PLAIN 219 | } 220 | } 221 | 222 | gradle.buildFile( 223 | globalConfig: """ 224 | http { 225 | library = '$library' 226 | config { 227 | request.uri = '${ersatz.httpUrl}' 228 | } 229 | } 230 | """, 231 | taskConfig: """ 232 | get { 233 | request.uri.path = '/notify' 234 | response.success { fs, obj -> 235 | println 'I received: ' + obj 236 | } 237 | } 238 | """) 239 | 240 | when: 241 | BuildResult result = gradle.runner('makeRequest').build() 242 | 243 | then: 244 | totalSuccess result 245 | 246 | and: 247 | textContainsLines result.output, ['I received: ok'] 248 | 249 | and: 250 | ersatz.verify() 251 | 252 | where: 253 | library << HttpLibrary.values()*.name()*.toLowerCase() 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/test/groovy/io/github/httpbuilderng/http/HttpTaskPatchSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 HttpBuilder-NG Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.httpbuilderng.http 17 | 18 | import com.stehno.ersatz.Decoders 19 | import com.stehno.ersatz.ErsatzServer 20 | import com.stehno.gradle.testing.GradleBuild 21 | import org.gradle.testkit.runner.BuildResult 22 | import org.junit.Rule 23 | import spock.lang.AutoCleanup 24 | import spock.lang.Specification 25 | import spock.lang.Unroll 26 | 27 | import static GradleBuild.textContainsLines 28 | import static GradleBuild.totalSuccess 29 | import static com.stehno.ersatz.ContentType.APPLICATION_JSON 30 | 31 | class HttpTaskPatchSpec extends Specification { 32 | 33 | @Rule GradleBuild gradle = new GradleBuild( 34 | template: ''' 35 | plugins { 36 | id 'io.github.http-builder-ng.http-plugin' 37 | } 38 | repositories { 39 | jcenter() 40 | } 41 | 42 | import groovyx.net.http.HttpConfig 43 | import java.util.function.Consumer 44 | 45 | ${config.globalConfig ?: ''} 46 | 47 | task makeRequest(type:io.github.httpbuilderng.http.HttpTask){ 48 | ${config.taskConfig ?: ''} 49 | } 50 | ''' 51 | ) 52 | 53 | @AutoCleanup(value = 'stop') private ErsatzServer ersatz = new ErsatzServer({ 54 | decoder APPLICATION_JSON, Decoders.parseJson 55 | }) 56 | 57 | def 'single PATCH request'() { 58 | setup: 59 | ersatz.expectations { 60 | patch('/notify') { 61 | called 1 62 | body APPLICATION_JSON, id: 42 63 | responds().code(200) 64 | } 65 | } 66 | 67 | gradle.buildFile( 68 | globalConfig: """ 69 | http { 70 | library = 'apache' 71 | } 72 | """, 73 | taskConfig: """ 74 | config { 75 | request.uri = '${ersatz.httpUrl}' 76 | } 77 | patch { 78 | request.uri.path = '/notify' 79 | request.body = [id:42] 80 | request.contentType = 'application/json' 81 | response.when(200){ 82 | println 'I succeeded' 83 | } 84 | } 85 | """) 86 | 87 | when: 88 | BuildResult result = gradle.runner('makeRequest').build() 89 | 90 | then: 91 | totalSuccess result 92 | 93 | and: 94 | textContainsLines result.output, ['I succeeded'] 95 | 96 | and: 97 | ersatz.verify() 98 | } 99 | 100 | def 'multiple PATCH requests'() { 101 | setup: 102 | ersatz.expectations { 103 | patch('/multiple') { 104 | called 3 105 | body APPLICATION_JSON, id: 42 106 | responds().code(200) 107 | } 108 | } 109 | 110 | gradle.buildFile( 111 | globalConfig: """ 112 | http { 113 | library = 'okhttp' 114 | } 115 | """, 116 | taskConfig: """ 117 | config { 118 | request.uri = '${ersatz.httpUrl}' 119 | response.success { 120 | println 'I succeeded' 121 | } 122 | } 123 | patchAsync { 124 | request.uri.path = '/multiple' 125 | request.body = [id:42] 126 | request.contentType = 'application/json' 127 | } 128 | patch { 129 | request.uri.path = '/multiple' 130 | request.body = [id:42] 131 | request.contentType = 'application/json' 132 | } 133 | patch { 134 | request.uri.path = '/multiple' 135 | request.body = [id:42] 136 | request.contentType = 'application/json' 137 | } 138 | """) 139 | 140 | when: 141 | BuildResult result = gradle.runner('makeRequest').build() 142 | 143 | then: 144 | totalSuccess result 145 | 146 | and: 147 | textContainsLines result.output, ['I succeeded'] 148 | 149 | and: 150 | ersatz.verify() 151 | } 152 | 153 | def 'multiple PATCH requests (consumer)'() { 154 | setup: 155 | ersatz.expectations { 156 | patch('/multiple') { 157 | called 2 158 | body APPLICATION_JSON, id: 42 159 | responds().code(200) 160 | } 161 | } 162 | 163 | gradle.buildFile( 164 | globalConfig: """ 165 | http { 166 | library = 'okhttp' 167 | } 168 | """, 169 | taskConfig: """ 170 | config { 171 | request.uri = '${ersatz.httpUrl}' 172 | response.success { 173 | println 'I succeeded' 174 | } 175 | } 176 | patchAsync(new Consumer() { 177 | @Override void accept(HttpConfig cfg) { 178 | cfg.request.uri.path = '/multiple' 179 | cfg.request.body = [id:42] 180 | cfg.request.contentType = 'application/json' 181 | } 182 | }) 183 | patch(new Consumer() { 184 | @Override void accept(HttpConfig cfg) { 185 | cfg.request.uri.path = '/multiple' 186 | cfg.request.body = [id:42] 187 | cfg.request.contentType = 'application/json' 188 | } 189 | }) 190 | """) 191 | 192 | when: 193 | BuildResult result = gradle.runner('makeRequest').build() 194 | 195 | then: 196 | totalSuccess result 197 | 198 | and: 199 | textContainsLines result.output, ['I succeeded'] 200 | 201 | and: 202 | ersatz.verify() 203 | } 204 | 205 | @Unroll 'single PATCH request (external config with #library)'() { 206 | setup: 207 | ersatz.expectations { 208 | patch('/something') { 209 | called 1 210 | body APPLICATION_JSON, id: 42 211 | responds().code(200) 212 | } 213 | } 214 | 215 | gradle.buildFile( 216 | globalConfig: """ 217 | http { 218 | library = $library 219 | config { 220 | request.uri = '${ersatz.httpUrl}' 221 | } 222 | } 223 | """, 224 | taskConfig: """ 225 | patch { 226 | request.uri.path = '/something' 227 | request.body = [id:42] 228 | request.contentType = 'application/json' 229 | response.success { 230 | println 'I succeeded' 231 | } 232 | } 233 | """) 234 | 235 | when: 236 | BuildResult result = gradle.runner('makeRequest').build() 237 | 238 | then: 239 | totalSuccess result 240 | 241 | and: 242 | textContainsLines result.output, ['I succeeded'] 243 | 244 | and: 245 | ersatz.verify() 246 | 247 | where: 248 | // Note: the core library does not support PATCH 249 | library << [ 250 | "io.github.httpbuilderng.http.HttpLibrary.APACHE", 251 | "io.github.httpbuilderng.http.HttpLibrary.OKHTTP", 252 | "'apache'", 253 | "'okhttp'", 254 | ] 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /src/site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Gradle HTTP Plugin 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | 82 | 83 | 84 |
85 | 86 | 87 | 116 |
117 | Fork me on GitHub 121 | 122 |

HTTP Plugin

123 | 124 |

Do you need to make HTTP requests from your build environment? Do you like HttpBuilder-NG? Do you like Gradle? Well then this plugin is for you! It allows the configuration and execution of HTTP requests as Gradle tasks, using HttpBuilder-NG and any 126 | of its supported client implementation libraries. 127 |

128 | 129 |

Features

130 | 131 |

The plugin encapsulates the functionality of the HttpBuilder-NG library and 132 | has access to most of its features, summarized below:

133 | 134 |
    135 |
  • Support for GET, HEAD, POST, PUT, DELETE, and PATCH request methods
  • 136 |
  • Multiple client implementations (core-Java, Apache, OkHttp)
  • 137 |
  • HTTPS support (with ability to ignore SSL issues)
  • 138 |
  • Content compression
  • 139 |
  • Multipart request/response support
  • 140 |
  • BASIC and DIGEST authentication support
  • 141 |
  • Support for configuring client proxies
  • 142 |
  • Allows for multiple request executions in a single task
  • 143 |
  • Provides a means of shared base configuration across multiple tasks
  • 144 |
  • Full support for the HttpBuilder-NG DSL
  • 145 |
146 | 147 |

Installation

148 | 149 |

The Gradle HTTP plugin is available via the Gradle Plugins Repository 150 | (io.github.http-builder-ng.http-plugin). It can 151 | be applied to your build.gradle file with the following:

152 | 153 |
plugins {
154 |     id "io.github.http-builder-ng.http-plugin" version "0.1.1"
155 | }
156 | 157 |

Usage

158 | 159 |

Once you have applied the plugin to your build, you need to create a custom task to use it. The following code would create a task 160 | called "notify", which would send a POST request to http://something.com/notify with the specified body content whenever the task is 161 | executed.

162 | 163 |
task notify(type:HttpTask){
164 |     config {
165 |         request.uri = 'http://something.com'
166 |     }
167 |     post {
168 |         request.uri.path = '/notify'
169 |         request.body = [event: 'activated']
170 |         response.success {
171 |             println 'The event notification was successful'
172 |         }
173 |     }
174 | }
175 | 176 |

The User Guide provides more detailed information about configuring and using the plugin.

177 | 178 |
179 |
180 |
181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /src/test/groovy/io/github/httpbuilderng/http/HttpTaskPutSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 HttpBuilder-NG Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.httpbuilderng.http 17 | 18 | import com.stehno.ersatz.Decoders 19 | import com.stehno.ersatz.ErsatzServer 20 | import com.stehno.gradle.testing.GradleBuild 21 | import org.gradle.testkit.runner.BuildResult 22 | import org.junit.Rule 23 | import spock.lang.AutoCleanup 24 | import spock.lang.Specification 25 | import spock.lang.Unroll 26 | 27 | import static GradleBuild.textContainsLines 28 | import static GradleBuild.totalSuccess 29 | import static com.stehno.ersatz.ContentType.APPLICATION_JSON 30 | import static com.stehno.ersatz.ContentType.TEXT_PLAIN 31 | 32 | class HttpTaskPutSpec extends Specification { 33 | 34 | @Rule GradleBuild gradle = new GradleBuild( 35 | template: ''' 36 | plugins { 37 | id 'io.github.http-builder-ng.http-plugin' 38 | } 39 | repositories { 40 | jcenter() 41 | } 42 | 43 | import groovyx.net.http.HttpConfig 44 | import java.util.function.Consumer 45 | 46 | ${config.globalConfig ?: ''} 47 | 48 | task makeRequest(type:io.github.httpbuilderng.http.HttpTask){ 49 | ${config.taskConfig ?: ''} 50 | } 51 | ''' 52 | ) 53 | 54 | @AutoCleanup(value = 'stop') private ErsatzServer ersatz = new ErsatzServer({ 55 | decoder APPLICATION_JSON, Decoders.parseJson 56 | }) 57 | 58 | def 'single PUT request'() { 59 | setup: 60 | ersatz.expectations { 61 | put('/notify') { 62 | called 1 63 | body APPLICATION_JSON, id: 42 64 | responds().code(200) 65 | } 66 | } 67 | 68 | gradle.buildFile(taskConfig: """ 69 | config { 70 | request.uri = '${ersatz.httpUrl}' 71 | } 72 | put { 73 | request.uri.path = '/notify' 74 | request.body = [id:42] 75 | request.contentType = 'application/json' 76 | response.when(200){ 77 | println 'I succeeded' 78 | } 79 | } 80 | """) 81 | 82 | when: 83 | BuildResult result = gradle.runner('makeRequest').build() 84 | 85 | then: 86 | totalSuccess result 87 | 88 | and: 89 | textContainsLines result.output, ['I succeeded'] 90 | 91 | and: 92 | ersatz.verify() 93 | } 94 | 95 | def 'multiple PUT requests'() { 96 | setup: 97 | ersatz.expectations { 98 | put('/multiple') { 99 | called 3 100 | body APPLICATION_JSON, id: 42 101 | responds().code(200) 102 | } 103 | } 104 | 105 | gradle.buildFile(taskConfig: """ 106 | config { 107 | request.uri = '${ersatz.httpUrl}' 108 | response.success { 109 | println 'I succeeded' 110 | } 111 | } 112 | putAsync { 113 | request.uri.path = '/multiple' 114 | request.body = [id:42] 115 | request.contentType = 'application/json' 116 | } 117 | put { 118 | request.uri.path = '/multiple' 119 | request.body = [id:42] 120 | request.contentType = 'application/json' 121 | } 122 | put { 123 | request.uri.path = '/multiple' 124 | request.body = [id:42] 125 | request.contentType = 'application/json' 126 | } 127 | """) 128 | 129 | when: 130 | BuildResult result = gradle.runner('makeRequest').build() 131 | 132 | then: 133 | totalSuccess result 134 | 135 | and: 136 | textContainsLines result.output, ['I succeeded'] 137 | 138 | and: 139 | ersatz.verify() 140 | } 141 | 142 | def 'multiple PUT requests (consumer)'() { 143 | setup: 144 | ersatz.expectations { 145 | put('/multiple') { 146 | called 2 147 | body APPLICATION_JSON, id: 42 148 | responds().code(200) 149 | } 150 | } 151 | 152 | gradle.buildFile(taskConfig: """ 153 | config { 154 | request.uri = '${ersatz.httpUrl}' 155 | response.success { 156 | println 'I succeeded' 157 | } 158 | } 159 | putAsync(new Consumer() { 160 | @Override void accept(HttpConfig cfg) { 161 | cfg.request.uri.path = '/multiple' 162 | cfg.request.body = [id:42] 163 | cfg.request.contentType = 'application/json' 164 | } 165 | }) 166 | put(new Consumer() { 167 | @Override void accept(HttpConfig cfg) { 168 | cfg.request.uri.path = '/multiple' 169 | cfg.request.body = [id:42] 170 | cfg.request.contentType = 'application/json' 171 | } 172 | }) 173 | """) 174 | 175 | when: 176 | BuildResult result = gradle.runner('makeRequest').build() 177 | 178 | then: 179 | totalSuccess result 180 | 181 | and: 182 | textContainsLines result.output, ['I succeeded'] 183 | 184 | and: 185 | ersatz.verify() 186 | } 187 | 188 | @Unroll 'single PUT request (external config with #library)'() { 189 | setup: 190 | ersatz.expectations { 191 | put('/something') { 192 | called 1 193 | body APPLICATION_JSON, id: 42 194 | responds().code(200) 195 | } 196 | } 197 | 198 | gradle.buildFile( 199 | globalConfig: """ 200 | http { 201 | library = io.github.httpbuilderng.http.HttpLibrary.$library 202 | config { 203 | request.uri = '${ersatz.httpUrl}' 204 | } 205 | } 206 | """, 207 | taskConfig: """ 208 | put { 209 | request.uri.path = '/something' 210 | request.body = [id:42] 211 | request.contentType = 'application/json' 212 | response.success { 213 | println 'I succeeded' 214 | } 215 | } 216 | """) 217 | 218 | when: 219 | BuildResult result = gradle.runner('makeRequest').build() 220 | 221 | then: 222 | totalSuccess result 223 | 224 | and: 225 | textContainsLines result.output, ['I succeeded'] 226 | 227 | and: 228 | ersatz.verify() 229 | 230 | where: 231 | library << HttpLibrary.values()*.name() 232 | } 233 | 234 | @Unroll 'single PUT request (external config with #library as string)'() { 235 | setup: 236 | ersatz.expectations { 237 | put('/something') { 238 | called 1 239 | body APPLICATION_JSON, id: 42 240 | responds().code(200) 241 | } 242 | } 243 | 244 | gradle.buildFile( 245 | globalConfig: """ 246 | http { 247 | library = '$library' 248 | config { 249 | request.uri = '${ersatz.httpUrl}' 250 | } 251 | } 252 | """, 253 | taskConfig: """ 254 | put { 255 | request.uri.path = '/something' 256 | request.body = [id:42] 257 | request.contentType = 'application/json' 258 | response.success { 259 | println 'I succeeded' 260 | } 261 | } 262 | """) 263 | 264 | when: 265 | BuildResult result = gradle.runner('makeRequest').build() 266 | 267 | then: 268 | totalSuccess result 269 | 270 | and: 271 | textContainsLines result.output, ['I succeeded'] 272 | 273 | and: 274 | ersatz.verify() 275 | 276 | where: 277 | library << HttpLibrary.values()*.name()*.toLowerCase() 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /src/test/groovy/io/github/httpbuilderng/http/HttpTaskPostSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 HttpBuilder-NG Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.httpbuilderng.http 17 | 18 | import com.stehno.ersatz.Decoders 19 | import com.stehno.ersatz.ErsatzServer 20 | import com.stehno.gradle.testing.GradleBuild 21 | import org.gradle.testkit.runner.BuildResult 22 | import org.junit.Rule 23 | import spock.lang.AutoCleanup 24 | import spock.lang.Specification 25 | import spock.lang.Unroll 26 | 27 | import static GradleBuild.textContainsLines 28 | import static GradleBuild.totalSuccess 29 | import static com.stehno.ersatz.ContentType.APPLICATION_JSON 30 | import static com.stehno.ersatz.ContentType.TEXT_PLAIN 31 | 32 | class HttpTaskPostSpec extends Specification { 33 | 34 | @Rule GradleBuild gradle = new GradleBuild( 35 | template: ''' 36 | plugins { 37 | id 'io.github.http-builder-ng.http-plugin' 38 | } 39 | repositories { 40 | jcenter() 41 | } 42 | 43 | import groovyx.net.http.HttpConfig 44 | import java.util.function.Consumer 45 | 46 | ${config.globalConfig ?: ''} 47 | 48 | task makeRequest(type:io.github.httpbuilderng.http.HttpTask){ 49 | ${config.taskConfig ?: ''} 50 | } 51 | ''' 52 | ) 53 | 54 | @AutoCleanup(value = 'stop') private ErsatzServer ersatz = new ErsatzServer({ 55 | decoder APPLICATION_JSON, Decoders.parseJson 56 | }) 57 | 58 | def 'single POST request'() { 59 | setup: 60 | ersatz.expectations { 61 | post('/notify') { 62 | called 1 63 | body APPLICATION_JSON, id: 42 64 | responder { 65 | code 200 66 | content 'ok', TEXT_PLAIN 67 | } 68 | } 69 | } 70 | 71 | gradle.buildFile(taskConfig: """ 72 | config { 73 | request.uri = '${ersatz.httpUrl}' 74 | } 75 | post { 76 | request.uri.path = '/notify' 77 | request.body = [id:42] 78 | request.contentType = 'application/json' 79 | response.success { fs, obj -> 80 | println 'I received: ' + obj 81 | } 82 | } 83 | """) 84 | 85 | when: 86 | BuildResult result = gradle.runner('makeRequest').build() 87 | 88 | then: 89 | totalSuccess result 90 | 91 | and: 92 | textContainsLines result.output, ['I received: ok'] 93 | 94 | and: 95 | ersatz.verify() 96 | } 97 | 98 | def 'multiple POST requests'() { 99 | setup: 100 | ersatz.expectations { 101 | post('/multiple') { 102 | called 3 103 | body APPLICATION_JSON, id: 42 104 | responder { 105 | content 'ok', TEXT_PLAIN 106 | } 107 | responder { 108 | content 'good', TEXT_PLAIN 109 | } 110 | responder { 111 | content 'bueno', TEXT_PLAIN 112 | } 113 | } 114 | } 115 | 116 | gradle.buildFile(taskConfig: """ 117 | config { 118 | request.uri = '${ersatz.httpUrl}' 119 | response.success { fs, obj -> 120 | println 'I received: ' + obj 121 | } 122 | } 123 | postAsync { 124 | request.uri.path = '/multiple' 125 | request.body = [id:42] 126 | request.contentType = 'application/json' 127 | } 128 | post { 129 | request.uri.path = '/multiple' 130 | request.body = [id:42] 131 | request.contentType = 'application/json' 132 | } 133 | post { 134 | request.uri.path = '/multiple' 135 | request.body = [id:42] 136 | request.contentType = 'application/json' 137 | } 138 | """) 139 | 140 | when: 141 | BuildResult result = gradle.runner('makeRequest').build() 142 | 143 | then: 144 | totalSuccess result 145 | 146 | and: 147 | textContainsLines result.output, ['I received: ok', 'I received: good', 'I received: bueno'] 148 | 149 | and: 150 | ersatz.verify() 151 | } 152 | 153 | def 'multiple POST requests (consumer)'() { 154 | setup: 155 | ersatz.expectations { 156 | post('/multiple') { 157 | called 2 158 | body APPLICATION_JSON, id: 42 159 | responder { 160 | content 'ok', TEXT_PLAIN 161 | } 162 | responder { 163 | content 'good', TEXT_PLAIN 164 | } 165 | } 166 | } 167 | 168 | gradle.buildFile(taskConfig: """ 169 | config { 170 | request.uri = '${ersatz.httpUrl}' 171 | response.success { fs, obj -> 172 | println 'I received: ' + obj 173 | } 174 | } 175 | postAsync(new Consumer() { 176 | @Override void accept(HttpConfig cfg) { 177 | cfg.request.uri.path = '/multiple' 178 | cfg.request.body = [id:42] 179 | cfg.request.contentType = 'application/json' 180 | } 181 | }) 182 | post(new Consumer() { 183 | @Override void accept(HttpConfig cfg) { 184 | cfg.request.uri.path = '/multiple' 185 | cfg.request.body = [id:42] 186 | cfg.request.contentType = 'application/json' 187 | } 188 | }) 189 | """) 190 | 191 | when: 192 | BuildResult result = gradle.runner('makeRequest').build() 193 | 194 | then: 195 | totalSuccess result 196 | 197 | and: 198 | textContainsLines result.output, ['I received: ok', 'I received: good'] 199 | 200 | and: 201 | ersatz.verify() 202 | } 203 | 204 | @Unroll 'single POST request (external config with #library)'() { 205 | setup: 206 | ersatz.expectations { 207 | post('/something') { 208 | called 1 209 | body APPLICATION_JSON, id: 42 210 | responder { 211 | content 'ok', TEXT_PLAIN 212 | } 213 | } 214 | } 215 | 216 | gradle.buildFile( 217 | globalConfig: """ 218 | http { 219 | library = io.github.httpbuilderng.http.HttpLibrary.$library 220 | config { 221 | request.uri = '${ersatz.httpUrl}' 222 | } 223 | } 224 | """, 225 | taskConfig: """ 226 | post { 227 | request.uri.path = '/something' 228 | request.body = [id:42] 229 | request.contentType = 'application/json' 230 | response.success { fs, obj -> 231 | println 'I received: ' + obj 232 | } 233 | } 234 | """) 235 | 236 | when: 237 | BuildResult result = gradle.runner('makeRequest').build() 238 | 239 | then: 240 | totalSuccess result 241 | 242 | and: 243 | textContainsLines result.output, ['I received: ok'] 244 | 245 | and: 246 | ersatz.verify() 247 | 248 | where: 249 | library << HttpLibrary.values()*.name() 250 | } 251 | 252 | @Unroll 'single POST request (external config with #library as string)'() { 253 | setup: 254 | ersatz.expectations { 255 | post('/something') { 256 | called 1 257 | body APPLICATION_JSON, id: 42 258 | responder { 259 | content 'ok', TEXT_PLAIN 260 | } 261 | } 262 | } 263 | 264 | gradle.buildFile( 265 | globalConfig: """ 266 | http { 267 | library = '$library' 268 | config { 269 | request.uri = '${ersatz.httpUrl}' 270 | } 271 | } 272 | """, 273 | taskConfig: """ 274 | post { 275 | request.uri.path = '/something' 276 | request.body = [id:42] 277 | request.contentType = 'application/json' 278 | response.success { fs, obj -> 279 | println 'I received: ' + obj 280 | } 281 | } 282 | """) 283 | 284 | when: 285 | BuildResult result = gradle.runner('makeRequest').build() 286 | 287 | then: 288 | totalSuccess result 289 | 290 | and: 291 | textContainsLines result.output, ['I received: ok'] 292 | 293 | and: 294 | ersatz.verify() 295 | 296 | where: 297 | library << HttpLibrary.values()*.name()*.toLowerCase() 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/site/js/prettify.js: -------------------------------------------------------------------------------- 1 | !function(){var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; 2 | (function(){function S(a){function d(e){var b=e.charCodeAt(0);if(b!==92)return b;var a=e.charAt(1);return(b=r[a])?b:"0"<=a&&a<="7"?parseInt(e.substring(1),8):a==="u"||a==="x"?parseInt(e.substring(2),16):e.charCodeAt(1)}function g(e){if(e<32)return(e<16?"\\x0":"\\x")+e.toString(16);e=String.fromCharCode(e);return e==="\\"||e==="-"||e==="]"||e==="^"?"\\"+e:e}function b(e){var b=e.substring(1,e.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),e=[],a= 3 | b[0]==="^",c=["["];a&&c.push("^");for(var a=a?1:0,f=b.length;a122||(l<65||h>90||e.push([Math.max(65,h)|32,Math.min(l,90)|32]),l<97||h>122||e.push([Math.max(97,h)&-33,Math.min(l,122)&-33]))}}e.sort(function(e,a){return e[0]-a[0]||a[1]-e[1]});b=[];f=[];for(a=0;ah[0]&&(h[1]+1>h[0]&&c.push("-"),c.push(g(h[1])));c.push("]");return c.join("")}function s(e){for(var a=e.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),c=a.length,d=[],f=0,h=0;f=2&&e==="["?a[f]=b(l):e!=="\\"&&(a[f]=l.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return a.join("")}for(var x=0,m=!1,j=!1,k=0,c=a.length;k=5&&"lang-"===w.substring(0,5))&&!(t&&typeof t[1]==="string"))f=!1,w="src";f||(r[z]=w)}h=c;c+=z.length;if(f){f=t[1];var l=z.indexOf(f),B=l+f.length;t[2]&&(B=z.length-t[2].length,l=B-f.length);w=w.substring(5);H(j+h,z.substring(0,l),g,k);H(j+h+l,f,I(w,f),k);H(j+h+B,z.substring(B),g,k)}else k.push(j+h,w)}a.g=k}var b={},s;(function(){for(var g=a.concat(d),j=[],k={},c=0,i=g.length;c=0;)b[n.charAt(e)]=r;r=r[1];n=""+r;k.hasOwnProperty(n)||(j.push(r),k[n]=q)}j.push(/[\S\s]/);s=S(j)})();var x=d.length;return g}function v(a){var d=[],g=[];a.tripleQuotedStrings?d.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?d.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, 10 | q,"'\"`"]):d.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&g.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var b=a.hashComments;b&&(a.cStyleComments?(b>1?d.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):d.push(["com",/^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),g.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h(?:h|pp|\+\+)?|[a-z]\w*)>/,q])):d.push(["com", 11 | /^#[^\n\r]*/,q,"#"]));a.cStyleComments&&(g.push(["com",/^\/\/[^\n\r]*/,q]),g.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));if(b=a.regexLiterals){var s=(b=b>1?"":"\n\r")?".":"[\\S\\s]";g.push(["lang-regex",RegExp("^(?:^^\\.?|[+-]|[!=]=?=?|\\#|%=?|&&?=?|\\(|\\*=?|[+\\-]=|->|\\/=?|::?|<>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*("+("/(?=[^/*"+b+"])(?:[^/\\x5B\\x5C"+b+"]|\\x5C"+s+"|\\x5B(?:[^\\x5C\\x5D"+b+"]|\\x5C"+ 12 | s+")*(?:\\x5D|$))+/")+")")])}(b=a.types)&&g.push(["typ",b]);b=(""+a.keywords).replace(/^ | $/g,"");b.length&&g.push(["kwd",RegExp("^(?:"+b.replace(/[\s,]+/g,"|")+")\\b"),q]);d.push(["pln",/^\s+/,q," \r\n\t\u00a0"]);b="^.[^\\s\\w.$@'\"`/\\\\]*";a.regexLiterals&&(b+="(?!s*/)");g.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/, 13 | q],["pun",RegExp(b),q]);return C(d,g)}function J(a,d,g){function b(a){var c=a.nodeType;if(c==1&&!x.test(a.className))if("br"===a.nodeName)s(a),a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)b(a);else if((c==3||c==4)&&g){var d=a.nodeValue,i=d.match(m);if(i)c=d.substring(0,i.index),a.nodeValue=c,(d=d.substring(i.index+i[0].length))&&a.parentNode.insertBefore(j.createTextNode(d),a.nextSibling),s(a),c||a.parentNode.removeChild(a)}}function s(a){function b(a,c){var d= 14 | c?a.cloneNode(!1):a,e=a.parentNode;if(e){var e=b(e,1),g=a.nextSibling;e.appendChild(d);for(var i=g;i;i=g)g=i.nextSibling,e.appendChild(i)}return d}for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),d;(d=a.parentNode)&&d.nodeType===1;)a=d;c.push(a)}for(var x=/(?:^|\s)nocode(?:\s|$)/,m=/\r\n?|\n/,j=a.ownerDocument,k=j.createElement("li");a.firstChild;)k.appendChild(a.firstChild);for(var c=[k],i=0;i=0;){var b=d[g];F.hasOwnProperty(b)?D.console&&console.warn("cannot override language handler %s",b):F[b]=a}}function I(a,d){if(!a||!F.hasOwnProperty(a))a=/^\s*=l&&(b+=2);g>=B&&(r+=2)}}finally{if(f)f.style.display=h}}catch(u){D.console&&console.log(u&&u.stack||u)}}var D=window,y=["break,continue,do,else,for,if,return,while"],E=[[y,"auto,case,char,const,default,double,enum,extern,float,goto,inline,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], 18 | "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],M=[E,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,delegate,dynamic_cast,explicit,export,friend,generic,late_check,mutable,namespace,nullptr,property,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],N=[E,"abstract,assert,boolean,byte,extends,final,finally,implements,import,instanceof,interface,null,native,package,strictfp,super,synchronized,throws,transient"], 19 | O=[N,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,internal,into,is,let,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var,virtual,where"],E=[E,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],P=[y,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], 20 | Q=[y,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],W=[y,"as,assert,const,copy,drop,enum,extern,fail,false,fn,impl,let,log,loop,match,mod,move,mut,priv,pub,pure,ref,self,static,struct,true,trait,type,unsafe,use"],y=[y,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],R=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)\b/, 21 | V=/\S/,X=v({keywords:[M,O,E,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",P,Q,y],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),F={};p(X,["default-code"]);p(C([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-", 22 | /^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);p(C([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/], 23 | ["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);p(C([],[["atv",/^[\S\s]+/]]),["uq.val"]);p(v({keywords:M,hashComments:!0,cStyleComments:!0,types:R}),["c","cc","cpp","cxx","cyc","m"]);p(v({keywords:"null,true,false"}),["json"]);p(v({keywords:O,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:R}), 24 | ["cs"]);p(v({keywords:N,cStyleComments:!0}),["java"]);p(v({keywords:y,hashComments:!0,multiLineStrings:!0}),["bash","bsh","csh","sh"]);p(v({keywords:P,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),["cv","py","python"]);p(v({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:2}),["perl","pl","pm"]);p(v({keywords:Q, 25 | hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb","ruby"]);p(v({keywords:E,cStyleComments:!0,regexLiterals:!0}),["javascript","js"]);p(v({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,throw,true,try,unless,until,when,while,yes",hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);p(v({keywords:W,cStyleComments:!0,multilineStrings:!0}),["rc","rs","rust"]); 26 | p(C([],[["str",/^[\S\s]+/]]),["regex"]);var Y=D.PR={createSimpleLexer:C,registerLangHandler:p,sourceDecorator:v,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ",prettyPrintOne:D.prettyPrintOne=function(a,d,g){var b=document.createElement("div");b.innerHTML="
"+a+"
";b=b.firstChild;g&&J(b,g,!0);K({h:d,j:g,c:b,i:1}); 27 | return b.innerHTML},prettyPrint:D.prettyPrint=function(a,d){function g(){for(var b=D.PR_SHOULD_USE_CONTINUATION?c.now()+250:Infinity;ithis.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}if(e.hasClass("active"))return this.sliding=!1;var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});return this.$element.trigger(j),j.isDefaultPrevented()?void 0:(this.sliding=!0,f&&this.pause(),this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid.bs.carousel",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")?(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid.bs.carousel")},0)}).emulateTransitionEnd(1e3*d.css("transition-duration").slice(0,-1))):(d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid.bs.carousel")),f&&this.cycle(),this)};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("collapse in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?void this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);!e&&f.toggle&&"show"==c&&(c=!c),e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(jQuery),+function(a){"use strict";function b(b){a(d).remove(),a(e).each(function(){var d=c(a(this)),e={relatedTarget:this};d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown",e)),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown",e))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(''}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;(e||"destroy"!=c)&&(e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]())})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(a(c).is("body")?window:c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);{var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})}},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);if(g&&b<=e[0])return g!=(a=f[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parentsUntil(this.options.target,".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=this.pinnedOffset=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(b.RESET).addClass("affix");var a=this.$window.scrollTop(),c=this.$element.offset();return this.pinnedOffset=c.top-a},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"top"==this.affixed&&(e.top+=d),"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top(this.$element)),"function"==typeof h&&(h=f.bottom(this.$element));var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;if(this.affixed!==i){this.unpin&&this.$element.css("top","");var j="affix"+(i?"-"+i:""),k=a.Event(j+".bs.affix");this.$element.trigger(k),k.isDefaultPrevented()||(this.affixed=i,this.unpin="bottom"==i?this.getPinnedOffset():null,this.$element.removeClass(b.RESET).addClass(j).trigger(a.Event(j.replace("affix","affixed"))),"bottom"==i&&this.$element.offset({top:c-h-this.$element.height()}))}}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(jQuery); -------------------------------------------------------------------------------- /src/site/css/asciidoctor.css: -------------------------------------------------------------------------------- 1 | @import url(http://cdnjs.cloudflare.com/ajax/libs/font-awesome/3.2.0/css/font-awesome.css); 2 | /* ========================================================================== Embedded content ========================================================================== */ 3 | /** Remove border when inside `a` element in IE 8/9. */ 4 | img { border: 0; } 5 | 6 | /** Correct overflow displayed oddly in IE 9. */ 7 | svg:not(:root) { overflow: hidden; } 8 | 9 | /* ========================================================================== Figures ========================================================================== */ 10 | /** Address margin not present in IE 8/9 and Safari 5. */ 11 | figure { margin: 0; } 12 | 13 | /* ========================================================================== Forms ========================================================================== */ 14 | /** Define consistent border, margin, and padding. */ 15 | fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em; } 16 | 17 | /** 1. Correct `color` not being inherited in IE 8/9. 2. Remove padding so people aren't caught out if they zero out fieldsets. */ 18 | legend { border: 0; /* 1 */ padding: 0; /* 2 */ } 19 | 20 | /** 1. Correct font family not being inherited in all browsers. 2. Correct font size not being inherited in all browsers. 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. */ 21 | button, input, select, textarea { font-family: inherit; /* 1 */ font-size: 100%; /* 2 */ margin: 0; /* 3 */ } 22 | 23 | /** Address Firefox 4+ setting `line-height` on `input` using `!important` in the UA stylesheet. */ 24 | button, input { line-height: normal; } 25 | 26 | /** Address inconsistent `text-transform` inheritance for `button` and `select`. All other form control elements do not inherit `text-transform` values. Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+. Correct `select` style inheritance in Firefox 4+ and Opera. */ 27 | button, select { text-transform: none; } 28 | 29 | /** 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` and `video` controls. 2. Correct inability to style clickable `input` types in iOS. 3. Improve usability and consistency of cursor style between image-type `input` and others. */ 30 | button, html input[type="button"], input[type="reset"], input[type="submit"] { -webkit-appearance: button; /* 2 */ cursor: pointer; /* 3 */ } 31 | 32 | /** Re-set default cursor for disabled elements. */ 33 | button[disabled], html input[disabled] { cursor: default; } 34 | 35 | /** 1. Address box sizing set to `content-box` in IE 8/9. 2. Remove excess padding in IE 8/9. */ 36 | input[type="checkbox"], input[type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ } 37 | 38 | /** 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome (include `-moz` to future-proof). */ 39 | input[type="search"] { -webkit-appearance: textfield; /* 1 */ -moz-box-sizing: content-box; -webkit-box-sizing: content-box; /* 2 */ box-sizing: content-box; } 40 | 41 | /** Remove inner padding and search cancel button in Safari 5 and Chrome on OS X. */ 42 | input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } 43 | 44 | /** Remove inner padding and border in Firefox 4+. */ 45 | button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } 46 | 47 | /** 1. Remove default vertical scrollbar in IE 8/9. 2. Improve readability and alignment in all browsers. */ 48 | textarea { overflow: auto; /* 1 */ vertical-align: top; /* 2 */ } 49 | 50 | /* ========================================================================== Tables ========================================================================== */ 51 | /** Remove most spacing between table cells. */ 52 | table { border-collapse: collapse; border-spacing: 0; } 53 | 54 | *, *:before, *:after { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } 55 | 56 | img, object, embed { max-width: 100%; height: auto; } 57 | 58 | object, embed { height: 100%; } 59 | 60 | img { -ms-interpolation-mode: bicubic; } 61 | 62 | #map_canvas img, #map_canvas embed, #map_canvas object, .map_canvas img, .map_canvas embed, .map_canvas object { max-width: none !important; } 63 | 64 | .left { float: left !important; } 65 | 66 | .right { float: right !important; } 67 | 68 | .text-left { text-align: left !important; } 69 | 70 | .text-right { text-align: right !important; } 71 | 72 | .text-center { text-align: center !important; } 73 | 74 | .text-justify { text-align: justify !important; } 75 | 76 | .hide { display: none; } 77 | 78 | .antialiased, body { -webkit-font-smoothing: antialiased; } 79 | 80 | img { display: inline-block; vertical-align: middle; } 81 | 82 | textarea { height: auto; min-height: 50px; } 83 | 84 | select { width: 100%; } 85 | 86 | p.lead, .paragraph.lead > p, #preamble > .sectionbody > .paragraph:first-of-type p { font-size: 1.21875em; line-height: 1.6; } 87 | 88 | .subheader, #content #toctitle, .admonitionblock td.content > .title, .exampleblock > .title, .imageblock > .title, .videoblock > .title, .listingblock > .title, .literalblock > .title, .openblock > .title, .paragraph > .title, .quoteblock > .title, .sidebarblock > .title, .tableblock > .title, .verseblock > .title, .dlist > .title, .olist > .title, .ulist > .title, .qlist > .title, .hdlist > .title, .tableblock > caption { line-height: 1.4; color: #7a2518; font-weight: 300; margin-top: 0.2em; margin-bottom: 0.5em; } 89 | 90 | /* Lists */ 91 | ul, ol, dl { font-size: 1em; line-height: 1.6; margin-bottom: 1.25em; list-style-position: outside; font-family: inherit; } 92 | 93 | ul, ol { margin-left: 1.5em; } 94 | 95 | /* Unordered Lists */ 96 | ul li ul, ul li ol { margin-left: 1.25em; margin-bottom: 0; font-size: 1em; /* Override nested font-size change */ } 97 | ul.square li ul, ul.circle li ul, ul.disc li ul { list-style: inherit; } 98 | ul.square { list-style-type: square; } 99 | ul.circle { list-style-type: circle; } 100 | ul.disc { list-style-type: disc; } 101 | ul.no-bullet { list-style: none; } 102 | 103 | /* Ordered Lists */ 104 | ol li ul, ol li ol { margin-left: 1.25em; margin-bottom: 0; } 105 | 106 | /* Definition Lists */ 107 | dl dt { margin-bottom: 0.3125em; font-weight: bold; } 108 | dl dd { margin-bottom: 1.25em; } 109 | 110 | /* Abbreviations */ 111 | abbr, acronym { text-transform: uppercase; font-size: 90%; color: #222222; border-bottom: 1px dotted #dddddd; cursor: help; } 112 | 113 | abbr { text-transform: none; } 114 | 115 | /* Blockquotes */ 116 | blockquote { margin: 0 0 1.25em; padding: 0.5625em 1.25em 0 1.1875em; border-left: 1px solid #dddddd; } 117 | blockquote cite { display: block; font-size: inherit; color: #555555; } 118 | blockquote cite:before { content: "\2014 \0020"; } 119 | blockquote cite a, blockquote cite a:visited { color: #555555; } 120 | 121 | blockquote, blockquote p { line-height: 1.6; color: #6f6f6f; } 122 | 123 | /* Microformats */ 124 | .vcard { display: inline-block; margin: 0 0 1.25em 0; border: 1px solid #dddddd; padding: 0.625em 0.75em; } 125 | .vcard li { margin: 0; display: block; } 126 | .vcard .fn { font-weight: bold; font-size: 0.9375em; } 127 | 128 | .vevent .summary { font-weight: bold; } 129 | .vevent abbr { cursor: auto; text-decoration: none; font-weight: bold; border: none; padding: 0 0.0625em; } 130 | 131 | @media only screen and (min-width: 768px) { h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6 { line-height: 1.4; } 132 | h1 { font-size: 2.75em; } 133 | h2 { font-size: 2.3125em; } 134 | h3, #toctitle, .sidebarblock > .content > .title { font-size: 1.6875em; } 135 | h4 { font-size: 1.4375em; } } 136 | /* Print styles. Inlined to avoid required HTTP connection: www.phpied.com/delay-loading-your-print-css/ Credit to Paul Irish and HTML5 Boilerplate (html5boilerplate.com) 137 | */ 138 | .print-only { display: none !important; } 139 | 140 | @media print { * { background: transparent !important; color: #000 !important; /* Black prints faster: h5bp.com/s */ box-shadow: none !important; text-shadow: none !important; } 141 | a, a:visited { text-decoration: underline; } 142 | a[href]:after { content: " (" attr(href) ")"; } 143 | abbr[title]:after { content: " (" attr(title) ")"; } 144 | .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } 145 | pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } 146 | thead { display: table-header-group; /* h5bp.com/t */ } 147 | tr, img { page-break-inside: avoid; } 148 | img { max-width: 100% !important; } 149 | @page { margin: 0.5cm; } 150 | p, h2, h3, #toctitle, .sidebarblock > .content > .title { orphans: 3; widows: 3; } 151 | h2, h3, #toctitle, .sidebarblock > .content > .title { page-break-after: avoid; } 152 | .hide-on-print { display: none !important; } 153 | .print-only { display: block !important; } 154 | .hide-for-print { display: none !important; } 155 | .show-for-print { display: inherit !important; } } 156 | /* Tables */ 157 | table { background: white; margin-bottom: 1.25em; border: solid 1px #dddddd; } 158 | table thead, table tfoot { background: whitesmoke; font-weight: bold; } 159 | table thead tr th, table thead tr td, table tfoot tr th, table tfoot tr td { padding: 0.5em 0.625em 0.625em; font-size: inherit; color: #222222; text-align: left; } 160 | table tr th, table tr td { padding: 0.5625em 0.625em; font-size: inherit; color: #222222; } 161 | table tr.even, table tr.alt, table tr:nth-of-type(even) { background: #f9f9f9; } 162 | table thead tr th, table tfoot tr th, table tbody tr td, table tr td, table tfoot tr td { display: table-cell; line-height: 1.6; } 163 | 164 | .clearfix:before, .clearfix:after, .float-group:before, .float-group:after { content: " "; display: table; } 165 | .clearfix:after, .float-group:after { clear: both; } 166 | 167 | *:not(pre) > code { font-size: 0.9375em; padding: 1px 3px 0; white-space: nowrap; background-color: #f2f2f2; border: 1px solid #cccccc; -webkit-border-radius: 4px; border-radius: 4px; text-shadow: none; } 168 | 169 | /*pre, pre > code { line-height: 1.4; color: inherit; font-family: Consolas, "Liberation Mono", Courier, monospace; font-weight: normal; }*/ 170 | 171 | kbd.keyseq { color: #555555; } 172 | 173 | kbd:not(.keyseq) { display: inline-block; color: #222222; font-size: 0.75em; line-height: 1.4; background-color: #F7F7F7; border: 1px solid #ccc; -webkit-border-radius: 3px; border-radius: 3px; -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 2px white inset; box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 2px white inset; margin: -0.15em 0.15em 0 0.15em; padding: 0.2em 0.6em 0.2em 0.5em; vertical-align: middle; white-space: nowrap; } 174 | 175 | kbd kbd:first-child { margin-left: 0; } 176 | 177 | kbd kbd:last-child { margin-right: 0; } 178 | 179 | .menuseq, .menu { color: #090909; } 180 | 181 | p a > code:hover { color: #561309; } 182 | 183 | #header, #content, #footnotes, #footer { width: 100%; margin-left: auto; margin-right: auto; margin-top: 0; margin-bottom: 0; max-width: 62.5em; *zoom: 1; position: relative; padding-left: 0.9375em; padding-right: 0.9375em; } 184 | #header:before, #header:after, #content:before, #content:after, #footnotes:before, #footnotes:after, #footer:before, #footer:after { content: " "; display: table; } 185 | #header:after, #content:after, #footnotes:after, #footer:after { clear: both; } 186 | 187 | #header { margin-bottom: 2.5em; } 188 | #header > h1 { color: black; font-weight: normal; border-bottom: 1px solid #dddddd; margin-bottom: -28px; padding-bottom: 32px; } 189 | #header span { color: #6f6f6f; } 190 | #header #revnumber { text-transform: capitalize; } 191 | #header br { display: none; } 192 | #header br + span { padding-left: 3px; } 193 | #header br + span:before { content: "\2013 \0020"; } 194 | #header br + span.author { padding-left: 0; } 195 | #header br + span.author:before { content: ", "; } 196 | 197 | #toc { border-bottom: 3px double #ebebeb; padding-bottom: 1.25em; } 198 | #toc > ul { margin-left: 0.25em; } 199 | #toc ul.sectlevel0 > li > a { font-style: italic; } 200 | #toc ul.sectlevel0 ul.sectlevel1 { margin-left: 0; margin-top: 0.5em; margin-bottom: 0.5em; } 201 | #toc ul { list-style-type: none; } 202 | 203 | #toctitle { color: #7a2518; } 204 | 205 | @media only screen and (min-width: 1280px) { body.toc2 { padding-left: 20em; } 206 | #toc.toc2 { position: fixed; width: 20em; left: 0; top: 0; border-right: 1px solid #ebebeb; border-bottom: 0; z-index: 1000; padding: 1em; height: 100%; overflow: auto; } 207 | #toc.toc2 #toctitle { margin-top: 0; } 208 | #toc.toc2 > ul { font-size: .95em; } 209 | #toc.toc2 ul ul { margin-left: 0; padding-left: 1.25em; } 210 | #toc.toc2 ul.sectlevel0 ul.sectlevel1 { padding-left: 0; margin-top: 0.5em; margin-bottom: 0.5em; } 211 | body.toc2.toc-right { padding-left: 0; padding-right: 20em; } 212 | body.toc2.toc-right #toc.toc2 { border-right: 0; border-left: 1px solid #ebebeb; left: auto; right: 0; } } 213 | #content #toc { border-style: solid; border-width: 1px; border-color: #d9d9d9; margin-bottom: 1.25em; padding: 1.25em; background: #f2f2f2; border-width: 0; -webkit-border-radius: 4px; border-radius: 4px; } 214 | #content #toc > :first-child { margin-top: 0; } 215 | #content #toc > :last-child { margin-bottom: 0; } 216 | #content #toc a { text-decoration: none; } 217 | 218 | #content #toctitle { font-weight: bold; font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif; font-size: 1em; padding-left: 0.125em; } 219 | 220 | #footer { max-width: 100%; background-color: #222222; padding: 1.25em; } 221 | 222 | #footer-text { color: #dddddd; line-height: 1.44; } 223 | 224 | .sect1 { padding-bottom: 1.25em; } 225 | 226 | .sect1 + .sect1 { border-top: 3px double #ebebeb; } 227 | 228 | #content h1 > a.anchor, h2 > a.anchor, h3 > a.anchor, #toctitle > a.anchor, .sidebarblock > .content > .title > a.anchor, h4 > a.anchor, h5 > a.anchor, h6 > a.anchor { position: absolute; width: 1em; margin-left: -1em; display: block; text-decoration: none; visibility: hidden; text-align: center; font-weight: normal; } 229 | #content h1 > a.anchor:before, h2 > a.anchor:before, h3 > a.anchor:before, #toctitle > a.anchor:before, .sidebarblock > .content > .title > a.anchor:before, h4 > a.anchor:before, h5 > a.anchor:before, h6 > a.anchor:before { content: '\00A7'; font-size: .85em; vertical-align: text-top; display: block; margin-top: 0.05em; } 230 | #content h1:hover > a.anchor, #content h1 > a.anchor:hover, h2:hover > a.anchor, h2 > a.anchor:hover, h3:hover > a.anchor, #toctitle:hover > a.anchor, .sidebarblock > .content > .title:hover > a.anchor, h3 > a.anchor:hover, #toctitle > a.anchor:hover, .sidebarblock > .content > .title > a.anchor:hover, h4:hover > a.anchor, h4 > a.anchor:hover, h5:hover > a.anchor, h5 > a.anchor:hover, h6:hover > a.anchor, h6 > a.anchor:hover { visibility: visible; } 231 | #content h1 > a.link, h2 > a.link, h3 > a.link, #toctitle > a.link, .sidebarblock > .content > .title > a.link, h4 > a.link, h5 > a.link, h6 > a.link { color: #ba3925; text-decoration: none; } 232 | #content h1 > a.link:hover, h2 > a.link:hover, h3 > a.link:hover, #toctitle > a.link:hover, .sidebarblock > .content > .title > a.link:hover, h4 > a.link:hover, h5 > a.link:hover, h6 > a.link:hover { color: #a53221; } 233 | 234 | .imageblock, .literalblock, .listingblock, .verseblock, .videoblock { margin-bottom: 1.25em; } 235 | 236 | .admonitionblock td.content > .title, .exampleblock > .title, .imageblock > .title, .videoblock > .title, .listingblock > .title, .literalblock > .title, .openblock > .title, .paragraph > .title, .quoteblock > .title, .sidebarblock > .title, .tableblock > .title, .verseblock > .title, .dlist > .title, .olist > .title, .ulist > .title, .qlist > .title, .hdlist > .title { text-align: left; font-weight: bold; } 237 | 238 | .tableblock > caption { text-align: left; font-weight: bold; white-space: nowrap; overflow: visible; max-width: 0; } 239 | 240 | table.tableblock #preamble > .sectionbody > .paragraph:first-of-type p { font-size: inherit; } 241 | 242 | .admonitionblock > table { border: 0; background: none; width: 100%; } 243 | .admonitionblock > table td.icon { text-align: center; width: 80px; } 244 | .admonitionblock > table td.icon img { max-width: none; } 245 | .admonitionblock > table td.icon .title { font-weight: bold; text-transform: uppercase; } 246 | .admonitionblock > table td.content { padding-left: 1.125em; padding-right: 1.25em; border-left: 1px solid #dddddd; color: #6f6f6f; } 247 | .admonitionblock > table td.content > :last-child > :last-child { margin-bottom: 0; } 248 | 249 | .exampleblock > .content { border-style: solid; border-width: 1px; border-color: #e6e6e6; margin-bottom: 1.25em; padding: 1.25em; background: white; -webkit-border-radius: 4px; border-radius: 4px; } 250 | .exampleblock > .content > :first-child { margin-top: 0; } 251 | .exampleblock > .content > :last-child { margin-bottom: 0; } 252 | .exampleblock > .content h1, .exampleblock > .content h2, .exampleblock > .content h3, .exampleblock > .content #toctitle, .sidebarblock.exampleblock > .content > .title, .exampleblock > .content h4, .exampleblock > .content h5, .exampleblock > .content h6, .exampleblock > .content p { color: #333333; } 253 | .exampleblock > .content h1, .exampleblock > .content h2, .exampleblock > .content h3, .exampleblock > .content #toctitle, .sidebarblock.exampleblock > .content > .title, .exampleblock > .content h4, .exampleblock > .content h5, .exampleblock > .content h6 { line-height: 1; margin-bottom: 0.625em; } 254 | .exampleblock > .content h1.subheader, .exampleblock > .content h2.subheader, .exampleblock > .content h3.subheader, .exampleblock > .content .subheader#toctitle, .sidebarblock.exampleblock > .content > .subheader.title, .exampleblock > .content h4.subheader, .exampleblock > .content h5.subheader, .exampleblock > .content h6.subheader { line-height: 1.4; } 255 | 256 | .exampleblock.result > .content { -webkit-box-shadow: 0 1px 8px #d9d9d9; box-shadow: 0 1px 8px #d9d9d9; } 257 | 258 | .sidebarblock { border-style: solid; border-width: 1px; border-color: #d9d9d9; margin-bottom: 1.25em; padding: 1.25em; background: #f2f2f2; -webkit-border-radius: 4px; border-radius: 4px; } 259 | .sidebarblock > :first-child { margin-top: 0; } 260 | .sidebarblock > :last-child { margin-bottom: 0; } 261 | .sidebarblock h1, .sidebarblock h2, .sidebarblock h3, .sidebarblock #toctitle, .sidebarblock > .content > .title, .sidebarblock h4, .sidebarblock h5, .sidebarblock h6, .sidebarblock p { color: #333333; } 262 | .sidebarblock h1, .sidebarblock h2, .sidebarblock h3, .sidebarblock #toctitle, .sidebarblock > .content > .title, .sidebarblock h4, .sidebarblock h5, .sidebarblock h6 { line-height: 1; margin-bottom: 0.625em; } 263 | .sidebarblock h1.subheader, .sidebarblock h2.subheader, .sidebarblock h3.subheader, .sidebarblock .subheader#toctitle, .sidebarblock > .content > .subheader.title, .sidebarblock h4.subheader, .sidebarblock h5.subheader, .sidebarblock h6.subheader { line-height: 1.4; } 264 | .sidebarblock > .content > .title { color: #7a2518; margin-top: 0; line-height: 1.6; } 265 | 266 | .exampleblock > .content > :last-child > :last-child, .exampleblock > .content .olist > ol > li:last-child > :last-child, .exampleblock > .content .ulist > ul > li:last-child > :last-child, .exampleblock > .content .qlist > ol > li:last-child > :last-child, .sidebarblock > .content > :last-child > :last-child, .sidebarblock > .content .olist > ol > li:last-child > :last-child, .sidebarblock > .content .ulist > ul > li:last-child > :last-child, .sidebarblock > .content .qlist > ol > li:last-child > :last-child { margin-bottom: 0; } 267 | 268 | /*.literalblock > .content pre, .listingblock > .content pre { background: none; border-width: 1px 0; border-style: dotted; border-color: #bfbfbf; -webkit-border-radius: 4px; border-radius: 4px; padding: 0.75em 0.75em 0.5em 0.75em; word-wrap: break-word; }*/ 269 | .literalblock > .content pre.nowrap, .listingblock > .content pre.nowrap { overflow-x: auto; white-space: pre; word-wrap: normal; } 270 | .literalblock > .content pre > code, .listingblock > .content pre > code { display: block; } 271 | @media only screen { .literalblock > .content pre, .listingblock > .content pre { font-size: 0.8em; } } 272 | @media only screen and (min-width: 768px) { .literalblock > .content pre, .listingblock > .content pre { font-size: 0.9em; } } 273 | @media only screen and (min-width: 1280px) { .literalblock > .content pre, .listingblock > .content pre { font-size: 1em; } } 274 | 275 | .listingblock > .content { position: relative; } 276 | 277 | .listingblock:hover code[class*=" language-"]:before { text-transform: uppercase; font-size: 0.9em; color: #999; position: absolute; top: 0.375em; right: 0.375em; } 278 | 279 | .listingblock:hover code.asciidoc:before { content: "asciidoc"; } 280 | .listingblock:hover code.clojure:before { content: "clojure"; } 281 | .listingblock:hover code.css:before { content: "css"; } 282 | .listingblock:hover code.groovy:before { content: "groovy"; } 283 | .listingblock:hover code.html:before { content: "html"; } 284 | .listingblock:hover code.java:before { content: "java"; } 285 | .listingblock:hover code.javascript:before { content: "javascript"; } 286 | .listingblock:hover code.python:before { content: "python"; } 287 | .listingblock:hover code.ruby:before { content: "ruby"; } 288 | .listingblock:hover code.scss:before { content: "scss"; } 289 | .listingblock:hover code.xml:before { content: "xml"; } 290 | .listingblock:hover code.yaml:before { content: "yaml"; } 291 | 292 | .listingblock.terminal pre .command:before { content: attr(data-prompt); padding-right: 0.5em; color: #999; } 293 | 294 | .listingblock.terminal pre .command:not([data-prompt]):before { content: '$'; } 295 | 296 | table.pyhltable { border: 0; margin-bottom: 0; } 297 | 298 | table.pyhltable td { vertical-align: top; padding-top: 0; padding-bottom: 0; } 299 | 300 | table.pyhltable td.code { padding-left: .75em; padding-right: 0; } 301 | 302 | .highlight.pygments .lineno, table.pyhltable td:not(.code) { color: #999; padding-left: 0; padding-right: .5em; border-right: 1px solid #dddddd; } 303 | 304 | .highlight.pygments .lineno { display: inline-block; margin-right: .25em; } 305 | 306 | table.pyhltable .linenodiv { background-color: transparent !important; padding-right: 0 !important; } 307 | 308 | .quoteblock { margin: 0 0 1.25em; padding: 0.5625em 1.25em 0 1.1875em; border-left: 1px solid #dddddd; } 309 | .quoteblock blockquote { margin: 0 0 1.25em 0; padding: 0 0 0.5625em 0; border: 0; } 310 | .quoteblock blockquote > .paragraph:last-child p { margin-bottom: 0; } 311 | .quoteblock .attribution { margin-top: -.25em; padding-bottom: 0.5625em; font-size: inherit; color: #555555; } 312 | .quoteblock .attribution br { display: none; } 313 | .quoteblock .attribution cite { display: block; margin-bottom: 0.625em; } 314 | 315 | table thead th, table tfoot th { font-weight: bold; } 316 | 317 | table.tableblock.grid-all { border-collapse: separate; border-spacing: 1px; -webkit-border-radius: 4px; border-radius: 4px; border-top: 1px solid #dddddd; border-bottom: 1px solid #dddddd; } 318 | 319 | table.tableblock.frame-topbot, table.tableblock.frame-none { border-left: 0; border-right: 0; } 320 | 321 | table.tableblock.frame-sides, table.tableblock.frame-none { border-top: 0; border-bottom: 0; } 322 | 323 | table.tableblock td .paragraph:last-child p, table.tableblock td > p:last-child { margin-bottom: 0; } 324 | 325 | th.tableblock.halign-left, td.tableblock.halign-left { text-align: left; } 326 | 327 | th.tableblock.halign-right, td.tableblock.halign-right { text-align: right; } 328 | 329 | th.tableblock.halign-center, td.tableblock.halign-center { text-align: center; } 330 | 331 | th.tableblock.valign-top, td.tableblock.valign-top { vertical-align: top; } 332 | 333 | th.tableblock.valign-bottom, td.tableblock.valign-bottom { vertical-align: bottom; } 334 | 335 | th.tableblock.valign-middle, td.tableblock.valign-middle { vertical-align: middle; } 336 | 337 | p.tableblock.header { color: #222222; font-weight: bold; } 338 | 339 | td > div.verse { white-space: pre; } 340 | 341 | ol { margin-left: 1.75em; } 342 | 343 | ul li ol { margin-left: 1.5em; } 344 | 345 | dl dd { margin-left: 1.125em; } 346 | 347 | dl dd:last-child, dl dd:last-child > :last-child { margin-bottom: 0; } 348 | 349 | ol > li p, ul > li p, ul dd, ol dd, .olist .olist, .ulist .ulist, .ulist .olist, .olist .ulist { margin-bottom: 0.625em; } 350 | 351 | ul.unstyled, ol.unnumbered, ul.checklist, ul.none { list-style-type: none; } 352 | 353 | ul.unstyled, ol.unnumbered, ul.checklist { margin-left: 0.625em; } 354 | 355 | ul.checklist li > p:first-child > i[class^="icon-check"]:first-child, ul.checklist li > p:first-child > input[type="checkbox"]:first-child { margin-right: 0.25em; } 356 | 357 | ul.checklist li > p:first-child > input[type="checkbox"]:first-child { position: relative; top: 1px; } 358 | 359 | ul.inline { margin: 0 auto 0.625em auto; margin-left: -1.375em; margin-right: 0; padding: 0; list-style: none; overflow: hidden; } 360 | ul.inline > li { list-style: none; float: left; margin-left: 1.375em; display: block; } 361 | ul.inline > li > * { display: block; } 362 | 363 | .unstyled dl dt { font-weight: normal; font-style: normal; } 364 | 365 | ol.arabic { list-style-type: decimal; } 366 | 367 | ol.decimal { list-style-type: decimal-leading-zero; } 368 | 369 | ol.loweralpha { list-style-type: lower-alpha; } 370 | 371 | ol.upperalpha { list-style-type: upper-alpha; } 372 | 373 | ol.lowerroman { list-style-type: lower-roman; } 374 | 375 | ol.upperroman { list-style-type: upper-roman; } 376 | 377 | ol.lowergreek { list-style-type: lower-greek; } 378 | 379 | .hdlist > table, .colist > table { border: 0; background: none; } 380 | .hdlist > table > tbody > tr, .colist > table > tbody > tr { background: none; } 381 | 382 | td.hdlist1 { padding-right: .8em; font-weight: bold; } 383 | 384 | td.hdlist1, td.hdlist2 { vertical-align: top; } 385 | 386 | .literalblock + .colist, .listingblock + .colist { margin-top: -0.5em; } 387 | 388 | .colist > table tr > td:first-of-type { padding: 0 .8em; line-height: 1; } 389 | .colist > table tr > td:last-of-type { padding: 0.25em 0; } 390 | 391 | .qanda > ol > li > p > em:only-child { color: #00467f; } 392 | 393 | .thumb, .th { line-height: 0; display: inline-block; border: solid 4px white; -webkit-box-shadow: 0 0 0 1px #dddddd; box-shadow: 0 0 0 1px #dddddd; } 394 | 395 | .imageblock.left, .imageblock[style*="float: left"] { margin: 0.25em 0.625em 1.25em 0; } 396 | .imageblock.right, .imageblock[style*="float: right"] { margin: 0.25em 0 1.25em 0.625em; } 397 | .imageblock > .title { margin-bottom: 0; } 398 | .imageblock.thumb, .imageblock.th { border-width: 6px; } 399 | .imageblock.thumb > .title, .imageblock.th > .title { padding: 0 0.125em; } 400 | 401 | .image.left, .image.right { margin-top: 0.25em; margin-bottom: 0.25em; display: inline-block; line-height: 0; } 402 | .image.left { margin-right: 0.625em; } 403 | .image.right { margin-left: 0.625em; } 404 | 405 | a.image { text-decoration: none; } 406 | 407 | span.footnote, span.footnoteref { vertical-align: super; font-size: 0.875em; } 408 | span.footnote a, span.footnoteref a { text-decoration: none; } 409 | 410 | #footnotes { padding-top: 0.75em; padding-bottom: 0.75em; margin-bottom: 0.625em; } 411 | #footnotes hr { width: 20%; min-width: 6.25em; margin: -.25em 0 .75em 0; border-width: 1px 0 0 0; } 412 | #footnotes .footnote { padding: 0 0.375em; line-height: 1.3; font-size: 0.875em; margin-left: 1.2em; text-indent: -1.2em; margin-bottom: .2em; } 413 | #footnotes .footnote a:first-of-type { font-weight: bold; text-decoration: none; } 414 | #footnotes .footnote:last-of-type { margin-bottom: 0; } 415 | 416 | #content #footnotes { margin-top: -0.625em; margin-bottom: 0; padding: 0.75em 0; } 417 | 418 | .gist .file-data > table { border: none; background: #fff; width: 100%; margin-bottom: 0; } 419 | .gist .file-data > table td.line-data { width: 99%; } 420 | 421 | div.unbreakable { page-break-inside: avoid; } 422 | 423 | .big { font-size: larger; } 424 | 425 | .small { font-size: smaller; } 426 | 427 | .underline { text-decoration: underline; } 428 | 429 | .overline { text-decoration: overline; } 430 | 431 | .line-through { text-decoration: line-through; } 432 | 433 | .aqua { color: #00bfbf; } 434 | 435 | .aqua-background { background-color: #00fafa; } 436 | 437 | .black { color: black; } 438 | 439 | .black-background { background-color: black; } 440 | 441 | .blue { color: #0000bf; } 442 | 443 | .blue-background { background-color: #0000fa; } 444 | 445 | .fuchsia { color: #bf00bf; } 446 | 447 | .fuchsia-background { background-color: #fa00fa; } 448 | 449 | .gray { color: #606060; } 450 | 451 | .gray-background { background-color: #7d7d7d; } 452 | 453 | .green { color: #006000; } 454 | 455 | .green-background { background-color: #007d00; } 456 | 457 | .lime { color: #00bf00; } 458 | 459 | .lime-background { background-color: #00fa00; } 460 | 461 | .maroon { color: #600000; } 462 | 463 | .maroon-background { background-color: #7d0000; } 464 | 465 | .navy { color: #000060; } 466 | 467 | .navy-background { background-color: #00007d; } 468 | 469 | .olive { color: #606000; } 470 | 471 | .olive-background { background-color: #7d7d00; } 472 | 473 | .purple { color: #600060; } 474 | 475 | .purple-background { background-color: #7d007d; } 476 | 477 | .red { color: #bf0000; } 478 | 479 | .red-background { background-color: #fa0000; } 480 | 481 | .silver { color: #909090; } 482 | 483 | .silver-background { background-color: #bcbcbc; } 484 | 485 | .teal { color: #006060; } 486 | 487 | .teal-background { background-color: #007d7d; } 488 | 489 | .white { color: #bfbfbf; } 490 | 491 | .white-background { background-color: #fafafa; } 492 | 493 | .yellow { color: #bfbf00; } 494 | 495 | .yellow-background { background-color: #fafa00; } 496 | 497 | span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } 498 | 499 | .admonitionblock td.icon [class^="icon-"]:before { font-size: 2.5em; text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); cursor: default; } 500 | .admonitionblock td.icon .icon-note:before { content: "\f05a"; color: #005498; color: #003f72; } 501 | .admonitionblock td.icon .icon-tip:before { content: "\f0eb"; text-shadow: 1px 1px 2px rgba(155, 155, 0, 0.8); color: #111; } 502 | .admonitionblock td.icon .icon-warning:before { content: "\f071"; color: #bf6900; } 503 | .admonitionblock td.icon .icon-caution:before { content: "\f06d"; color: #bf3400; } 504 | .admonitionblock td.icon .icon-important:before { content: "\f06a"; color: #bf0000; } 505 | 506 | .conum { display: inline-block; color: white !important; background-color: #222222; -webkit-border-radius: 100px; border-radius: 100px; text-align: center; width: 20px; height: 20px; font-size: 12px; font-weight: bold; line-height: 20px; font-family: Arial, sans-serif; font-style: normal; position: relative; top: -2px; letter-spacing: -1px; } 507 | .conum * { color: white !important; } 508 | .conum + b { display: none; } 509 | .conum:after { content: attr(data-value); } 510 | .conum:not([data-value]):empty { display: none; } 511 | 512 | .literalblock > .content > pre, .listingblock > .content > pre { -webkit-border-radius: 0; border-radius: 0; } 513 | --------------------------------------------------------------------------------