├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── docs ├── dependency-graph.png ├── diff-report.png └── output-row.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── inspector ├── build.gradle ├── gradle.properties ├── libs │ └── com.zutubi.diff-3.0.dev.jar └── src │ ├── main │ ├── groovy │ │ └── com │ │ │ └── jakeout │ │ │ └── gradle │ │ │ └── utils │ │ │ └── gtree │ │ │ ├── GTree.groovy │ │ │ ├── GTreeBuilder.groovy │ │ │ ├── GTreeDiffLeaf.groovy │ │ │ ├── GTreeDiffNode.groovy │ │ │ ├── GTreeLeaf.groovy │ │ │ ├── GTreeLeafBuilder.groovy │ │ │ ├── GTreeNode.groovy │ │ │ ├── GTreeNodeBuilder.groovy │ │ │ ├── GTreeValuedLeaf.groovy │ │ │ ├── GTrees.groovy │ │ │ └── HasDiff.groovy │ ├── kotlin │ │ ├── com │ │ │ └── jakeout │ │ │ │ └── gradle │ │ │ │ ├── inspector │ │ │ │ ├── InspectorConfig.kt │ │ │ │ ├── InspectorGradleListener.kt │ │ │ │ ├── InspectorPlugin.kt │ │ │ │ ├── InspectorPluginExtension.kt │ │ │ │ └── tasks │ │ │ │ │ ├── TaskAnalyzer.kt │ │ │ │ │ ├── model │ │ │ │ │ ├── AnalysisResult.kt │ │ │ │ │ ├── TaskDiffResults.kt │ │ │ │ │ └── TaskExecutionResults.kt │ │ │ │ │ └── output │ │ │ │ │ ├── DiffWriter.kt │ │ │ │ │ └── IndexWriter.kt │ │ │ │ └── utils │ │ │ │ ├── DiffUtil.kt │ │ │ │ ├── StringUtil.kt │ │ │ │ └── TaskUtil.kt │ │ └── kotlinx │ │ │ └── html │ │ │ ├── AttributeTypes.kt │ │ │ ├── Attributes.kt │ │ │ ├── HtmlBody.kt │ │ │ ├── HtmlBuilder.kt │ │ │ ├── HtmlDocument.kt │ │ │ ├── HtmlHead.kt │ │ │ └── Link.kt │ └── resources │ │ ├── META-INF │ │ └── gradle-plugins │ │ │ └── com.jakeout.gradle-inspector.properties │ │ ├── diff.css │ │ ├── font │ │ ├── css │ │ │ ├── font-awesome.css │ │ │ └── font-awesome.min.css │ │ └── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ │ ├── vis-report.html │ │ └── vis │ │ ├── d3.v3.min.js │ │ ├── dag.js │ │ ├── dagre-d3.js │ │ ├── jquery-1.9.1.min.js │ │ ├── tipsy.css │ │ └── tipsy.js │ └── test │ └── groovy │ └── com │ └── jakeout │ └── gradle │ └── utils │ └── gtree │ └── GTreeTest.groovy └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | *.iml 3 | **/.idea 4 | !.idea/runConfigurations/ 5 | build/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 jakeouellette 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Inspector 2 | 3 | Gradle build inspector, clarifies what's going on inside your Gradle build. Features: 4 | 5 | - Makes it easy to understand your task dependencies by generating a task dependency graph. 6 | 7 | - Makes it easy to know what files have changed on disk by running diff (windows not supported) 8 | 9 | - *(incubating)* Compare the differences in file changes on disk between two builds by using -PcompareLastBuild 10 | 11 | ## Usage 12 | 13 | **Easy Setup**: Navigate to the the [Gradle Plugin Repository](https://plugins.gradle.org/plugin/com.jakeout.gradle-inspector). Follow the directions hosted there to set up the latest release. 14 | 15 | **To install from source**: Checkout and install locally: 16 | 17 | > ./gradlew install 18 | 19 | Then, add the plugin and mavenLocal() to your build: 20 | 21 | buildscript { 22 | repositories { 23 | mavenLocal() 24 | } 25 | dependencies { 26 | classpath 'com.jakeout:gradle-inspector:+' 27 | } 28 | } 29 | 30 | 31 | allprojects { 32 | apply plugin: 'com.jakeout.gradle-inspector' 33 | } 34 | 35 | ## Features 36 | 37 | Understanding Gradle Task graphs can be challenging. Gradle typically outputs a linear view of all of the tasks run during a build, with no sense of time or dependencies. 38 | 39 | The Gradle build inspector elucidates the task graph by displaying it as a canvas directed-acyclic-graph using [d3](http://d3js.org/) as a rendering engine with [dagre](https://github.com/cpettitt/dagre-d3) as a layout manager. Uses [Kotlinx.html](https://github.com/kotlinx/kotlinx.html) to generate html. 40 | 41 | Inspector generates a task graph for each app / library it is applied to, and will attempt to link them if you apply it to all projects in a hierarchy. 42 | 43 | > ![Dependency Graph](docs/dependency-graph.png) 44 | 45 | Each task can be clicked and explored deeper. Tasks are colored "green" if they only have declared changes on disk. Tasks are colored blue if they have unexpected / undeclared side effects. A Gradle clean task deletes things, and therefore their removal is not explicitly declared as part of its output, though, most other gradle tasks are additive and so this can help identify missed side-effects. 46 | 47 | Inspector is not intended to be a fast build tool. It shells out to the command line to run [diff](http://unixhelp.ed.ac.uk/CGI/man-cgi?diff) and outputs a `buildProfile` folder, caching the `build` folder after EVERY task runs. This means that it can be slow with large builds. 48 | 49 | > ![Diff Report](docs/diff-report.png) 50 | 51 | The shell'd out Diff output is read using [Zutubi Diff](https://github.com/Zutubi/com.zutubi.diff), and 52 | 53 | Diff report features: 54 | 55 | - *new / fixed* Show non-text files! -- Show files that have been added or deleted by a task. Show images / media content that change 56 | 57 | - Diffs files -- A handful of lines of files are shown, the rest are hidden and can be expanded. 58 | 59 | - Diffs between two builds -- compare the differences (e.g., compare two Gradle builds, before and after Gradle is updated). Currently, only tasks for the second build are shown in the directed acyclic graph. 60 | 61 | - Identify other running tasks and warns when diff may be inaccurate. Diff actions run at the start and end of each task, so if other tasks are running simultaneously, build behavior may produce strange outputs. (turning off parallel is recommended unless profiling this explicitly) 62 | 63 | > ![Output Row](docs/output-row.png) 64 | 65 | Output rows have red icons of files with questions marks (provided by [Font awesome](https://github.com/FortAwesome/Font-Awesome) ) if they were not declared by the Gradle task. If the output was declared, but it was a folder, not a file, it has a subtle border splitting it from the child files that were modified within that declared folder. 66 | 67 | ## Optional Properties 68 | 69 | *Run with -PpropertyName to enable* 70 | 71 | - __showInspection__: to auto-open the web URL. 72 | 73 | - *(incubating)* __compareLastBuild__: Generates a report comparing this build's inputs and outputs against the last 74 | build you made. These changes are written to the report page for each task (above the normal report.) This can be handy (e.g., if you're trying to figure out what is different about a new version of a Gradle plugin.) This feature is slower and more disk intensive, because of great caching and comparisons being made. 75 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | task wrapper(type: Wrapper) { 2 | gradleVersion = '1.12' 3 | } 4 | -------------------------------------------------------------------------------- /docs/dependency-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakeouellette/inspector/7ee81d50bdee4763ce6298a95ae589032ab4d749/docs/dependency-graph.png -------------------------------------------------------------------------------- /docs/diff-report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakeouellette/inspector/7ee81d50bdee4763ce6298a95ae589032ab4d749/docs/diff-report.png -------------------------------------------------------------------------------- /docs/output-row.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakeouellette/inspector/7ee81d50bdee4763ce6298a95ae589032ab4d749/docs/output-row.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakeouellette/inspector/7ee81d50bdee4763ce6298a95ae589032ab4d749/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Sep 10 11:46:12 PDT 2014 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-1.12-all.zip 7 | distributionUrlOldestSupported=https\://services.gradle.org/distributions/gradle-1.12-all.zip 8 | distributionUrlLatestSupported=https\://services.gradle.org/distributions/gradle-2.4-all.zip 9 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 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 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 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 Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /inspector/build.gradle: -------------------------------------------------------------------------------- 1 | // First, apply the publishing plugin 2 | buildscript { 3 | ext.kotlin_version = '0.12.613' 4 | repositories { 5 | maven { 6 | url "https://plugins.gradle.org/m2/" 7 | } 8 | mavenCentral() 9 | } 10 | dependencies { 11 | classpath "com.gradle.publish:plugin-publish-plugin:0.9.0" 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 13 | } 14 | } 15 | 16 | apply plugin: "com.gradle.plugin-publish" 17 | apply plugin: 'groovy' 18 | apply plugin: 'maven' 19 | apply plugin: 'kotlin' 20 | 21 | repositories { 22 | mavenLocal() 23 | jcenter() 24 | mavenCentral() 25 | } 26 | 27 | configurations { 28 | fatJar 29 | } 30 | 31 | dependencies { 32 | compile gradleApi() 33 | compile localGroovy() 34 | testCompile 'junit:junit:4.11' 35 | compile 'commons-io:commons-io:2.4' 36 | compile 'org.springframework:spring-web:4.1.6.RELEASE' 37 | fatJar files('libs/com.zutubi.diff-3.0.dev.jar') 38 | compile fileTree(dir: 'libs', include: '*.jar') 39 | compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 40 | } 41 | 42 | // Make it so the fatJar classes can be seen during compilation. 43 | sourceSets.main.compileClasspath += configurations.fatJar 44 | 45 | jar { 46 | // Create a "fat" JAR by including all fatJar dependencies 47 | from { 48 | configurations.fatJar.collect { 49 | it.isDirectory() ? it : zipTree(it) 50 | } 51 | } 52 | 53 | manifest { 54 | attributes( 55 | 'Implementation-Title': "${group}.${artifact}", 56 | 'Implementation-Version': version) 57 | } 58 | } 59 | 60 | def pomConfigured = { thisPom -> 61 | thisPom.artifactId = artifact 62 | thisPom.version = version 63 | thisPom.dependencies.removeAll { it.scope == 'test' } 64 | } 65 | 66 | task writePom << { 67 | // We write to the default 68 | pom { 69 | whenConfigured(pomConfigured) 70 | }.writeTo("${buildDir}/poms/pom-default.xml") 71 | } 72 | 73 | install { 74 | repositories.mavenInstaller { 75 | pom.whenConfigured(pomConfigured) 76 | } 77 | } 78 | 79 | // If assemble is called instead of install, write the pom explicitly 80 | // Otherwise, the install task will write the pom to the same location. 81 | assemble.dependsOn(writePom) 82 | 83 | 84 | pluginBundle { 85 | website = 'https://github.com/jakeouellette/inspector' 86 | vcsUrl = 'https://github.com/jakeouellette/inspector' 87 | 88 | description = 'See github page for more information!' 89 | 90 | plugins { 91 | inspectorPlugin { 92 | id = 'com.jakeout.gradle-inspector' 93 | displayName = 'Gradle Inspector plugin' 94 | tags = ['gradle', 'profiling', 'metrics', 'visualization'] 95 | } 96 | } 97 | } 98 | 99 | sourceSets { 100 | main.java.srcDirs += 'src/main/kotlin' 101 | } 102 | -------------------------------------------------------------------------------- /inspector/gradle.properties: -------------------------------------------------------------------------------- 1 | version=0.3.0 2 | group=com.jakeout 3 | artifact=gradle-inspector -------------------------------------------------------------------------------- /inspector/libs/com.zutubi.diff-3.0.dev.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakeouellette/inspector/7ee81d50bdee4763ce6298a95ae589032ab4d749/inspector/libs/com.zutubi.diff-3.0.dev.jar -------------------------------------------------------------------------------- /inspector/src/main/groovy/com/jakeout/gradle/utils/gtree/GTree.groovy: -------------------------------------------------------------------------------- 1 | package com.jakeout.gradle.utils.gtree 2 | 3 | 4 | public class GTree { 5 | public final String name 6 | 7 | public GTree(String name) { 8 | this.name = name 9 | } 10 | 11 | @Override 12 | public String toString() { 13 | return name 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /inspector/src/main/groovy/com/jakeout/gradle/utils/gtree/GTreeBuilder.groovy: -------------------------------------------------------------------------------- 1 | package com.jakeout.gradle.utils.gtree 2 | 3 | public interface GTreeBuilder { 4 | public GTree build() 5 | } -------------------------------------------------------------------------------- /inspector/src/main/groovy/com/jakeout/gradle/utils/gtree/GTreeDiffLeaf.groovy: -------------------------------------------------------------------------------- 1 | package com.jakeout.gradle.utils.gtree 2 | 3 | 4 | public class GTreeDiffLeaf extends GTreeLeaf implements HasDiff { 5 | 6 | public HasDiff.Diff changeType 7 | 8 | public String originalValueIfExisted 9 | 10 | public String newValueIfAny 11 | 12 | public GTreeDiffLeaf(String name, String originalValueIfExisted, String newValueIfAny, HasDiff.Diff changeType) { 13 | super(name) 14 | this.changeType = changeType 15 | this.originalValueIfExisted = originalValueIfExisted 16 | this.newValueIfAny = newValueIfAny 17 | 18 | } 19 | 20 | @Override 21 | HasDiff.Diff getType() { 22 | return changeType 23 | } 24 | 25 | 26 | @Override 27 | public String toString() { 28 | if (HasDiff.Diff.ADDED.equals(changeType)) { 29 | return "+" + name + " " + newValueIfAny 30 | } else if (HasDiff.Diff.REMOVED.equals(changeType)) { 31 | return "-" + name + " " + originalValueIfExisted 32 | } else if (HasDiff.Diff.CONTENTS_CHANGED.equals(changeType)) { 33 | return "~" + name + " " + originalValueIfExisted + " -> " + newValueIfAny 34 | } else { 35 | return name + " " + originalValueIfExisted 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /inspector/src/main/groovy/com/jakeout/gradle/utils/gtree/GTreeDiffNode.groovy: -------------------------------------------------------------------------------- 1 | package com.jakeout.gradle.utils.gtree 2 | 3 | 4 | public class GTreeDiffNode extends GTreeNode implements HasDiff { 5 | 6 | HasDiff.Diff changeType 7 | 8 | public GTreeDiffNode(String name, List children, HasDiff.Diff type) { 9 | super(name, children) 10 | this.changeType = type 11 | } 12 | 13 | @Override 14 | HasDiff.Diff getType() { 15 | return changeType 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | return changeType.toString() + name + "{\n" + 21 | StringUtil.tabAppender(1).apply(" " + children.inject("", { i, c -> i + " " + c + "\n" })) + "}" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /inspector/src/main/groovy/com/jakeout/gradle/utils/gtree/GTreeLeaf.groovy: -------------------------------------------------------------------------------- 1 | package com.jakeout.gradle.utils.gtree 2 | 3 | public class GTreeLeaf extends GTree { 4 | public GTreeLeaf(String name) { 5 | super(name) 6 | } 7 | 8 | @Override 9 | public String toString() { 10 | return name 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /inspector/src/main/groovy/com/jakeout/gradle/utils/gtree/GTreeLeafBuilder.groovy: -------------------------------------------------------------------------------- 1 | package com.jakeout.gradle.utils.gtree 2 | 3 | class GTreeLeafBuilder implements GTreeBuilder { 4 | 5 | String name 6 | 7 | public GTreeLeafBuilder(String name) { 8 | this.name = name 9 | } 10 | 11 | @Override 12 | public GTree build() { 13 | return new GTreeLeaf(name) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /inspector/src/main/groovy/com/jakeout/gradle/utils/gtree/GTreeNode.groovy: -------------------------------------------------------------------------------- 1 | package com.jakeout.gradle.utils.gtree 2 | 3 | public class GTreeNode extends GTree { 4 | 5 | public List children 6 | 7 | public GTreeNode(String name, List children) { 8 | super(name) 9 | this.children = children 10 | } 11 | 12 | // TODO: this is a bit inefficient a lookup 13 | def propertyMissing(String name) { 14 | for (GTree child : children) { 15 | if (child.name.equals(name)) { 16 | return child 17 | } 18 | } 19 | throw new IllegalArgumentException(name) 20 | } 21 | 22 | @Override 23 | public String toString() { 24 | return name + "{\n" + children.inject("", { i, c -> i + " " + c + "\n" }) + "}" 25 | } 26 | } -------------------------------------------------------------------------------- /inspector/src/main/groovy/com/jakeout/gradle/utils/gtree/GTreeNodeBuilder.groovy: -------------------------------------------------------------------------------- 1 | package com.jakeout.gradle.utils.gtree 2 | 3 | public class GTreeNodeBuilder implements GTreeBuilder { 4 | 5 | public final List subtrees 6 | 7 | public String name 8 | 9 | public static GTreeNodeBuilder create(String s, Closure c) { 10 | def builder = new GTreeNodeBuilder(s) 11 | c.delegate = builder 12 | c.resolveStrategy = Closure.DELEGATE_ONLY 13 | c() 14 | return builder 15 | } 16 | 17 | public GTreeNodeBuilder(String s) { 18 | this.name = s 19 | this.subtrees = new LinkedList<>() 20 | } 21 | 22 | // TODO: Value'd leaf builder via methodMissing(String, String) 23 | 24 | def methodMissing(String name, args) { 25 | if (args.length == 0) { 26 | subtrees.add(new GTreeLeafBuilder(name)) 27 | } else { 28 | if (args[0] instanceof Closure) { 29 | subtrees.add(create(name, (Closure) args[0])) 30 | } else { 31 | // TODO: make into valued builder 32 | subtrees.add(new GTreeBuilder() { 33 | @Override 34 | GTree build() { 35 | return new GTreeValuedLeaf(name, args[0].toString()) 36 | } 37 | }) 38 | } 39 | } 40 | } 41 | 42 | @Override 43 | public GTree build() { 44 | return new GTreeNode(name, subtrees.collect { gtb -> gtb.build() }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /inspector/src/main/groovy/com/jakeout/gradle/utils/gtree/GTreeValuedLeaf.groovy: -------------------------------------------------------------------------------- 1 | package com.jakeout.gradle.utils.gtree 2 | 3 | public class GTreeValuedLeaf extends GTree { 4 | public final String value 5 | 6 | public GTreeValuedLeaf(String name, String value) { 7 | super(name) 8 | this.value = value; 9 | } 10 | 11 | 12 | @Override 13 | public String toString() { 14 | return name + " " + value 15 | } 16 | } -------------------------------------------------------------------------------- /inspector/src/main/groovy/com/jakeout/gradle/utils/gtree/GTrees.groovy: -------------------------------------------------------------------------------- 1 | package com.jakeout.gradle.utils.gtree 2 | 3 | public class GTrees { 4 | 5 | public static GTree create(String s, Closure c) { 6 | GTreeNodeBuilder.create(s, c).build() 7 | } 8 | 9 | public static GTree createTemplate(Closure c) { 10 | GTreeNodeBuilder.create("template", c).build() 11 | } 12 | 13 | public static GTree extract(GTree template, Object o) { 14 | extractNode((GTreeNode)template, o) 15 | } 16 | 17 | public static GTree extractNode(GTreeNode parameterTree, Object value) { 18 | List children = new LinkedList<>() 19 | String name = parameterTree.name 20 | for (GTree tree : parameterTree.children) { 21 | children.add(extractRecursive(tree, value)) 22 | } 23 | return new GTreeNode(name, children) 24 | } 25 | 26 | private static GTree extractRecursive(GTree parameterTree, Object o) { 27 | Object value = o.("${parameterTree.name}") 28 | if (parameterTree instanceof GTreeLeaf) { 29 | new GTreeValuedLeaf(parameterTree.name, String.valueOf(value)) 30 | } else if (parameterTree instanceof GTreeNode) { 31 | extractNode(parameterTree, value) 32 | } else { 33 | throw new IllegalArgumentException("Tree type was invalid: " + parameterTree.class) 34 | } 35 | } 36 | 37 | public static GTree diff(GTree valuedTree, GTree comparedTree) { 38 | if (valuedTree == null) { 39 | if (comparedTree instanceof GTreeValuedLeaf) { 40 | GTreeValuedLeaf second = (GTreeValuedLeaf) comparedTree 41 | return new GTreeDiffLeaf(second.name, null, second.value, HasDiff.Diff.ADDED) 42 | } else if (comparedTree instanceof GTreeNode) { 43 | GTreeNode second = (GTreeNode) comparedTree 44 | return new GTreeDiffNode(second.name, second.children.collect { c -> diff(null, c) }, HasDiff.Diff.ADDED) 45 | } else { 46 | throw new IllegalArgumentException("cannot compare two nulls.") 47 | } 48 | } 49 | 50 | if (valuedTree instanceof GTreeValuedLeaf) { 51 | if (comparedTree == null) { 52 | GTreeValuedLeaf first = (GTreeValuedLeaf) valuedTree 53 | return new GTreeDiffLeaf(first.name, first.value, null, HasDiff.Diff.REMOVED) 54 | } 55 | 56 | if (comparedTree instanceof GTreeValuedLeaf) { 57 | GTreeValuedLeaf first = (GTreeValuedLeaf) valuedTree 58 | GTreeValuedLeaf second = (GTreeValuedLeaf) comparedTree 59 | if (first.value.equals(second.value)) { 60 | return new GTreeDiffLeaf(first.name, first.value, null, HasDiff.Diff.UNCHANGED) 61 | } else { 62 | return new GTreeDiffLeaf(first.name, first.value, second.value, HasDiff.Diff.CONTENTS_CHANGED) 63 | } 64 | } else if (comparedTree instanceof GTreeNode) { 65 | throw new IllegalArgumentException("Leaves cannot change into nodes") 66 | } 67 | } else if (valuedTree instanceof GTreeNode) { 68 | if (comparedTree == null) { 69 | GTreeNode first = (GTreeNode) valuedTree 70 | return new GTreeDiffNode(first.name, first.children.collect { c -> diff(c, null) }, HasDiff.Diff.REMOVED) 71 | } 72 | 73 | if (comparedTree instanceof GTreeNode) { 74 | 75 | GTreeNode first = (GTreeNode) valuedTree 76 | GTreeNode second = (GTreeNode) comparedTree 77 | List childrenAccountedFor = new LinkedList<>(first.children) 78 | List childrenAccountedFor2 = new LinkedList<>(second.children) 79 | 80 | List childComparisons = new LinkedList<>() 81 | base: 82 | Iterator children1 = childrenAccountedFor.iterator() 83 | while (children1.hasNext()) { 84 | GTree child = children1.next() 85 | Iterator children2 = childrenAccountedFor2.iterator() 86 | while (children2.hasNext()) { 87 | GTree child2 = children2.next() 88 | if (child.name.equals(child2.name)) { 89 | children1.remove() 90 | children2.remove() 91 | childComparisons.add(diff(child, child2)) 92 | continue base; 93 | } 94 | 95 | } 96 | } 97 | 98 | for (GTree child : childrenAccountedFor) { 99 | childComparisons.add(diff(child, null)) 100 | } 101 | 102 | for (GTree child : childrenAccountedFor2) { 103 | childComparisons.add(diff(null, child)) 104 | } 105 | 106 | return new GTreeDiffNode(first.name, childComparisons, HasDiff.Diff.UNCHANGED) 107 | } else if (comparedTree instanceof GTreeValuedLeaf) { 108 | throw new IllegalArgumentException("Nodes cannot change into leaves") 109 | } else { 110 | throw new IllegalArgumentException("Trees must be nodes or kv pairs") 111 | } 112 | } else { 113 | throw new IllegalArgumentException("Tree already has value for key.") 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /inspector/src/main/groovy/com/jakeout/gradle/utils/gtree/HasDiff.groovy: -------------------------------------------------------------------------------- 1 | package com.jakeout.gradle.utils.gtree 2 | 3 | 4 | public interface HasDiff { 5 | 6 | public enum Diff { 7 | ADDED("+"), REMOVED("-"), CONTENTS_CHANGED("~"), UNCHANGED(" ") 8 | 9 | String symbol 10 | 11 | public Diff(String symbol) { 12 | this.symbol = symbol 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | symbol 18 | } 19 | } 20 | 21 | public Diff getType() 22 | } -------------------------------------------------------------------------------- /inspector/src/main/kotlin/com/jakeout/gradle/inspector/InspectorConfig.kt: -------------------------------------------------------------------------------- 1 | package com.jakeout.gradle.inspector 2 | 3 | import org.gradle.api.Task 4 | import java.io.File 5 | 6 | data class InspectorConfig( 7 | val cleanUpEachTask: Boolean, 8 | val compareBuild: Boolean, 9 | val showInspection: Boolean, 10 | val incrementalDir: File, 11 | val inspectionRoot: File, 12 | val projectBuildDir: File, 13 | val reportDir: File, 14 | val compareIncrementalDir: File, 15 | val compareInspectionRoot: File, 16 | val compareReportDir: File) { 17 | 18 | fun taskOut(task: Task) = File(incrementalDir, task.nameString() + ".diff") 19 | fun compareTaskOut(task: Task) = File(incrementalDir, task.nameString() + ".compare.diff") 20 | fun taskReport(task: Task) = task.nameString() + "-report.html" 21 | fun taskDir(task: Task) = File(incrementalDir, task.nameString()) 22 | fun compareTaskDir(task: Task) = File(compareIncrementalDir, task.nameString()) 23 | fun Task.nameString() = getName().replace(":", ".") 24 | fun index() = File(reportDir, "index.html") 25 | } 26 | -------------------------------------------------------------------------------- /inspector/src/main/kotlin/com/jakeout/gradle/inspector/InspectorGradleListener.kt: -------------------------------------------------------------------------------- 1 | package com.jakeout.gradle.inspector 2 | 3 | import com.jakeout.gradle.inspector.tasks.TaskAnalyzer 4 | import com.jakeout.gradle.inspector.tasks.model.AnalysisResult 5 | import com.jakeout.gradle.inspector.tasks.output.DiffWriter 6 | import com.jakeout.gradle.inspector.tasks.output.IndexWriter 7 | import com.jakeout.gradle.utils.DiffUtil 8 | import org.apache.commons.io.FileUtils 9 | import org.gradle.BuildListener 10 | import org.gradle.BuildResult 11 | import org.gradle.api.Project 12 | import org.gradle.api.Task 13 | import org.gradle.api.execution.TaskExecutionListener 14 | import org.gradle.api.initialization.Settings 15 | import org.gradle.api.invocation.Gradle 16 | import org.gradle.api.tasks.TaskState 17 | import java.awt.Desktop 18 | import java.io.File 19 | import java.net.URI 20 | import java.nio.file.Files 21 | import java.util.HashMap 22 | import java.util.LinkedHashMap 23 | import java.util.LinkedList 24 | 25 | 26 | public class InspectorGradleListener(val config: InspectorConfig, val project: Project) : TaskExecutionListener, BuildListener { 27 | companion object { 28 | val SHOW_INSPECTION_PROPERTY = "showInspection" 29 | val COMPARE_LAST_BUILD_PROPERTY = "compareLastBuild" 30 | val PROFILE_PATH = ".buildProfile" 31 | val DIFF_INCREMENTAL = "diffIncremental" 32 | val DIFF_REPORT = "report" 33 | val PROFILE_PATH_COMPARE = "buildProfileCompare" 34 | 35 | fun setupFolderDiff(project: Project): InspectorGradleListener { 36 | 37 | val inspectionRoot = File(project.getProjectDir(), PROFILE_PATH) 38 | val compareInspectionRoot = File(project.getProjectDir(), PROFILE_PATH_COMPARE) 39 | 40 | val incrementalDir = File(inspectionRoot, DIFF_INCREMENTAL) 41 | val compareIncrementalDir = File(compareInspectionRoot, DIFF_INCREMENTAL) 42 | 43 | val reportDir = File(inspectionRoot, DIFF_REPORT) 44 | val compareReportDir = File(compareInspectionRoot, DIFF_REPORT) 45 | 46 | 47 | val showInspection = project.hasProperty(SHOW_INSPECTION_PROPERTY) 48 | 49 | val compareBuild = project.hasProperty(COMPARE_LAST_BUILD_PROPERTY) 50 | val cleanUpEachTask = !compareBuild 51 | 52 | val config = InspectorConfig( 53 | incrementalDir = incrementalDir, 54 | reportDir = reportDir, 55 | inspectionRoot = inspectionRoot, 56 | projectBuildDir = project.getBuildDir(), 57 | cleanUpEachTask = cleanUpEachTask, 58 | compareBuild = compareBuild, 59 | compareInspectionRoot = compareInspectionRoot, 60 | compareIncrementalDir = compareIncrementalDir, 61 | compareReportDir = compareReportDir, 62 | showInspection = showInspection) 63 | 64 | if (config.compareBuild) { 65 | FileUtils.deleteDirectory(config.compareInspectionRoot) 66 | FileUtils.moveDirectory(config.inspectionRoot, config.compareInspectionRoot) 67 | } 68 | 69 | FileUtils.deleteDirectory(config.reportDir) 70 | FileUtils.deleteDirectory(config.incrementalDir) 71 | FileUtils.deleteDirectory(config.inspectionRoot) 72 | FileUtils.forceMkdir(config.reportDir) 73 | FileUtils.forceMkdir(config.incrementalDir) 74 | 75 | (project.getExtensions().create("inspector", javaClass()) as InspectorPluginExtension).index = config.index() 76 | 77 | val hook = InspectorGradleListener(config, project) 78 | project.getGradle().addListener(hook) 79 | return hook 80 | } 81 | 82 | fun getOverlappingTasks(analysisResults: List): Map> { 83 | val overlappingTasks = HashMap>() 84 | 85 | for (result in analysisResults) { 86 | val task1 = result.executionResults.task.getName() 87 | overlappingTasks.put(task1, LinkedList()) 88 | val task1Start = result.executionResults.startTime 89 | val task1End = result.executionResults.endTime 90 | for (result2 in analysisResults) { 91 | val task2 = result2.executionResults.task.getName() 92 | if (!task2.equals(task1)) { 93 | val task2Start = result2.executionResults.startTime 94 | val task2End = result2.executionResults.endTime 95 | 96 | if ((task1Start > task2Start && task1Start < task2End) || 97 | (task1End > task2Start && task1End < task2End)) { 98 | 99 | val overlapping: LinkedList = overlappingTasks.get(task1) 100 | overlapping.add(task2) 101 | } 102 | } 103 | 104 | } 105 | } 106 | 107 | return overlappingTasks 108 | } 109 | } 110 | 111 | val taskAnalyzers = LinkedHashMap() 112 | 113 | override fun beforeExecute(task: Task) { 114 | if (task.getProject().equals(project)) { 115 | // In case a clean task removed the build folder. 116 | FileUtils.forceMkdir(config.projectBuildDir) 117 | 118 | taskAnalyzers.put(task.getName(), TaskAnalyzer(config, task, System.currentTimeMillis())) 119 | DiffUtil.backup(config.projectBuildDir, config.taskDir(task)) 120 | } 121 | } 122 | 123 | override fun afterExecute(task: Task, taskState: TaskState) { 124 | if (task.getProject().equals(project)) { 125 | val listener = taskAnalyzers.get(task.getName()) 126 | if (listener != null) { 127 | listener.onAfterExecute(taskState) 128 | } else { 129 | println("No task listener for : ${task.getName()}") 130 | } 131 | } 132 | } 133 | 134 | override fun buildFinished(buildResult: BuildResult) { 135 | try { 136 | FileUtils.forceMkdir(File(config.reportDir, "vis")) 137 | // TODO: infer these dynamically 138 | makeFile("diff.css") 139 | makeFile("vis/d3.v3.min.js") 140 | makeFile("vis/dag.js") 141 | makeFile("vis/dagre-d3.js") 142 | makeFile("vis/jquery-1.9.1.min.js") 143 | makeFile("vis/tipsy.css") 144 | makeFile("vis/tipsy.js") 145 | 146 | makeFile("font/css/font-awesome.css") 147 | makeFile("font/css/font-awesome.min.css") 148 | makeFile("font/fonts/FontAwesome.otf") 149 | makeFile("font/fonts/fontawesome-webfont.eot") 150 | makeFile("font/fonts/fontawesome-webfont.svg") 151 | makeFile("font/fonts/fontawesome-webfont.ttf") 152 | makeFile("font/fonts/fontawesome-webfont.woff") 153 | makeFile("font/fonts/fontawesome-webfont.woff2") 154 | 155 | val subprojectsByFile = HashMap() 156 | for (subProj in project.getChildProjects().values()) { 157 | if (subProj.getExtensions().getByName("inspector") != null) { 158 | subprojectsByFile.put(subProj.getName(), (subProj.getExtensions().getByName("inspector") as InspectorPluginExtension).index) 159 | } 160 | } 161 | 162 | Files.copy( 163 | this.javaClass.getClassLoader().getResourceAsStream("vis-report.html"), 164 | File(config.reportDir, "index.html").toPath()) 165 | 166 | 167 | val sortedResults = taskAnalyzers.values() 168 | .map { t: TaskAnalyzer -> t.results } 169 | .filterNotNull() 170 | .toSortedListBy { a: AnalysisResult -> a.executionResults.startTime } 171 | 172 | val overlappingTasks = getOverlappingTasks(sortedResults) 173 | 174 | for (analysisResult in sortedResults) { 175 | val overlappingName = overlappingTasks.get(analysisResult.executionResults.task.getName()) ?: LinkedList() 176 | DiffWriter.write( 177 | File(config.reportDir, analysisResult.executionResults.path), 178 | analysisResult.executionResults, 179 | analysisResult.diffResults, 180 | analysisResult.comparisonResults, 181 | overlappingName) 182 | } 183 | 184 | IndexWriter.write( 185 | config.index(), 186 | subprojectsByFile, 187 | sortedResults, 188 | File(File(config.reportDir, "vis"), "dag.js")) 189 | println("Build inspection written to file://${config.index()}") 190 | if (config.showInspection) { 191 | Desktop.getDesktop().browse(URI("file://${config.index()}")) 192 | } 193 | } catch (e: Throwable) { 194 | e.printStackTrace() 195 | } 196 | 197 | } 198 | 199 | fun makeFile(path: String) { 200 | val file = File(config.reportDir, path).toPath() 201 | if (Files.exists(file)) { 202 | Files.delete(file) 203 | } 204 | 205 | file.getParent().toFile().mkdirs() 206 | 207 | Files.copy( 208 | this.javaClass.getClassLoader().getResourceAsStream(path), 209 | file) 210 | } 211 | 212 | override fun buildStarted(gradle: Gradle) { 213 | } 214 | 215 | override fun projectsEvaluated(gradle: Gradle) { 216 | } 217 | 218 | override fun projectsLoaded(gradle: Gradle) { 219 | } 220 | 221 | override fun settingsEvaluated(settings: Settings) { 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /inspector/src/main/kotlin/com/jakeout/gradle/inspector/InspectorPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.jakeout.gradle.inspector 2 | 3 | import org.gradle.api.Plugin 4 | import org.gradle.api.Project 5 | 6 | class InspectorPlugin : Plugin { 7 | override fun apply(project: Project?) { 8 | if (project != null) { 9 | listener = InspectorGradleListener.setupFolderDiff(project) 10 | } 11 | } 12 | 13 | var listener: InspectorGradleListener? = null 14 | } 15 | -------------------------------------------------------------------------------- /inspector/src/main/kotlin/com/jakeout/gradle/inspector/InspectorPluginExtension.kt: -------------------------------------------------------------------------------- 1 | package com.jakeout.gradle.inspector 2 | 3 | import java.io.File 4 | 5 | public open class InspectorPluginExtension { 6 | 7 | public open var index: File? = null 8 | } -------------------------------------------------------------------------------- /inspector/src/main/kotlin/com/jakeout/gradle/inspector/tasks/TaskAnalyzer.kt: -------------------------------------------------------------------------------- 1 | package com.jakeout.gradle.inspector.tasks 2 | 3 | import com.jakeout.gradle.inspector.InspectorConfig 4 | import com.jakeout.gradle.inspector.tasks.model.* 5 | import com.jakeout.gradle.utils.DiffUtil 6 | import com.zutubi.diff.PatchFile 7 | import com.zutubi.diff.unified.UnifiedHunk 8 | import com.zutubi.diff.unified.UnifiedPatch 9 | import org.apache.commons.io.FileUtils 10 | import org.apache.commons.io.FilenameUtils 11 | import org.gradle.api.Task 12 | import org.gradle.api.file.FileCollection 13 | import org.gradle.api.logging.Logging 14 | import org.gradle.api.tasks.TaskState 15 | import java.util.HashMap 16 | import java.util.LinkedList 17 | 18 | class TaskAnalyzer(val config: InspectorConfig, val task: Task, val buildStarted: Long) { 19 | 20 | companion object { 21 | fun evaluateDiff(executionResults: TaskExecutionResults, patch: PatchFile?, binaryPatchFile: PatchFile?): TaskDiffResults { 22 | var filesTouched = 0 23 | var added = 0 24 | var removed = 0 25 | val changesByType = HashMap() 26 | val changedFiles = LinkedList() 27 | var anyUndeclaredChanges = false 28 | val contentChange = LinkedList() 29 | if (patch != null && binaryPatchFile != null) { 30 | val patches = patch.getPatches() 31 | val binaryPatches = binaryPatchFile.getPatches() 32 | val isBinary = HashMap() 33 | for (p in binaryPatches) { 34 | isBinary.put(p.getNewFile(), true) 35 | } 36 | 37 | for (p in patches) { 38 | isBinary.put(p.getNewFile(), false) 39 | filesTouched++ 40 | 41 | val rootDirOfDeclaredOutput: String? = findOutput(p.getNewFile(), executionResults.task.getOutputs().getFiles()) 42 | 43 | if (rootDirOfDeclaredOutput == null) { 44 | anyUndeclaredChanges = true 45 | } 46 | 47 | incrementExtension(changesByType, p.getNewFile()) 48 | 49 | val changes = LinkedList() 50 | if (p is UnifiedPatch) { 51 | for (hunk in p.getHunks()) { 52 | changes.add(hunk) 53 | for (l in hunk.getLines()) { 54 | when (l.getType()) { 55 | UnifiedHunk.LineType.ADDED -> added++ 56 | UnifiedHunk.LineType.DELETED -> removed++ 57 | UnifiedHunk.LineType.COMMON -> { 58 | } 59 | } 60 | } 61 | } 62 | } 63 | 64 | contentChange.add(FileContentsDifference( 65 | p.getNewFile(), 66 | p.getOldFile(), 67 | p.getType(), 68 | changes)) 69 | } 70 | 71 | for (p in binaryPatches) { 72 | filesTouched++ 73 | if (isBinary.get(p.getNewFile())) { 74 | var state = FileState.UNKNOWN 75 | if (p is UnifiedPatch) { 76 | for (h in p.getHunks()) { 77 | for (l in h.getLines()) { 78 | when (l.getType()) { 79 | UnifiedHunk.LineType.ADDED -> FileState.ADDED 80 | UnifiedHunk.LineType.DELETED -> FileState.DELETED 81 | } 82 | } 83 | } 84 | } 85 | incrementExtension(changesByType, p.getNewFile()); 86 | changedFiles.add(FileDifference(p.getNewFile(), p.getOldFile(), state)) 87 | } 88 | } 89 | } 90 | 91 | return TaskDiffResults( 92 | filesTouched = filesTouched, 93 | hunksAdded = added, 94 | hunksRemoved = removed, 95 | anyUndeclaredChanges = anyUndeclaredChanges, 96 | changesByType = changesByType, 97 | changedContents = contentChange, 98 | changedFiles = changedFiles) 99 | } 100 | 101 | fun findOutput(file: String, files: FileCollection): String? { 102 | for (f in files) { 103 | if (file.equals(f.getAbsolutePath()) || file.startsWith(f.getAbsolutePath() + '/')) { 104 | return f.getAbsolutePath(); 105 | } 106 | } 107 | return null 108 | } 109 | 110 | private fun incrementExtension(changesByType: HashMap, file: String) { 111 | val extension = FilenameUtils.getExtension(file) 112 | 113 | val count = changesByType.get(extension) 114 | if (count == null) { 115 | changesByType.put(extension, 1) 116 | } else { 117 | changesByType.put(extension, count + 1) 118 | } 119 | } 120 | } 121 | 122 | var results: AnalysisResult? = null 123 | 124 | fun onAfterExecute(state: TaskState): AnalysisResult? { 125 | try { 126 | val execution = getExecutionResults() 127 | 128 | val patchFile: PatchFile? = DiffUtil.diff( 129 | config.projectBuildDir, 130 | config.taskDir(task), 131 | config.taskOut(task), false) 132 | 133 | val binaryPatchFile: PatchFile? = DiffUtil.diff( 134 | config.projectBuildDir, 135 | config.taskDir(task), 136 | config.taskOut(task), true) 137 | 138 | val results = AnalysisResult( 139 | diffResults = evaluateDiff(execution, patchFile, binaryPatchFile), 140 | executionResults = execution, 141 | comparisonResults = getComparisonResults(execution)) 142 | this.results = results 143 | } catch (e: Exception) { 144 | Logging.getLogger(javaClass()).error("Analyzer failed to diff task: " + task.getName(), e) 145 | } finally { 146 | // Don't clean up, e.g., if the user wants to preserve this data to compare against the next build. 147 | if (config.cleanUpEachTask) { 148 | FileUtils.deleteDirectory(config.taskDir(task)) 149 | } 150 | } 151 | return this.results 152 | } 153 | 154 | fun getComparisonResults(execution: TaskExecutionResults): TaskDiffResults? { 155 | if (!config.compareBuild) { 156 | return null 157 | } else { 158 | val compareTaskOut = config.compareTaskOut(task) 159 | val comparePatchFile = DiffUtil.diff(config.compareTaskDir(task), config.taskDir(task), compareTaskOut, false) 160 | val binaryComparePatchFile = DiffUtil.diff(config.compareTaskDir(task), config.taskDir(task), compareTaskOut, true) 161 | 162 | return evaluateDiff(execution, comparePatchFile, binaryComparePatchFile) 163 | } 164 | } 165 | 166 | fun getExecutionResults(): TaskExecutionResults { 167 | val endTime = System.currentTimeMillis() 168 | val dependsOnTasks = LinkedList() 169 | for (t in task.getDependsOn()) { 170 | if (t is Task) { 171 | dependsOnTasks.add(t) 172 | } 173 | } 174 | 175 | val tReportRel = config.taskReport(task) 176 | 177 | return TaskExecutionResults( 178 | name = task.getName(), 179 | path = tReportRel, 180 | dependsOnTasks = dependsOnTasks, 181 | task = task, 182 | startTime = buildStarted, 183 | endTime = endTime) 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /inspector/src/main/kotlin/com/jakeout/gradle/inspector/tasks/model/AnalysisResult.kt: -------------------------------------------------------------------------------- 1 | package com.jakeout.gradle.inspector.tasks.model 2 | 3 | data class AnalysisResult( 4 | val diffResults: TaskDiffResults, 5 | val executionResults: TaskExecutionResults, 6 | val comparisonResults: TaskDiffResults?) 7 | -------------------------------------------------------------------------------- /inspector/src/main/kotlin/com/jakeout/gradle/inspector/tasks/model/TaskDiffResults.kt: -------------------------------------------------------------------------------- 1 | package com.jakeout.gradle.inspector.tasks.model 2 | 3 | import com.zutubi.diff.PatchType 4 | import com.zutubi.diff.unified.UnifiedHunk 5 | 6 | public enum class FileState { ADDED, DELETED, CHANGED, UNKNOWN } 7 | 8 | data class FileDifference( 9 | val file: String, 10 | val comparedFile: String, 11 | val state: FileState) 12 | 13 | data class FileContentsDifference( 14 | val file: String, 15 | val comparedFile: String, 16 | val type: PatchType, 17 | val changes: List) 18 | 19 | data class TaskDiffResults( 20 | val filesTouched: Int, 21 | val hunksAdded: Int, 22 | val hunksRemoved: Int, 23 | val anyUndeclaredChanges: Boolean, 24 | val changesByType: Map, 25 | val changedContents: List, 26 | val changedFiles: List) 27 | -------------------------------------------------------------------------------- /inspector/src/main/kotlin/com/jakeout/gradle/inspector/tasks/model/TaskExecutionResults.kt: -------------------------------------------------------------------------------- 1 | package com.jakeout.gradle.inspector.tasks.model 2 | 3 | import org.gradle.api.Task 4 | 5 | data class TaskExecutionResults( 6 | val startTime: Long, 7 | val endTime: Long, 8 | val path: String, 9 | val name: String, 10 | val dependsOnTasks: List, 11 | val task: Task) 12 | -------------------------------------------------------------------------------- /inspector/src/main/kotlin/com/jakeout/gradle/inspector/tasks/output/DiffWriter.kt: -------------------------------------------------------------------------------- 1 | package com.jakeout.gradle.inspector.tasks.output 2 | 3 | import com.jakeout.gradle.inspector.tasks.TaskAnalyzer 4 | import com.jakeout.gradle.inspector.tasks.model.* 5 | import com.zutubi.diff.unified.UnifiedHunk 6 | import kotlinx.html.* 7 | import org.apache.commons.io.FilenameUtils 8 | import java.io.BufferedWriter 9 | import java.io.File 10 | import java.io.FileWriter 11 | 12 | class DiffWriter { 13 | companion object { 14 | val SUPPORTED_IMAGE_FORMATS = setOf("png", "jpg", "jpeg", "svg", "gif") 15 | 16 | fun write(out: File, 17 | executionResults: TaskExecutionResults, 18 | diffResults: TaskDiffResults, 19 | comparisonResults: TaskDiffResults?, 20 | overlappingTasks: List) { 21 | val bw = BufferedWriter(FileWriter(out)) 22 | 23 | bw.write( 24 | html { 25 | head { 26 | title { +"Changes from ${executionResults.name}" } 27 | link(rel = "stylesheet", href = "font/css/font-awesome.min.css") 28 | link(rel = "stylesheet", href = "diff.css") 29 | } 30 | 31 | body { 32 | if (!overlappingTasks.isEmpty()) { 33 | overlappingTaskHtml(diffResults, overlappingTasks) 34 | } 35 | 36 | if (comparisonResults != null) { 37 | h1 { +"Comparison to last build" } 38 | writeDiff(comparisonResults, executionResults) 39 | // Attached to next output 40 | } 41 | 42 | h1 { +"Changes from start of task to task completion" } 43 | writeDiff(diffResults, executionResults) 44 | } 45 | 46 | }.toString() 47 | ) 48 | bw.close() 49 | } 50 | 51 | private fun HtmlBodyTag.writeDiff(diff: TaskDiffResults, executionResults: TaskExecutionResults) { 52 | if (diff.changesByType.size() == 0) { 53 | h2 { +"No changes from start of task to task completion" } 54 | return 55 | } 56 | 57 | if (diff.changedFiles.size() > 0) { 58 | h2 { +"Non-plaintext Files" } 59 | for (file in diff.changedFiles) { 60 | writeBinary(file, executionResults) 61 | } 62 | } 63 | 64 | if (diff.changedContents.size() > 0) { 65 | h2 { +"Plaintext Files" } 66 | for (patch in diff.changedContents) { 67 | writePatch(patch, executionResults) 68 | } 69 | } 70 | } 71 | 72 | fun HtmlBodyTag.overlappingTaskHtml(taskDiffResults: TaskDiffResults, overlappingTasks: List) { 73 | p { 74 | +(if (taskDiffResults.anyUndeclaredChanges) 75 | "Note that this task was run in parallel (potentially explaining undeclared changes) with:" else 76 | "Note that this task was run in parallel with:") 77 | 78 | ul { 79 | for (it in overlappingTasks) li { +it } 80 | } 81 | } 82 | } 83 | 84 | fun HtmlBodyTag.writeBinary(difference: FileDifference, taskExecutionResults: TaskExecutionResults) { 85 | div(c = "binaryFile changeBlock") { 86 | div(c = "changeHeader") { 87 | span(c = difference.state.toString().toLowerCase()) { } 88 | writeName(difference.file, difference.state, taskExecutionResults) 89 | } 90 | visualize(difference.file) 91 | } 92 | } 93 | 94 | fun HtmlBodyTag.writePatch(difference: FileContentsDifference, taskExecutionResults: TaskExecutionResults) { 95 | div(c = "patchFile changeBlock") { 96 | div(c = "changeHeader") { 97 | writeName(difference.file, null, taskExecutionResults) 98 | } 99 | visualize(difference.file) 100 | div(c = "patches") { 101 | for (hunk in difference.changes) { 102 | writePatch(hunk) 103 | } 104 | } 105 | } 106 | } 107 | 108 | fun HtmlBodyTag.visualize(newFile: String) { 109 | val extension = FilenameUtils.getExtension(newFile) 110 | if (SUPPORTED_IMAGE_FORMATS.contains(extension)) { 111 | div(c = "fileView") { img { src = DirectLink(newFile) } } 112 | } 113 | } 114 | 115 | fun HtmlBodyTag.writePatch(hunk: UnifiedHunk) { 116 | div(c = "hunk") { 117 | val hunkItr = hunk.getLines().iterator() 118 | 119 | for (line in 1..4) { 120 | if (!hunkItr.hasNext()) { 121 | break 122 | } 123 | writeLine(hunkItr.next()) 124 | } 125 | 126 | details { 127 | summary { +"..." } 128 | while (hunkItr.hasNext()) { 129 | writeLine(hunkItr.next()) 130 | } 131 | 132 | } 133 | } 134 | } 135 | 136 | fun HtmlBodyTag.writeName(name: String, state: FileState? = null, taskExecutionResults: TaskExecutionResults) { 137 | val rootDirOfDeclaredOutput: String? = TaskAnalyzer.findOutput(name, taskExecutionResults.task.getOutputs().getFiles()) 138 | 139 | if (rootDirOfDeclaredOutput != null) { 140 | knownFile() 141 | noteFileState(state) 142 | span(c = "filename") { +rootDirOfDeclaredOutput } 143 | val suffix = name.replaceFirstLiteral(rootDirOfDeclaredOutput, "") 144 | 145 | if (!suffix.isEmpty()) { 146 | span(c = "filenameSuffix") { +suffix } 147 | } 148 | } else { 149 | unknownFile() 150 | noteFileState(state) 151 | span(c = "filenameSuffix") { +name } 152 | } 153 | } 154 | 155 | fun HtmlBodyTag.writeLine(line: UnifiedHunk.Line) { 156 | when (line.getType()) { 157 | UnifiedHunk.LineType.ADDED -> div(c = "added") { code { +"+ ${line.getContent()}" } } 158 | UnifiedHunk.LineType.DELETED -> div(c = "deleted") { code { +"- ${line.getContent()}" } } 159 | UnifiedHunk.LineType.COMMON -> div(c = "common") { code { +"  ${line.getContent()}" } } 160 | } 161 | } 162 | 163 | fun HtmlBodyTag.noteFileState(fileState: FileState?) { 164 | if (fileState != null) { 165 | span(c = "fa-stack file-type-icon") { 166 | if (fileState.equals(FileState.ADDED)) { 167 | i(c = "font-shadow-added fa fa-circle fa-stack-2x") 168 | i(c = "fa fa-plus fa-stack-1x fa-stacked-symbol") 169 | i(c = "fa fa-file-o fa-stack-1x") 170 | } else if (fileState.equals(FileState.DELETED)) { 171 | i(c = "font-shadow-deleted fa fa-circle fa-stack-2x") 172 | i(c = "fa fa-minus fa-stack-1x fa-stacked-symbol") 173 | i(c = "fa fa-file-o fa-stack-1x") 174 | } else { 175 | i(c = "fa fa-file-o fa-stack-1x") 176 | } 177 | } 178 | } 179 | } 180 | 181 | fun HtmlBodyTag.unknownFile() { 182 | span(c = "fa-stack file-type-icon") { 183 | i(c = "fa fa-question fa-stack-1x") 184 | } 185 | span(c = "warning") { em { +"undeclared output" } } 186 | } 187 | 188 | 189 | fun HtmlBodyTag.knownFile() { 190 | // empty -- display nothing for known files 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /inspector/src/main/kotlin/com/jakeout/gradle/inspector/tasks/output/IndexWriter.kt: -------------------------------------------------------------------------------- 1 | package com.jakeout.gradle.inspector.tasks.output 2 | 3 | import com.jakeout.gradle.inspector.tasks.model.AnalysisResult 4 | import kotlinx.html.* 5 | import java.io.* 6 | import java.nio.file.Files 7 | 8 | object IndexWriter { 9 | val REPLACED_NODES = "// !-- REPLACE WITH NODES --!" 10 | val REPLACED_EDGES = "// !-- REPLACE WITH EDGES --!" 11 | val REPLACED_SIDEBAR = "" 12 | 13 | fun write(index: File, 14 | subProjectIndicies: Map, 15 | children: List, 16 | visFile: File) { 17 | updateFile(index, getSidebarReplacer(children, subProjectIndicies)) 18 | updateFile(visFile, getGraphReplacer(children)) 19 | } 20 | 21 | fun getSidebarReplacer(children: List, subProjectIndicies: Map) 22 | : (BufferedReader, BufferedWriter) -> Unit { 23 | return { br, bw -> 24 | var s = br.readLine() 25 | val partial = partial { 26 | while (s != null) { 27 | if (s.contains(REPLACED_SIDEBAR)) { 28 | if (subProjectIndicies.size() > 0) { 29 | ul(c = "subprojects") { 30 | for (entry in subProjectIndicies.entrySet()) { 31 | li { 32 | a(c = "subproject") { 33 | attribute("href", entry.getValue().toString()) 34 | +entry.getKey() 35 | } 36 | } 37 | } 38 | } 39 | } 40 | if (children.size() > 0) { 41 | ul(c = "tasks") { 42 | for (analysisResult in children) { 43 | val executionResults = analysisResult.executionResults 44 | val diffResults = analysisResult.diffResults 45 | if (diffResults.filesTouched > 0) { 46 | li { 47 | a(c = "task") { 48 | attribute("href", executionResults.path) 49 | +executionResults.name 50 | } 51 | } 52 | } else { 53 | li { 54 | span(c = "task") { 55 | +executionResults.name 56 | } 57 | } 58 | } 59 | } 60 | } 61 | } 62 | } else { 63 | !(s + "\n") 64 | } 65 | s = br.readLine() 66 | } 67 | } 68 | bw.write(partial.toString()) 69 | } 70 | } 71 | 72 | fun getGraphReplacer(children: List) 73 | : (BufferedReader, BufferedWriter) -> Unit { 74 | return { br, bw -> 75 | var s = br.readLine() 76 | while (s != null) { 77 | if (s.equals(REPLACED_NODES)) { 78 | writeNodes(bw, children) 79 | } else if (s.equals(REPLACED_EDGES)) { 80 | writeEdges(bw, children) 81 | } else { 82 | bw.write(s) 83 | bw.newLine() 84 | } 85 | s = br.readLine() 86 | } 87 | } 88 | } 89 | 90 | fun updateFile(visFile: File, replacement: (BufferedReader, BufferedWriter) -> Unit) { 91 | val tmpFile = File(visFile.getAbsolutePath() + ".tmp") 92 | val bw = BufferedWriter(FileWriter(tmpFile)) 93 | val fr = FileReader(visFile) 94 | val br = BufferedReader(fr) 95 | 96 | replacement(br, bw) 97 | 98 | bw.close() 99 | br.close() 100 | Files.delete(visFile.toPath()) 101 | Files.move(tmpFile.toPath(), visFile.toPath()) 102 | } 103 | 104 | fun writeNodes(bw: BufferedWriter, results: List) { 105 | var first = true 106 | for (analysisResult in results) { 107 | val tes = analysisResult.executionResults 108 | val d = analysisResult.diffResults 109 | if (!first) { 110 | bw.write(", \n") 111 | } 112 | 113 | first = false 114 | val color = 115 | if (d.changesByType.isEmpty()) 116 | "#fff" 117 | else 118 | if (d.anyUndeclaredChanges) "#88f" else "#8f8" 119 | 120 | val description = if (d.changesByType.isEmpty()) "" else d.changesByType.toString() 121 | val style = 122 | "basefill: \"$color\", \n" + 123 | "style: \"fill:$color\", \n" 124 | bw.write("\"${tes.name}\": { \n $style" + 125 | "description: \"$description\" }") 126 | } 127 | } 128 | 129 | fun writeEdges(bw: BufferedWriter, tasks: List) { 130 | val names = tasks.map { d: AnalysisResult -> d.executionResults.name }.toSet() 131 | 132 | for (analysisResult in tasks) { 133 | val tes = analysisResult.executionResults 134 | for (dependsOn in tes.dependsOnTasks) { 135 | // val inputs = TaskUtil.dependentFiles(tes.task, dependsOn) 136 | // TODO: add an on hover effect 137 | if (names.contains(tes.name) && names.contains(dependsOn.getName())) { 138 | bw.write("g.setEdge(\"${dependsOn.getName()}\", \"${tes.name}\", { label: \"\" });") 139 | bw.newLine() 140 | } 141 | } 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /inspector/src/main/kotlin/com/jakeout/gradle/utils/DiffUtil.kt: -------------------------------------------------------------------------------- 1 | package com.jakeout.gradle.utils 2 | 3 | import com.zutubi.diff.PatchFile 4 | import com.zutubi.diff.PatchFileParser 5 | import com.zutubi.diff.unified.UnifiedPatchParser 6 | import org.apache.commons.io.FileUtils 7 | import org.codehaus.groovy.runtime.DefaultGroovyMethods 8 | import org.codehaus.groovy.runtime.ProcessGroovyMethods 9 | import java.io.* 10 | import java.nio.file.Files 11 | import java.util.regex.Pattern 12 | 13 | public object DiffUtil { 14 | 15 | fun backup(f: File, target: File) { 16 | FileUtils.copyDirectory(f, target) 17 | } 18 | 19 | fun diff(source: File, target: File, out: File, includeBinary: Boolean): PatchFile? { 20 | 21 | val cmd = "diff -rNu ${target.getAbsolutePath()} ${source.getAbsolutePath()}" 22 | val sout = StringBuffer() 23 | val serr = StringBuffer() 24 | val proc = DefaultGroovyMethods.execute(cmd) 25 | ProcessGroovyMethods.consumeProcessOutput(proc, sout, serr) 26 | proc.waitFor() 27 | val soutStr: String = sout.toString() 28 | if (soutStr.length() != 0) { 29 | val bw = BufferedWriter(FileWriter(out)); 30 | bw.write(soutStr); 31 | bw.close(); 32 | } 33 | 34 | if (out.exists() && out.length() > 0) { 35 | if (includeBinary) { 36 | updateFile(out, binaryWrap()) 37 | } 38 | return PatchFileParser(UnifiedPatchParser()).parse(FileReader(out)) 39 | } else { 40 | return null 41 | } 42 | } 43 | 44 | fun updateFile(diff: File, replacement: (BufferedReader, BufferedWriter) -> Unit) { 45 | val tmpFile = File(diff.getAbsolutePath() + ".tmp") 46 | val bw = BufferedWriter(FileWriter(tmpFile)) 47 | val fr = FileReader(diff) 48 | val br = BufferedReader(fr) 49 | 50 | replacement(br, bw) 51 | 52 | bw.close() 53 | br.close() 54 | Files.delete(diff.toPath()) 55 | Files.move(tmpFile.toPath(), diff.toPath()) 56 | } 57 | 58 | fun binaryWrap(): (BufferedReader, BufferedWriter) -> Unit { 59 | return { br, bw -> 60 | var s = br.readLine() 61 | while (s != null) { 62 | bw.write(binaryWrap(s)) 63 | s = br.readLine() 64 | } 65 | } 66 | } 67 | 68 | fun binaryWrap(diff: String): String { 69 | val pattern = Pattern.compile(("Binary files (.*) and (.*) differ")) 70 | val matcher = pattern.matcher(diff.toString()) 71 | if (matcher.matches() && matcher.groupCount() == 2) { 72 | val from = matcher.group(1) 73 | val to = matcher.group(2) 74 | val fromFile = File(from) 75 | val toFile = File(to) 76 | var retString = 77 | "diff -rNuaq " + from + " " + to + "\n" + 78 | "--- " + from + "\t1969-12-31 19:00:00.000000000 -0500 \n" + 79 | "+++ " + to + "\t1969-12-31 19:00:00.000000000 -0500 \n" 80 | if (fromFile.exists() && toFile.exists()) { 81 | // should never happen 82 | retString += "@@ -0,0 +0,0 @@\n" 83 | } else if (fromFile.exists() && !toFile.exists()) { 84 | retString += "@@ -1,1 +0,0 @@\n-" 85 | } else if (!fromFile.exists() && toFile.exists()) { 86 | retString += "@@ +1,1 +0,0 @@\n+" 87 | } else { 88 | // should never happen 89 | retString += "@@ -0,0 +0,0 @@\n" 90 | } 91 | return retString 92 | } 93 | return diff + "\n" 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /inspector/src/main/kotlin/com/jakeout/gradle/utils/StringUtil.kt: -------------------------------------------------------------------------------- 1 | package com.jakeout.gradle.utils 2 | 3 | import com.google.common.base.Function 4 | import com.google.common.base.Strings 5 | 6 | public object StringUtil { 7 | 8 | val TAB_CHARACTER: String = " " 9 | 10 | fun tabAppender(numTabs: Int): Function { 11 | return object : Function { 12 | override fun apply(s: String?): String? { 13 | return Strings.repeat(TAB_CHARACTER, numTabs) + s!!.replace("\n", "\n" + Strings.repeat(TAB_CHARACTER, numTabs)) 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /inspector/src/main/kotlin/com/jakeout/gradle/utils/TaskUtil.kt: -------------------------------------------------------------------------------- 1 | package com.jakeout.gradle.utils 2 | 3 | import org.gradle.api.Task 4 | import java.util.LinkedList 5 | 6 | public object TaskUtil { 7 | 8 | fun dependentFiles(parent: Task, child: Task): List { 9 | val dependentFiles = LinkedList() 10 | 11 | for (intoParent in parent.getInputs().getFiles()) { 12 | for (outFromChild in child.getOutputs().getFiles()) { 13 | if (intoParent.getAbsolutePath().equals(outFromChild.getAbsolutePath())) { 14 | dependentFiles.add(intoParent.getAbsolutePath()) 15 | } else if (intoParent.getAbsolutePath().startsWith(outFromChild.getAbsolutePath())) { 16 | dependentFiles.add(intoParent.getAbsolutePath() + " (" + outFromChild.getAbsolutePath() + ")") 17 | } 18 | } 19 | } 20 | return dependentFiles 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /inspector/src/main/kotlin/kotlinx/html/AttributeTypes.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.html 2 | 3 | public abstract class Attribute(val name: String) { 4 | public fun get(tag: HtmlTag, property: PropertyMetadata): T { 5 | return decode(tag[name]); 6 | } 7 | 8 | public fun set(tag: HtmlTag, property: PropertyMetadata, value: T) { 9 | tag[name] = encode(value); 10 | } 11 | 12 | public abstract fun encode(t: T): String 13 | public abstract fun decode(s: String): T 14 | } 15 | 16 | public open class StringAttribute(name: String) : Attribute(name) { 17 | public override fun encode(t: String): String { 18 | return t // TODO: it actually might need HTML escaping 19 | } 20 | 21 | public override fun decode(s: String): String { 22 | return s // TODO: it actually might need decode 23 | } 24 | } 25 | 26 | public class TextAttribute(name: String) : StringAttribute(name) 27 | public class RegexpAttribute(name: String) : StringAttribute(name) 28 | public class IdAttribute(name: String) : StringAttribute(name) 29 | public class MimeAttribute(name: String) : StringAttribute(name) 30 | 31 | public class IntAttribute(name: String) : Attribute(name) { 32 | public override fun encode(t: Int): String { 33 | return t.toString() 34 | } 35 | 36 | public override fun decode(s: String): Int { 37 | return s.toInt() 38 | } 39 | } 40 | 41 | public open class BooleanAttribute(name: String, val trueValue: String = "true", val falseValue: String = "false") : Attribute(name) { 42 | public override fun encode(t: Boolean): String { 43 | return if (t) trueValue else falseValue 44 | } 45 | 46 | public override fun decode(s: String): Boolean { 47 | return when (s) { 48 | trueValue -> true 49 | falseValue -> false 50 | else -> throw RuntimeException("Unknown value for $name=$s") 51 | } 52 | } 53 | } 54 | 55 | public class TickerAttribute(name: String) : BooleanAttribute(name, name, "") 56 | 57 | public class LinkAttribute(name: String) : Attribute(name) { 58 | public override fun encode(t: Link): String { 59 | return t.href() 60 | } 61 | 62 | public override fun decode(s: String): Link { 63 | return DirectLink(s) 64 | } 65 | } 66 | 67 | public interface StringEnum> : Enum { 68 | public val value: String get() = name() 69 | } 70 | 71 | public class EnumAttribute>(name: String, val klass: Class) : Attribute(name) { 72 | public override fun encode(t: T): String { 73 | return t.value 74 | } 75 | 76 | public override fun decode(s: String): T { 77 | for (c in klass.getEnumConstants()) { 78 | if (encode(c) == s) return c 79 | } 80 | 81 | throw RuntimeException("Can't decode '$s' as value of '${klass.getName()}'") 82 | } 83 | } 84 | 85 | public class MimeTypesAttribute(name: String) : Attribute>(name) { 86 | public override fun encode(t: List): String { 87 | return t.join(",") 88 | } 89 | 90 | public override fun decode(s: String): List { 91 | return s.split(',').map { it.trim() } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /inspector/src/main/kotlin/kotlinx/html/Attributes.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.html 2 | 3 | public object Attributes { 4 | public val id: Attribute = IdAttribute("id") 5 | public val style: Attribute = StringAttribute("style") 6 | public val title: Attribute = IdAttribute("title") 7 | public val href: Attribute = LinkAttribute("href") 8 | public val cite: Attribute = LinkAttribute("cite") 9 | public val rel: Attribute = StringAttribute("rel") 10 | public val target: Attribute = StringAttribute("target") 11 | public val mimeType: Attribute = MimeAttribute("type") 12 | public val width: Attribute = IntAttribute("width") 13 | public val height: Attribute = IntAttribute("height") 14 | public val action: Attribute = LinkAttribute("action") 15 | public val enctype: Attribute = StringAttribute("enctype") 16 | public val method: Attribute = StringAttribute("method") 17 | public val src: Attribute = LinkAttribute("src") 18 | public val alt: Attribute = TextAttribute("alt") 19 | public val autocomplete: Attribute = BooleanAttribute("autocomplete", "on", "off") 20 | public val autofocus: Attribute = TickerAttribute("autofocus") 21 | public val checked: Attribute = TickerAttribute("checked") 22 | public val disabled: Attribute = TickerAttribute("disabled") 23 | public val maxlength: Attribute = IntAttribute("maxlength") 24 | public val multiple: Attribute = TickerAttribute("multiple") 25 | public val inputType: Attribute = StringAttribute("type") 26 | public val buttonType: Attribute = StringAttribute("type") 27 | public val name: Attribute = StringAttribute("name") 28 | public val pattern: Attribute = RegexpAttribute("pattern") 29 | public val placeholder: Attribute = TextAttribute("placeholder") 30 | public val readonly: Attribute = TickerAttribute("readonly") 31 | public val required: Attribute = TickerAttribute("required") 32 | public val size: Attribute = IntAttribute("size") 33 | public val step: Attribute = IntAttribute("step") 34 | public val value: Attribute = StringAttribute("value") 35 | public val forId: Attribute = IdAttribute("for") 36 | public val label: Attribute = TextAttribute("label") 37 | public val selected: Attribute = TickerAttribute("selected") 38 | public val cols: Attribute = IntAttribute("cols") 39 | public val rows: Attribute = IntAttribute("rows") 40 | public val wrap: Attribute = StringAttribute("wrap") 41 | } 42 | -------------------------------------------------------------------------------- /inspector/src/main/kotlin/kotlinx/html/HtmlBody.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.html 2 | 3 | val empty_contents: T.() -> Unit = { } 4 | 5 | public abstract class HtmlBodyTag(containingTag: HtmlTag?, name: String, renderStyle: RenderStyle = RenderStyle.expanded, contentStyle: ContentStyle = ContentStyle.block) : HtmlTag(containingTag, name, renderStyle, contentStyle) { 6 | public var id: String by Attributes.id 7 | public var style: String by Attributes.style 8 | 9 | public fun addClass(c: String) { 10 | val old = tryGet("class") 11 | setClass(if (old != null && old.isNotEmpty()) "$old $c" else c) 12 | } 13 | 14 | public fun setClass(c: String) { 15 | attribute("class", c) 16 | } 17 | } 18 | 19 | public fun HtmlBodyTag.contentTag(tag: T, styleClass: String? = null, contents: T.() -> Unit = empty_contents) { 20 | if (styleClass != null) tag.addClass(styleClass) 21 | build(tag, contents) 22 | } 23 | 24 | 25 | public fun HtmlBodyTag.a(c: String? = null, contents: A.() -> Unit = empty_contents): Unit = contentTag(A(this), c, contents) 26 | public open class A(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "a", contentStyle = ContentStyle.propagate) { 27 | public var href: Link by Attributes.href 28 | public var rel: String by Attributes.rel 29 | public var target: String by Attributes.target 30 | } 31 | 32 | public fun HtmlBodyTag.button(c: String? = null, contents: BUTTON.() -> Unit = empty_contents): Unit = contentTag(BUTTON(this), c, contents) 33 | public open class BUTTON(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "button", RenderStyle.expanded, ContentStyle.propagate) { 34 | public var name: String by Attributes.name 35 | public var value: String by Attributes.value 36 | public var buttonType: String by Attributes.buttonType 37 | public var href: Link by Attributes.href 38 | } 39 | 40 | public fun HTML.body(c: String? = null, contents: BODY.() -> Unit) { 41 | val tag = BODY(this) 42 | if (c != null) tag.addClass(c) 43 | build(tag, contents) 44 | } 45 | 46 | public class BODY(containingTag: HTML) : HtmlBodyTag(containingTag, "body") 47 | 48 | public fun HtmlBodyTag.hr(c: String? = null): Unit = contentTag(HR(this), c) 49 | public fun HtmlBodyTag.br(c: String? = null): Unit = contentTag(BR(this), c) 50 | public fun HtmlBodyTag.wbr(c: String? = null): Unit = contentTag(WBR(this), c) 51 | public fun HtmlBodyTag.div(c: String? = null, contents: DIV.() -> Unit = empty_contents): Unit = contentTag(DIV(this), c, contents) 52 | public fun HtmlBodyTag.b(c: String? = null, contents: B.() -> Unit = empty_contents): Unit = contentTag(B(this), c, contents) 53 | public fun HtmlBodyTag.i(c: String? = null, contents: I.() -> Unit = empty_contents): Unit = contentTag(I(this), c, contents) 54 | public fun HtmlBodyTag.p(c: String? = null, contents: P.() -> Unit = empty_contents): Unit = contentTag(P(this), c, contents) 55 | public fun HtmlBodyTag.pre(c: String? = null, contents: PRE.() -> Unit = empty_contents): Unit = contentTag(PRE(this), c, contents) 56 | public fun HtmlBodyTag.span(c: String? = null, contents: SPAN.() -> Unit = empty_contents): Unit = contentTag(SPAN(this), c, contents) 57 | public fun HtmlBodyTag.sub(c: String? = null, contents: SUB.() -> Unit = empty_contents): Unit = contentTag(SUB(this), c, contents) 58 | public fun HtmlBodyTag.sup(c: String? = null, contents: SUP.() -> Unit = empty_contents): Unit = contentTag(SUP(this), c, contents) 59 | public fun HtmlBodyTag.ins(c: String? = null, contents: INS.() -> Unit = empty_contents): Unit = contentTag(INS(this), c, contents) 60 | public fun HtmlBodyTag.del(c: String? = null, contents: DEL.() -> Unit = empty_contents): Unit = contentTag(DEL(this), c, contents) 61 | public fun HtmlBodyTag.s(c: String? = null, contents: S.() -> Unit = empty_contents): Unit = contentTag(S(this), c, contents) 62 | public fun HtmlBodyTag.u(c: String? = null, contents: U.() -> Unit = empty_contents): Unit = contentTag(U(this), c, contents) 63 | public fun HtmlBodyTag.abbr(c: String? = null, contents: ABBR.() -> Unit = empty_contents): Unit = contentTag(ABBR(this), c, contents) 64 | public fun HtmlBodyTag.small(c: String? = null, contents: SMALL.() -> Unit = empty_contents): Unit = contentTag(SMALL(this), c, contents) 65 | public fun HtmlBodyTag.mark(c: String? = null, contents: MARK.() -> Unit = empty_contents): Unit = contentTag(MARK(this), c, contents) 66 | public fun HtmlBodyTag.address(c: String? = null, contents: ADDRESS.() -> Unit = empty_contents): Unit = contentTag(ADDRESS(this), c, contents) 67 | public fun HtmlBodyTag.time(c: String? = null, contents: TIME.() -> Unit = empty_contents): Unit = contentTag(TIME(this), c, contents) 68 | public fun HtmlBodyTag.cite(c: String? = null, contents: CITE.() -> Unit = empty_contents): Unit = contentTag(CITE(this), c, contents) 69 | public fun HtmlBodyTag.q(c: String? = null, contents: Q.() -> Unit = empty_contents): Unit = contentTag(Q(this), c, contents) 70 | public fun HtmlBodyTag.blockquote(c: String? = null, contents: BLOCKQUOTE.() -> Unit = empty_contents): Unit = contentTag(BLOCKQUOTE(this), c, contents) 71 | 72 | public open class HR(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "hr", RenderStyle.empty) 73 | public open class BR(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "br", RenderStyle.empty) 74 | public open class WBR(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "wbr", RenderStyle.empty) 75 | public open class DIV(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "div") 76 | public open class B(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "b", contentStyle = ContentStyle.propagate) 77 | public open class I(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "i", contentStyle = ContentStyle.propagate) 78 | public open class P(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "p") 79 | public open class PRE(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "pre") 80 | public open class SPAN(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "span", contentStyle = ContentStyle.propagate) 81 | public open class SUB(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "sub", contentStyle = ContentStyle.propagate) 82 | public open class SUP(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "sup", contentStyle = ContentStyle.propagate) 83 | public open class INS(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "ins", contentStyle = ContentStyle.propagate) 84 | public open class DEL(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "del", contentStyle = ContentStyle.propagate) 85 | public open class S(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "s", contentStyle = ContentStyle.propagate) 86 | public open class U(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "u", contentStyle = ContentStyle.propagate) 87 | public open class ABBR(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "abbr", contentStyle = ContentStyle.propagate) 88 | public open class SMALL(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "small", contentStyle = ContentStyle.propagate) 89 | public open class MARK(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "mark", contentStyle = ContentStyle.propagate) 90 | public open class TIME(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "time") 91 | public open class ADDRESS(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "address") 92 | public open class CITE(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "cite", contentStyle = ContentStyle.propagate) 93 | public open class Q(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "q", contentStyle = ContentStyle.propagate) 94 | public open class BLOCKQUOTE(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "blockquote") { 95 | public var cite: Link by Attributes.cite 96 | } 97 | 98 | public fun HtmlBodyTag.em(c: String? = null, contents: EM.() -> Unit = empty_contents): Unit = contentTag(EM(this), c, contents) 99 | public fun HtmlBodyTag.strong(c: String? = null, contents: STRONG.() -> Unit = empty_contents): Unit = contentTag(STRONG(this), c, contents) 100 | public fun HtmlBodyTag.code(c: String? = null, contents: CODE.() -> Unit = empty_contents): Unit = contentTag(CODE(this), c, contents) 101 | public fun HtmlBodyTag.kbd(c: String? = null, contents: KBD.() -> Unit = empty_contents): Unit = contentTag(KBD(this), c, contents) 102 | public fun HtmlBodyTag.dfn(c: String? = null, contents: DFN.() -> Unit = empty_contents): Unit = contentTag(DFN(this), c, contents) 103 | public fun HtmlBodyTag.samp(c: String? = null, contents: SAMP.() -> Unit = empty_contents): Unit = contentTag(SAMP(this), c, contents) 104 | public fun HtmlBodyTag.variable(c: String? = null, contents: VARIABLE.() -> Unit = empty_contents): Unit = contentTag(VARIABLE(this), c, contents) 105 | public open class EM(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "em", contentStyle = ContentStyle.propagate) 106 | public open class STRONG(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "strong", contentStyle = ContentStyle.propagate) 107 | public open class CODE(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "code") 108 | public open class KBD(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "kbd", contentStyle = ContentStyle.propagate) 109 | public open class DFN(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "dfn", contentStyle = ContentStyle.propagate) 110 | public open class SAMP(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "samp", contentStyle = ContentStyle.propagate) 111 | public open class VARIABLE(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "var", contentStyle = ContentStyle.propagate) 112 | 113 | public fun HtmlBodyTag.progress(c: String? = null, contents: PROGRESS.() -> Unit = empty_contents): Unit = contentTag(PROGRESS(this), c, contents) 114 | public fun HtmlBodyTag.meter(c: String? = null, contents: METER.() -> Unit = empty_contents): Unit = contentTag(METER(this), c, contents) 115 | public open class PROGRESS(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "progress", contentStyle = ContentStyle.propagate) 116 | public open class METER(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "meter", contentStyle = ContentStyle.propagate) 117 | 118 | public fun HtmlBodyTag.dl(c: String? = null, contents: DL.() -> Unit = empty_contents): Unit = contentTag(DL(this), c, contents) 119 | public fun DL.dt(c: String? = null, contents: DT.() -> Unit = empty_contents): Unit = contentTag(DT(this), c, contents) 120 | public fun DL.dd(c: String? = null, contents: DD.() -> Unit = empty_contents): Unit = contentTag(DD(this), c, contents) 121 | 122 | public open class DL(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "dl") 123 | public open class DD(containingTag: DL) : HtmlBodyTag(containingTag, "dd", contentStyle = ContentStyle.propagate) 124 | public open class DT(containingTag: DL) : HtmlBodyTag(containingTag, "dt", contentStyle = ContentStyle.propagate) 125 | 126 | public fun HtmlBodyTag.ul(c: String? = null, contents: UL.() -> Unit = empty_contents): Unit = contentTag(UL(this), c, contents) 127 | public fun HtmlBodyTag.ol(c: String? = null, contents: OL.() -> Unit = empty_contents): Unit = contentTag(OL(this), c, contents) 128 | public fun ListTag.li(c: String? = null, contents: LI.() -> Unit = empty_contents): Unit = contentTag(LI(this), c, contents) 129 | 130 | public abstract class ListTag(containingTag: HtmlBodyTag, name: String) : HtmlBodyTag(containingTag, name) 131 | public open class OL(containingTag: HtmlBodyTag) : ListTag(containingTag, "ol") 132 | public open class UL(containingTag: HtmlBodyTag) : ListTag(containingTag, "ul") 133 | public open class LI(containingTag: ListTag) : HtmlBodyTag(containingTag, "li") 134 | 135 | public fun HtmlBodyTag.h1(c: String? = null, contents: H1.() -> Unit = empty_contents): Unit = contentTag(H1(this), c, contents) 136 | public fun HtmlBodyTag.h2(c: String? = null, contents: H2.() -> Unit = empty_contents): Unit = contentTag(H2(this), c, contents) 137 | public fun HtmlBodyTag.h3(c: String? = null, contents: H3.() -> Unit = empty_contents): Unit = contentTag(H3(this), c, contents) 138 | public fun HtmlBodyTag.h4(c: String? = null, contents: H4.() -> Unit = empty_contents): Unit = contentTag(H4(this), c, contents) 139 | public fun HtmlBodyTag.h5(c: String? = null, contents: H5.() -> Unit = empty_contents): Unit = contentTag(H5(this), c, contents) 140 | public fun HtmlBodyTag.h6(c: String? = null, contents: H6.() -> Unit = empty_contents): Unit = contentTag(H6(this), c, contents) 141 | 142 | public open class H1(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "h1") 143 | public open class H2(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "h2") 144 | public open class H3(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "h3") 145 | public open class H4(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "h4") 146 | public open class H5(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "h5") 147 | public open class H6(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "h6") 148 | 149 | public fun HtmlBodyTag.img(c: String? = null, contents: IMG.() -> Unit = empty_contents): Unit = contentTag(IMG(this), c, contents) 150 | public open class IMG(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "img", RenderStyle.empty, ContentStyle.text) { 151 | public var width: Int by Attributes.width 152 | public var height: Int by Attributes.height 153 | public var src: Link by Attributes.src 154 | public var alt: String by Attributes.alt 155 | } 156 | 157 | 158 | public fun HtmlBodyTag.table(c: String? = null, contents: TABLE.() -> Unit = empty_contents): Unit = contentTag(TABLE(this), c, contents) 159 | public fun TABLE.caption(c: String? = null, contents: CAPTION.() -> Unit = empty_contents): Unit = contentTag(CAPTION(this), c, contents) 160 | public fun TABLE.tbody(c: String? = null, contents: TBODY.() -> Unit = empty_contents): Unit = contentTag(TBODY(this), c, contents) 161 | public fun TABLE.thead(c: String? = null, contents: THEAD.() -> Unit = empty_contents): Unit = contentTag(THEAD(this), c, contents) 162 | public fun TABLE.colgroup(c: String? = null, contents: COLGROUP.() -> Unit = empty_contents): Unit = contentTag(COLGROUP(this), c, contents) 163 | public fun COLGROUP.col(c: String? = null, contents: COL.() -> Unit = empty_contents): Unit = contentTag(COL(this), c, contents) 164 | public fun TABLE.tfoot(c: String? = null, contents: TFOOT.() -> Unit = empty_contents): Unit = contentTag(TFOOT(this), c, contents) 165 | public fun TableTag.tr(c: String? = null, contents: TR.() -> Unit = empty_contents): Unit = contentTag(TR(this), c, contents) 166 | public fun TR.th(c: String? = null, contents: TH.() -> Unit = empty_contents): Unit = contentTag(TH(this), c, contents) 167 | public fun TR.td(c: String? = null, contents: TD.() -> Unit = empty_contents): Unit = contentTag(TD(this), c, contents) 168 | 169 | public abstract class TableTag(containingTag: HtmlBodyTag, name: String) : HtmlBodyTag(containingTag, name) 170 | public open class TABLE(containingTag: HtmlBodyTag) : TableTag(containingTag, "table") 171 | public open class CAPTION(containingTag: TABLE) : HtmlBodyTag(containingTag, "caption") 172 | public open class COLGROUP(containingTag: TABLE) : HtmlBodyTag(containingTag, "colgroup") 173 | public open class COL(containingTag: COLGROUP) : HtmlBodyTag(containingTag, "col") 174 | public open class THEAD(containingTag: TABLE) : TableTag(containingTag, "thead") 175 | public open class TFOOT(containingTag: TABLE) : TableTag(containingTag, "tfoot") 176 | public open class TBODY(containingTag: TABLE) : TableTag(containingTag, "tbody") 177 | public open class TR(containingTag: TableTag) : HtmlBodyTag(containingTag, "tr") 178 | public open class TH(containingTag: TR) : HtmlBodyTag(containingTag, "th") 179 | public open class TD(containingTag: TR) : HtmlBodyTag(containingTag, "td") 180 | 181 | public fun HtmlBodyTag.form(c: String? = null, contents: FORM.() -> Unit = empty_contents): Unit = contentTag(FORM(this), c, contents) 182 | 183 | public open class FORM(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "form") { 184 | public var action: Link by Attributes.action 185 | public var enctype: String by Attributes.enctype 186 | public var method: String by Attributes.method 187 | } 188 | 189 | public fun HtmlBodyTag.select(c: String? = null, contents: SELECT.() -> Unit = empty_contents): Unit = contentTag(SELECT(this), c, contents) 190 | public fun SelectTag.option(c: String? = null, contents: OPTION.() -> Unit = empty_contents): Unit = contentTag(OPTION(this), c, contents) 191 | public fun SELECT.optgroup(c: String? = null, contents: OPTGROUP.() -> Unit = empty_contents): Unit = contentTag(OPTGROUP(this), c, contents) 192 | public abstract class SelectTag(containingTag: HtmlBodyTag, name: String) : HtmlBodyTag(containingTag, name) 193 | public open class SELECT(containingTag: HtmlBodyTag) : SelectTag(containingTag, "select") { 194 | public var name: String by Attributes.name 195 | public var size: Int by Attributes.size 196 | public var multiple: Boolean by Attributes.multiple 197 | public var disabled: Boolean by Attributes.disabled 198 | public var required: Boolean by Attributes.required 199 | } 200 | 201 | public open class OPTION(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "option") { 202 | public var value: String by Attributes.value 203 | public var label: String by Attributes.label 204 | public var disabled: Boolean by Attributes.disabled 205 | public var selected: Boolean by Attributes.selected 206 | } 207 | 208 | public open class OPTGROUP(containingTag: HtmlBodyTag) : SelectTag(containingTag, "optgroup") 209 | 210 | public fun HtmlBodyTag.input(c: String? = null, contents: INPUT.() -> Unit = empty_contents): Unit = contentTag(INPUT(this), c, contents) 211 | public open class INPUT(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "input", RenderStyle.expanded, ContentStyle.propagate) { 212 | public var alt: String by Attributes.alt 213 | public var autocomplete: Boolean by Attributes.autocomplete 214 | public var autofocus: Boolean by Attributes.autofocus 215 | public var checked: Boolean by Attributes.checked 216 | public var disabled: Boolean by Attributes.disabled 217 | public var height: Int by Attributes.height 218 | public var maxlength: Int by Attributes.maxlength 219 | public var multiple: Boolean by Attributes.multiple 220 | public var inputType: String by Attributes.inputType 221 | public var name: String by Attributes.name 222 | public var pattern: String by Attributes.pattern 223 | public var placeholder: String by Attributes.placeholder 224 | public var readonly: Boolean by Attributes.readonly 225 | public var required: Boolean by Attributes.required 226 | public var size: Int by Attributes.size 227 | public var src: Link by Attributes.src 228 | public var step: Int by Attributes.step 229 | public var value: String by Attributes.value 230 | public var width: Int by Attributes.width 231 | } 232 | 233 | public fun HtmlBodyTag.label(c: String? = null, contents: LABEL.() -> Unit = empty_contents): Unit = contentTag(LABEL(this), c, contents) 234 | public open class LABEL(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "label") { 235 | public var forId: String by Attributes.forId 236 | } 237 | 238 | public fun HtmlBodyTag.textarea(c: String? = null, contents: TEXTAREA.() -> Unit = empty_contents): Unit = contentTag(TEXTAREA(this), c, contents) 239 | public open class TEXTAREA(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "textarea") { 240 | public var autofocus: Boolean by Attributes.autofocus 241 | public var cols: Int by Attributes.cols 242 | public var disabled: Boolean by Attributes.disabled 243 | public var maxlength: Int by Attributes.maxlength 244 | public var name: String by Attributes.name 245 | public var placeholder: String by Attributes.placeholder 246 | public var readonly: Boolean by Attributes.readonly 247 | public var required: Boolean by Attributes.required 248 | public var rows: Int by Attributes.rows 249 | public var wrap: String by Attributes.wrap 250 | } 251 | 252 | public fun HtmlBodyTag.fieldset(c: String? = null, contents: FIELDSET.() -> Unit = empty_contents): Unit = contentTag(FIELDSET(this), c, contents) 253 | public fun FIELDSET.legend(c: String? = null, contents: LEGEND.() -> Unit = empty_contents): Unit = contentTag(LEGEND(this), c, contents) 254 | public open class FIELDSET(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "fieldset") 255 | public open class LEGEND(containingTag: FIELDSET) : HtmlBodyTag(containingTag, "legend") 256 | 257 | public fun HtmlBodyTag.figure(c: String? = null, contents: FIGURE.() -> Unit = empty_contents): Unit = contentTag(FIGURE(this), c, contents) 258 | public fun FIGURE.figcaption(c: String? = null, contents: FIGCAPTION.() -> Unit = empty_contents): Unit = contentTag(FIGCAPTION(this), c, contents) 259 | public open class FIGURE(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "figure") 260 | public open class FIGCAPTION(containingTag: FIGURE) : HtmlBodyTag(containingTag, "figcaption") 261 | 262 | public fun HtmlBodyTag.canvas(c: String? = null, contents: CANVAS.() -> Unit = empty_contents): Unit = contentTag(CANVAS(this), c, contents) 263 | public open class CANVAS(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "canvas") { 264 | public var width: Int by Attributes.width 265 | public var height: Int by Attributes.height 266 | } 267 | 268 | public fun HtmlBodyTag.nav(c: String? = null, contents: NAV.() -> Unit = empty_contents): Unit = contentTag(NAV(this), c, contents) 269 | public fun HtmlBodyTag.article(c: String? = null, contents: ARTICLE.() -> Unit = empty_contents): Unit = contentTag(ARTICLE(this), c, contents) 270 | public fun HtmlBodyTag.aside(c: String? = null, contents: ASIDE.() -> Unit = empty_contents): Unit = contentTag(ASIDE(this), c, contents) 271 | public fun HtmlBodyTag.section(c: String? = null, contents: SECTION.() -> Unit = empty_contents): Unit = contentTag(SECTION(this), c, contents) 272 | public fun HtmlBodyTag.header(c: String? = null, contents: HEADER.() -> Unit = empty_contents): Unit = contentTag(HEADER(this), c, contents) 273 | public fun HtmlBodyTag.footer(c: String? = null, contents: FOOTER.() -> Unit = empty_contents): Unit = contentTag(FOOTER(this), c, contents) 274 | public fun HtmlBodyTag.details(c: String? = null, contents: DETAILS.() -> Unit = empty_contents): Unit = contentTag(DETAILS(this), c, contents) 275 | public fun DETAILS.summary(c: String? = null, contents: SUMMARY.() -> Unit = empty_contents): Unit = contentTag(SUMMARY(this), c, contents) 276 | 277 | public open class NAV(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "nav") 278 | public open class ARTICLE(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "article") 279 | public open class ASIDE(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "aside") 280 | public open class SECTION(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "section") 281 | public open class HEADER(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "header") 282 | public open class FOOTER(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "footer") 283 | public open class DETAILS(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "details") 284 | public open class SUMMARY(containingTag: DETAILS) : HtmlBodyTag(containingTag, "summary") 285 | 286 | public fun HtmlBodyTag.audio(c: String? = null, contents: AUDIO.() -> Unit = empty_contents): Unit = contentTag(AUDIO(this), c, contents) 287 | public fun HtmlBodyTag.video(c: String? = null, contents: VIDEO.() -> Unit = empty_contents): Unit = contentTag(VIDEO(this), c, contents) 288 | public fun MediaTag.track(c: String? = null, contents: TRACK.() -> Unit = empty_contents): Unit = contentTag(TRACK(this), c, contents) 289 | public fun MediaTag.source(c: String? = null, contents: SOURCE.() -> Unit = empty_contents): Unit = contentTag(SOURCE(this), c, contents) 290 | public abstract class MediaTag(containingTag: HtmlBodyTag, name: String) : HtmlBodyTag(containingTag, name) 291 | public open class AUDIO(containingTag: HtmlBodyTag) : MediaTag(containingTag, "audio") 292 | public open class VIDEO(containingTag: HtmlBodyTag) : MediaTag(containingTag, "video") 293 | public open class TRACK(containingTag: MediaTag) : HtmlBodyTag(containingTag, "track") 294 | public open class SOURCE(containingTag: MediaTag) : HtmlBodyTag(containingTag, "source") 295 | 296 | public fun HtmlBodyTag.embed(c: String? = null, contents: EMBED.() -> Unit = empty_contents): Unit = contentTag(EMBED(this), c, contents) 297 | public open class EMBED(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "embed") 298 | -------------------------------------------------------------------------------- /inspector/src/main/kotlin/kotlinx/html/HtmlBuilder.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.html 2 | 3 | import java.util.ArrayList 4 | import java.util.LinkedHashMap 5 | 6 | public abstract class HtmlElement(val containingElement: HtmlElement?, val contentStyle: ContentStyle = ContentStyle.block) { 7 | init { 8 | appendTo(containingElement) 9 | } 10 | 11 | private fun appendTo(element: HtmlElement?) = element?.children?.add(this) 12 | 13 | public val children: MutableList = ArrayList() 14 | 15 | protected abstract fun renderElement(builder: StringBuilder, indent: String) 16 | 17 | public override fun toString(): String { 18 | val builder = StringBuilder() 19 | renderElement(builder, "") 20 | return builder.toString() 21 | } 22 | } 23 | 24 | public enum class RenderStyle { 25 | adaptive, 26 | expanded, 27 | empty 28 | } 29 | 30 | public enum class ContentStyle { 31 | block, 32 | text, 33 | propagate 34 | } 35 | 36 | private fun HtmlElement.computeContentStyle(): ContentStyle { 37 | return when (contentStyle) { 38 | ContentStyle.block, ContentStyle.text -> contentStyle 39 | ContentStyle.propagate -> if (children.all { it.computeContentStyle() == ContentStyle.text }) ContentStyle.text else ContentStyle.block 40 | } 41 | } 42 | 43 | private fun String.htmlEscapeTo(builder: StringBuilder) { 44 | val len = length() 45 | 46 | for (i in 0..len - 1) { 47 | val c = charAt(i) 48 | when (c) { 49 | '<' -> builder.append("<") 50 | '>' -> builder.append(">") 51 | '\"' -> builder.append(""") 52 | '&' -> builder.append("&") 53 | else -> builder.append(c) 54 | } 55 | } 56 | } 57 | 58 | public fun build(tag: T, contents: T.() -> Unit): T { 59 | tag.contents() 60 | return tag 61 | } 62 | 63 | public abstract class HtmlTag(containingTag: HtmlTag?, val tagName: String, val renderStyle: RenderStyle = RenderStyle.expanded, contentStyle: ContentStyle = ContentStyle.block) : HtmlElement(containingTag, contentStyle) { 64 | private val attributes = LinkedHashMap() 65 | 66 | protected override fun renderElement(builder: StringBuilder, indent: String) { 67 | val count = children.size() 68 | builder.append(indent).append('<').append(tagName) 69 | renderAttributes(builder) 70 | 71 | when { 72 | count == 0 && renderStyle != RenderStyle.expanded -> { 73 | builder.append('/') 74 | } 75 | count != 0 && renderStyle == RenderStyle.empty -> { 76 | throw InvalidHtmlException("Empty tag has children") 77 | } 78 | children.all { it.computeContentStyle() == ContentStyle.text } -> { 79 | builder.append(">") 80 | for (c in children) { 81 | c.renderElement(builder, "") 82 | } 83 | builder.append(" { 87 | builder.append("> { 91 | builder.append(">\n") 92 | val childIndent = indent + " " 93 | for (c in children) { 94 | c.renderElement(builder, childIndent) 95 | } 96 | builder.append(indent) 97 | builder.append("') 102 | 103 | if (indent.isNotEmpty()) { 104 | builder.append("\n") 105 | } 106 | } 107 | 108 | protected fun renderAttributes(builder: StringBuilder) { 109 | for (a in attributes.keySet()) { 110 | val attr = attributes[a] 111 | if (attr.length() > 0) { 112 | builder.append(' ').append(a).append("=\"") 113 | attr.htmlEscapeTo(builder) 114 | builder.append("\"") 115 | } 116 | } 117 | } 118 | 119 | public fun attribute(name: String, value: String) { 120 | attributes[name] = value 121 | } 122 | 123 | public fun tryGet(attributeName: String): String? { 124 | return attributes[attributeName] 125 | } 126 | 127 | public fun hasAttribute(attributeName: String): Boolean { 128 | return attributes.containsKey(attributeName) 129 | } 130 | 131 | public fun get(attributeName: String): String { 132 | val answer = attributes[attributeName] 133 | if (answer == null) throw RuntimeException("Atrribute $attributeName is missing") 134 | return answer 135 | } 136 | 137 | public fun set(attName: String, attValue: String) { 138 | attributes[attName] = attValue 139 | } 140 | 141 | /** 142 | * Override the not operator to add raw content 143 | */ 144 | public fun String.not(): Unit { 145 | RawHtml(this@HtmlTag, this) 146 | } 147 | 148 | /** 149 | * Override the plus operator to add a text element. 150 | */ 151 | public fun String.plus() { 152 | HtmlText(this@HtmlTag, this) 153 | } 154 | 155 | /** 156 | * Yet another way to set the text content of the node. 157 | */ 158 | public var text: String? 159 | get() { 160 | if (children.size() > 0) 161 | return children[0].toString() 162 | return "" 163 | } 164 | set(value) { 165 | children.clear() 166 | if (value != null) 167 | HtmlText(this@HtmlTag, value) 168 | } 169 | 170 | } 171 | 172 | public open class TransparentTag(containtingTag: HtmlTag?) : HtmlBodyTag(containtingTag, "\$\$transaprent\$\$", contentStyle = ContentStyle.propagate) { 173 | protected override fun renderElement(builder: StringBuilder, indent: String) { 174 | for (child in children) { 175 | child.renderElement(builder, indent) 176 | } 177 | } 178 | } 179 | 180 | public class RawHtml(containingTag: HtmlTag?, private val html: String) : HtmlElement(containingTag, ContentStyle.text) { 181 | protected override fun renderElement(builder: StringBuilder, indent: String) { 182 | if (html.contains("\n")) { 183 | builder.append(indent) 184 | builder.append(html) 185 | if (indent != "") 186 | builder.append("\n") 187 | } else { 188 | builder.append(html) 189 | } 190 | } 191 | } 192 | 193 | public class HtmlText(containingTag: HtmlTag?, private val text: String) : HtmlElement(containingTag, ContentStyle.text) { 194 | protected override fun renderElement(builder: StringBuilder, indent: String) { 195 | builder.append(indent) 196 | text.htmlEscapeTo(builder) 197 | if (indent != "") 198 | builder.append("\n") 199 | } 200 | } 201 | 202 | public class InvalidHtmlException(message: String) : RuntimeException(message) { 203 | 204 | } 205 | 206 | public class HtmlPartial() : HtmlBodyTag(null, "", RenderStyle.expanded) { 207 | protected override fun renderElement(builder: StringBuilder, indent: String) { 208 | if (children.size() > 0) { 209 | children.forEach { it.renderElement(builder, "") } 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /inspector/src/main/kotlin/kotlinx/html/HtmlDocument.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.html 2 | 3 | public open class HTML() : HtmlTag(null, "html", RenderStyle.adaptive) { 4 | 5 | public var doctype: String = "" 6 | 7 | override fun renderElement(builder: StringBuilder, indent: String) { 8 | builder.append("$doctype\n") 9 | super.renderElement(builder, indent) 10 | } 11 | } 12 | 13 | public fun html(init: HTML.() -> Unit): HTML = build(HTML(), init) 14 | 15 | public fun partial(init: HtmlBodyTag.() -> Unit): HtmlBodyTag = build(HtmlPartial(), init) 16 | -------------------------------------------------------------------------------- /inspector/src/main/kotlin/kotlinx/html/HtmlHead.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.html 2 | 3 | public fun HTML.head(init: HEAD.() -> Unit): Unit { 4 | build(HEAD(this), init) 5 | } 6 | 7 | public fun HEAD.title(init: TITLE.() -> Unit = { }): Unit { 8 | build(TITLE(this), init) 9 | } 10 | 11 | public fun HEAD.title(text: String) { 12 | build(TITLE(this), { +text }) 13 | } 14 | 15 | public fun HEAD.link(href: String, rel: String = "stylesheet", mimeType: String = "text/css", content: _LINK.() -> Unit = { }) { 16 | return link(DirectLink(href), rel, mimeType, content) 17 | } 18 | 19 | public fun HEAD.link(href: Link, rel: String = "stylesheet", mimeType: String = "text/css", content: _LINK.() -> Unit = { }) { 20 | val tag = build(_LINK(this), content) 21 | tag.href = href 22 | tag.rel = rel 23 | tag.mimeType = mimeType 24 | } 25 | 26 | public fun HEAD.meta(name: String, content: String, body: META.() -> Unit = empty_contents) { 27 | val tag = build(META(this), body) 28 | tag.name = name 29 | tag.content = content 30 | } 31 | 32 | public fun HEAD.base(href: String, target: String, content: BASE.() -> Unit = empty_contents) { 33 | val tag = build(BASE(this), content) 34 | tag.href = href 35 | tag.target = target 36 | } 37 | 38 | public fun HtmlTag.script(src: Link, mimeType: String = "text/javascript") { 39 | val tag = build(SCRIPTSRC(this), { }) 40 | tag.src = src 41 | tag.mimeType = mimeType 42 | } 43 | 44 | public fun HtmlTag.script(mimeType: String = "text/javascript", content: SCRIPTBLOCK.() -> Unit) { 45 | val tag = build(SCRIPTBLOCK(this), content) 46 | tag.mimeType = mimeType 47 | } 48 | 49 | public class HEAD(containingTag: HTML) : HtmlTag(containingTag, "head") { 50 | } 51 | 52 | public class META(containingTag: HEAD) : HtmlTag(containingTag, "meta") { 53 | public var name: String by Attributes.name 54 | public var content: String by StringAttribute("content") 55 | } 56 | 57 | public class BASE(containingTag: HEAD) : HtmlTag(containingTag, "base") { 58 | public var href: String by StringAttribute("href") 59 | public var target: String by StringAttribute("target") 60 | } 61 | 62 | public class _LINK(containingTag: HEAD) : HtmlTag(containingTag, "link", RenderStyle.empty) { 63 | public var href: Link by Attributes.href 64 | public var rel: String by Attributes.rel 65 | public var mimeType: String by Attributes.mimeType 66 | 67 | init { 68 | rel = "stylesheet" 69 | mimeType = "text/css" 70 | } 71 | } 72 | 73 | public class SCRIPTSRC(containingTag: HtmlTag) : HtmlTag(containingTag, "script") { 74 | public var src: Link by Attributes.src 75 | public var mimeType: String by Attributes.mimeType 76 | 77 | init { 78 | mimeType = "text/javascript" 79 | } 80 | } 81 | 82 | public class SCRIPTBLOCK(containingTag: HtmlTag) : HtmlTag(containingTag, "script") { 83 | public var mimeType: String by Attributes.mimeType 84 | 85 | init { 86 | mimeType = "text/javascript" 87 | } 88 | } 89 | 90 | public class TITLE(containingTag: HEAD) : HtmlTag(containingTag, "title") 91 | 92 | public fun HtmlBodyTag.noscript(c: String? = null, contents: NOSCRIPT.() -> Unit = empty_contents): Unit = contentTag(NOSCRIPT(this), c, contents) 93 | public class NOSCRIPT(containingTag: HtmlBodyTag) : HtmlBodyTag(containingTag, "noscript") 94 | -------------------------------------------------------------------------------- /inspector/src/main/kotlin/kotlinx/html/Link.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.html 2 | 3 | public interface Link { 4 | public fun href(): String 5 | } 6 | 7 | public class DirectLink(private val href: String) : Link { 8 | public override fun href() = href 9 | } 10 | -------------------------------------------------------------------------------- /inspector/src/main/resources/META-INF/gradle-plugins/com.jakeout.gradle-inspector.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.jakeout.gradle.inspector.InspectorPlugin -------------------------------------------------------------------------------- /inspector/src/main/resources/diff.css: -------------------------------------------------------------------------------- 1 | #container { 2 | width: 1274px; 3 | } 4 | 5 | #sidebar { 6 | float: left; 7 | } 8 | 9 | .tasks, .subprojects { 10 | overflow-x: hidden; 11 | box-shadow: 1px 1px 1px #ccc; 12 | background-color: #F5F5F5; 13 | width: 250px; 14 | } 15 | 16 | #sidebar ul { 17 | font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; 18 | font-size: 0.9em; 19 | padding-right: 0px; 20 | padding-left: 0px; 21 | } 22 | 23 | #sidebar .task, #sidebar .subproject { 24 | padding: 8px 8px; 25 | display: block; 26 | text-decoration: none; 27 | } 28 | 29 | #sidebar li { 30 | list-style: none; 31 | border-bottom: 1px solid #EEE; 32 | } 33 | 34 | #sidebar li:last-child { 35 | border-bottom: 0px solid #000; 36 | } 37 | 38 | #sidebar a { 39 | color: #88f; 40 | } 41 | 42 | #sidebar a:hover { 43 | border-right: 6px solid gray; 44 | background-color: #F0F0F0; 45 | } 46 | 47 | #graph { 48 | float: right; 49 | width: 1024px; 50 | } 51 | 52 | h1 { 53 | font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; 54 | font-size: 1.4em; margin: .1em 0; 55 | line-height: 2em; 56 | } 57 | 58 | h2 { 59 | font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; 60 | font-size: 1.2em; margin: .1em 0; 61 | line-height: 2em; 62 | } 63 | 64 | .info { 65 | padding-left: 5px; 66 | padding-right: 5px; 67 | } 68 | 69 | .changeBlock { 70 | background-color: whitesmoke; 71 | border-color: gainsboro; 72 | border-top-left-radius: 7px; 73 | border-top-right-radius: 7px; 74 | border-bottom-style: solid; 75 | border-width: 1px; 76 | } 77 | 78 | .filename, .filenameSuffix { 79 | border-width: 0px; 80 | border-bottom-width: 1px; 81 | border-color: lightgray; 82 | border-radius: 5px; 83 | padding: 6px; 84 | margin: 2px; 85 | border-style: solid; 86 | background-color: white; 87 | } 88 | 89 | .changeHeader { 90 | padding: 9px; 91 | min-height: 40px; 92 | } 93 | 94 | .fileView { 95 | background-image: -webkit-linear-gradient(45deg, #ccc 25%, transparent 25%,transparent 75%, #ccc 75%, #ccc 100%), -webkit-linear-gradient(45deg, #ccc 25%, transparent 25%,transparent 75%, #ccc 75%, #ccc 100%); 96 | -moz-background-size: 100px 100px; 97 | -webkit-background-size: 21px; 98 | background-position: 0 0, 50px 50px; 99 | background-color: #ddd; 100 | padding: 8px; 101 | } 102 | 103 | .fileView img { 104 | border: 2px solid #021a40; 105 | border-style: dashed; 106 | display: block; 107 | margin-left: auto; 108 | margin-right: auto; 109 | } 110 | 111 | .changeBlock { 112 | margin-bottom: 15px; 113 | margin-top: 20px; 114 | border-width: 1px; 115 | border-style: solid; 116 | border-top-left-radius: 7px; 117 | border-top-right-radius: 7px; 118 | border-color: gainsboro; 119 | overflow: scroll; 120 | } 121 | 122 | code { 123 | white-space: pre; 124 | } 125 | .file-type-icon { 126 | margin-right: 15px; 127 | } 128 | 129 | .added { 130 | background-color: #cfc; 131 | } 132 | 133 | .deleted { 134 | background-color: #fcc; 135 | } 136 | 137 | .common { 138 | background-color: #fff; 139 | } 140 | 141 | img.added { 142 | height: 100dp; 143 | width: 100dp; 144 | } 145 | 146 | text { 147 | font-weight: 300; 148 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serf; 149 | font-size: 14px; 150 | } 151 | 152 | .node rect { 153 | stroke: #333; 154 | fill: #fff; 155 | } 156 | 157 | .edgePath path { 158 | stroke: #333; 159 | fill: #333; 160 | stroke-width: 1.5px; 161 | } 162 | 163 | .node text { 164 | pointer-events: none; 165 | } 166 | 167 | /* This styles the title of the tooltip */ 168 | .tipsy .name { 169 | font-size: 1.5em; 170 | font-weight: bold; 171 | color: #60b1fc; 172 | margin: 0; 173 | } 174 | 175 | /* This styles the body of the tooltip */ 176 | .tipsy .description { 177 | font-size: 1.2em; 178 | } 179 | 180 | .fa-stacked-symbol { 181 | font-size: 0.3em; 182 | display:block; margin-top:2px; 183 | } 184 | 185 | .font-warn { 186 | text-shadow: 0px 2px 3px #000000; 187 | color: #d25035 188 | } 189 | 190 | .font-shadow-added { 191 | text-shadow: 0px 1px 1px #565; 192 | color: #dfd 193 | } 194 | 195 | details[open] summary { 196 | display: none; 197 | } 198 | 199 | .font-shadow-deleted { 200 | text-shadow: 0px 1px 1px #655; 201 | color: #fdd 202 | } 203 | 204 | 205 | .changeHeader span.warning { 206 | display: none; 207 | color: #fff; 208 | text-decoration: none; 209 | padding: 3px; 210 | } 211 | 212 | .changeHeader:hover span.warning { 213 | display: block; 214 | position: absolute; 215 | background-color: #d25035; 216 | box-shadow: 0px 2px 3px #000000; 217 | margin: 2px 10px; 218 | z-index: 2; 219 | } -------------------------------------------------------------------------------- /inspector/src/main/resources/font/css/font-awesome.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.3.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | /* FONT PATH 6 | * -------------------------- */ 7 | @font-face { 8 | font-family: 'FontAwesome'; 9 | src: url('../fonts/fontawesome-webfont.eot?v=4.3.0'); 10 | src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.3.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.3.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.3.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.3.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.3.0#fontawesomeregular') format('svg'); 11 | font-weight: normal; 12 | font-style: normal; 13 | } 14 | .fa { 15 | display: inline-block; 16 | font: normal normal normal 14px/1 FontAwesome; 17 | font-size: inherit; 18 | text-rendering: auto; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-osx-font-smoothing: grayscale; 21 | transform: translate(0, 0); 22 | } 23 | /* makes the font 33% larger relative to the icon container */ 24 | .fa-lg { 25 | font-size: 1.33333333em; 26 | line-height: 0.75em; 27 | vertical-align: -15%; 28 | } 29 | .fa-2x { 30 | font-size: 2em; 31 | } 32 | .fa-3x { 33 | font-size: 3em; 34 | } 35 | .fa-4x { 36 | font-size: 4em; 37 | } 38 | .fa-5x { 39 | font-size: 5em; 40 | } 41 | .fa-fw { 42 | width: 1.28571429em; 43 | text-align: center; 44 | } 45 | .fa-ul { 46 | padding-left: 0; 47 | margin-left: 2.14285714em; 48 | list-style-type: none; 49 | } 50 | .fa-ul > li { 51 | position: relative; 52 | } 53 | .fa-li { 54 | position: absolute; 55 | left: -2.14285714em; 56 | width: 2.14285714em; 57 | top: 0.14285714em; 58 | text-align: center; 59 | } 60 | .fa-li.fa-lg { 61 | left: -1.85714286em; 62 | } 63 | .fa-border { 64 | padding: .2em .25em .15em; 65 | border: solid 0.08em #eeeeee; 66 | border-radius: .1em; 67 | } 68 | .pull-right { 69 | float: right; 70 | } 71 | .pull-left { 72 | float: left; 73 | } 74 | .fa.pull-left { 75 | margin-right: .3em; 76 | } 77 | .fa.pull-right { 78 | margin-left: .3em; 79 | } 80 | .fa-spin { 81 | -webkit-animation: fa-spin 2s infinite linear; 82 | animation: fa-spin 2s infinite linear; 83 | } 84 | .fa-pulse { 85 | -webkit-animation: fa-spin 1s infinite steps(8); 86 | animation: fa-spin 1s infinite steps(8); 87 | } 88 | @-webkit-keyframes fa-spin { 89 | 0% { 90 | -webkit-transform: rotate(0deg); 91 | transform: rotate(0deg); 92 | } 93 | 100% { 94 | -webkit-transform: rotate(359deg); 95 | transform: rotate(359deg); 96 | } 97 | } 98 | @keyframes fa-spin { 99 | 0% { 100 | -webkit-transform: rotate(0deg); 101 | transform: rotate(0deg); 102 | } 103 | 100% { 104 | -webkit-transform: rotate(359deg); 105 | transform: rotate(359deg); 106 | } 107 | } 108 | .fa-rotate-90 { 109 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); 110 | -webkit-transform: rotate(90deg); 111 | -ms-transform: rotate(90deg); 112 | transform: rotate(90deg); 113 | } 114 | .fa-rotate-180 { 115 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); 116 | -webkit-transform: rotate(180deg); 117 | -ms-transform: rotate(180deg); 118 | transform: rotate(180deg); 119 | } 120 | .fa-rotate-270 { 121 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); 122 | -webkit-transform: rotate(270deg); 123 | -ms-transform: rotate(270deg); 124 | transform: rotate(270deg); 125 | } 126 | .fa-flip-horizontal { 127 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1); 128 | -webkit-transform: scale(-1, 1); 129 | -ms-transform: scale(-1, 1); 130 | transform: scale(-1, 1); 131 | } 132 | .fa-flip-vertical { 133 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1); 134 | -webkit-transform: scale(1, -1); 135 | -ms-transform: scale(1, -1); 136 | transform: scale(1, -1); 137 | } 138 | :root .fa-rotate-90, 139 | :root .fa-rotate-180, 140 | :root .fa-rotate-270, 141 | :root .fa-flip-horizontal, 142 | :root .fa-flip-vertical { 143 | filter: none; 144 | } 145 | .fa-stack { 146 | position: relative; 147 | display: inline-block; 148 | width: 2em; 149 | height: 2em; 150 | line-height: 2em; 151 | vertical-align: middle; 152 | } 153 | .fa-stack-1x, 154 | .fa-stack-2x { 155 | position: absolute; 156 | left: 0; 157 | width: 100%; 158 | text-align: center; 159 | } 160 | .fa-stack-1x { 161 | line-height: inherit; 162 | } 163 | .fa-stack-2x { 164 | font-size: 2em; 165 | } 166 | .fa-inverse { 167 | color: #ffffff; 168 | } 169 | /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen 170 | readers do not read off random characters that represent icons */ 171 | .fa-glass:before { 172 | content: "\f000"; 173 | } 174 | .fa-music:before { 175 | content: "\f001"; 176 | } 177 | .fa-search:before { 178 | content: "\f002"; 179 | } 180 | .fa-envelope-o:before { 181 | content: "\f003"; 182 | } 183 | .fa-heart:before { 184 | content: "\f004"; 185 | } 186 | .fa-star:before { 187 | content: "\f005"; 188 | } 189 | .fa-star-o:before { 190 | content: "\f006"; 191 | } 192 | .fa-user:before { 193 | content: "\f007"; 194 | } 195 | .fa-film:before { 196 | content: "\f008"; 197 | } 198 | .fa-th-large:before { 199 | content: "\f009"; 200 | } 201 | .fa-th:before { 202 | content: "\f00a"; 203 | } 204 | .fa-th-list:before { 205 | content: "\f00b"; 206 | } 207 | .fa-check:before { 208 | content: "\f00c"; 209 | } 210 | .fa-remove:before, 211 | .fa-close:before, 212 | .fa-times:before { 213 | content: "\f00d"; 214 | } 215 | .fa-search-plus:before { 216 | content: "\f00e"; 217 | } 218 | .fa-search-minus:before { 219 | content: "\f010"; 220 | } 221 | .fa-power-off:before { 222 | content: "\f011"; 223 | } 224 | .fa-signal:before { 225 | content: "\f012"; 226 | } 227 | .fa-gear:before, 228 | .fa-cog:before { 229 | content: "\f013"; 230 | } 231 | .fa-trash-o:before { 232 | content: "\f014"; 233 | } 234 | .fa-home:before { 235 | content: "\f015"; 236 | } 237 | .fa-file-o:before { 238 | content: "\f016"; 239 | } 240 | .fa-clock-o:before { 241 | content: "\f017"; 242 | } 243 | .fa-road:before { 244 | content: "\f018"; 245 | } 246 | .fa-download:before { 247 | content: "\f019"; 248 | } 249 | .fa-arrow-circle-o-down:before { 250 | content: "\f01a"; 251 | } 252 | .fa-arrow-circle-o-up:before { 253 | content: "\f01b"; 254 | } 255 | .fa-inbox:before { 256 | content: "\f01c"; 257 | } 258 | .fa-play-circle-o:before { 259 | content: "\f01d"; 260 | } 261 | .fa-rotate-right:before, 262 | .fa-repeat:before { 263 | content: "\f01e"; 264 | } 265 | .fa-refresh:before { 266 | content: "\f021"; 267 | } 268 | .fa-list-alt:before { 269 | content: "\f022"; 270 | } 271 | .fa-lock:before { 272 | content: "\f023"; 273 | } 274 | .fa-flag:before { 275 | content: "\f024"; 276 | } 277 | .fa-headphones:before { 278 | content: "\f025"; 279 | } 280 | .fa-volume-off:before { 281 | content: "\f026"; 282 | } 283 | .fa-volume-down:before { 284 | content: "\f027"; 285 | } 286 | .fa-volume-up:before { 287 | content: "\f028"; 288 | } 289 | .fa-qrcode:before { 290 | content: "\f029"; 291 | } 292 | .fa-barcode:before { 293 | content: "\f02a"; 294 | } 295 | .fa-tag:before { 296 | content: "\f02b"; 297 | } 298 | .fa-tags:before { 299 | content: "\f02c"; 300 | } 301 | .fa-book:before { 302 | content: "\f02d"; 303 | } 304 | .fa-bookmark:before { 305 | content: "\f02e"; 306 | } 307 | .fa-print:before { 308 | content: "\f02f"; 309 | } 310 | .fa-camera:before { 311 | content: "\f030"; 312 | } 313 | .fa-font:before { 314 | content: "\f031"; 315 | } 316 | .fa-bold:before { 317 | content: "\f032"; 318 | } 319 | .fa-italic:before { 320 | content: "\f033"; 321 | } 322 | .fa-text-height:before { 323 | content: "\f034"; 324 | } 325 | .fa-text-width:before { 326 | content: "\f035"; 327 | } 328 | .fa-align-left:before { 329 | content: "\f036"; 330 | } 331 | .fa-align-center:before { 332 | content: "\f037"; 333 | } 334 | .fa-align-right:before { 335 | content: "\f038"; 336 | } 337 | .fa-align-justify:before { 338 | content: "\f039"; 339 | } 340 | .fa-list:before { 341 | content: "\f03a"; 342 | } 343 | .fa-dedent:before, 344 | .fa-outdent:before { 345 | content: "\f03b"; 346 | } 347 | .fa-indent:before { 348 | content: "\f03c"; 349 | } 350 | .fa-video-camera:before { 351 | content: "\f03d"; 352 | } 353 | .fa-photo:before, 354 | .fa-image:before, 355 | .fa-picture-o:before { 356 | content: "\f03e"; 357 | } 358 | .fa-pencil:before { 359 | content: "\f040"; 360 | } 361 | .fa-map-marker:before { 362 | content: "\f041"; 363 | } 364 | .fa-adjust:before { 365 | content: "\f042"; 366 | } 367 | .fa-tint:before { 368 | content: "\f043"; 369 | } 370 | .fa-edit:before, 371 | .fa-pencil-square-o:before { 372 | content: "\f044"; 373 | } 374 | .fa-share-square-o:before { 375 | content: "\f045"; 376 | } 377 | .fa-check-square-o:before { 378 | content: "\f046"; 379 | } 380 | .fa-arrows:before { 381 | content: "\f047"; 382 | } 383 | .fa-step-backward:before { 384 | content: "\f048"; 385 | } 386 | .fa-fast-backward:before { 387 | content: "\f049"; 388 | } 389 | .fa-backward:before { 390 | content: "\f04a"; 391 | } 392 | .fa-play:before { 393 | content: "\f04b"; 394 | } 395 | .fa-pause:before { 396 | content: "\f04c"; 397 | } 398 | .fa-stop:before { 399 | content: "\f04d"; 400 | } 401 | .fa-forward:before { 402 | content: "\f04e"; 403 | } 404 | .fa-fast-forward:before { 405 | content: "\f050"; 406 | } 407 | .fa-step-forward:before { 408 | content: "\f051"; 409 | } 410 | .fa-eject:before { 411 | content: "\f052"; 412 | } 413 | .fa-chevron-left:before { 414 | content: "\f053"; 415 | } 416 | .fa-chevron-right:before { 417 | content: "\f054"; 418 | } 419 | .fa-plus-circle:before { 420 | content: "\f055"; 421 | } 422 | .fa-minus-circle:before { 423 | content: "\f056"; 424 | } 425 | .fa-times-circle:before { 426 | content: "\f057"; 427 | } 428 | .fa-check-circle:before { 429 | content: "\f058"; 430 | } 431 | .fa-question-circle:before { 432 | content: "\f059"; 433 | } 434 | .fa-info-circle:before { 435 | content: "\f05a"; 436 | } 437 | .fa-crosshairs:before { 438 | content: "\f05b"; 439 | } 440 | .fa-times-circle-o:before { 441 | content: "\f05c"; 442 | } 443 | .fa-check-circle-o:before { 444 | content: "\f05d"; 445 | } 446 | .fa-ban:before { 447 | content: "\f05e"; 448 | } 449 | .fa-arrow-left:before { 450 | content: "\f060"; 451 | } 452 | .fa-arrow-right:before { 453 | content: "\f061"; 454 | } 455 | .fa-arrow-up:before { 456 | content: "\f062"; 457 | } 458 | .fa-arrow-down:before { 459 | content: "\f063"; 460 | } 461 | .fa-mail-forward:before, 462 | .fa-share:before { 463 | content: "\f064"; 464 | } 465 | .fa-expand:before { 466 | content: "\f065"; 467 | } 468 | .fa-compress:before { 469 | content: "\f066"; 470 | } 471 | .fa-plus:before { 472 | content: "\f067"; 473 | } 474 | .fa-minus:before { 475 | content: "\f068"; 476 | } 477 | .fa-asterisk:before { 478 | content: "\f069"; 479 | } 480 | .fa-exclamation-circle:before { 481 | content: "\f06a"; 482 | } 483 | .fa-gift:before { 484 | content: "\f06b"; 485 | } 486 | .fa-leaf:before { 487 | content: "\f06c"; 488 | } 489 | .fa-fire:before { 490 | content: "\f06d"; 491 | } 492 | .fa-eye:before { 493 | content: "\f06e"; 494 | } 495 | .fa-eye-slash:before { 496 | content: "\f070"; 497 | } 498 | .fa-warning:before, 499 | .fa-exclamation-triangle:before { 500 | content: "\f071"; 501 | } 502 | .fa-plane:before { 503 | content: "\f072"; 504 | } 505 | .fa-calendar:before { 506 | content: "\f073"; 507 | } 508 | .fa-random:before { 509 | content: "\f074"; 510 | } 511 | .fa-comment:before { 512 | content: "\f075"; 513 | } 514 | .fa-magnet:before { 515 | content: "\f076"; 516 | } 517 | .fa-chevron-up:before { 518 | content: "\f077"; 519 | } 520 | .fa-chevron-down:before { 521 | content: "\f078"; 522 | } 523 | .fa-retweet:before { 524 | content: "\f079"; 525 | } 526 | .fa-shopping-cart:before { 527 | content: "\f07a"; 528 | } 529 | .fa-folder:before { 530 | content: "\f07b"; 531 | } 532 | .fa-folder-open:before { 533 | content: "\f07c"; 534 | } 535 | .fa-arrows-v:before { 536 | content: "\f07d"; 537 | } 538 | .fa-arrows-h:before { 539 | content: "\f07e"; 540 | } 541 | .fa-bar-chart-o:before, 542 | .fa-bar-chart:before { 543 | content: "\f080"; 544 | } 545 | .fa-twitter-square:before { 546 | content: "\f081"; 547 | } 548 | .fa-facebook-square:before { 549 | content: "\f082"; 550 | } 551 | .fa-camera-retro:before { 552 | content: "\f083"; 553 | } 554 | .fa-key:before { 555 | content: "\f084"; 556 | } 557 | .fa-gears:before, 558 | .fa-cogs:before { 559 | content: "\f085"; 560 | } 561 | .fa-comments:before { 562 | content: "\f086"; 563 | } 564 | .fa-thumbs-o-up:before { 565 | content: "\f087"; 566 | } 567 | .fa-thumbs-o-down:before { 568 | content: "\f088"; 569 | } 570 | .fa-star-half:before { 571 | content: "\f089"; 572 | } 573 | .fa-heart-o:before { 574 | content: "\f08a"; 575 | } 576 | .fa-sign-out:before { 577 | content: "\f08b"; 578 | } 579 | .fa-linkedin-square:before { 580 | content: "\f08c"; 581 | } 582 | .fa-thumb-tack:before { 583 | content: "\f08d"; 584 | } 585 | .fa-external-link:before { 586 | content: "\f08e"; 587 | } 588 | .fa-sign-in:before { 589 | content: "\f090"; 590 | } 591 | .fa-trophy:before { 592 | content: "\f091"; 593 | } 594 | .fa-github-square:before { 595 | content: "\f092"; 596 | } 597 | .fa-upload:before { 598 | content: "\f093"; 599 | } 600 | .fa-lemon-o:before { 601 | content: "\f094"; 602 | } 603 | .fa-phone:before { 604 | content: "\f095"; 605 | } 606 | .fa-square-o:before { 607 | content: "\f096"; 608 | } 609 | .fa-bookmark-o:before { 610 | content: "\f097"; 611 | } 612 | .fa-phone-square:before { 613 | content: "\f098"; 614 | } 615 | .fa-twitter:before { 616 | content: "\f099"; 617 | } 618 | .fa-facebook-f:before, 619 | .fa-facebook:before { 620 | content: "\f09a"; 621 | } 622 | .fa-github:before { 623 | content: "\f09b"; 624 | } 625 | .fa-unlock:before { 626 | content: "\f09c"; 627 | } 628 | .fa-credit-card:before { 629 | content: "\f09d"; 630 | } 631 | .fa-rss:before { 632 | content: "\f09e"; 633 | } 634 | .fa-hdd-o:before { 635 | content: "\f0a0"; 636 | } 637 | .fa-bullhorn:before { 638 | content: "\f0a1"; 639 | } 640 | .fa-bell:before { 641 | content: "\f0f3"; 642 | } 643 | .fa-certificate:before { 644 | content: "\f0a3"; 645 | } 646 | .fa-hand-o-right:before { 647 | content: "\f0a4"; 648 | } 649 | .fa-hand-o-left:before { 650 | content: "\f0a5"; 651 | } 652 | .fa-hand-o-up:before { 653 | content: "\f0a6"; 654 | } 655 | .fa-hand-o-down:before { 656 | content: "\f0a7"; 657 | } 658 | .fa-arrow-circle-left:before { 659 | content: "\f0a8"; 660 | } 661 | .fa-arrow-circle-right:before { 662 | content: "\f0a9"; 663 | } 664 | .fa-arrow-circle-up:before { 665 | content: "\f0aa"; 666 | } 667 | .fa-arrow-circle-down:before { 668 | content: "\f0ab"; 669 | } 670 | .fa-globe:before { 671 | content: "\f0ac"; 672 | } 673 | .fa-wrench:before { 674 | content: "\f0ad"; 675 | } 676 | .fa-tasks:before { 677 | content: "\f0ae"; 678 | } 679 | .fa-filter:before { 680 | content: "\f0b0"; 681 | } 682 | .fa-briefcase:before { 683 | content: "\f0b1"; 684 | } 685 | .fa-arrows-alt:before { 686 | content: "\f0b2"; 687 | } 688 | .fa-group:before, 689 | .fa-users:before { 690 | content: "\f0c0"; 691 | } 692 | .fa-chain:before, 693 | .fa-link:before { 694 | content: "\f0c1"; 695 | } 696 | .fa-cloud:before { 697 | content: "\f0c2"; 698 | } 699 | .fa-flask:before { 700 | content: "\f0c3"; 701 | } 702 | .fa-cut:before, 703 | .fa-scissors:before { 704 | content: "\f0c4"; 705 | } 706 | .fa-copy:before, 707 | .fa-files-o:before { 708 | content: "\f0c5"; 709 | } 710 | .fa-paperclip:before { 711 | content: "\f0c6"; 712 | } 713 | .fa-save:before, 714 | .fa-floppy-o:before { 715 | content: "\f0c7"; 716 | } 717 | .fa-square:before { 718 | content: "\f0c8"; 719 | } 720 | .fa-navicon:before, 721 | .fa-reorder:before, 722 | .fa-bars:before { 723 | content: "\f0c9"; 724 | } 725 | .fa-list-ul:before { 726 | content: "\f0ca"; 727 | } 728 | .fa-list-ol:before { 729 | content: "\f0cb"; 730 | } 731 | .fa-strikethrough:before { 732 | content: "\f0cc"; 733 | } 734 | .fa-underline:before { 735 | content: "\f0cd"; 736 | } 737 | .fa-table:before { 738 | content: "\f0ce"; 739 | } 740 | .fa-magic:before { 741 | content: "\f0d0"; 742 | } 743 | .fa-truck:before { 744 | content: "\f0d1"; 745 | } 746 | .fa-pinterest:before { 747 | content: "\f0d2"; 748 | } 749 | .fa-pinterest-square:before { 750 | content: "\f0d3"; 751 | } 752 | .fa-google-plus-square:before { 753 | content: "\f0d4"; 754 | } 755 | .fa-google-plus:before { 756 | content: "\f0d5"; 757 | } 758 | .fa-money:before { 759 | content: "\f0d6"; 760 | } 761 | .fa-caret-down:before { 762 | content: "\f0d7"; 763 | } 764 | .fa-caret-up:before { 765 | content: "\f0d8"; 766 | } 767 | .fa-caret-left:before { 768 | content: "\f0d9"; 769 | } 770 | .fa-caret-right:before { 771 | content: "\f0da"; 772 | } 773 | .fa-columns:before { 774 | content: "\f0db"; 775 | } 776 | .fa-unsorted:before, 777 | .fa-sort:before { 778 | content: "\f0dc"; 779 | } 780 | .fa-sort-down:before, 781 | .fa-sort-desc:before { 782 | content: "\f0dd"; 783 | } 784 | .fa-sort-up:before, 785 | .fa-sort-asc:before { 786 | content: "\f0de"; 787 | } 788 | .fa-envelope:before { 789 | content: "\f0e0"; 790 | } 791 | .fa-linkedin:before { 792 | content: "\f0e1"; 793 | } 794 | .fa-rotate-left:before, 795 | .fa-undo:before { 796 | content: "\f0e2"; 797 | } 798 | .fa-legal:before, 799 | .fa-gavel:before { 800 | content: "\f0e3"; 801 | } 802 | .fa-dashboard:before, 803 | .fa-tachometer:before { 804 | content: "\f0e4"; 805 | } 806 | .fa-comment-o:before { 807 | content: "\f0e5"; 808 | } 809 | .fa-comments-o:before { 810 | content: "\f0e6"; 811 | } 812 | .fa-flash:before, 813 | .fa-bolt:before { 814 | content: "\f0e7"; 815 | } 816 | .fa-sitemap:before { 817 | content: "\f0e8"; 818 | } 819 | .fa-umbrella:before { 820 | content: "\f0e9"; 821 | } 822 | .fa-paste:before, 823 | .fa-clipboard:before { 824 | content: "\f0ea"; 825 | } 826 | .fa-lightbulb-o:before { 827 | content: "\f0eb"; 828 | } 829 | .fa-exchange:before { 830 | content: "\f0ec"; 831 | } 832 | .fa-cloud-download:before { 833 | content: "\f0ed"; 834 | } 835 | .fa-cloud-upload:before { 836 | content: "\f0ee"; 837 | } 838 | .fa-user-md:before { 839 | content: "\f0f0"; 840 | } 841 | .fa-stethoscope:before { 842 | content: "\f0f1"; 843 | } 844 | .fa-suitcase:before { 845 | content: "\f0f2"; 846 | } 847 | .fa-bell-o:before { 848 | content: "\f0a2"; 849 | } 850 | .fa-coffee:before { 851 | content: "\f0f4"; 852 | } 853 | .fa-cutlery:before { 854 | content: "\f0f5"; 855 | } 856 | .fa-file-text-o:before { 857 | content: "\f0f6"; 858 | } 859 | .fa-building-o:before { 860 | content: "\f0f7"; 861 | } 862 | .fa-hospital-o:before { 863 | content: "\f0f8"; 864 | } 865 | .fa-ambulance:before { 866 | content: "\f0f9"; 867 | } 868 | .fa-medkit:before { 869 | content: "\f0fa"; 870 | } 871 | .fa-fighter-jet:before { 872 | content: "\f0fb"; 873 | } 874 | .fa-beer:before { 875 | content: "\f0fc"; 876 | } 877 | .fa-h-square:before { 878 | content: "\f0fd"; 879 | } 880 | .fa-plus-square:before { 881 | content: "\f0fe"; 882 | } 883 | .fa-angle-double-left:before { 884 | content: "\f100"; 885 | } 886 | .fa-angle-double-right:before { 887 | content: "\f101"; 888 | } 889 | .fa-angle-double-up:before { 890 | content: "\f102"; 891 | } 892 | .fa-angle-double-down:before { 893 | content: "\f103"; 894 | } 895 | .fa-angle-left:before { 896 | content: "\f104"; 897 | } 898 | .fa-angle-right:before { 899 | content: "\f105"; 900 | } 901 | .fa-angle-up:before { 902 | content: "\f106"; 903 | } 904 | .fa-angle-down:before { 905 | content: "\f107"; 906 | } 907 | .fa-desktop:before { 908 | content: "\f108"; 909 | } 910 | .fa-laptop:before { 911 | content: "\f109"; 912 | } 913 | .fa-tablet:before { 914 | content: "\f10a"; 915 | } 916 | .fa-mobile-phone:before, 917 | .fa-mobile:before { 918 | content: "\f10b"; 919 | } 920 | .fa-circle-o:before { 921 | content: "\f10c"; 922 | } 923 | .fa-quote-left:before { 924 | content: "\f10d"; 925 | } 926 | .fa-quote-right:before { 927 | content: "\f10e"; 928 | } 929 | .fa-spinner:before { 930 | content: "\f110"; 931 | } 932 | .fa-circle:before { 933 | content: "\f111"; 934 | } 935 | .fa-mail-reply:before, 936 | .fa-reply:before { 937 | content: "\f112"; 938 | } 939 | .fa-github-alt:before { 940 | content: "\f113"; 941 | } 942 | .fa-folder-o:before { 943 | content: "\f114"; 944 | } 945 | .fa-folder-open-o:before { 946 | content: "\f115"; 947 | } 948 | .fa-smile-o:before { 949 | content: "\f118"; 950 | } 951 | .fa-frown-o:before { 952 | content: "\f119"; 953 | } 954 | .fa-meh-o:before { 955 | content: "\f11a"; 956 | } 957 | .fa-gamepad:before { 958 | content: "\f11b"; 959 | } 960 | .fa-keyboard-o:before { 961 | content: "\f11c"; 962 | } 963 | .fa-flag-o:before { 964 | content: "\f11d"; 965 | } 966 | .fa-flag-checkered:before { 967 | content: "\f11e"; 968 | } 969 | .fa-terminal:before { 970 | content: "\f120"; 971 | } 972 | .fa-code:before { 973 | content: "\f121"; 974 | } 975 | .fa-mail-reply-all:before, 976 | .fa-reply-all:before { 977 | content: "\f122"; 978 | } 979 | .fa-star-half-empty:before, 980 | .fa-star-half-full:before, 981 | .fa-star-half-o:before { 982 | content: "\f123"; 983 | } 984 | .fa-location-arrow:before { 985 | content: "\f124"; 986 | } 987 | .fa-crop:before { 988 | content: "\f125"; 989 | } 990 | .fa-code-fork:before { 991 | content: "\f126"; 992 | } 993 | .fa-unlink:before, 994 | .fa-chain-broken:before { 995 | content: "\f127"; 996 | } 997 | .fa-question:before { 998 | content: "\f128"; 999 | } 1000 | .fa-info:before { 1001 | content: "\f129"; 1002 | } 1003 | .fa-exclamation:before { 1004 | content: "\f12a"; 1005 | } 1006 | .fa-superscript:before { 1007 | content: "\f12b"; 1008 | } 1009 | .fa-subscript:before { 1010 | content: "\f12c"; 1011 | } 1012 | .fa-eraser:before { 1013 | content: "\f12d"; 1014 | } 1015 | .fa-puzzle-piece:before { 1016 | content: "\f12e"; 1017 | } 1018 | .fa-microphone:before { 1019 | content: "\f130"; 1020 | } 1021 | .fa-microphone-slash:before { 1022 | content: "\f131"; 1023 | } 1024 | .fa-shield:before { 1025 | content: "\f132"; 1026 | } 1027 | .fa-calendar-o:before { 1028 | content: "\f133"; 1029 | } 1030 | .fa-fire-extinguisher:before { 1031 | content: "\f134"; 1032 | } 1033 | .fa-rocket:before { 1034 | content: "\f135"; 1035 | } 1036 | .fa-maxcdn:before { 1037 | content: "\f136"; 1038 | } 1039 | .fa-chevron-circle-left:before { 1040 | content: "\f137"; 1041 | } 1042 | .fa-chevron-circle-right:before { 1043 | content: "\f138"; 1044 | } 1045 | .fa-chevron-circle-up:before { 1046 | content: "\f139"; 1047 | } 1048 | .fa-chevron-circle-down:before { 1049 | content: "\f13a"; 1050 | } 1051 | .fa-html5:before { 1052 | content: "\f13b"; 1053 | } 1054 | .fa-css3:before { 1055 | content: "\f13c"; 1056 | } 1057 | .fa-anchor:before { 1058 | content: "\f13d"; 1059 | } 1060 | .fa-unlock-alt:before { 1061 | content: "\f13e"; 1062 | } 1063 | .fa-bullseye:before { 1064 | content: "\f140"; 1065 | } 1066 | .fa-ellipsis-h:before { 1067 | content: "\f141"; 1068 | } 1069 | .fa-ellipsis-v:before { 1070 | content: "\f142"; 1071 | } 1072 | .fa-rss-square:before { 1073 | content: "\f143"; 1074 | } 1075 | .fa-play-circle:before { 1076 | content: "\f144"; 1077 | } 1078 | .fa-ticket:before { 1079 | content: "\f145"; 1080 | } 1081 | .fa-minus-square:before { 1082 | content: "\f146"; 1083 | } 1084 | .fa-minus-square-o:before { 1085 | content: "\f147"; 1086 | } 1087 | .fa-level-up:before { 1088 | content: "\f148"; 1089 | } 1090 | .fa-level-down:before { 1091 | content: "\f149"; 1092 | } 1093 | .fa-check-square:before { 1094 | content: "\f14a"; 1095 | } 1096 | .fa-pencil-square:before { 1097 | content: "\f14b"; 1098 | } 1099 | .fa-external-link-square:before { 1100 | content: "\f14c"; 1101 | } 1102 | .fa-share-square:before { 1103 | content: "\f14d"; 1104 | } 1105 | .fa-compass:before { 1106 | content: "\f14e"; 1107 | } 1108 | .fa-toggle-down:before, 1109 | .fa-caret-square-o-down:before { 1110 | content: "\f150"; 1111 | } 1112 | .fa-toggle-up:before, 1113 | .fa-caret-square-o-up:before { 1114 | content: "\f151"; 1115 | } 1116 | .fa-toggle-right:before, 1117 | .fa-caret-square-o-right:before { 1118 | content: "\f152"; 1119 | } 1120 | .fa-euro:before, 1121 | .fa-eur:before { 1122 | content: "\f153"; 1123 | } 1124 | .fa-gbp:before { 1125 | content: "\f154"; 1126 | } 1127 | .fa-dollar:before, 1128 | .fa-usd:before { 1129 | content: "\f155"; 1130 | } 1131 | .fa-rupee:before, 1132 | .fa-inr:before { 1133 | content: "\f156"; 1134 | } 1135 | .fa-cny:before, 1136 | .fa-rmb:before, 1137 | .fa-yen:before, 1138 | .fa-jpy:before { 1139 | content: "\f157"; 1140 | } 1141 | .fa-ruble:before, 1142 | .fa-rouble:before, 1143 | .fa-rub:before { 1144 | content: "\f158"; 1145 | } 1146 | .fa-won:before, 1147 | .fa-krw:before { 1148 | content: "\f159"; 1149 | } 1150 | .fa-bitcoin:before, 1151 | .fa-btc:before { 1152 | content: "\f15a"; 1153 | } 1154 | .fa-file:before { 1155 | content: "\f15b"; 1156 | } 1157 | .fa-file-text:before { 1158 | content: "\f15c"; 1159 | } 1160 | .fa-sort-alpha-asc:before { 1161 | content: "\f15d"; 1162 | } 1163 | .fa-sort-alpha-desc:before { 1164 | content: "\f15e"; 1165 | } 1166 | .fa-sort-amount-asc:before { 1167 | content: "\f160"; 1168 | } 1169 | .fa-sort-amount-desc:before { 1170 | content: "\f161"; 1171 | } 1172 | .fa-sort-numeric-asc:before { 1173 | content: "\f162"; 1174 | } 1175 | .fa-sort-numeric-desc:before { 1176 | content: "\f163"; 1177 | } 1178 | .fa-thumbs-up:before { 1179 | content: "\f164"; 1180 | } 1181 | .fa-thumbs-down:before { 1182 | content: "\f165"; 1183 | } 1184 | .fa-youtube-square:before { 1185 | content: "\f166"; 1186 | } 1187 | .fa-youtube:before { 1188 | content: "\f167"; 1189 | } 1190 | .fa-xing:before { 1191 | content: "\f168"; 1192 | } 1193 | .fa-xing-square:before { 1194 | content: "\f169"; 1195 | } 1196 | .fa-youtube-play:before { 1197 | content: "\f16a"; 1198 | } 1199 | .fa-dropbox:before { 1200 | content: "\f16b"; 1201 | } 1202 | .fa-stack-overflow:before { 1203 | content: "\f16c"; 1204 | } 1205 | .fa-instagram:before { 1206 | content: "\f16d"; 1207 | } 1208 | .fa-flickr:before { 1209 | content: "\f16e"; 1210 | } 1211 | .fa-adn:before { 1212 | content: "\f170"; 1213 | } 1214 | .fa-bitbucket:before { 1215 | content: "\f171"; 1216 | } 1217 | .fa-bitbucket-square:before { 1218 | content: "\f172"; 1219 | } 1220 | .fa-tumblr:before { 1221 | content: "\f173"; 1222 | } 1223 | .fa-tumblr-square:before { 1224 | content: "\f174"; 1225 | } 1226 | .fa-long-arrow-down:before { 1227 | content: "\f175"; 1228 | } 1229 | .fa-long-arrow-up:before { 1230 | content: "\f176"; 1231 | } 1232 | .fa-long-arrow-left:before { 1233 | content: "\f177"; 1234 | } 1235 | .fa-long-arrow-right:before { 1236 | content: "\f178"; 1237 | } 1238 | .fa-apple:before { 1239 | content: "\f179"; 1240 | } 1241 | .fa-windows:before { 1242 | content: "\f17a"; 1243 | } 1244 | .fa-android:before { 1245 | content: "\f17b"; 1246 | } 1247 | .fa-linux:before { 1248 | content: "\f17c"; 1249 | } 1250 | .fa-dribbble:before { 1251 | content: "\f17d"; 1252 | } 1253 | .fa-skype:before { 1254 | content: "\f17e"; 1255 | } 1256 | .fa-foursquare:before { 1257 | content: "\f180"; 1258 | } 1259 | .fa-trello:before { 1260 | content: "\f181"; 1261 | } 1262 | .fa-female:before { 1263 | content: "\f182"; 1264 | } 1265 | .fa-male:before { 1266 | content: "\f183"; 1267 | } 1268 | .fa-gittip:before, 1269 | .fa-gratipay:before { 1270 | content: "\f184"; 1271 | } 1272 | .fa-sun-o:before { 1273 | content: "\f185"; 1274 | } 1275 | .fa-moon-o:before { 1276 | content: "\f186"; 1277 | } 1278 | .fa-archive:before { 1279 | content: "\f187"; 1280 | } 1281 | .fa-bug:before { 1282 | content: "\f188"; 1283 | } 1284 | .fa-vk:before { 1285 | content: "\f189"; 1286 | } 1287 | .fa-weibo:before { 1288 | content: "\f18a"; 1289 | } 1290 | .fa-renren:before { 1291 | content: "\f18b"; 1292 | } 1293 | .fa-pagelines:before { 1294 | content: "\f18c"; 1295 | } 1296 | .fa-stack-exchange:before { 1297 | content: "\f18d"; 1298 | } 1299 | .fa-arrow-circle-o-right:before { 1300 | content: "\f18e"; 1301 | } 1302 | .fa-arrow-circle-o-left:before { 1303 | content: "\f190"; 1304 | } 1305 | .fa-toggle-left:before, 1306 | .fa-caret-square-o-left:before { 1307 | content: "\f191"; 1308 | } 1309 | .fa-dot-circle-o:before { 1310 | content: "\f192"; 1311 | } 1312 | .fa-wheelchair:before { 1313 | content: "\f193"; 1314 | } 1315 | .fa-vimeo-square:before { 1316 | content: "\f194"; 1317 | } 1318 | .fa-turkish-lira:before, 1319 | .fa-try:before { 1320 | content: "\f195"; 1321 | } 1322 | .fa-plus-square-o:before { 1323 | content: "\f196"; 1324 | } 1325 | .fa-space-shuttle:before { 1326 | content: "\f197"; 1327 | } 1328 | .fa-slack:before { 1329 | content: "\f198"; 1330 | } 1331 | .fa-envelope-square:before { 1332 | content: "\f199"; 1333 | } 1334 | .fa-wordpress:before { 1335 | content: "\f19a"; 1336 | } 1337 | .fa-openid:before { 1338 | content: "\f19b"; 1339 | } 1340 | .fa-institution:before, 1341 | .fa-bank:before, 1342 | .fa-university:before { 1343 | content: "\f19c"; 1344 | } 1345 | .fa-mortar-board:before, 1346 | .fa-graduation-cap:before { 1347 | content: "\f19d"; 1348 | } 1349 | .fa-yahoo:before { 1350 | content: "\f19e"; 1351 | } 1352 | .fa-google:before { 1353 | content: "\f1a0"; 1354 | } 1355 | .fa-reddit:before { 1356 | content: "\f1a1"; 1357 | } 1358 | .fa-reddit-square:before { 1359 | content: "\f1a2"; 1360 | } 1361 | .fa-stumbleupon-circle:before { 1362 | content: "\f1a3"; 1363 | } 1364 | .fa-stumbleupon:before { 1365 | content: "\f1a4"; 1366 | } 1367 | .fa-delicious:before { 1368 | content: "\f1a5"; 1369 | } 1370 | .fa-digg:before { 1371 | content: "\f1a6"; 1372 | } 1373 | .fa-pied-piper:before { 1374 | content: "\f1a7"; 1375 | } 1376 | .fa-pied-piper-alt:before { 1377 | content: "\f1a8"; 1378 | } 1379 | .fa-drupal:before { 1380 | content: "\f1a9"; 1381 | } 1382 | .fa-joomla:before { 1383 | content: "\f1aa"; 1384 | } 1385 | .fa-language:before { 1386 | content: "\f1ab"; 1387 | } 1388 | .fa-fax:before { 1389 | content: "\f1ac"; 1390 | } 1391 | .fa-building:before { 1392 | content: "\f1ad"; 1393 | } 1394 | .fa-child:before { 1395 | content: "\f1ae"; 1396 | } 1397 | .fa-paw:before { 1398 | content: "\f1b0"; 1399 | } 1400 | .fa-spoon:before { 1401 | content: "\f1b1"; 1402 | } 1403 | .fa-cube:before { 1404 | content: "\f1b2"; 1405 | } 1406 | .fa-cubes:before { 1407 | content: "\f1b3"; 1408 | } 1409 | .fa-behance:before { 1410 | content: "\f1b4"; 1411 | } 1412 | .fa-behance-square:before { 1413 | content: "\f1b5"; 1414 | } 1415 | .fa-steam:before { 1416 | content: "\f1b6"; 1417 | } 1418 | .fa-steam-square:before { 1419 | content: "\f1b7"; 1420 | } 1421 | .fa-recycle:before { 1422 | content: "\f1b8"; 1423 | } 1424 | .fa-automobile:before, 1425 | .fa-car:before { 1426 | content: "\f1b9"; 1427 | } 1428 | .fa-cab:before, 1429 | .fa-taxi:before { 1430 | content: "\f1ba"; 1431 | } 1432 | .fa-tree:before { 1433 | content: "\f1bb"; 1434 | } 1435 | .fa-spotify:before { 1436 | content: "\f1bc"; 1437 | } 1438 | .fa-deviantart:before { 1439 | content: "\f1bd"; 1440 | } 1441 | .fa-soundcloud:before { 1442 | content: "\f1be"; 1443 | } 1444 | .fa-database:before { 1445 | content: "\f1c0"; 1446 | } 1447 | .fa-file-pdf-o:before { 1448 | content: "\f1c1"; 1449 | } 1450 | .fa-file-word-o:before { 1451 | content: "\f1c2"; 1452 | } 1453 | .fa-file-excel-o:before { 1454 | content: "\f1c3"; 1455 | } 1456 | .fa-file-powerpoint-o:before { 1457 | content: "\f1c4"; 1458 | } 1459 | .fa-file-photo-o:before, 1460 | .fa-file-picture-o:before, 1461 | .fa-file-image-o:before { 1462 | content: "\f1c5"; 1463 | } 1464 | .fa-file-zip-o:before, 1465 | .fa-file-archive-o:before { 1466 | content: "\f1c6"; 1467 | } 1468 | .fa-file-sound-o:before, 1469 | .fa-file-audio-o:before { 1470 | content: "\f1c7"; 1471 | } 1472 | .fa-file-movie-o:before, 1473 | .fa-file-video-o:before { 1474 | content: "\f1c8"; 1475 | } 1476 | .fa-file-code-o:before { 1477 | content: "\f1c9"; 1478 | } 1479 | .fa-vine:before { 1480 | content: "\f1ca"; 1481 | } 1482 | .fa-codepen:before { 1483 | content: "\f1cb"; 1484 | } 1485 | .fa-jsfiddle:before { 1486 | content: "\f1cc"; 1487 | } 1488 | .fa-life-bouy:before, 1489 | .fa-life-buoy:before, 1490 | .fa-life-saver:before, 1491 | .fa-support:before, 1492 | .fa-life-ring:before { 1493 | content: "\f1cd"; 1494 | } 1495 | .fa-circle-o-notch:before { 1496 | content: "\f1ce"; 1497 | } 1498 | .fa-ra:before, 1499 | .fa-rebel:before { 1500 | content: "\f1d0"; 1501 | } 1502 | .fa-ge:before, 1503 | .fa-empire:before { 1504 | content: "\f1d1"; 1505 | } 1506 | .fa-git-square:before { 1507 | content: "\f1d2"; 1508 | } 1509 | .fa-git:before { 1510 | content: "\f1d3"; 1511 | } 1512 | .fa-hacker-news:before { 1513 | content: "\f1d4"; 1514 | } 1515 | .fa-tencent-weibo:before { 1516 | content: "\f1d5"; 1517 | } 1518 | .fa-qq:before { 1519 | content: "\f1d6"; 1520 | } 1521 | .fa-wechat:before, 1522 | .fa-weixin:before { 1523 | content: "\f1d7"; 1524 | } 1525 | .fa-send:before, 1526 | .fa-paper-plane:before { 1527 | content: "\f1d8"; 1528 | } 1529 | .fa-send-o:before, 1530 | .fa-paper-plane-o:before { 1531 | content: "\f1d9"; 1532 | } 1533 | .fa-history:before { 1534 | content: "\f1da"; 1535 | } 1536 | .fa-genderless:before, 1537 | .fa-circle-thin:before { 1538 | content: "\f1db"; 1539 | } 1540 | .fa-header:before { 1541 | content: "\f1dc"; 1542 | } 1543 | .fa-paragraph:before { 1544 | content: "\f1dd"; 1545 | } 1546 | .fa-sliders:before { 1547 | content: "\f1de"; 1548 | } 1549 | .fa-share-alt:before { 1550 | content: "\f1e0"; 1551 | } 1552 | .fa-share-alt-square:before { 1553 | content: "\f1e1"; 1554 | } 1555 | .fa-bomb:before { 1556 | content: "\f1e2"; 1557 | } 1558 | .fa-soccer-ball-o:before, 1559 | .fa-futbol-o:before { 1560 | content: "\f1e3"; 1561 | } 1562 | .fa-tty:before { 1563 | content: "\f1e4"; 1564 | } 1565 | .fa-binoculars:before { 1566 | content: "\f1e5"; 1567 | } 1568 | .fa-plug:before { 1569 | content: "\f1e6"; 1570 | } 1571 | .fa-slideshare:before { 1572 | content: "\f1e7"; 1573 | } 1574 | .fa-twitch:before { 1575 | content: "\f1e8"; 1576 | } 1577 | .fa-yelp:before { 1578 | content: "\f1e9"; 1579 | } 1580 | .fa-newspaper-o:before { 1581 | content: "\f1ea"; 1582 | } 1583 | .fa-wifi:before { 1584 | content: "\f1eb"; 1585 | } 1586 | .fa-calculator:before { 1587 | content: "\f1ec"; 1588 | } 1589 | .fa-paypal:before { 1590 | content: "\f1ed"; 1591 | } 1592 | .fa-google-wallet:before { 1593 | content: "\f1ee"; 1594 | } 1595 | .fa-cc-visa:before { 1596 | content: "\f1f0"; 1597 | } 1598 | .fa-cc-mastercard:before { 1599 | content: "\f1f1"; 1600 | } 1601 | .fa-cc-discover:before { 1602 | content: "\f1f2"; 1603 | } 1604 | .fa-cc-amex:before { 1605 | content: "\f1f3"; 1606 | } 1607 | .fa-cc-paypal:before { 1608 | content: "\f1f4"; 1609 | } 1610 | .fa-cc-stripe:before { 1611 | content: "\f1f5"; 1612 | } 1613 | .fa-bell-slash:before { 1614 | content: "\f1f6"; 1615 | } 1616 | .fa-bell-slash-o:before { 1617 | content: "\f1f7"; 1618 | } 1619 | .fa-trash:before { 1620 | content: "\f1f8"; 1621 | } 1622 | .fa-copyright:before { 1623 | content: "\f1f9"; 1624 | } 1625 | .fa-at:before { 1626 | content: "\f1fa"; 1627 | } 1628 | .fa-eyedropper:before { 1629 | content: "\f1fb"; 1630 | } 1631 | .fa-paint-brush:before { 1632 | content: "\f1fc"; 1633 | } 1634 | .fa-birthday-cake:before { 1635 | content: "\f1fd"; 1636 | } 1637 | .fa-area-chart:before { 1638 | content: "\f1fe"; 1639 | } 1640 | .fa-pie-chart:before { 1641 | content: "\f200"; 1642 | } 1643 | .fa-line-chart:before { 1644 | content: "\f201"; 1645 | } 1646 | .fa-lastfm:before { 1647 | content: "\f202"; 1648 | } 1649 | .fa-lastfm-square:before { 1650 | content: "\f203"; 1651 | } 1652 | .fa-toggle-off:before { 1653 | content: "\f204"; 1654 | } 1655 | .fa-toggle-on:before { 1656 | content: "\f205"; 1657 | } 1658 | .fa-bicycle:before { 1659 | content: "\f206"; 1660 | } 1661 | .fa-bus:before { 1662 | content: "\f207"; 1663 | } 1664 | .fa-ioxhost:before { 1665 | content: "\f208"; 1666 | } 1667 | .fa-angellist:before { 1668 | content: "\f209"; 1669 | } 1670 | .fa-cc:before { 1671 | content: "\f20a"; 1672 | } 1673 | .fa-shekel:before, 1674 | .fa-sheqel:before, 1675 | .fa-ils:before { 1676 | content: "\f20b"; 1677 | } 1678 | .fa-meanpath:before { 1679 | content: "\f20c"; 1680 | } 1681 | .fa-buysellads:before { 1682 | content: "\f20d"; 1683 | } 1684 | .fa-connectdevelop:before { 1685 | content: "\f20e"; 1686 | } 1687 | .fa-dashcube:before { 1688 | content: "\f210"; 1689 | } 1690 | .fa-forumbee:before { 1691 | content: "\f211"; 1692 | } 1693 | .fa-leanpub:before { 1694 | content: "\f212"; 1695 | } 1696 | .fa-sellsy:before { 1697 | content: "\f213"; 1698 | } 1699 | .fa-shirtsinbulk:before { 1700 | content: "\f214"; 1701 | } 1702 | .fa-simplybuilt:before { 1703 | content: "\f215"; 1704 | } 1705 | .fa-skyatlas:before { 1706 | content: "\f216"; 1707 | } 1708 | .fa-cart-plus:before { 1709 | content: "\f217"; 1710 | } 1711 | .fa-cart-arrow-down:before { 1712 | content: "\f218"; 1713 | } 1714 | .fa-diamond:before { 1715 | content: "\f219"; 1716 | } 1717 | .fa-ship:before { 1718 | content: "\f21a"; 1719 | } 1720 | .fa-user-secret:before { 1721 | content: "\f21b"; 1722 | } 1723 | .fa-motorcycle:before { 1724 | content: "\f21c"; 1725 | } 1726 | .fa-street-view:before { 1727 | content: "\f21d"; 1728 | } 1729 | .fa-heartbeat:before { 1730 | content: "\f21e"; 1731 | } 1732 | .fa-venus:before { 1733 | content: "\f221"; 1734 | } 1735 | .fa-mars:before { 1736 | content: "\f222"; 1737 | } 1738 | .fa-mercury:before { 1739 | content: "\f223"; 1740 | } 1741 | .fa-transgender:before { 1742 | content: "\f224"; 1743 | } 1744 | .fa-transgender-alt:before { 1745 | content: "\f225"; 1746 | } 1747 | .fa-venus-double:before { 1748 | content: "\f226"; 1749 | } 1750 | .fa-mars-double:before { 1751 | content: "\f227"; 1752 | } 1753 | .fa-venus-mars:before { 1754 | content: "\f228"; 1755 | } 1756 | .fa-mars-stroke:before { 1757 | content: "\f229"; 1758 | } 1759 | .fa-mars-stroke-v:before { 1760 | content: "\f22a"; 1761 | } 1762 | .fa-mars-stroke-h:before { 1763 | content: "\f22b"; 1764 | } 1765 | .fa-neuter:before { 1766 | content: "\f22c"; 1767 | } 1768 | .fa-facebook-official:before { 1769 | content: "\f230"; 1770 | } 1771 | .fa-pinterest-p:before { 1772 | content: "\f231"; 1773 | } 1774 | .fa-whatsapp:before { 1775 | content: "\f232"; 1776 | } 1777 | .fa-server:before { 1778 | content: "\f233"; 1779 | } 1780 | .fa-user-plus:before { 1781 | content: "\f234"; 1782 | } 1783 | .fa-user-times:before { 1784 | content: "\f235"; 1785 | } 1786 | .fa-hotel:before, 1787 | .fa-bed:before { 1788 | content: "\f236"; 1789 | } 1790 | .fa-viacoin:before { 1791 | content: "\f237"; 1792 | } 1793 | .fa-train:before { 1794 | content: "\f238"; 1795 | } 1796 | .fa-subway:before { 1797 | content: "\f239"; 1798 | } 1799 | .fa-medium:before { 1800 | content: "\f23a"; 1801 | } 1802 | -------------------------------------------------------------------------------- /inspector/src/main/resources/font/css/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.3.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.3.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.3.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.3.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.3.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.3.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.3.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;transform:translate(0, 0)}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-genderless:before,.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"} -------------------------------------------------------------------------------- /inspector/src/main/resources/font/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakeouellette/inspector/7ee81d50bdee4763ce6298a95ae589032ab4d749/inspector/src/main/resources/font/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /inspector/src/main/resources/font/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakeouellette/inspector/7ee81d50bdee4763ce6298a95ae589032ab4d749/inspector/src/main/resources/font/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /inspector/src/main/resources/font/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakeouellette/inspector/7ee81d50bdee4763ce6298a95ae589032ab4d749/inspector/src/main/resources/font/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /inspector/src/main/resources/font/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakeouellette/inspector/7ee81d50bdee4763ce6298a95ae589032ab4d749/inspector/src/main/resources/font/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /inspector/src/main/resources/font/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakeouellette/inspector/7ee81d50bdee4763ce6298a95ae589032ab4d749/inspector/src/main/resources/font/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /inspector/src/main/resources/vis-report.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Visual report 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 19 |
20 | 21 | 22 | 23 |
24 |
25 | -------------------------------------------------------------------------------- /inspector/src/main/resources/vis/dag.js: -------------------------------------------------------------------------------- 1 | // Create a new directed graph 2 | var g = new dagreD3.graphlib.Graph().setGraph({}); 3 | 4 | // States and transitions from RFC 793 5 | var states = { 6 | // !-- REPLACE WITH NODES --! 7 | 8 | }; 9 | 10 | // Add states to the graph, set labels, and style 11 | Object.keys(states).forEach(function(state) { 12 | var value = states[state]; 13 | value.label = state; 14 | value.rx = value.ry = 5; 15 | g.setNode(state, value); 16 | }); 17 | 18 | // Set up the edges 19 | // !-- REPLACE WITH EDGES --! 20 | 21 | 22 | // Create the renderer 23 | var render = new dagreD3.render(); 24 | 25 | // Set up an SVG group so that we can translate the final graph. 26 | var svg = d3.select("svg"), 27 | inner = svg.append("g"); 28 | 29 | // Set up zoom support 30 | var zoom = d3.behavior.zoom().on("zoom", function() { 31 | inner.attr("transform", "translate(" + d3.event.translate + ")" + 32 | "scale(" + d3.event.scale + ")"); 33 | }); 34 | svg.call(zoom); 35 | 36 | // Simple function to style the tooltip for the given node. 37 | var styleTooltip = function(name, description) { 38 | return "

" + name + "

" + description + "

"; 39 | }; 40 | 41 | // Run the renderer. This is what draws the final graph. 42 | render(inner, g); 43 | 44 | inner.selectAll("g.node") 45 | .attr("title", function(v) { return styleTooltip(v, g.node(v).description) }) 46 | .each(function(v) { $(this).tipsy({ gravity: "w", opacity: 1, html: true }); }); 47 | 48 | inner.selectAll("g.node") 49 | .on("click", function(n){ 50 | var url = n + "-report.html"; 51 | $(location).attr('href', url); 52 | window.location = url; 53 | }); 54 | 55 | inner.selectAll("g.node") 56 | .on('mouseover', function(d){ 57 | d3.select(this).select('rect').style("fill", "white"); 58 | }) 59 | .on('mouseout', function(d){ 60 | d3.select(this).select('rect').style("fill", g.node(d).basefill); 61 | }); 62 | 63 | // Center the graph 64 | var initialScale = 0.75; 65 | zoom 66 | .translate([(svg.attr("width") - g.graph().width * initialScale) / 2, 20]) 67 | .scale(initialScale) 68 | .event(svg); 69 | svg.attr('height', g.graph().height * initialScale + 40); -------------------------------------------------------------------------------- /inspector/src/main/resources/vis/tipsy.css: -------------------------------------------------------------------------------- 1 | .tipsy { font-size: 10px; position: absolute; padding: 5px; z-index: 100000; } 2 | .tipsy-inner { background-color: #000; color: #FFF; max-width: 200px; padding: 5px 8px 4px 8px; text-align: center; } 3 | 4 | /* Rounded corners */ 5 | .tipsy-inner { border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; } 6 | 7 | /* Uncomment for shadow */ 8 | .tipsy-inner { box-shadow: 0 0 5px #000000; -webkit-box-shadow: 0 0 5px #000000; -moz-box-shadow: 0 0 5px #000000; } 9 | 10 | .tipsy-arrow { position: absolute; width: 0; height: 0; line-height: 0; border: 5px dashed #000; } 11 | 12 | /* Rules to colour arrows */ 13 | .tipsy-arrow-n { border-bottom-color: #000; } 14 | .tipsy-arrow-s { border-top-color: #000; } 15 | .tipsy-arrow-e { border-left-color: #000; } 16 | .tipsy-arrow-w { border-right-color: #000; } 17 | 18 | .tipsy-n .tipsy-arrow { top: 0px; left: 50%; margin-left: -5px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent; } 19 | .tipsy-nw .tipsy-arrow { top: 0; left: 10px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent;} 20 | .tipsy-ne .tipsy-arrow { top: 0; right: 10px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent;} 21 | .tipsy-s .tipsy-arrow { bottom: 0; left: 50%; margin-left: -5px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; } 22 | .tipsy-sw .tipsy-arrow { bottom: 0; left: 10px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; } 23 | .tipsy-se .tipsy-arrow { bottom: 0; right: 10px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; } 24 | .tipsy-e .tipsy-arrow { right: 0; top: 50%; margin-top: -5px; border-left-style: solid; border-right: none; border-top-color: transparent; border-bottom-color: transparent; } 25 | .tipsy-w .tipsy-arrow { left: 0; top: 50%; margin-top: -5px; border-right-style: solid; border-left: none; border-top-color: transparent; border-bottom-color: transparent; } -------------------------------------------------------------------------------- /inspector/src/main/resources/vis/tipsy.js: -------------------------------------------------------------------------------- 1 | // tipsy, facebook style tooltips for jquery 2 | // version 1.0.0a 3 | // (c) 2008-2010 jason frame [jason@onehackoranother.com] 4 | // released under the MIT license 5 | 6 | (function($) { 7 | 8 | function maybeCall(thing, ctx) { 9 | return (typeof thing == 'function') ? (thing.call(ctx)) : thing; 10 | } 11 | 12 | function Tipsy(element, options) { 13 | this.$element = $(element); 14 | this.options = options; 15 | this.enabled = true; 16 | this.fixTitle(); 17 | } 18 | 19 | Tipsy.prototype = { 20 | show: function() { 21 | var title = this.getTitle(); 22 | if (title && this.enabled) { 23 | var $tip = this.tip(); 24 | 25 | $tip.find('.tipsy-inner')[this.options.html ? 'html' : 'text'](title); 26 | $tip[0].className = 'tipsy'; // reset classname in case of dynamic gravity 27 | $tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).prependTo(document.body); 28 | 29 | var pos = $.extend({}, this.$element.offset(), { 30 | width: this.$element[0].offsetWidth || 0, 31 | height: this.$element[0].offsetHeight || 0 32 | }); 33 | 34 | if (typeof this.$element[0].nearestViewportElement == 'object') { 35 | // SVG 36 | var el = this.$element[0]; 37 | var rect = el.getBoundingClientRect(); 38 | pos.width = rect.width; 39 | pos.height = rect.height; 40 | } 41 | 42 | 43 | var actualWidth = $tip[0].offsetWidth, 44 | actualHeight = $tip[0].offsetHeight, 45 | gravity = maybeCall(this.options.gravity, this.$element[0]); 46 | 47 | var tp; 48 | switch (gravity.charAt(0)) { 49 | case 'n': 50 | tp = {top: pos.top + pos.height + this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2}; 51 | break; 52 | case 's': 53 | tp = {top: pos.top - actualHeight - this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2}; 54 | break; 55 | case 'e': 56 | tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth - this.options.offset}; 57 | break; 58 | case 'w': 59 | tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width + this.options.offset}; 60 | break; 61 | } 62 | 63 | if (gravity.length == 2) { 64 | if (gravity.charAt(1) == 'w') { 65 | tp.left = pos.left + pos.width / 2 - 15; 66 | } else { 67 | tp.left = pos.left + pos.width / 2 - actualWidth + 15; 68 | } 69 | } 70 | 71 | $tip.css(tp).addClass('tipsy-' + gravity); 72 | $tip.find('.tipsy-arrow')[0].className = 'tipsy-arrow tipsy-arrow-' + gravity.charAt(0); 73 | if (this.options.className) { 74 | $tip.addClass(maybeCall(this.options.className, this.$element[0])); 75 | } 76 | 77 | if (this.options.fade) { 78 | $tip.stop().css({opacity: 0, display: 'block', visibility: 'visible'}).animate({opacity: this.options.opacity}); 79 | } else { 80 | $tip.css({visibility: 'visible', opacity: this.options.opacity}); 81 | } 82 | 83 | var t = this; 84 | var set_hovered = function(set_hover){ 85 | return function(){ 86 | t.$tip.stop(); 87 | t.tipHovered = set_hover; 88 | if (!set_hover){ 89 | if (t.options.delayOut === 0) { 90 | t.hide(); 91 | } else { 92 | setTimeout(function() { 93 | if (t.hoverState == 'out') t.hide(); }, t.options.delayOut); 94 | } 95 | } 96 | }; 97 | }; 98 | $tip.hover(set_hovered(true), set_hovered(false)); 99 | } 100 | }, 101 | 102 | hide: function() { 103 | if (this.options.fade) { 104 | this.tip().stop().fadeOut(function() { $(this).remove(); }); 105 | } else { 106 | this.tip().remove(); 107 | } 108 | }, 109 | 110 | fixTitle: function() { 111 | var $e = this.$element; 112 | 113 | if ($e.attr('title') || typeof($e.attr('original-title')) != 'string') { 114 | $e.attr('original-title', $e.attr('title') || '').removeAttr('title'); 115 | } 116 | if (typeof $e.context.nearestViewportElement == 'object'){ 117 | if ($e.children('title').length){ 118 | $e.append('' + ($e.children('title').text() || '') + '') 119 | .children('title').remove(); 120 | } 121 | } 122 | }, 123 | 124 | getTitle: function() { 125 | 126 | var title, $e = this.$element, o = this.options; 127 | this.fixTitle(); 128 | 129 | if (typeof o.title == 'string') { 130 | var title_name = o.title == 'title' ? 'original-title' : o.title; 131 | if ($e.children(title_name).length){ 132 | title = $e.children(title_name).html(); 133 | } else{ 134 | title = $e.attr(title_name); 135 | } 136 | 137 | } else if (typeof o.title == 'function') { 138 | title = o.title.call($e[0]); 139 | } 140 | title = ('' + title).replace(/(^\s*|\s*$)/, ""); 141 | return title || o.fallback; 142 | }, 143 | 144 | tip: function() { 145 | if (!this.$tip) { 146 | this.$tip = $('
').html('
'); 147 | } 148 | return this.$tip; 149 | }, 150 | 151 | validate: function() { 152 | if (!this.$element[0].parentNode) { 153 | this.hide(); 154 | this.$element = null; 155 | this.options = null; 156 | } 157 | }, 158 | 159 | enable: function() { this.enabled = true; }, 160 | disable: function() { this.enabled = false; }, 161 | toggleEnabled: function() { this.enabled = !this.enabled; } 162 | }; 163 | 164 | $.fn.tipsy = function(options) { 165 | 166 | if (options === true) { 167 | return this.data('tipsy'); 168 | } else if (typeof options == 'string') { 169 | var tipsy = this.data('tipsy'); 170 | if (tipsy) tipsy[options](); 171 | return this; 172 | } 173 | 174 | options = $.extend({}, $.fn.tipsy.defaults, options); 175 | 176 | if (options.hoverlock && options.delayOut === 0) { 177 | options.delayOut = 100; 178 | } 179 | 180 | function get(ele) { 181 | var tipsy = $.data(ele, 'tipsy'); 182 | if (!tipsy) { 183 | tipsy = new Tipsy(ele, $.fn.tipsy.elementOptions(ele, options)); 184 | $.data(ele, 'tipsy', tipsy); 185 | } 186 | return tipsy; 187 | } 188 | 189 | function enter() { 190 | var tipsy = get(this); 191 | tipsy.hoverState = 'in'; 192 | if (options.delayIn === 0) { 193 | tipsy.show(); 194 | } else { 195 | tipsy.fixTitle(); 196 | setTimeout(function() { if (tipsy.hoverState == 'in') tipsy.show(); }, options.delayIn); 197 | } 198 | } 199 | 200 | function leave() { 201 | var tipsy = get(this); 202 | tipsy.hoverState = 'out'; 203 | if (options.delayOut === 0) { 204 | tipsy.hide(); 205 | } else { 206 | var to = function() { 207 | if (!tipsy.tipHovered || !options.hoverlock){ 208 | if (tipsy.hoverState == 'out') tipsy.hide(); 209 | } 210 | }; 211 | setTimeout(to, options.delayOut); 212 | } 213 | } 214 | 215 | if (options.trigger != 'manual') { 216 | var binder = options.live ? 'live' : 'bind', 217 | eventIn = options.trigger == 'hover' ? 'mouseenter' : 'focus', 218 | eventOut = options.trigger == 'hover' ? 'mouseleave' : 'blur'; 219 | this[binder](eventIn, enter)[binder](eventOut, leave); 220 | } 221 | 222 | return this; 223 | 224 | }; 225 | 226 | $.fn.tipsy.defaults = { 227 | className: null, 228 | delayIn: 0, 229 | delayOut: 0, 230 | fade: false, 231 | fallback: '', 232 | gravity: 'n', 233 | html: false, 234 | live: false, 235 | offset: 0, 236 | opacity: 0.8, 237 | title: 'title', 238 | trigger: 'hover', 239 | hoverlock: false 240 | }; 241 | 242 | // Overwrite this method to provide options on a per-element basis. 243 | // For example, you could store the gravity in a 'tipsy-gravity' attribute: 244 | // return $.extend({}, options, {gravity: $(ele).attr('tipsy-gravity') || 'n' }); 245 | // (remember - do not modify 'options' in place!) 246 | $.fn.tipsy.elementOptions = function(ele, options) { 247 | return $.metadata ? $.extend({}, options, $(ele).metadata()) : options; 248 | }; 249 | 250 | $.fn.tipsy.autoNS = function() { 251 | return $(this).offset().top > ($(document).scrollTop() + $(window).height() / 2) ? 's' : 'n'; 252 | }; 253 | 254 | $.fn.tipsy.autoWE = function() { 255 | return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'e' : 'w'; 256 | }; 257 | 258 | /** 259 | * yields a closure of the supplied parameters, producing a function that takes 260 | * no arguments and is suitable for use as an autogravity function like so: 261 | * 262 | * @param margin (int) - distance from the viewable region edge that an 263 | * element should be before setting its tooltip's gravity to be away 264 | * from that edge. 265 | * @param prefer (string, e.g. 'n', 'sw', 'w') - the direction to prefer 266 | * if there are no viewable region edges effecting the tooltip's 267 | * gravity. It will try to vary from this minimally, for example, 268 | * if 'sw' is preferred and an element is near the right viewable 269 | * region edge, but not the top edge, it will set the gravity for 270 | * that element's tooltip to be 'se', preserving the southern 271 | * component. 272 | */ 273 | $.fn.tipsy.autoBounds = function(margin, prefer) { 274 | return function() { 275 | var dir = {ns: prefer[0], ew: (prefer.length > 1 ? prefer[1] : false)}, 276 | boundTop = $(document).scrollTop() + margin, 277 | boundLeft = $(document).scrollLeft() + margin, 278 | $this = $(this); 279 | 280 | if ($this.offset().top < boundTop) dir.ns = 'n'; 281 | if ($this.offset().left < boundLeft) dir.ew = 'w'; 282 | if ($(window).width() + $(document).scrollLeft() - $this.offset().left < margin) dir.ew = 'e'; 283 | if ($(window).height() + $(document).scrollTop() - $this.offset().top < margin) dir.ns = 's'; 284 | 285 | return dir.ns + (dir.ew ? dir.ew : ''); 286 | }; 287 | }; 288 | })(jQuery); -------------------------------------------------------------------------------- /inspector/src/test/groovy/com/jakeout/gradle/utils/gtree/GTreeTest.groovy: -------------------------------------------------------------------------------- 1 | package com.jakeout.gradle.utils.gtree 2 | 3 | import org.junit.Assert 4 | import org.junit.Test 5 | 6 | class GTreeTest { 7 | 8 | // TODO: The current way I'm testing diffs is a bit heuristic. 9 | // Avoid searching into strings. 10 | 11 | @Test 12 | public void testTreesAreSame() { 13 | def node1 = GTrees.create("a") { 14 | b "2" 15 | } 16 | 17 | def node2 = GTrees.create("a") { 18 | b "2" 19 | } 20 | 21 | println("testTreesAreSame:") 22 | println(node1) 23 | println(node2) 24 | println(GTrees.diff(node1, node2)) 25 | def diff = GTrees.diff(node1, node2) 26 | println(diff) 27 | Assert.assertEquals(0, diff.toString().count("-b")) 28 | Assert.assertEquals(0, diff.toString().count("+b")) 29 | Assert.assertEquals(0, diff.toString().count("~b")) 30 | } 31 | 32 | @Test 33 | public void testTreesAreDifferent() { 34 | def a = GTrees.create("a") { 35 | b "1" 36 | } 37 | 38 | def b = GTrees.create("a") { 39 | b "2" 40 | } 41 | println("testTreesAreDifferent:") 42 | println(a) 43 | println(b) 44 | def diff = GTrees.diff(a, b) 45 | println(diff) 46 | Assert.assertEquals(0, diff.toString().count("-b")) 47 | Assert.assertEquals(0, diff.toString().count("+b")) 48 | Assert.assertEquals(1, diff.toString().count("~b")) 49 | } 50 | 51 | @Test 52 | public void testTreesSupportDeleteAndAdd() { 53 | def node1 = GTrees.create("a") { 54 | b { 55 | c { 56 | d { 57 | e 1 58 | 59 | i { 60 | 61 | } 62 | } 63 | 64 | f 3 65 | } 66 | } 67 | } 68 | 69 | def node2 = GTrees.create("a") { 70 | b { 71 | c { 72 | d { 73 | e 2 74 | } 75 | 76 | } 77 | 78 | g { 79 | h 3 80 | } 81 | } 82 | } 83 | 84 | def diff = GTrees.diff(node1, node2) 85 | println(diff) 86 | Assert.assertEquals(1, diff.toString().count("-f")) 87 | Assert.assertEquals(1, diff.toString().count("-i")) 88 | Assert.assertEquals(1, diff.toString().count("+g")) 89 | Assert.assertEquals(1, diff.toString().count("+h")) 90 | Assert.assertEquals(1, diff.toString().count("~e")) 91 | } 92 | 93 | @Test 94 | public void treeIsExtractable() { 95 | def exampleProjectMap = [android: [defaultConfig: 96 | [minSdkVersion : 1, 97 | targetSdkVersion: 2]]] 98 | 99 | def exampleProjectTree = GTrees.create("project") { 100 | android { 101 | defaultConfig { 102 | minSdkVersion 1 103 | targetSdkVersion 2 104 | } 105 | } 106 | } 107 | 108 | def template = GTrees.createTemplate { 109 | android { 110 | defaultConfig { 111 | minSdkVersion() 112 | targetSdkVersion() 113 | } 114 | } 115 | } 116 | 117 | def result = GTrees.extract(template, exampleProjectMap) 118 | 119 | def diff = GTrees.diff(exampleProjectTree, result) 120 | 121 | println(diff) 122 | Assert.assertEquals("1", result.android.defaultConfig.minSdkVersion.value) 123 | Assert.assertEquals("2", result.android.defaultConfig.targetSdkVersion.value) 124 | } 125 | 126 | @Test 127 | public void extractedTreeHasOneDifference() { 128 | 129 | def exampleProjectMap = [android: [defaultConfig: 130 | [minSdkVersion : 2, 131 | targetSdkVersion: 2]]] 132 | 133 | def exampleProjectMap2 = [android: [defaultConfig: 134 | [minSdkVersion : 1, 135 | targetSdkVersion: 2]]] 136 | 137 | def template = GTrees.createTemplate { 138 | android { 139 | defaultConfig { 140 | minSdkVersion() 141 | targetSdkVersion() 142 | } 143 | } 144 | } 145 | 146 | def extraction1 = GTrees.extract(template, exampleProjectMap) 147 | def extraction2 = GTrees.extract(template, exampleProjectMap2) 148 | 149 | def diff = GTrees.diff(extraction1, extraction2) 150 | 151 | println(diff) 152 | Assert.assertEquals("1", diff.android.defaultConfig.minSdkVersion.newValueIfAny) 153 | Assert.assertEquals(HasDiff.Diff.CONTENTS_CHANGED, diff.android.defaultConfig.minSdkVersion.changeType) 154 | Assert.assertEquals("2", diff.android.defaultConfig.targetSdkVersion.originalValueIfExisted) 155 | Assert.assertEquals(HasDiff.Diff.UNCHANGED, diff.android.defaultConfig.targetSdkVersion.changeType) 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':inspector' --------------------------------------------------------------------------------