-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#5 Adding DSL to define properties #9
Changes from all commits
fd5372a
4fcb622
9ae9696
58464bd
5abf246
7abe9f3
72a78f7
f7674c5
39717a7
1d5d6a1
860533d
ec43ca7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,7 @@ Newcomers of Gradle Build System very often complain about that in Gradle there | |
* [Sample build script](#sample-build-script) | ||
* [Defining and executing configurations](#defining-and-executing-configurations) | ||
* [Providing properties](#providing-properties) | ||
* [Defining project properties](#defining-project-properties) | ||
* [Sample output](#sample-output) | ||
* [License](#license) | ||
|
||
|
@@ -129,6 +130,50 @@ Properties can be provided by (order makes precedence): | |
|
||
4. Mixed approach. | ||
|
||
### Defining project properties | ||
|
||
Configuring of project properties can be enhanced by providing properties definitions which can be used for property value validation, e.g.: | ||
```kotlin | ||
fork { | ||
properties { | ||
property("enableSomething") { | ||
checkbox(defaultValue = true) | ||
} | ||
property("someJvaOpts") { | ||
optional() | ||
text(defaultValue = "-server -Xmx1024m -XX:MaxPermSize=256M -Djava.awt.headless=true") | ||
validator { | ||
if (!value.startsWith("-")) error("This is not a JVM option!") | ||
} | ||
} | ||
property("someUserName") { | ||
text(defaultValue = System.getProperty("user.name")) | ||
} | ||
property("projectGroup") { | ||
text(defaultValue = "org.neva") | ||
} | ||
} | ||
} | ||
``` | ||
|
||
#### Property definition | ||
Property definition can consists of: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. blank line after header |
||
* type specification: `type = TYPE_NAME` | ||
* there are five types available: `TEXT` x(default one), `CHECKBOX` (representing boolean), `PASSWORD`, `PATH` & `URL`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
* there is default convention of type inference using property name (case insensitive): | ||
* ends with "password" -> `PASSWORD` | ||
* starts with "enable", "disable" -> `CHECKBOX` | ||
* ends with "enabled", "disabled" -> `CHECKBOX` | ||
* ends with "url" -> `URL` | ||
* ends with "path" -> `PATH` | ||
* else -> `TEXT` | ||
* default value specification: `defaultValue = System.getProperty("user.name")` | ||
* if no value would be provided for property `defaultValue` is used | ||
* declaring property as optional: `optional()` | ||
* by default all properties are required | ||
* specifying custom validator: `validator = {if (!value.startsWith("-")) error("This is not a JVM option!")}` | ||
* by default `URL` & `PATH` properties gets basic validation which can be overridden or suppressed: `validator = {}` | ||
|
||
### Sample output | ||
|
||
After executing command `gradlew fork`, there will be a cloned project with correctly changed directory names, with replaced project name and label in text files (all stuff being previously performed manually). | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,10 @@ package com.neva.gradle.fork | |
import com.neva.gradle.fork.config.Config | ||
import com.neva.gradle.fork.config.InPlaceConfig | ||
import com.neva.gradle.fork.config.SourceTargetConfig | ||
import com.neva.gradle.fork.config.properties.PropertyDefinition | ||
import com.neva.gradle.fork.config.properties.PropertyDefinitions | ||
import groovy.lang.Closure | ||
import org.gradle.api.Action | ||
import org.gradle.api.Project | ||
import org.gradle.api.tasks.Input | ||
import org.gradle.util.ConfigureUtil | ||
|
@@ -13,6 +16,8 @@ open class ForkExtension(val project: Project) { | |
@Input | ||
val configs = mutableListOf<Config>() | ||
|
||
val propertyDefinitions = PropertyDefinitions() | ||
|
||
fun config(name: String): Config { | ||
return configs.find { it.name == name } | ||
?: throw ForkException("Fork configuration '$name' is yet not defined.") | ||
|
@@ -23,23 +28,33 @@ open class ForkExtension(val project: Project) { | |
} | ||
|
||
fun config(configurer: Config.() -> Unit) { | ||
config(SourceTargetConfig(project, Config.NAME_DEFAULT), configurer) | ||
config(SourceTargetConfig(this, Config.NAME_DEFAULT), configurer) | ||
} | ||
|
||
fun config(name: String, configurer: Closure<*>) { | ||
config(name) { ConfigureUtil.configure(configurer, this) } | ||
} | ||
|
||
fun config(name: String, configurer: Config.() -> Unit) { | ||
config(SourceTargetConfig(project, name), configurer) | ||
config(SourceTargetConfig(this, name), configurer) | ||
} | ||
|
||
fun inPlaceConfig(name: String, configurer: Closure<*>) { | ||
inPlaceConfig(name) { ConfigureUtil.configure(configurer, this) } | ||
} | ||
|
||
fun inPlaceConfig(name: String, configurer: Config.() -> Unit) { | ||
config(InPlaceConfig(project, name), configurer) | ||
config(InPlaceConfig(this, name), configurer) | ||
} | ||
|
||
fun properties(action: Action<in ForkExtension>) { | ||
action.execute(this) | ||
} | ||
|
||
fun property(name: String, action: Action<in PropertyDefinition>) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why |
||
val definition = project.objects.newInstance(PropertyDefinition::class.java, name) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you used new API introduced in 4.9 :) hope you know the purpose, teach me :) |
||
action.execute(definition) | ||
propertyDefinitions.add(definition) | ||
} | ||
|
||
private fun config(config: Config, configurer: Config.() -> Unit) { | ||
|
@@ -49,11 +64,11 @@ open class ForkExtension(val project: Project) { | |
|
||
companion object { | ||
|
||
const val NAME = "fork" | ||
const val NAME = "fork" | ||
|
||
fun of(project: Project): ForkExtension { | ||
return project.extensions.getByType(ForkExtension::class.java) | ||
} | ||
fun of(project: Project): ForkExtension { | ||
return project.extensions.getByType(ForkExtension::class.java) | ||
} | ||
|
||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,13 @@ | ||
package com.neva.gradle.fork.config | ||
|
||
import com.neva.gradle.fork.ForkException | ||
import com.neva.gradle.fork.ForkExtension | ||
import com.neva.gradle.fork.config.properties.Property | ||
import com.neva.gradle.fork.config.properties.PropertyPrompt | ||
import com.neva.gradle.fork.config.rule.* | ||
import com.neva.gradle.fork.gui.PropertyDialog | ||
import com.neva.gradle.fork.template.TemplateEngine | ||
import groovy.lang.Closure | ||
import org.gradle.api.Project | ||
import org.gradle.api.file.FileTree | ||
import org.gradle.util.ConfigureUtil | ||
import org.gradle.util.GFileUtils | ||
|
@@ -14,13 +16,18 @@ import java.io.FileInputStream | |
import java.io.FileOutputStream | ||
import java.util.* | ||
|
||
abstract class Config(val project: Project, val name: String) { | ||
abstract class Config(private val forkExtension: ForkExtension, val name: String) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just |
||
|
||
val prompts = mutableMapOf<String, PropertyPrompt>() | ||
private val prompts = mutableMapOf<String, PropertyPrompt>() | ||
|
||
val props by lazy { promptFill() } | ||
private val props by lazy { promptFill() } | ||
|
||
val rules = mutableListOf<Rule>() | ||
private val rules = mutableListOf<Rule>() | ||
|
||
val project = forkExtension.project | ||
|
||
val properties: List<Property> | ||
get() = this.prompts.values.map { prompt -> forkExtension.propertyDefinitions.getProperty(prompt) } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
abstract val sourcePath: String | ||
|
||
|
@@ -61,19 +68,19 @@ abstract class Config(val project: Project, val name: String) { | |
return if (!value.isBlank()) value.toBoolean() else true | ||
} | ||
|
||
fun promptProp(prop: String, defaultProvider: () -> String?): () -> String { | ||
fun promptProp(prop: String, defaultProvider: () -> String): () -> String { | ||
prompts[prop] = PropertyPrompt(prop, defaultProvider) | ||
|
||
return { props[prop] ?: throw ForkException("Fork prompt property '$prop' not bound.") } | ||
} | ||
|
||
fun promptProp(prop: String): () -> String { | ||
return promptProp(prop) { null } | ||
return promptProp(prop) | ||
} | ||
|
||
fun promptTemplate(template: String): () -> String { | ||
templateEngine.parse(template).forEach { prop, defaultValue -> | ||
prompts[prop] = PropertyPrompt(prop) { defaultValue } | ||
templateEngine.parse(template).forEach { prop -> | ||
prompts[prop] = PropertyPrompt(prop) | ||
} | ||
|
||
return { renderTemplate(template) } | ||
|
@@ -83,7 +90,7 @@ abstract class Config(val project: Project, val name: String) { | |
return templateEngine.render(template, props) | ||
} | ||
|
||
private fun promptFill(): Map<String, String> { | ||
private fun promptFill(): Map<String, String?> { | ||
promptFillPropertiesFile(previousPropsFile) | ||
|
||
try { | ||
|
@@ -136,7 +143,7 @@ abstract class Config(val project: Project, val name: String) { | |
} | ||
|
||
private fun promptValidate() { | ||
val invalidProps = prompts.values.filter { !it.valid }.map { it.name } | ||
val invalidProps = properties.filter(Property::isInvalid).map { it.name } | ||
if (invalidProps.isNotEmpty()) { | ||
throw ForkException("Fork cannot be performed, because of missing properties: $invalidProps." | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing or invalid There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean, please correct message in exception There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I mean, we need to update message because now the cause of exception here is not filled property or property filled but not passing validation, build user should be informed about that` |
||
+ " Specify them via properties file $propsFile or interactive mode.") | ||
|
@@ -232,6 +239,7 @@ abstract class Config(val project: Project, val name: String) { | |
|
||
companion object { | ||
const val NAME_DEFAULT = "default" | ||
const val NAME_PROPERTIES = "properties" | ||
} | ||
|
||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package com.neva.gradle.fork.config.properties | ||
|
||
import java.net.MalformedURLException | ||
import java.net.URL | ||
import java.nio.file.InvalidPathException | ||
import java.nio.file.Paths | ||
|
||
class Property(private val definition: PropertyDefinition, private val prompt: PropertyPrompt) { | ||
|
||
val name: String | ||
get() = prompt.name | ||
|
||
var value: String | ||
set(newValue) { | ||
prompt.value = newValue | ||
} | ||
get() = prompt.valueOrDefault ?: definition.defaultValue | ||
|
||
val label: String | ||
get() = if (required) "${prompt.label}*" else prompt.label | ||
|
||
val type: PropertyType = definition.type | ||
|
||
private val required: Boolean | ||
get() = definition.required | ||
|
||
fun validate(): Validator { | ||
val validator = Validator(value) | ||
if (required && value.isBlank()) { | ||
validator.error("This property is required.") | ||
return validator | ||
} | ||
if (shouldBeValidated()) { | ||
when (definition.validator) { | ||
null -> applyDefaultValidation(validator) | ||
else -> definition.validator?.execute(validator) | ||
} | ||
} | ||
return validator | ||
} | ||
|
||
fun isInvalid() = validate().hasErrors() | ||
pun-ky marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
private fun applyDefaultValidation(validator: Validator) = when (type) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cool |
||
PropertyType.PATH -> validatePath(validator) | ||
PropertyType.URL -> validateUrl(validator) | ||
else -> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this empty else branch looks strange, when is not exhaustive, maybe you could just remove it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. then there is a warning, and I have to add curly braces, either way it is not perfect :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not just |
||
} | ||
} | ||
|
||
private fun validateUrl(validator: Validator) { | ||
try { | ||
URL(value) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure about it. I am more sure about Apache Commons validator / https://github.com/Cognifide/gradle-aem-plugin/blob/master/src/main/kotlin/com/cognifide/gradle/aem/common/Formats.kt#L20 but who knows which is better, nvm |
||
} catch (e: MalformedURLException) { | ||
validator.error("This URL is invalid: \"${e.message}\"") | ||
} | ||
} | ||
|
||
private fun validatePath(validator: Validator) { | ||
try { | ||
Paths.get(value) | ||
} catch (e: InvalidPathException) { | ||
validator.error("This path is invalid: \"${e.message}\"") | ||
} | ||
} | ||
|
||
private fun shouldBeValidated() = required || value.isNotBlank() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cool |
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (value.split(" ").any{ !it.startsWith("-"))
:)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK