Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial commit

  • Loading branch information...
commit dd7e4f7f903ffc796f4c4f1662f78e6488d521b9 1 parent 54aca7a
Rob Fletcher authored
View
12 .gitignore
@@ -0,0 +1,12 @@
+.idea
+*.iml
+*.ipr
+*.iws
+.classpath
+target
+out
+plugin.xml
+*.zip
+*.log
+.project
+.settings
View
72 FeatureSwitchGrailsPlugin.groovy
@@ -0,0 +1,72 @@
+class FeatureSwitchGrailsPlugin {
+ // the plugin version
+ def version = "0.1"
+ // the version or versions of Grails the plugin is designed for
+ def grailsVersion = "2.0 > *"
+ // the other plugins this plugin depends on
+ def dependsOn = [:]
+ // resources that are excluded from plugin packaging
+ def pluginExcludes = [
+ "grails-app/views/error.gsp"
+ ]
+
+ // TODO Fill in these fields
+ def title = "Feature Switch Plugin"
+ def author = "Antony Jones and Matt Tortolani"
+ def authorEmail = ""
+ def description = 'Allows turning on and off of features'
+
+ // URL to the plugin's documentation
+ def documentation = "http://grails.org/plugin/feature-switch"
+
+ // Extra (optional) plugin metadata
+
+ // License: one of 'APACHE', 'GPL2', 'GPL3'
+ def license = "APACHE"
+
+ // Details of company behind the plugin (if there is one)
+// def organization = [ name: "My Company", url: "http://www.my-company.com/" ]
+
+ // Any additional developers beyond the author specified above.
+ def developers = [
+ [ name: "Antony Jones", email: "aj/desirableobjects.co.uk" ],
+ [ name: "Matt Tortolani", email: "hello/doodlemoonch.com", ]
+ ]
+
+ // Location of the plugin's issue tracker.
+// def issueManagement = [ system: "JIRA", url: "http://jira.grails.org/browse/GPMYPLUGIN" ]
+
+ // Online location of the plugin's browseable source code.
+// def scm = [ url: "http://svn.grails-plugins.codehaus.org/browse/grails-plugins/" ]
+
+ def doWithWebDescriptor = { xml ->
+ // TODO Implement additions to web.xml (optional), this event occurs before
+ }
+
+ def doWithSpring = {
+ // TODO Implement runtime spring config (optional)
+ }
+
+ def doWithDynamicMethods = { ctx ->
+ // TODO Implement registering dynamic methods to classes (optional)
+ }
+
+ def doWithApplicationContext = { applicationContext ->
+ // TODO Implement post initialization spring config (optional)
+ }
+
+ def onChange = { event ->
+ // TODO Implement code that is executed when any artefact that this plugin is
+ // watching is modified and reloaded. The event contains: event.source,
+ // event.application, event.manager, event.ctx, and event.plugin.
+ }
+
+ def onConfigChange = { event ->
+ // TODO Implement code that is executed when the project configuration changes.
+ // The event is the same as for 'onChange'.
+ }
+
+ def onShutdown = { event ->
+ // TODO Implement code that is executed when the application shuts down (optional)
+ }
+}
View
5 application.properties
@@ -0,0 +1,5 @@
+#Grails Metadata file
+#Mon Aug 20 15:21:45 BST 2012
+app.grails.version=2.0.3
+app.name=feature-switch
+plugins.svn=1.0.1
View
41 grails-app/conf/BuildConfig.groovy
@@ -0,0 +1,41 @@
+grails.project.class.dir = "target/classes"
+grails.project.test.class.dir = "target/test-classes"
+grails.project.test.reports.dir = "target/test-reports"
+grails.project.target.level = 1.6
+//grails.project.war.file = "target/${appName}-${appVersion}.war"
+
+grails.project.dependency.resolution = {
+ // inherit Grails' default dependencies
+ inherits("global") {
+ // uncomment to disable ehcache
+ // excludes 'ehcache'
+ }
+ log "warn" // log level of Ivy resolver, either 'error', 'warn', 'info', 'debug' or 'verbose'
+ repositories {
+ grailsCentral()
+ // uncomment the below to enable remote dependency resolution
+ // from public Maven repositories
+ mavenLocal
+ mavenCentral()
+ //mavenRepo "http://snapshots.repository.codehaus.org"
+ //mavenRepo "http://repository.codehaus.org"
+ //mavenRepo "http://download.java.net/maven/2/"
+ //mavenRepo "http://repository.jboss.com/maven2/"
+ }
+ dependencies {
+
+ }
+
+ plugins {
+
+ test ':spock:0.6'
+
+ build(":tomcat:$grailsVersion",
+ ":release:1.0.0") {
+ export = false
+ }
+
+
+
+ }
+}
View
24 grails-app/conf/Config.groovy
@@ -0,0 +1,24 @@
+// configuration for plugin testing - will not be included in the plugin zip
+
+log4j = {
+ // Example of changing the log pattern for the default console
+ // appender:
+ //
+ //appenders {
+ // console name:'stdout', layout:pattern(conversionPattern: '%c{2} %m%n')
+ //}
+
+ error 'org.codehaus.groovy.grails.web.servlet', // controllers
+ 'org.codehaus.groovy.grails.web.pages', // GSP
+ 'org.codehaus.groovy.grails.web.sitemesh', // layouts
+ 'org.codehaus.groovy.grails.web.mapping.filter', // URL mapping
+ 'org.codehaus.groovy.grails.web.mapping', // URL mapping
+ 'org.codehaus.groovy.grails.commons', // core / classloading
+ 'org.codehaus.groovy.grails.plugins', // plugins
+ 'org.codehaus.groovy.grails.orm.hibernate', // hibernate integration
+ 'org.springframework',
+ 'org.hibernate',
+ 'net.sf.ehcache.hibernate'
+
+ warn 'org.mortbay.log'
+}
View
43 grails-app/conf/DataSource.groovy
@@ -0,0 +1,43 @@
+dataSource {
+ pooled = true
+ driverClassName = "org.h2.Driver"
+ username = "sa"
+ password = ""
+}
+hibernate {
+ cache.use_second_level_cache = true
+ cache.use_query_cache = false
+ cache.region.factory_class = 'net.sf.ehcache.hibernate.EhCacheRegionFactory'
+}
+// environment specific settings
+environments {
+ development {
+ dataSource {
+ dbCreate = "create-drop" // one of 'create', 'create-drop', 'update', 'validate', ''
+ url = "jdbc:h2:mem:devDb;MVCC=TRUE"
+ }
+ }
+ test {
+ dataSource {
+ dbCreate = "update"
+ url = "jdbc:h2:mem:testDb;MVCC=TRUE"
+ }
+ }
+ production {
+ dataSource {
+ dbCreate = "update"
+ url = "jdbc:h2:prodDb;MVCC=TRUE"
+ pooled = true
+ properties {
+ maxActive = -1
+ minEvictableIdleTimeMillis=1800000
+ timeBetweenEvictionRunsMillis=1800000
+ numTestsPerEvictionRun=3
+ testOnBorrow=true
+ testWhileIdle=true
+ testOnReturn=true
+ validationQuery="SELECT 1"
+ }
+ }
+ }
+}
View
13 grails-app/conf/UrlMappings.groovy
@@ -0,0 +1,13 @@
+class UrlMappings {
+
+ static mappings = {
+ "/$controller/$action?/$id?"{
+ constraints {
+ // apply constraints here
+ }
+ }
+
+ "/"(view:"/index")
+ "500"(view:'/error')
+ }
+}
View
23 grails-app/services/uk/co/desirableobjects/featureswitch/FeatureSwitchService.groovy
@@ -0,0 +1,23 @@
+package uk.co.desirableobjects.featureswitch
+
+import org.codehaus.groovy.grails.commons.GrailsApplication
+
+class FeatureSwitchService {
+
+ GrailsApplication grailsApplication
+
+ boolean hasFeature(String feature) {
+
+ return grailsApplication.config.features[feature].enabled
+
+ }
+
+ def withFeature(String feature, Closure closure) {
+
+ if (hasFeature(feature)) {
+ closure()
+ }
+
+ }
+
+}
View
30 grails-app/taglib/uk/co/desirableobjects/featureswitch/FeatureSwitchTagLib.groovy
@@ -0,0 +1,30 @@
+package uk.co.desirableobjects.featureswitch
+
+class FeatureSwitchTagLib {
+
+ static namespace = 'feature'
+
+ FeatureSwitchService featureSwitchService
+
+ def enabled = { attrs, body ->
+
+ renderByFeature(attrs, true, body)
+
+ }
+
+ private renderByFeature(Map attrs, boolean condition, Closure body) {
+
+ String feature = attrs.feature
+
+ if (featureSwitchService.hasFeature(feature) == condition) {
+ out << body()
+ }
+ }
+
+ def disabled = { attrs, body ->
+
+ renderByFeature(attrs, false, body)
+
+ }
+
+}
View
11 grails-app/views/error.gsp
@@ -0,0 +1,11 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Grails Runtime Exception</title>
+ <meta name="layout" content="main">
+ <link rel="stylesheet" href="${resource(dir: 'css', file: 'errors.css')}" type="text/css">
+ </head>
+ <body>
+ <g:renderException exception="${exception}" />
+ </body>
+</html>
View
10 scripts/_Install.groovy
@@ -0,0 +1,10 @@
+//
+// This script is executed by Grails after plugin was installed to project.
+// This script is a Gant script so you can use all special variables provided
+// by Gant (such as 'baseDir' which points on project base dir). You can
+// use 'ant' to access a global instance of AntBuilder
+//
+// For example you can create directory under project tree:
+//
+// ant.mkdir(dir:"${basedir}/grails-app/jobs")
+//
View
5 scripts/_Uninstall.groovy
@@ -0,0 +1,5 @@
+//
+// This script is executed by Grails when the plugin is uninstalled from project.
+// Use this script if you intend to do any additional clean-up on uninstall, but
+// beware of messing up SVN directories!
+//
View
10 scripts/_Upgrade.groovy
@@ -0,0 +1,10 @@
+//
+// This script is executed by Grails during application upgrade ('grails upgrade'
+// command). This script is a Gant script so you can use all special variables
+// provided by Gant (such as 'baseDir' which points on project base dir). You can
+// use 'ant' to access a global instance of AntBuilder
+//
+// For example you can create directory under project tree:
+//
+// ant.mkdir(dir:"${basedir}/grails-app/jobs")
+//
View
13 src/groovy/uk/co/desirableobjects/featureswitch/InnocentClass.groovy
@@ -0,0 +1,13 @@
+package uk.co.desirableobjects.featureswitch
+
+class InnocentClass {
+
+ def testWith() {
+ boolean result = false
+ withFeature('eggs') {
+ result = true
+ }
+ return result
+ }
+
+}
View
67 test/unit/uk/co/desirableobjects/featureswitch/SwitchFeatureSpec.groovy
@@ -0,0 +1,67 @@
+package uk.co.desirableobjects.featureswitch
+
+import spock.lang.Specification
+
+import grails.test.mixin.support.GrailsUnitTestMixin
+import spock.lang.Unroll
+import grails.test.mixin.TestFor
+
+@Mixin(GrailsUnitTestMixin)
+@TestFor(FeatureSwitchService)
+class SwitchFeatureSpec extends Specification {
+
+ void setup() {
+
+ InnocentClass.metaClass.withFeature = { String feature, Closure closure -> service.withFeature(feature, closure) }
+
+ }
+
+ @Unroll
+ def 'A feature can be set to enabled = #enabled'() {
+
+ given:
+ service.grailsApplication.config.features.eggs.enabled = enabled
+
+ expect:
+ service.hasFeature('eggs') == enabled
+
+ where:
+ enabled << [true, false]
+
+ }
+
+ def 'When there is no configuration'() {
+
+ given:
+ service.grailsApplication.config = [:]
+
+ expect:
+ !service.hasFeature('peas')
+
+ }
+
+ def 'When a requested feature does not exist'() {
+
+ given:
+ service.grailsApplication.config.features.eggs.enabled = true
+
+ expect:
+ !service.hasFeature('dogs')
+
+ }
+
+ @Unroll
+ def 'User can use withFeature in a class which is decorated with it, where feature = #enabled'() {
+
+ given:
+ service.grailsApplication.config.features.eggs.enabled = enabled
+
+ expect:
+ new InnocentClass().testWith() == enabled
+
+ where:
+ enabled << [true, false]
+
+ }
+
+}
View
56 test/unit/uk/co/desirableobjects/featureswitch/SwitchFeatureTagLibSpec.groovy
@@ -0,0 +1,56 @@
+package uk.co.desirableobjects.featureswitch
+
+import grails.test.mixin.TestFor
+import spock.lang.Specification
+import spock.lang.Unroll
+
+@TestFor(FeatureSwitchTagLib)
+class SwitchFeatureTagLibSpec extends Specification {
+
+ void setup() {
+
+ tagLib.featureSwitchService = Mock(FeatureSwitchService)
+
+ }
+
+ @Unroll
+ def 'Render content depending on whether the feature is enabled or not [#enabled]'() {
+
+ when:
+ String output = applyTemplate('<feature:enabled feature="colons">Colons!</feature:enabled>')
+
+ then:
+ 1 * tagLib.featureSwitchService.hasFeature('colons') >> { return enabled }
+ 0 * _
+
+ and:
+ output == expectedOutput
+
+ where:
+ enabled | expectedOutput
+ true | 'Colons!'
+ false | ''
+
+ }
+
+ @Unroll
+ def 'Render content depending on whether the feature is disabled or not [#enabled]'() {
+
+ when:
+ String output = applyTemplate('<feature:disabled feature="colons">Colons!</feature:disabled>')
+
+ then:
+ 1 * tagLib.featureSwitchService.hasFeature('colons') >> { return enabled }
+ 0 * _
+
+ and:
+ output == expectedOutput
+
+ where:
+ enabled | expectedOutput
+ true | ''
+ false | 'Colons!'
+
+ }
+
+}
Please sign in to comment.
Something went wrong with that request. Please try again.