Custom Tasks Should Provide Usage when configured incorrectly

How do I help task users understand what to do when a task is configured incorrectly?

When a test lacks its argument or worse delegates configuration to a closure the errors can be very confusing.

I created a ClosureConfigurationDelegator and had my task + object implement it.
I create a usageError with sample usage, wrap the passing of closure to object in a try catch so I can display usage on those errors. I also note config problems so I can report them as well.

If my BuildValidationTask has a faulty content closure, the somewhat failure is easier to follow.

trait ClosureConfigurationDelegator {
abstract void usageError(def message)

/**
 * Always report delegation errors with usage - add railings to the learning curve
 * helping folks learn the system
 * @param configuration closure - see implementations for details
 */
public void delegateClosure(Closure config) {
    try {
        config.delegate = this
        config()
    } catch (Exception e) {
        usageError(e.getMessage())
    }
}

}

Usage

public FileContentsValidationSpec(Closure config) {
    delegateClosure(config)

    // Gradle @ Runtime via gradle file requires this even though unit tests do not.
    this.description = config.description
    this.contentFileTree = config.contentFileTree

    def configErrors = []

    if (! description) {
        configErrors.add("contents lacks description configuration.")
    }
    if ( ! contentFileTree) {
        configErrors.add("contents lacks files filetree.")
    }

    if ( ! required && ! prohibited) {
        configErrors.add("contents lacks pattern lists for both required and prohibited text.  Can't determine how to validate content.")
    }

    // Compile Regex
    try {
        required = stringsToPatterns(config.required)
        prohibited = stringsToPatterns(config.prohibited)
    } catch (Exception e) {
        configErrors.add("StringToPattern Failure: ${e.getMessage()}")
    }

    // Convert boolean into FaultType
    if (failOnError) {
        this.fType = FaultType.FAIL
    } else {
        this.fType = FaultType.WARN
    }

    // Throw on problems found
    if (configErrors.size() > 0) {
        usageError(configErrors.join("\n\t"))
    }
}

void usageError(def message) {
    throw new GradleException("""Misconfigured content:

${message}
Artifact:
${this.toString()}

| Example:
| content {
| description = “installer”
| contentFileTree= fileTree(dir: “./build”, includes: [“**/target/dist/AIEinstall”])
| required= [“BUILD SUCCESSFUL”]
| prohibited= [“BUILD FAILED”, “Conflict Count [1-9]"]
| failOnError = true /
Defaults to false */
| }
“””)
}