├── .gitignore ├── README.md ├── build.gradle └── src ├── main ├── groovy │ └── com │ │ └── siprell │ │ └── plugin │ │ └── gradle │ │ └── bootstrap │ │ └── BootstrapGradlePlugin.groovy └── resources │ └── META-INF │ └── gradle-plugins │ └── com.siprell.plugins.bootstrap-framework.properties └── test └── groovy └── com └── siprell └── plugin └── gradle └── bootstrap └── GradlePluginSpec.groovy /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.iml 3 | *.ipr 4 | *.iws 5 | *.log 6 | /*.sha1 7 | /*.zip 8 | /.classpath 9 | /.project 10 | /.settings 11 | *Db.properties 12 | *Db.script 13 | /*DB.* 14 | /.gradle 15 | /.idea 16 | /.link_to_grails_plugins 17 | /build 18 | /userHome 19 | /grails-app 20 | /src/assets 21 | /src/main/webapp 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## This plugin is no longer maintained. 2 | 3 | ### Gradle plugin for integrating the Bootstrap Framework and Font Awesome 4 | 5 | [ ![Download](https://api.bintray.com/packages/kensiprell/gradle-plugins/bootstrap-framework/images/download.svg) ](https://bintray.com/kensiprell/gradle-plugins/bootstrap-framework/_latestVersion) 6 | 7 | [Bootstrap](http://getbootstrap.com) bills itself as "the most popular HTML, CSS, and JS framework for developing responsive, mobile first projects on the web." 8 | 9 | [Font Awesome](http://fortawesome.github.io/Font-Awesome/) is the "iconic font and CSS toolkit." 10 | 11 | If you have a question, suggestion, or want to report a bug, please submit an [issue](https://github.com/kensiprell/bootstrap-framework/issues). I will reply as soon as I can. 12 | 13 | ### Highlights 14 | 15 | * The Bootstrap version can be configured in your application's ```build.gradle``` file, which means you do not have to install a different version of the plugin to get a different Bootstrap version, nor do you have to wait on a plugin update to use the latest Bootstrap release. 16 | 17 | * The plugin can also manage Font Awesome files, including LESS support. 18 | 19 | * The plugin supports the [asset-pipeline-core](https://github.com/bertramdev/asset-pipeline-core) plugin and its [less-asset-pipeline](https://github.com/bertramdev/less-asset-pipeline) module out of the box. 20 | 21 | ### Sample Application 22 | 23 | This Grails [sample application](https://github.com/kensiprell/bootstrap-framework-sample) demonstrates how to use the plugin. 24 | 25 | ### Installation 26 | 27 | Add the following lines to your application's ```build.gradle``` file. The commented-out lines show the plugin default values, and if you are using Grails, they are not required for the plugin to work. See the [Configuration Options](https://github.com/kensiprell/bootstrap-framework#configuration-options) section for the details. 28 | 29 | buildscript { 30 | ext { 31 | //bootstrapFramework = [ 32 | // version : "3.3.5", 33 | // cssPath : "grails-app/assets/stylesheets", 34 | // jsPath : "grails-app/assets/javascripts", 35 | // useIndividualJs : false, 36 | // useLess : false, 37 | // invalidVersionFails : false, 38 | // fontAwesome : [ 39 | // install : false 40 | // version : "4.3.0", 41 | // useLess : false 42 | // invalidVersionFails : false 43 | // ] 44 | //] 45 | } 46 | repositories { 47 | jcenter() 48 | } 49 | dependencies { 50 | classpath "com.siprell.plugins:bootstrap-framework:1.0.3" 51 | } 52 | } 53 | 54 | apply plugin: "com.siprell.plugins.bootstrap-framework" 55 | 56 | ### Configuration Options 57 | 58 | The following options can be configured in the ```bootstrapFramework``` Map. 59 | 60 | #### bootstrapFramework.version 61 | 62 | Use the property below to change the Bootstrap Framework version used by your application. 63 | 64 | version : "3.3.1" 65 | 66 | #### bootstrapFramework.cssPath 67 | 68 | Use the property below to define the location where the Bootstrap CSS, fonts, and LESS files are copied. 69 | 70 | cssPath : "src/main/web-app/resources/css" 71 | 72 | #### bootstrapFramework.jsPath 73 | 74 | Use the property below to define the location where the Bootstrap JavaScript files are copied. 75 | 76 | jsPath : "src/main/web-app/resources/js" 77 | 78 | #### bootstrapFramework.useIndividualJs 79 | 80 | If the property below is set to ```true```, the plugin will copy all individual JavaScript files to ```"${bootstrapFramework.jsPath}/bootstrap"```. Otherwise, it will only copy the ```bootstrap.js``` file to the directory. 81 | 82 | useIndividualJs : true 83 | 84 | #### bootstrapFramework.useLess 85 | 86 | If the property below is set to ```true```, the plugin will copy all Bootstrap Framework LESS and mixin files to ```"${bootstrapFramework.cssPath}/bootstrap/less"```. 87 | 88 | useLess : true 89 | 90 | #### invalidVersionFails 91 | 92 | The ```invalidVersionFails``` property can be configured individually for ```bootstrapFramework``` and ```bootstrapFramework.fontAwesome```. When the property is at its default of ```false``` and you have entered an invalid version number, the plugin will search the ```build/tmp``` directory for other versions of the appropriate zip file and use the one with the highest version number. It will also display a warning message in the console. 93 | 94 | You can disable this behavior by setting this property to ```true```. If the plugin cannot download the version you specified, it will throw an ```org.gradle.api.InvalidUserDataException```. 95 | 96 | bootstrapFramework = [ 97 | invalidVersionFails : true 98 | fontAwesome : [ 99 | install : true, 100 | invalidVersionFails : true 101 | ] 102 | ] 103 | 104 | #### bootstrapFramework.fontAwesome.install 105 | 106 | If ```bootstrapFramework.fontAwesome.install``` is set to ```true```, the plugin will install the Font Awesome fonts using the default plugin version without LESS support. 107 | 108 | bootstrapFramework = [ 109 | fontAwesome : [ 110 | install : true 111 | ] 112 | ] 113 | 114 | #### bootstrapFramework.fontAwesome.version 115 | 116 | You can change the Font Awesome version by setting the ```bootstrapFramework.fontAwesome.version``` property. 117 | 118 | bootstrapFramework = [ 119 | fontAwesome : [ 120 | install : true 121 | version : "4.2.0" 122 | ] 123 | ] 124 | 125 | #### bootstrapFramework.fontAwesome.useLess 126 | 127 | You can add LESS support for Font Awesome by setting the ```bootstrapFramework.fontAwesome.useLess``` property. 128 | 129 | bootstrapFramework = [ 130 | fontAwesome : [ 131 | install : true 132 | useLess : true 133 | ] 134 | ] 135 | 136 | ### How the Plugin Works 137 | 138 | The plugin downloads the appropriate Bootstrap or Font Awesome zip file and copies it to your application's ```build/tmp``` directory. The plugin will extract the necessary files and copy them to the directories defined by the ```bootstrapFramework.cssPath``` and ```bootstrapFramework.jsPath``` properties. 139 | 140 | The files are copied into the directory trees shown below. It is important that you do not put any files in the two ```bootstrap``` directories (```"${bootstrapFramework.cssPath}/bootstrap"``` and ```"${bootstrapFramework.jsPath}/bootstrap"```) or the ```font-awesome``` directory (```"${bootstrapFramework.cssPath}/font-awesome"```) because they will be overwritten. 141 | 142 | The ```bootstrap-all.js```, ```bootstrap-all.css```, ```bootstrap-less.less```, ```font-awesome-all.css```, and ```font-awesome-less.less``` files are generated for the asset-pipeline plugin if you are using Grails or the word "assets" is contained in the ```bootstrapFramework.jsPath``` property. 143 | 144 | Directory ```bootstrapFramework.jsPath```: 145 | 146 | | bootstrap-all.js 147 | |----bootstrap/ 148 | | | affix.js 149 | | | alert.js 150 | | | bootstrap.js 151 | | | etc. 152 | 153 | Directory ```bootstrapFramework.cssPath```: 154 | 155 | | bootstrap-all.css 156 | | bootstrap-less.less 157 | |----bootstrap/ 158 | | |----css/ 159 | | | | bootstrap-theme.css 160 | | | | bootstrap.css 161 | | |----fonts/ 162 | | | | glyphicons-halflings-regular.eot 163 | | | | etc. 164 | | |----less/ 165 | | | | alerts.less 166 | | | | badges.less 167 | | | | etc. 168 | | | |----mixins/ 169 | | | | | alerts.less 170 | | | | | background-variant.less 171 | | | | | etc. 172 | | font-awesome-all.css 173 | | font-awesome-less.less 174 | |----font-awesome/ 175 | | |----css/ 176 | | | | font-awesome.css 177 | | |----fonts/ 178 | | | | FontAwesome.otf 179 | | | | etc. 180 | | |----less/ 181 | | | | animated.less 182 | | | | etc. 183 | 184 | ### User Task 185 | 186 | You can use the task shown below to display the default Bootstrap Framework and Font Awesome versions used by the plugin. 187 | 188 | ./gradlew bootstrapFrameworkVersions 189 | 190 | or 191 | 192 | ./gradlew bFV 193 | 194 | The output will be similar to: 195 | 196 | 3.3.5 is the default Bootstrap Framework version. 197 | 4.3.0 is the default Font Awesome version. 198 | 199 | ### Glyphicons 200 | 201 | The Glyphicons icons are available as described in the [Bootstrap Components](http://getbootstrap.com/components/) section of the Bootstrap Framework documentation. 202 | 203 | ### Asset Pipeline Usage 204 | 205 | The remaining sections outline how to include Bootstrap Framework and Font Awesome in your application using the ```asset-pipeline-core``` plugin and its ```less-asset-pipeline``` module. 206 | 207 | #### JavaScript 208 | 209 | The instructions below assume the manifest file is in the ```grails-app/assets/javascripts``` directory. 210 | 211 | ##### Add all Bootstrap JavaScript Files 212 | 213 | Add the line below to a manifest: 214 | 215 | //= require bootstrap-all 216 | 217 | Or add the line below to a GSP: 218 | 219 | 220 | 221 | ##### Add individual Bootstrap JavaScript files 222 | 223 | Ensure you set the parameter below to true as described [above](https://github.com/kensiprell/bootstrap-framework#bootstrapframeworkuseindividualjs): 224 | 225 | bootstrapFrameworkUseIndividualJs = true 226 | 227 | Add a line similar to the one below to a manifest: 228 | 229 | //= require bootstrap/bootstrap-affix 230 | 231 | Or add the line below to a GSP: 232 | 233 | 234 | 235 | #### Stylesheets 236 | 237 | The instructions below assume the manifest file is in the ```grails-app/assets/stylesheets``` directory. 238 | 239 | ##### Add all Bootstrap and Font Awesome CSS Files 240 | 241 | Add the lines below to a manifest: 242 | 243 | *= require bootstrap-all 244 | *= require font-awesome-all 245 | 246 | Or add the lines below to a GSP: 247 | 248 | 249 | 250 | 251 | ##### Add individual Bootstrap CSS Files 252 | 253 | Add the line below to a manifest: 254 | 255 | *= require bootstrap/css/bootstrap-theme 256 | 257 | Or add the line below to a GSP: 258 | 259 | 260 | 261 | ### LESS 262 | 263 | #### Add LESS Files 264 | 265 | Add the lines below to a manifest: 266 | 267 | *= require bootstrap-less 268 | *= require font-awesome-less 269 | 270 | Or add the line below to a GSP: 271 | 272 | 273 | 274 | 275 | #### LESS Customizations 276 | 277 | If LESS support is configured for either Bootstrap Framework or Font Awesome, the plugin will create the appropriate LESS file in your application's ```bootstrapFramework.cssPath``` directory. If you later decide not to use LESS, the plugin will delete the LESS and mixin files, but it will not delete the ```bootstrap-less.less``` or the ```font-awesome-less.less``` file. Should you decide to turn LESS support back on, your customized LESS files will still be available. 278 | 279 | Use ```bootstrap-less.less``` for customizing the Bootstrap Framework: 280 | 281 | /* 282 | * This file is for your Bootstrap Framework less and mixin customizations. 283 | * It was created by the bootstrap-framework plugin. 284 | * It will not be overwritten. 285 | * 286 | * You can import all less and mixin files as shown below, 287 | * or you can import them individually. 288 | * See https://github.com/kensiprell/bootstrap-framework/blob/master/README.md#less 289 | */ 290 | 291 | @import "bootstrap/less/bootstrap.less"; 292 | 293 | /* 294 | * Your customizations go below this section. 295 | */ 296 | 297 | Use ```font-awesome-less.less``` for customizing Font Awesome: 298 | 299 | * Font Awesome by Dave Gandy - http://fontawesome.io 300 | * 301 | * This file is for your Font Awesome less and mixin customizations. 302 | * It was created by the bootstrap-framework plugin. 303 | * It will not be overwritten. 304 | * 305 | * You can import all less and mixin files as shown below, 306 | * or you can import them individually. 307 | * See https://github.com/kensiprell/bootstrap-framework/blob/master/README.md#font-awesome-less 308 | */ 309 | 310 | @import "font-awesome/less/font-awesome.less"; 311 | 312 | @fa-font-path: "/assets/font-awesome/fonts"; 313 | 314 | /* 315 | * Your customizations go below this section. 316 | */ 317 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenLocal() 4 | jcenter() 5 | maven { 6 | url "https://plugins.gradle.org/m2/" 7 | } 8 | } 9 | dependencies {} 10 | } 11 | 12 | plugins { 13 | id "groovy" 14 | id "maven" 15 | id "maven-publish" 16 | id "com.gradle.plugin-publish" version "0.9.1" 17 | id "com.jfrog.bintray" version "1.2" 18 | } 19 | 20 | version "1.0.3" 21 | group = "com.siprell.plugins" 22 | sourceCompatibility = "1.7" 23 | targetCompatibility = "1.7" 24 | 25 | ext { 26 | isReleaseVersion = !version.endsWith("SNAPSHOT") 27 | } 28 | 29 | repositories { 30 | jcenter() 31 | mavenLocal() 32 | mavenCentral() 33 | } 34 | 35 | dependencies { 36 | compile gradleApi() 37 | compile localGroovy() 38 | testCompile("org.spockframework:spock-core:1.0-groovy-2.3") 39 | testCompile("org.springframework.boot:spring-boot:1.2.1.RELEASE") 40 | } 41 | 42 | task sourcesJar(type: Jar) { 43 | classifier = "sources" 44 | from sourceSets.main.allSource 45 | } 46 | 47 | task(console, dependsOn: "classes", type: JavaExec) { 48 | main = "groovy.ui.Console" 49 | classpath = sourceSets.main.runtimeClasspath 50 | } 51 | 52 | test { 53 | testLogging { 54 | showStandardStreams = true 55 | } 56 | } 57 | 58 | publishing { 59 | publications { 60 | maven(MavenPublication) { 61 | artifactId "bootstrap-framework" 62 | pom.withXml { 63 | asNode().children().last() + { 64 | resolveStrategy = Closure.DELEGATE_FIRST 65 | name "bootstrap-framework" 66 | description "Gradle plugin for integrating the Bootstrap Framework and Font Awesome" 67 | url "https://github.com/kensiprell/bootstrap-framework" 68 | scm { 69 | url "https://github.com/kensiprell/bootstrap-framework" 70 | connection "scm:https://github.com/kensiprell/bootstrap-framework.git" 71 | developerConnection "scm:git://github.com/kensiprell/bootstrap-framework.git" 72 | } 73 | licenses { 74 | license { 75 | name "The Apache Software License, Version 2.0" 76 | url "http://www.apache.org/license/LICENSE-2.0.txt" 77 | distribution "repo" 78 | } 79 | } 80 | developers { 81 | developer { 82 | id "kensiprell" 83 | name "Ken Siprell" 84 | email "ken.siprell@gmail.com" 85 | } 86 | } 87 | } 88 | } 89 | from components.java 90 | artifact sourcesJar 91 | } 92 | } 93 | } 94 | 95 | bintray { 96 | if (project.hasProperty("bintrayUser")) { 97 | user = bintrayUser 98 | key = bintrayKey 99 | } 100 | publications = ["maven"] 101 | pkg { 102 | repo = "gradle-plugins" 103 | name = "bootstrap-framework" 104 | desc = "Gradle plugin for integrating the Bootstrap Framework and Font Awesome" 105 | websiteUrl = "https://github.com/kensiprell/bootstrap-framework" 106 | issueTrackerUrl = "https://github.com/kensiprell/bootstrap-framework/issues" 107 | vcsUrl = "https://github.com/kensiprell/bootstrap-framework.git" 108 | licenses = ["Apache-2.0"] 109 | labels = ["bootstrap", "fontawesome", "font awesome", "gradle"] 110 | publicDownloadNumbers = true 111 | } 112 | } 113 | 114 | pluginBundle { 115 | website = "https://github.com/kensiprell/bootstrap-framework" 116 | vcsUrl = "https://github.com/kensiprell/bootstrap-framework.git" 117 | description = "Gradle plugin for integrating the Bootstrap Framework and Font Awesome" 118 | tags = ["bootstrap", "fontawesome", "font awesome", "gradle"] 119 | 120 | plugins { 121 | bootstrapFrameworkPlugin { 122 | id = "com.siprell.plugins.bootstrap-framework" 123 | displayName = "Bootstrap Framework Plugin for Gradle" 124 | } 125 | } 126 | } 127 | 128 | bintrayUpload.dependsOn build, sourcesJar 129 | -------------------------------------------------------------------------------- /src/main/groovy/com/siprell/plugin/gradle/bootstrap/BootstrapGradlePlugin.groovy: -------------------------------------------------------------------------------- 1 | package com.siprell.plugin.gradle.bootstrap 2 | 3 | import org.gradle.api.file.FileTree 4 | import org.gradle.api.InvalidUserDataException 5 | import org.gradle.api.Plugin 6 | import org.gradle.api.Project 7 | import org.gradle.api.tasks.Copy 8 | import org.gradle.api.tasks.Sync 9 | 10 | class BootstrapGradlePlugin implements Plugin { 11 | final BOOTSTRAP_DEFAULT_VERSION = "3.3.5" 12 | final FA_DEFAULT_VERSION = "4.3.0" 13 | 14 | void apply(Project project) { 15 | 16 | // Shared properties 17 | def template = new Template() 18 | def zipFile = new ZipFile() 19 | def properties = project.hasProperty("bootstrapFramework") ? project.bootstrapFramework : [:] 20 | String tmpDir = "${project.buildDir}/tmp" 21 | 22 | // Bootstrap Framework properties 23 | String bootstrapVersion = properties.version ?: BOOTSTRAP_DEFAULT_VERSION 24 | boolean useIndividualJs = properties.useIndividualJs != null ? properties.useIndividualJs : false 25 | boolean useLess = properties.useLess != null ? properties.useLess : false 26 | String jsPath = properties.jsPath ? properties.jsPath : "grails-app/assets/javascripts" 27 | String cssPath = properties.cssPath ? properties.cssPath : "grails-app/assets/stylesheets" 28 | String bootstrapJsPath = "${project.projectDir}/$jsPath/bootstrap" 29 | String bootstrapCssPath = "${project.projectDir}/$cssPath/bootstrap/css" 30 | String bootstrapFontsPath = "${project.projectDir}/$cssPath/bootstrap/fonts" 31 | String bootstrapLessPath = "${project.projectDir}/$cssPath/bootstrap/less" 32 | String bootstrapMixinsPath = "$bootstrapLessPath/mixins" 33 | boolean useAssetPipeline = jsPath.contains("assets") 34 | boolean bootstrapInvalidVersionFails = properties.invalidVersionFails != null ? properties.invalidVersionFails : false 35 | FileTree bootstrapZipTree 36 | 37 | // Font Awesome properties 38 | def fontAwesome = properties.fontAwesome 39 | boolean fontAwesomeInstall = fontAwesome?.install != null ? fontAwesome.install : false 40 | String fontAwesomeVersion = fontAwesome?.version ?: FA_DEFAULT_VERSION 41 | boolean fontAwesomeUseLess = fontAwesome?.useLess != null ? fontAwesome.useLess : false 42 | boolean fontAwesomeInvalidVersionFails = fontAwesome?.invalidVersionFails != null ? fontAwesome.invalidVersionFails : false 43 | String fontAwesomePath = "${project.projectDir}/$cssPath/font-awesome" 44 | String fontAwesomeCssPath = "$fontAwesomePath/css" 45 | String fontAwesomeFontsPath = "$fontAwesomePath/fonts" 46 | String fontAwesomeLessPath = "$fontAwesomePath/less" 47 | FileTree fontAwesomeZipTree 48 | 49 | project.afterEvaluate { 50 | project.tasks.processResources.dependsOn("createFontAwesomeLess") 51 | } 52 | 53 | project.task("bootstrapFrameworkVersions") << { 54 | println "$BOOTSTRAP_DEFAULT_VERSION is the default Bootstrap Framework version." 55 | println "$FA_DEFAULT_VERSION is the default Font Awesome version." 56 | } 57 | 58 | project.task("checkDirectories") { 59 | if (!project.file(cssPath).exists()) { 60 | throw new InvalidUserDataException("bootstrapFramework.cssPath directory ($cssPath) does not exist.") 61 | } 62 | if (!project.file(jsPath).exists()) { 63 | throw new InvalidUserDataException("bootstrapFramework.jsPath directory ($jsPath) does not exist.") 64 | } 65 | } 66 | 67 | project.task("downloadBootstrapZip") { 68 | String description = "Bootstrap Framework" 69 | String filePrefix = "bootstrap-v" 70 | String url = "https://github.com/twbs/bootstrap/archive/v${bootstrapVersion}.zip" 71 | String zipFilename = "${filePrefix}${bootstrapVersion}.zip" 72 | def file = zipFile.download(tmpDir, description, filePrefix, url, bootstrapVersion, zipFilename, bootstrapInvalidVersionFails) 73 | if (file instanceof File) { 74 | bootstrapZipTree = project.zipTree(file) 75 | } else if (file instanceof String) { 76 | throw new InvalidUserDataException(file) 77 | } else { 78 | throw new InvalidUserDataException("An unknown error occurred trying to download $url") 79 | } 80 | } 81 | 82 | project.task("downloadFontAwesomeZip") { 83 | if (fontAwesomeInstall) { 84 | String description = "Font Awesome" 85 | String filePrefix = "fontAwesome-v" 86 | String url = "https://github.com/FortAwesome/Font-Awesome/archive/v${fontAwesomeVersion}.zip" 87 | String zipFilename = "${filePrefix}${fontAwesomeVersion}.zip" 88 | def file = zipFile.download(tmpDir, description, filePrefix, url, fontAwesomeVersion, zipFilename, fontAwesomeInvalidVersionFails) 89 | if (file instanceof File) { 90 | fontAwesomeZipTree = project.zipTree(file) 91 | } else if (file instanceof String) { 92 | throw new InvalidUserDataException(file) 93 | } else { 94 | throw new InvalidUserDataException("An unknown error occurred trying to download $url") 95 | } 96 | } 97 | } 98 | 99 | project.task("manageBootstrapDirs") { 100 | if (!project.file(bootstrapJsPath).exists()) { 101 | project.mkdir(bootstrapJsPath) 102 | } 103 | if (!project.file(bootstrapCssPath).exists()) { 104 | project.mkdir(bootstrapCssPath) 105 | } 106 | if (!project.file(bootstrapFontsPath).exists()) { 107 | project.mkdir(bootstrapFontsPath) 108 | } 109 | if (useLess) { 110 | if (!project.file(bootstrapMixinsPath).exists()) { 111 | project.mkdir(bootstrapMixinsPath) 112 | } 113 | } else { 114 | project.delete(bootstrapLessPath) 115 | } 116 | if (fontAwesomeInstall) { 117 | def dirs = ["css", "fonts"] 118 | if (fontAwesomeUseLess) { 119 | dirs << "less" 120 | } else { 121 | project.delete(fontAwesomeLessPath) 122 | } 123 | dirs.each { 124 | project.mkdir("$fontAwesomePath/$it") 125 | } 126 | } else { 127 | project.delete(fontAwesomePath) 128 | } 129 | } 130 | 131 | project.task("createBootstrapJsAll", type: Copy, dependsOn: project.tasks.manageBootstrapDirs) { 132 | def path = "${project.projectDir}/$jsPath" 133 | def filename = "bootstrap-all.js" 134 | if (useAssetPipeline) { 135 | from template.getFile(project, "createBootstrapJsAll") 136 | rename ".*", filename 137 | into path 138 | onlyIf { !project.file("$path/$filename").exists() } 139 | } else { 140 | project.delete("$path/$filename") 141 | } 142 | } 143 | 144 | project.task("createBootstrapJs", type: Sync, dependsOn: project.tasks.createBootstrapJsAll) { 145 | def files = bootstrapZipTree.matching { 146 | include "*/dist/js/bootstrap.js" 147 | if (useIndividualJs) { 148 | include "*/js/*.js" 149 | } 150 | }.collect() 151 | from files 152 | into bootstrapJsPath 153 | } 154 | 155 | project.task("createBootstrapCssAll", type: Copy, dependsOn: project.tasks.createBootstrapJs) { 156 | def path = "${project.projectDir}/$cssPath" 157 | def filename = "bootstrap-all.css" 158 | if (useAssetPipeline) { 159 | from template.getFile(project, "createBootstrapCssAll") 160 | rename ".*", filename 161 | into path 162 | onlyIf { !project.file("$path/$filename").exists() } 163 | } else { 164 | project.delete("$path/$filename") 165 | } 166 | } 167 | 168 | project.task("createBootstrapFonts", type: Sync, dependsOn: project.tasks.createBootstrapCssAll) { 169 | def files = bootstrapZipTree.matching { 170 | include "*/fonts/*" 171 | }.collect() 172 | from files 173 | into bootstrapFontsPath 174 | } 175 | 176 | project.task("createBootstrapCssIndividual", type: Sync, dependsOn: project.tasks.createBootstrapFonts) { 177 | def files = bootstrapZipTree.matching { 178 | include "*/dist/css/*.css" 179 | exclude "*/dist/css/*.min.css" 180 | }.collect() 181 | from files 182 | into bootstrapCssPath 183 | } 184 | 185 | project.task("createBootstrapLessLess", type: Copy, dependsOn: project.tasks.createBootstrapCssIndividual) { 186 | def path = "${project.projectDir}/$cssPath" 187 | def filename = "bootstrap-less.less" 188 | if (useLess && useAssetPipeline) { 189 | from template.getFile(project, "createBootstrapLessLess") 190 | rename ".*", filename 191 | into path 192 | onlyIf { !project.file("$path/$filename").exists() } 193 | } 194 | } 195 | 196 | project.task("createBootstrapLess", type: Sync, dependsOn: project.tasks.createBootstrapLessLess) { 197 | def files = [] 198 | if (useLess) { 199 | files = bootstrapZipTree.matching { 200 | include "*/less/*.less" 201 | }.collect() 202 | } 203 | from files 204 | into bootstrapLessPath 205 | } 206 | 207 | project.task("createBootstrapMixins", type: Sync, dependsOn: project.tasks.createBootstrapLess) { 208 | def files = [] 209 | if (useLess) { 210 | files = bootstrapZipTree.matching { 211 | include "*/less/mixins/*.less" 212 | }.collect() 213 | } 214 | from files 215 | into bootstrapMixinsPath 216 | } 217 | 218 | project.task("createFontAwesomeCssAll", type: Copy, dependsOn: project.tasks.createBootstrapMixins) { 219 | def path = "${project.projectDir}/$cssPath" 220 | def filename = "font-awesome-all.css" 221 | if (fontAwesomeInstall && useAssetPipeline) { 222 | from template.getFile(project, "createFontAwesomeCssAll") 223 | rename ".*", filename 224 | into path 225 | onlyIf { !project.file("$path/$filename").exists() } 226 | } else { 227 | project.delete("$path/$filename") 228 | } 229 | } 230 | 231 | project.task("createFontAwesomeCssIndividual", type: Sync, dependsOn: project.tasks.createFontAwesomeCssAll) { 232 | def files = [] 233 | if (fontAwesomeInstall) { 234 | files = fontAwesomeZipTree.matching { 235 | include "*/css/font-awesome.css" 236 | }.collect() 237 | } 238 | from files 239 | into fontAwesomeCssPath 240 | } 241 | 242 | project.task("createFontAwesomeFonts", type: Sync, dependsOn: project.tasks.createFontAwesomeCssIndividual) { 243 | def files = [] 244 | if (fontAwesomeInstall) { 245 | files = fontAwesomeZipTree.matching { 246 | include "*/fonts/*" 247 | }.collect() 248 | } 249 | from files 250 | into fontAwesomeFontsPath 251 | } 252 | 253 | project.task("createFontAwesomeLessLess", type: Copy, dependsOn: project.tasks.createFontAwesomeFonts) { 254 | def path = "${project.projectDir}/$cssPath" 255 | def filename = "font-awesome-less.less" 256 | if (fontAwesomeInstall && fontAwesomeUseLess) { 257 | if (cssPath.contains("assets")) { 258 | from template.getFile(project, "createFontAwesomeLessLessAssets") 259 | } else { 260 | from template.getFile(project, "createFontAwesomeLessLess") 261 | } 262 | rename ".*", filename 263 | into path 264 | onlyIf { !project.file("$path/$filename").exists() } 265 | } 266 | } 267 | 268 | project.task("createFontAwesomeLess", type: Sync, dependsOn: project.tasks.createFontAwesomeLessLess) { 269 | def files = [] 270 | if (fontAwesomeInstall && fontAwesomeUseLess) { 271 | files = fontAwesomeZipTree.matching { 272 | include "*/less/*.less" 273 | }.collect() 274 | } 275 | from files 276 | into fontAwesomeLessPath 277 | } 278 | } 279 | } 280 | 281 | class Template { 282 | static getText(String template) { 283 | def text = "" 284 | switch (template) { 285 | case "createBootstrapJsAll": 286 | text = """// 287 | // Do not edit this file. It will be overwritten by the bootstrap-framework plugin. 288 | // 289 | //= require bootstrap/bootstrap.js 290 | // 291 | """ 292 | break 293 | case "createBootstrapCssAll": 294 | text = """/* 295 | * Do not edit this file. It will be overwritten by the bootstrap-framework plugin. 296 | * 297 | *= require_tree bootstrap/css 298 | */ 299 | """ 300 | break 301 | case "createBootstrapLessLess": 302 | text = """/* 303 | * This file is for your Bootstrap Framework less and mixin customizations. 304 | * It was created by the bootstrap-framework plugin. 305 | * It will not be overwritten. 306 | * 307 | * You can import all less and mixin files as shown below, 308 | * or you can import them individually. 309 | * See https://github.com/kensiprell/bootstrap-framework/blob/master/README.md#less 310 | */ 311 | 312 | @import "bootstrap/less/bootstrap.less"; 313 | 314 | /* 315 | * Your customizations go below this section. 316 | */ 317 | """ 318 | break 319 | case "createFontAwesomeCssAll": 320 | text = """/* 321 | * Font Awesome by Dave Gandy - http://fontawesome.io 322 | * 323 | * Do not edit this file. It will be overwritten by the bootstrap-framework plugin. 324 | * 325 | *= require font-awesome/css/font-awesome.css 326 | *= require_tree font-awesome/fonts 327 | */ 328 | """ 329 | break 330 | case "createFontAwesomeLessLessAssets": 331 | text = """/* 332 | * Font Awesome by Dave Gandy - http://fontawesome.io 333 | * 334 | * This file is for your Font Awesome less and mixin customizations. 335 | * It was created by the bootstrap-framework plugin. 336 | * It will not be overwritten. 337 | * 338 | * You can import all less and mixin files as shown below, 339 | * or you can import them individually. 340 | * See https://github.com/kensiprell/bootstrap-framework/blob/master/README.md#font-awesome-less 341 | */ 342 | 343 | @import "font-awesome/less/font-awesome.less"; 344 | 345 | @fa-font-path: "/assets/font-awesome/fonts"; 346 | 347 | /* 348 | * Your customizations go below this section. 349 | */ 350 | """ 351 | break 352 | case "createFontAwesomeLessLess": 353 | text = """/* 354 | * Font Awesome by Dave Gandy - http://fontawesome.io 355 | * 356 | * This file is for your Font Awesome less and mixin customizations. 357 | * It was created by the bootstrap-framework plugin. 358 | * It will not be overwritten. 359 | * 360 | * You can import all less and mixin files as shown below, 361 | * or you can import them individually. 362 | * See https://github.com/kensiprell/bootstrap-framework/blob/master/README.md#font-awesome-less 363 | */ 364 | 365 | @import "font-awesome/less/font-awesome.less"; 366 | 367 | @fa-font-path: "/font-awesome/fonts"; 368 | 369 | /* 370 | * Your customizations go below this section. 371 | */ 372 | """ 373 | break 374 | } 375 | text.toString() 376 | } 377 | 378 | static getFile(Project project, String template) { 379 | project.resources.text.fromString(getText(template)).asFile() 380 | } 381 | } 382 | 383 | class ZipFile { 384 | String fileSuffix = ".zip" 385 | 386 | def download(String tmp, String description, String filePrefix, String url, String version, String zipFilename, boolean invalidVersionFails) { 387 | def zipFile = new File("$tmp/$zipFilename") 388 | if (zipFile.exists()) { 389 | return zipFile 390 | } 391 | HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection() 392 | def invalidVersionMessage = "Could not download $url.\n$version is an invalid $description version, or you are not connected to the Internet." 393 | def tmpDir = new File("$tmp") 394 | if (!tmpDir.exists()) { 395 | tmpDir.mkdirs() 396 | } 397 | connection.setRequestMethod("GET") 398 | if (connection.getResponseCode() == 200) { 399 | def file = zipFile.newOutputStream() 400 | file << new URL(url).openStream() 401 | file.close() 402 | return zipFile 403 | } else { 404 | zipFile.delete() 405 | if (invalidVersionFails) { 406 | return invalidVersionMessage.toString() 407 | } else { 408 | println "Error: $invalidVersionMessage" 409 | } 410 | } 411 | List zipFiles = [] 412 | tmpDir.listFiles().each { 413 | if (it.name.startsWith(filePrefix)) { 414 | zipFiles << it 415 | } 416 | } 417 | if (zipFiles.size() > 0) { 418 | File zipFileOld 419 | if (zipFiles.size() == 1) { 420 | zipFileOld = zipFiles[0] 421 | } else { 422 | zipFileOld = zipFiles.sort(false) { a, b -> 423 | def tokens = [a.name.minus(filePrefix).minus(fileSuffix), b.name.minus(filePrefix).minus(fileSuffix)] 424 | tokens*.tokenize('.')*.collect { it as int }.with { u, v -> 425 | [u, v].transpose().findResult { x, y -> x <=> y ?: null } ?: u.size() <=> v.size() 426 | } 427 | }[-1] 428 | } 429 | String newVersion = zipFileOld.name.minus(filePrefix).minus(fileSuffix) 430 | println "Using $description version $newVersion instead of $version." 431 | return zipFileOld 432 | } else { 433 | return "No old $description zip files found in $tmpDir.".toString() 434 | } 435 | } 436 | } 437 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/gradle-plugins/com.siprell.plugins.bootstrap-framework.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.siprell.plugin.gradle.bootstrap.BootstrapGradlePlugin 2 | -------------------------------------------------------------------------------- /src/test/groovy/com/siprell/plugin/gradle/bootstrap/GradlePluginSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.siprell.plugin.gradle.bootstrap 2 | 3 | import java.text.SimpleDateFormat 4 | import org.gradle.api.InvalidUserDataException 5 | import org.gradle.api.Project 6 | import org.gradle.api.internal.plugins.PluginApplicationException 7 | import org.gradle.testfixtures.ProjectBuilder 8 | import org.junit.Rule 9 | import org.springframework.boot.test.OutputCapture 10 | import spock.lang.Specification 11 | 12 | class GradlePluginSpec extends Specification { 13 | 14 | @Rule 15 | OutputCapture capture = new OutputCapture() 16 | 17 | static final bootstrapDefaultVersion = "3.3.5" 18 | static final fontAwesomeDefaultVersion = "4.3.0" 19 | static final cssPath = "src/main/webapp/css" 20 | static final jsPath = "src/main/webapp/js" 21 | static final grailsCssPath = "grails-app/assets/stylesheets" 22 | static final grailsJsPath = "grails-app/assets/javascripts" 23 | static boolean useAssetPipeline = true 24 | 25 | def setupSpec() { 26 | deleteDirs() 27 | deleteZipFiles() 28 | } 29 | 30 | def setup() { 31 | useAssetPipeline = true 32 | } 33 | 34 | def cleanupSpec() { 35 | deleteDirs() 36 | } 37 | 38 | void "CSS resource directory does not exist"() { 39 | when: 40 | def properties = defaultProperties 41 | createProject(properties) 42 | 43 | then: 44 | final List lines = capture.toString().tokenize(System.properties["line.separator"]) 45 | lines.size() == 0 46 | PluginApplicationException exception = thrown() 47 | exception.cause.class == InvalidUserDataException 48 | exception.cause.message == "bootstrapFramework.cssPath directory ($grailsCssPath) does not exist.".toString() 49 | } 50 | 51 | void "JavaScript resource directory does not exist"() { 52 | given: 53 | new File("$filePath.root/$grailsCssPath").mkdirs() 54 | 55 | when: 56 | def properties = defaultProperties 57 | createProject(properties) 58 | 59 | then: 60 | final List lines = capture.toString().tokenize(System.properties["line.separator"]) 61 | lines.size() == 0 62 | PluginApplicationException exception = thrown() 63 | exception.cause.class == InvalidUserDataException 64 | exception.cause.message == "bootstrapFramework.jsPath directory ($grailsJsPath) does not exist.".toString() 65 | } 66 | 67 | void "use invalid Bootstrap Framework version with invalidVersionFails = false and no zip files available"() { 68 | given: 69 | createDirs() 70 | 71 | when: 72 | def version = "3.2.99" 73 | def properties = defaultProperties 74 | properties.version = version 75 | createProject(properties) 76 | 77 | then: 78 | final List lines = capture.toString().tokenize(System.properties["line.separator"]) 79 | lines[0] == "Error: Could not download https://github.com/twbs/bootstrap/archive/v${version}.zip.".toString() 80 | lines[1] == "${version} is an invalid Bootstrap Framework version, or you are not connected to the Internet.".toString() 81 | PluginApplicationException exception = thrown() 82 | exception.cause.class == InvalidUserDataException 83 | exception.cause.message.startsWith("No old Bootstrap Framework zip files found in") 84 | } 85 | 86 | void "use invalid Bootstrap Framework version with invalidVersionFails = true and no zip files available"() { 87 | when: 88 | def version = "3.2.99" 89 | def invalidVersionMessageStart = "Could not download".toString() 90 | def invalidVersionMessageEnd = "$version is an invalid Bootstrap Framework version, or you are not connected to the Internet.".toString() 91 | def properties = defaultProperties 92 | properties.version = version 93 | properties.invalidVersionFails = true 94 | createProject(properties) 95 | 96 | then: 97 | final List lines = capture.toString().tokenize(System.properties["line.separator"]) 98 | lines.size() == 0 99 | PluginApplicationException exception = thrown() 100 | exception.cause.class == InvalidUserDataException 101 | exception.cause.message.startsWith(invalidVersionMessageStart) 102 | exception.cause.message.endsWith(invalidVersionMessageEnd) 103 | } 104 | 105 | void "use invalid Font Awesome version with invalidVersionFails = false and no zip files available"() { 106 | when: 107 | def version = "3.2.99" 108 | def properties = defaultProperties 109 | properties.fontAwesome.install = true 110 | properties.fontAwesome.version = version 111 | createProject(properties) 112 | 113 | then: 114 | final List lines = capture.toString().tokenize(System.properties["line.separator"]) 115 | lines[0] == "Error: Could not download https://github.com/FortAwesome/Font-Awesome/archive/v${version}.zip.".toString() 116 | lines[1] == "${version} is an invalid Font Awesome version, or you are not connected to the Internet.".toString() 117 | PluginApplicationException exception = thrown() 118 | exception.cause.class == InvalidUserDataException 119 | exception.cause.message.startsWith("No old Font Awesome zip files found in") 120 | } 121 | 122 | void "use invalid Font Awesome version with invalidVersionFails = true and no zip files available"() { 123 | when: 124 | def version = "3.2.99" 125 | def invalidVersionMessageStart = "Could not download".toString() 126 | def invalidVersionMessageEnd = "$version is an invalid Font Awesome version, or you are not connected to the Internet.".toString() 127 | def properties = defaultProperties 128 | properties.fontAwesome.install = true 129 | properties.fontAwesome.version = version 130 | properties.fontAwesome.invalidVersionFails = true 131 | createProject(properties) 132 | 133 | then: 134 | final List lines = capture.toString().tokenize(System.properties["line.separator"]) 135 | lines.size() == 0 136 | PluginApplicationException exception = thrown() 137 | exception.cause.class == InvalidUserDataException 138 | exception.cause.message.startsWith(invalidVersionMessageStart) 139 | exception.cause.message.endsWith(invalidVersionMessageEnd) 140 | } 141 | 142 | void "change Bootstrap Framework version to #testVersion"() { 143 | when: 144 | def testVersion = "3.3.4" 145 | def prefix = "* Bootstrap v" 146 | def suffix = " (http://getbootstrap.com)" 147 | def properties = defaultProperties 148 | properties.version = testVersion 149 | createProject(properties) 150 | def cssFile = new File("$filePath.css/bootstrap.css") 151 | def jsFile = new File("$filePath.js/bootstrap.js") 152 | def cssFileVersion = cssFile.readLines().get(1).trim() - prefix - suffix 153 | def jsFilesVersion = jsFile.readLines().get(1).trim() - prefix - suffix 154 | 155 | then: 156 | testVersion == cssFileVersion 157 | testVersion == jsFilesVersion 158 | } 159 | 160 | void "apply plugin using default settings"() { 161 | when: 162 | def properties = defaultProperties 163 | createProject(properties) 164 | def data = currentData 165 | 166 | then: 167 | data.bootstrapJsAll.exists() 168 | data.javascriptsCount == 2 169 | data.bootstrapJs.exists() 170 | data.jsCount == 1 171 | data.bootstrapCssAll.exists() 172 | !data.bootstrapLessLess.exists() 173 | data.stylesheetsCount == 2 174 | data.stylesheetsBootstrapCount == 2 175 | data.bootstrapCss.exists() 176 | data.bootstrapThemeCss.exists() 177 | data.css.exists() 178 | data.cssCount == 2 179 | data.fonts.exists() 180 | data.fontsCount == 5 181 | !data.bootstrapLess.exists() 182 | !data.mixinsLess.exists() 183 | !data.less.exists() 184 | data.lessCount == 0 185 | !data.mixins.exists() 186 | data.mixinsCount == 0 187 | !data.fontAwesomeCssAll.exists() 188 | !data.fontAwesomeLessLess.exists() 189 | data.stylesheetsFontAwesomeCount == 0 190 | !data.fontAwesomeCss.exists() 191 | !data.fontAwesomeFonts.exists() 192 | data.fontAwesomeFontsCount == 0 193 | !data.fontAwesomeLess.exists() 194 | data.fontAwesomeLessCount == 0 195 | } 196 | 197 | void "use invalid Bootstrap Framework version with invalidVersionFails = false and zip files are available"() { 198 | when: 199 | def version = "3.2.99" 200 | def prefix = "* Bootstrap v" 201 | def suffix = " (http://getbootstrap.com)" 202 | def properties = defaultProperties 203 | properties.version = version 204 | createProject(properties) 205 | def cssFile = new File("$filePath.css/bootstrap.css") 206 | def jsFile = new File("$filePath.js/bootstrap.js") 207 | def cssFileVersion = cssFile.readLines().get(1).trim() - prefix - suffix 208 | def jsFilesVersion = jsFile.readLines().get(1).trim() - prefix - suffix 209 | 210 | then: 211 | bootstrapDefaultVersion == cssFileVersion 212 | bootstrapDefaultVersion == jsFilesVersion 213 | final List lines = capture.toString().tokenize(System.properties["line.separator"]) 214 | lines[0] == "Error: Could not download https://github.com/twbs/bootstrap/archive/v${version}.zip.".toString() 215 | lines[1] == "${version} is an invalid Bootstrap Framework version, or you are not connected to the Internet.".toString() 216 | lines[2] == "Using Bootstrap Framework version $bootstrapDefaultVersion instead of $version.".toString() 217 | notThrown(PluginApplicationException) 218 | } 219 | 220 | void "apply plugin without asset-pipeline"() { 221 | given: 222 | useAssetPipeline = false 223 | 224 | when: 225 | def properties = defaultProperties 226 | properties.cssPath = cssPath 227 | properties.jsPath = jsPath 228 | createProject(properties) 229 | def data = currentData 230 | 231 | then: 232 | !data.bootstrapJsAll.exists() 233 | data.javascriptsCount == 1 234 | data.bootstrapJs.exists() 235 | data.jsCount == 1 236 | !data.bootstrapCssAll.exists() 237 | !data.bootstrapLessLess.exists() 238 | data.stylesheetsCount == 1 239 | data.stylesheetsBootstrapCount == 2 240 | data.bootstrapCss.exists() 241 | data.bootstrapThemeCss.exists() 242 | data.css.exists() 243 | data.cssCount == 2 244 | data.fonts.exists() 245 | data.fontsCount == 5 246 | !data.bootstrapLess.exists() 247 | !data.mixinsLess.exists() 248 | !data.less.exists() 249 | data.lessCount == 0 250 | !data.mixins.exists() 251 | data.mixinsCount == 0 252 | !data.fontAwesomeCssAll.exists() 253 | !data.fontAwesomeLessLess.exists() 254 | data.stylesheetsFontAwesomeCount == 0 255 | !data.fontAwesomeCss.exists() 256 | !data.fontAwesomeFonts.exists() 257 | data.fontAwesomeFontsCount == 0 258 | !data.fontAwesomeLess.exists() 259 | data.fontAwesomeLessCount == 0 260 | } 261 | 262 | void "apply plugin using Bootstrap Framework individual JavaScript files"() { 263 | when: 264 | def properties = defaultProperties 265 | properties.useIndividualJs = true 266 | createProject(properties) 267 | def data = currentData 268 | 269 | then: 270 | data.bootstrapJsAll.exists() 271 | data.javascriptsCount == 2 272 | data.bootstrapJs.exists() 273 | data.jsCount == 13 274 | data.bootstrapCssAll.exists() 275 | !data.bootstrapLessLess.exists() 276 | data.stylesheetsCount == 2 277 | data.stylesheetsBootstrapCount == 2 278 | data.bootstrapCss.exists() 279 | data.bootstrapThemeCss.exists() 280 | data.css.exists() 281 | data.cssCount == 2 282 | data.fonts.exists() 283 | data.fontsCount == 5 284 | !data.bootstrapLess.exists() 285 | !data.mixinsLess.exists() 286 | !data.less.exists() 287 | data.lessCount == 0 288 | !data.mixins.exists() 289 | data.mixinsCount == 0 290 | !data.fontAwesomeCssAll.exists() 291 | !data.fontAwesomeLessLess.exists() 292 | data.stylesheetsFontAwesomeCount == 0 293 | !data.fontAwesomeCss.exists() 294 | !data.fontAwesomeFonts.exists() 295 | data.fontAwesomeFontsCount == 0 296 | !data.fontAwesomeLess.exists() 297 | data.fontAwesomeLessCount == 0 298 | } 299 | 300 | void "apply plugin using Bootstrap Framework LESS support"() { 301 | when: 302 | def properties = defaultProperties 303 | properties.useLess = true 304 | createProject(properties) 305 | def data = currentData 306 | 307 | then: 308 | data.bootstrapJsAll.exists() 309 | data.javascriptsCount == 2 310 | data.bootstrapJs.exists() 311 | data.jsCount == 1 312 | data.bootstrapCssAll.exists() 313 | data.bootstrapLessLess.exists() 314 | data.stylesheetsCount == 3 315 | data.stylesheetsBootstrapCount == 3 316 | data.bootstrapCss.exists() 317 | data.bootstrapThemeCss.exists() 318 | data.css.exists() 319 | data.cssCount == 2 320 | data.fonts.exists() 321 | data.fontsCount == 5 322 | data.bootstrapLess.exists() 323 | data.mixinsLess.exists() 324 | data.less.exists() 325 | data.lessCount == 42 326 | data.mixins.exists() 327 | data.mixinsCount == 30 328 | !data.fontAwesomeCssAll.exists() 329 | !data.fontAwesomeLessLess.exists() 330 | data.stylesheetsFontAwesomeCount == 0 331 | !data.fontAwesomeCss.exists() 332 | !data.fontAwesomeFonts.exists() 333 | data.fontAwesomeFontsCount == 0 334 | !data.fontAwesomeLess.exists() 335 | data.fontAwesomeLessCount == 0 336 | } 337 | 338 | void "apply plugin using Bootstrap Framework version LESS support and individual JavaScript files"() { 339 | when: 340 | def properties = defaultProperties 341 | properties.useIndividualJs = true 342 | properties.useLess = true 343 | createProject(properties) 344 | def data = currentData 345 | 346 | then: 347 | data.bootstrapJsAll.exists() 348 | data.javascriptsCount == 2 349 | data.bootstrapJs.exists() 350 | data.jsCount == 13 351 | data.bootstrapCssAll.exists() 352 | data.bootstrapLessLess.exists() 353 | data.stylesheetsCount == 3 354 | data.stylesheetsBootstrapCount == 3 355 | data.bootstrapCss.exists() 356 | data.bootstrapThemeCss.exists() 357 | data.css.exists() 358 | data.cssCount == 2 359 | data.fonts.exists() 360 | data.fontsCount == 5 361 | data.bootstrapLess.exists() 362 | data.mixinsLess.exists() 363 | data.less.exists() 364 | data.lessCount == 42 365 | data.mixins.exists() 366 | data.mixinsCount == 30 367 | !data.fontAwesomeCssAll.exists() 368 | !data.fontAwesomeLessLess.exists() 369 | data.stylesheetsFontAwesomeCount == 0 370 | !data.fontAwesomeCss.exists() 371 | !data.fontAwesomeFonts.exists() 372 | data.fontAwesomeFontsCount == 0 373 | !data.fontAwesomeLess.exists() 374 | data.fontAwesomeLessCount == 0 375 | } 376 | 377 | void "change Font Awesome version to #testVersion"() { 378 | when: 379 | def testVersion = "4.2.0" 380 | def prefix = "* Font Awesome " 381 | def suffix = " by @davegandy - http://fontawesome.io - @fontawesome" 382 | def properties = defaultProperties 383 | properties.fontAwesome.install = true 384 | properties.fontAwesome.version = testVersion 385 | createProject(properties) 386 | def cssFile = new File("$filePath.faCss/font-awesome.css") 387 | def cssFileVersion = cssFile.readLines().get(1).trim() - prefix - suffix 388 | 389 | then: 390 | testVersion == cssFileVersion 391 | } 392 | 393 | void "apply plugin using default Bootstrap Framework and Font Awesome settings"() { 394 | when: 395 | def properties = defaultProperties 396 | properties.fontAwesome.install = true 397 | createProject(properties) 398 | def data = currentData 399 | 400 | then: 401 | data.bootstrapJsAll.exists() 402 | data.javascriptsCount == 2 403 | data.bootstrapJs.exists() 404 | data.jsCount == 1 405 | data.bootstrapCssAll.exists() 406 | data.bootstrapLessLess.exists() 407 | data.stylesheetsCount == 5 408 | data.stylesheetsBootstrapCount == 2 409 | data.bootstrapCss.exists() 410 | data.bootstrapThemeCss.exists() 411 | data.css.exists() 412 | data.cssCount == 2 413 | data.fonts.exists() 414 | data.fontsCount == 5 415 | !data.bootstrapLess.exists() 416 | !data.mixinsLess.exists() 417 | !data.less.exists() 418 | data.lessCount == 0 419 | !data.mixins.exists() 420 | data.mixinsCount == 0 421 | data.fontAwesomeCssAll.exists() 422 | !data.fontAwesomeLessLess.exists() 423 | data.fontAwesomeCss.exists() 424 | data.fontAwesomeFonts.exists() 425 | data.fontAwesomeFontsCount == 6 426 | !data.fontAwesomeLess.exists() 427 | data.fontAwesomeLessCount == 0 428 | } 429 | 430 | void "apply plugin using Bootstrap Framework default settings and Font Awesome with LESS support"() { 431 | when: 432 | def properties = defaultProperties 433 | properties.fontAwesome.install = true 434 | properties.fontAwesome.useLess = true 435 | createProject(properties) 436 | def data = currentData 437 | 438 | then: 439 | data.bootstrapJsAll.exists() 440 | data.javascriptsCount == 2 441 | data.bootstrapJs.exists() 442 | data.jsCount == 1 443 | data.bootstrapCssAll.exists() 444 | data.bootstrapLessLess.exists() 445 | data.stylesheetsCount == 6 446 | data.stylesheetsBootstrapCount == 2 447 | data.bootstrapCss.exists() 448 | data.bootstrapThemeCss.exists() 449 | data.css.exists() 450 | data.cssCount == 2 451 | data.fonts.exists() 452 | data.fontsCount == 5 453 | !data.bootstrapLess.exists() 454 | !data.mixinsLess.exists() 455 | !data.less.exists() 456 | data.lessCount == 0 457 | !data.mixins.exists() 458 | data.mixinsCount == 0 459 | data.fontAwesomeCssAll.exists() 460 | data.fontAwesomeLessLess.exists() 461 | data.stylesheetsFontAwesomeCount == 3 462 | data.fontAwesomeCss.exists() 463 | data.fontAwesomeFonts.exists() 464 | data.fontAwesomeFontsCount == 6 465 | data.fontAwesomeLess.exists() 466 | data.fontAwesomeLessCount == 13 467 | } 468 | 469 | void "apply plugin with all options enabled and delete files"() { 470 | given: 471 | def properties = defaultProperties 472 | properties.useIndividualJs = true 473 | properties.useLess = true 474 | properties.fontAwesome.install = true 475 | properties.fontAwesome.useLess = true 476 | 477 | when: 478 | createProject(properties) 479 | def data = currentData 480 | 481 | then: 482 | data.bootstrapJsAll.exists() 483 | data.javascriptsCount == 2 484 | data.bootstrapJs.exists() 485 | data.jsCount == 13 486 | data.bootstrapCssAll.exists() 487 | data.bootstrapLessLess.exists() 488 | data.stylesheetsCount == 6 489 | data.stylesheetsBootstrapCount == 3 490 | data.bootstrapCss.exists() 491 | data.bootstrapThemeCss.exists() 492 | data.css.exists() 493 | data.cssCount == 2 494 | data.fonts.exists() 495 | data.fontsCount == 5 496 | data.bootstrapLess.exists() 497 | data.mixinsLess.exists() 498 | data.less.exists() 499 | data.lessCount == 42 500 | data.mixins.exists() 501 | data.mixinsCount == 30 502 | data.fontAwesomeCssAll.exists() 503 | data.fontAwesomeLessLess.exists() 504 | data.stylesheetsFontAwesomeCount == 3 505 | data.fontAwesomeCss.exists() 506 | data.fontAwesomeFonts.exists() 507 | data.fontAwesomeFontsCount == 6 508 | data.fontAwesomeLess.exists() 509 | data.fontAwesomeLessCount == 13 510 | 511 | when: 512 | new File("${filePath.javascripts}/bootstrap-all.js").delete() 513 | new File("${filePath.js}/affix.js").delete() 514 | new File("${filePath.stylesheets}/bootstrap-all.css").delete() 515 | new File("${filePath.stylesheets}/bootstrap-less.less").delete() 516 | new File("${filePath.stylesheets}/font-awesome-all.css").delete() 517 | new File("${filePath.stylesheets}/font-awesome-less.less").delete() 518 | new File("${filePath.css}/bootstrap.css").delete() 519 | new File("${filePath.fonts}/glyphicons-halflings-regular.eot").delete() 520 | new File("${filePath.less}/alerts.less").delete() 521 | new File("${filePath.mixins}/alerts.less").delete() 522 | new File("${filePath.faCss}/font-awesome.css").delete() 523 | new File("${filePath.faFonts}/fontawesome-webfont.eot").delete() 524 | new File("${filePath.faLess}/animated.less").delete() 525 | createProject(properties) 526 | data = currentData 527 | 528 | then: 529 | data.bootstrapJsAll.exists() 530 | data.javascriptsCount == 2 531 | data.bootstrapJs.exists() 532 | data.jsCount == 13 533 | data.bootstrapCssAll.exists() 534 | data.bootstrapLessLess.exists() 535 | data.stylesheetsCount == 6 536 | data.stylesheetsBootstrapCount == 3 537 | data.bootstrapCss.exists() 538 | data.bootstrapThemeCss.exists() 539 | data.css.exists() 540 | data.cssCount == 2 541 | data.fonts.exists() 542 | data.fontsCount == 5 543 | data.bootstrapLess.exists() 544 | data.mixinsLess.exists() 545 | data.less.exists() 546 | data.lessCount == 42 547 | data.mixins.exists() 548 | data.mixinsCount == 30 549 | data.fontAwesomeCssAll.exists() 550 | data.fontAwesomeLessLess.exists() 551 | data.stylesheetsFontAwesomeCount == 3 552 | data.fontAwesomeCss.exists() 553 | data.fontAwesomeFonts.exists() 554 | data.fontAwesomeFontsCount == 6 555 | data.fontAwesomeLess.exists() 556 | data.fontAwesomeLessCount == 13 557 | } 558 | 559 | void "use invalid Font Awesome version with invalidVersionFails = false and zip files are available"() { 560 | given: 561 | def version = "3.2.99" 562 | def prefix = "* Font Awesome " 563 | def suffix = " by @davegandy - http://fontawesome.io - @fontawesome" 564 | 565 | when: 566 | def properties = defaultProperties 567 | properties.fontAwesome.install = true 568 | properties.fontAwesome.version = version 569 | createProject(properties) 570 | def cssFile = new File("$filePath.faCss/font-awesome.css") 571 | def cssFileVersion = cssFile.readLines().get(1).trim() - prefix - suffix 572 | 573 | then: 574 | fontAwesomeDefaultVersion == cssFileVersion 575 | final List lines = capture.toString().tokenize(System.properties["line.separator"]) 576 | lines[0] == "Error: Could not download https://github.com/FortAwesome/Font-Awesome/archive/v${version}.zip.".toString() 577 | lines[1] == "${version} is an invalid Font Awesome version, or you are not connected to the Internet.".toString() 578 | lines[2] == "Using Font Awesome version $fontAwesomeDefaultVersion instead of $version.".toString() 579 | notThrown(PluginApplicationException) 580 | } 581 | 582 | static createDirs() { 583 | [ 584 | "$filePath.root/$cssPath", 585 | "$filePath.root/$jsPath", 586 | "$filePath.root/$grailsCssPath", 587 | "$filePath.root/$grailsJsPath" 588 | ].each { 589 | new File("$it").mkdirs() 590 | } 591 | } 592 | 593 | static deleteDirs() { 594 | new File("$filePath.root/grails-app").deleteDir() 595 | new File("$filePath.root/src/main/webapp").deleteDir() 596 | } 597 | 598 | static deleteZipFiles() { 599 | new File("$filePath.root/build/tmp").listFiles().each { 600 | if (it.name.startsWith("bootstrap") || it.name.startsWith("fontAwesome")) { 601 | it.delete() 602 | } 603 | } 604 | } 605 | 606 | static getDefaultProperties() { 607 | [ 608 | version : bootstrapDefaultVersion, 609 | cssPath : grailsCssPath, 610 | jsPath : grailsJsPath, 611 | useIndividualJs : false, 612 | useLess : false, 613 | invalidVersionFails: false, 614 | fontAwesome : [ 615 | install : false, 616 | version : fontAwesomeDefaultVersion, 617 | useLess : false, 618 | invalidVersionFails: false 619 | ] 620 | ] 621 | } 622 | 623 | static createProject(properties) { 624 | Project project = ProjectBuilder.builder().withProjectDir(new File(filePath.root)).build() 625 | project.ext.bootstrapFramework = properties 626 | project.pluginManager.apply "com.siprell.plugins.bootstrap-framework" 627 | project.tasks["downloadBootstrapZip"].execute() 628 | project.tasks["downloadFontAwesomeZip"].execute() 629 | project.tasks["manageBootstrapDirs"].execute() 630 | project.tasks["createBootstrapJsAll"].execute() 631 | project.tasks["createBootstrapJs"].execute() 632 | project.tasks["createBootstrapCssAll"].execute() 633 | project.tasks["createBootstrapFonts"].execute() 634 | project.tasks["createBootstrapCssIndividual"].execute() 635 | project.tasks["createBootstrapLessLess"].execute() 636 | project.tasks["createBootstrapLess"].execute() 637 | project.tasks["createBootstrapMixins"].execute() 638 | project.tasks["createFontAwesomeCssAll"].execute() 639 | project.tasks["createFontAwesomeCssIndividual"].execute() 640 | project.tasks["createFontAwesomeFonts"].execute() 641 | project.tasks["createFontAwesomeLessLess"].execute() 642 | project.tasks["createFontAwesomeLess"].execute() 643 | } 644 | 645 | static getFilePath() { 646 | String root = new File("").absolutePath.toString() 647 | String javascripts = useAssetPipeline ? grailsJsPath : jsPath 648 | String stylesheets = useAssetPipeline ? grailsCssPath : cssPath 649 | String js = "$javascripts/bootstrap" 650 | String css = "$stylesheets/bootstrap/css" 651 | String fonts = "$stylesheets/bootstrap/fonts" 652 | String less = "$stylesheets/bootstrap/less" 653 | String mixins = "$less/mixins" 654 | String faCss = "$stylesheets/font-awesome/css" 655 | String faFonts = "$stylesheets/font-awesome/fonts" 656 | String faLess = "$stylesheets/font-awesome/less" 657 | [ 658 | root : root, 659 | javascripts: javascripts, 660 | stylesheets: stylesheets, 661 | js : js, 662 | css : css, 663 | fonts : fonts, 664 | less : less, 665 | mixins : mixins, 666 | faCss : faCss, 667 | faFonts : faFonts, 668 | faLess : faLess 669 | ] 670 | } 671 | 672 | static getCurrentData() { 673 | def bootstrapJsAll = new File("${filePath.javascripts}/bootstrap-all.js") 674 | def javascriptsCount = new File(filePath.javascripts).listFiles().size() 675 | def bootstrapJs = new File("${filePath.js}/bootstrap.js") 676 | def jsCount = new File(filePath.js).listFiles().size() 677 | def bootstrapCssAll = new File("${filePath.stylesheets}/bootstrap-all.css") 678 | def bootstrapLessLess = new File("${filePath.stylesheets}/bootstrap-less.less") 679 | def stylesheetsCount = new File(filePath.stylesheets).listFiles().size() 680 | def stylesheetsBootstrapCount = new File("${filePath.stylesheets}/bootstrap").listFiles().size() 681 | def bootstrapCss = new File("${filePath.css}/bootstrap.css") 682 | def bootstrapThemeCss = new File("${filePath.css}/bootstrap-theme.css") 683 | def css = new File(filePath.css) 684 | def cssCount = css.exists() ? css.listFiles().size() : 0 685 | def fonts = new File(filePath.fonts) 686 | def fontsCount = fonts.exists() ? fonts.listFiles().size() : 0 687 | def bootstrapLess = new File("${filePath.less}/bootstrap.less") 688 | def mixinsLess = new File("${filePath.less}/mixins.less") 689 | def less = new File(filePath.less) 690 | def lessCount = less.exists() ? less.listFiles().size() : 0 691 | def mixins = new File(filePath.mixins) 692 | def mixinsCount = mixins.exists() ? mixins.listFiles().size() : 0 693 | def fontAwesomeCssAll = new File("${filePath.stylesheets}/font-awesome-all.css") 694 | def fontAwesomeLessLess = new File("${filePath.stylesheets}/font-awesome-less.less") 695 | def stylesheetsFontAwesome = new File("${filePath.stylesheets}/font-awesome") 696 | def stylesheetsFontAwesomeCount = stylesheetsFontAwesome.exists() ? stylesheetsFontAwesome.listFiles().size() : 0 697 | def fontAwesomeCss = new File("${filePath.faCss}/font-awesome.css") 698 | def fontAwesomeFonts = new File(filePath.faFonts) 699 | def fontAwesomeFontsCount = fontAwesomeFonts.exists() ? fontAwesomeFonts.listFiles().size() : 0 700 | def fontAwesomeLess = new File(filePath.faLess) 701 | def fontAwesomeLessCount = fontAwesomeLess.exists() ? fontAwesomeLess.listFiles().size() : 0 702 | [ 703 | bootstrapJsAll : bootstrapJsAll, 704 | javascriptsCount : javascriptsCount, 705 | bootstrapJs : bootstrapJs, 706 | jsCount : jsCount, 707 | bootstrapCssAll : bootstrapCssAll, 708 | bootstrapLessLess : bootstrapLessLess, 709 | stylesheetsCount : stylesheetsCount, 710 | stylesheetsBootstrapCount : stylesheetsBootstrapCount, 711 | bootstrapCss : bootstrapCss, 712 | bootstrapThemeCss : bootstrapThemeCss, 713 | css : css, 714 | cssCount : cssCount, 715 | fonts : fonts, 716 | fontsCount : fontsCount, 717 | bootstrapLess : bootstrapLess, 718 | mixinsLess : mixinsLess, 719 | less : less, 720 | lessCount : lessCount, 721 | mixins : mixins, 722 | mixinsCount : mixinsCount, 723 | fontAwesomeCssAll : fontAwesomeCssAll, 724 | fontAwesomeLessLess : fontAwesomeLessLess, 725 | stylesheetsFontAwesomeCount: stylesheetsFontAwesomeCount, 726 | fontAwesomeCss : fontAwesomeCss, 727 | fontAwesomeFonts : fontAwesomeFonts, 728 | fontAwesomeFontsCount : fontAwesomeFontsCount, 729 | fontAwesomeLess : fontAwesomeLess, 730 | fontAwesomeLessCount : fontAwesomeLessCount 731 | ] 732 | } 733 | 734 | static getPrettyDate(Long millis) { 735 | SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.S yyyy.MM.dd") 736 | sdf.format(new Date(millis)) 737 | } 738 | 739 | static getPrettyDate(Date date) { 740 | SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.S yyyy.MM.dd") 741 | sdf.format(date) 742 | } 743 | } 744 | --------------------------------------------------------------------------------