diff --git a/README.md b/README.md index 47356a7..4cba79d 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # json2yaml -Gradle plugin for JSON to YAML tranformation +Gradle plugin for JSON to YAML or YAML to JSON tranformation -### Step 1 +## Basic Usage + +### Install the plugin Apply the plugin @@ -12,11 +14,50 @@ plugins { } ``` -### Step 2 +### Original Tasks (single file conversion) -Set input and output file +The original plugin version offered a simple model where a single input and output file could be set for conversion. +For JSON to YAML: ``` json2yaml.inputFile = project.file('sample.json') json2yaml.outputFile = project.file('output.yaml') ``` + +For YAML to JSON: +``` +yaml2json.inputFile = project.file('sample.yaml') +yaml2json.outputFile = project.file('output.json') +``` + +### Enhanced Tasks (multiple file conversion) + +A newer set of enhanced tasks has been added which allows + + - multiple files to be converted + - adds support for Gradle incremental builds + - allows either a file collection to be supplied, with conversion outputs flattened, or a file tree which preserves the original directory structure + - allows file extension mapping to be specified using task configuration options. If the input file extension matches, it will be swapped with the configured output extension + + +For JSON to YAML - using file collection: +``` +task incremental(type: dev.castocolina.gradle.plugin.JsonToYaml) { + outDir = file("${buildDir}/generated") + // collection of files - converted files will appear directly under outDir + sourceFiles = layout.files('tst.json', 'src/resources/jsonschema/tst2.json') +} +``` + +For YAML to JSON - using file tree: +``` +YamlToJson { + outDir = file("${buildDir}/generated") + // file tree - converted files will use same sub-directory structure as originals + sourceFiles = fileTree(dir: "src", include: "resources/jsonschema/*.yaml") + // Extension mapping (values shown are actually the defaults). + yamlExt = ".yaml" + jsonExt = ".json" + +} +``` diff --git a/build.gradle b/build.gradle index 69094fa..fb399e1 100644 --- a/build.gradle +++ b/build.gradle @@ -27,13 +27,16 @@ ext { } dependencies { + implementation 'org.codehaus.groovy:groovy-all:3.0.5' + implementation 'org.codehaus.groovy:groovy-yaml:3.0.5' + implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:${jacksonVer}" implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:${jacksonVer}" // Use the awesome Spock testing and specification framework testImplementation 'org.spockframework:spock-core:1.3-groovy-2.5' } -version = "1.0.0-SNAPSHOT" +version = "1.1.0-SNAPSHOT" pluginBundle { website = 'https://github.com/castocolina/json2yaml' diff --git a/src/main/groovy/dev/castocolina/gradle/plugin/Json2YamlPlugin.groovy b/src/main/groovy/dev/castocolina/gradle/plugin/Json2YamlPlugin.groovy index 64f6ab6..6e1bec1 100644 --- a/src/main/groovy/dev/castocolina/gradle/plugin/Json2YamlPlugin.groovy +++ b/src/main/groovy/dev/castocolina/gradle/plugin/Json2YamlPlugin.groovy @@ -1,5 +1,7 @@ /* - * This Groovy source file was generated by the Gradle 'init' task. + * Various flavours of JSON <> YAML converstion task + * Note: I am definitely more of a Java coder than a Groovy one, and also on something of a Gradle learning process here + * So feel free to critique and suggest improvements! */ package dev.castocolina.gradle.plugin @@ -10,6 +12,20 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.dataformat.yaml.YAMLFactory import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature +import groovy.yaml.YamlSlurper +import com.fasterxml.jackson.core.JsonFactory + +import org.gradle.api.file.FileCollection +import org.gradle.api.file.FileTree +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.SkipWhenEmpty +import org.gradle.api.tasks.incremental.IncrementalTaskInputs + public class Json2YamlPluginExtension { File inputFile = new File('sample.json') File outputFile = new File('build/tmp/docs.yaml') @@ -17,23 +33,29 @@ public class Json2YamlPluginExtension { Boolean deleteTargetFirst = Boolean.TRUE } +public class Yaml2JsonPluginExtension { + File inputFile = new File('sample.yaml') + File outputFile = new File('build/tmp/docs.json') + Boolean deleteTargetFirst = Boolean.TRUE +} + /** - * A simple 'hello world' plugin. + * Main class to register plugin tasks */ public class Json2YamlPlugin implements Plugin { public void apply(Project project) { - def extension = project.extensions.create('json2yaml', Json2YamlPluginExtension) - // Register a task + // Register task from JSON to YAML - from code originally forked + def extToJson = project.extensions.create('json2yaml', Json2YamlPluginExtension) project.tasks.register("json2yaml") { def inJson, ouYaml, minQuote, delTarget doFirst { - inJson = extension.inputFile - ouYaml = extension.outputFile - minQuote = extension.minimizeQuote - delTarget = extension.deleteTargetFirst + inJson = extToJson.inputFile + ouYaml = extToJson.outputFile + minQuote = extToJson.minimizeQuote + delTarget = extToJson.deleteTargetFirst println "Transform input: " + inJson.absolutePath println "Transform output: " + ouYaml.absolutePath @@ -43,17 +65,146 @@ public class Json2YamlPlugin implements Plugin { } doLast { - def jsonText = inJson.text - def jSonMap = new JsonSlurper().parseText(jsonText) - def factory = new YAMLFactory() - if (minQuote){ - factory.enable(Feature.MINIMIZE_QUOTES) + JsonToYaml.doIt(inJson, ouYaml, minQuote) + println("File converted!!!") + } + + } + + // Register task from YAML to JSON - following same approach as code originally forked + def extToYaml = project.extensions.create('yaml2json', Yaml2JsonPluginExtension) + + project.tasks.register("yaml2json") { + def inYaml, ouJson, delTarget + + doFirst { + inYaml = extToYaml.inputFile + ouJson = extToYaml.outputFile + delTarget = extToYaml.deleteTargetFirst + + println "Transform input: " + inYaml.absolutePath + println "Transform output: " + ouJson.absolutePath + if (delTarget){ + println "Delete target first: " + ouJson.delete() } - def mapper = new ObjectMapper(factory) - mapper.writeValue(ouYaml, jSonMap) + } + + doLast { + YamlToJson.doIt(inYaml, ouJson) println("File converted!!!") } } + + // Alternate approach to register custom tasks for converrsion. This allows use of the + // injection of config properties and support for incremental build handling + project.tasks.register("JsonToYaml", JsonToYaml) + project.tasks.register("YamlToJson", YamlToJson) } } + +// Newer classes to provide conversion tasks with incremental build support + +public abstract class ConverterTask extends DefaultTask { + @OutputDirectory + File outDir = project.file("${project.buildDir}/generated") + + @InputFiles + @SkipWhenEmpty + FileCollection sourceFiles + + @Input + String yamlExt = ".yaml" // configurable default extension for YAML files + + @Input + String jsonExt = ".json" // configurable default extension for JSON files + + + @Internal + def convertMap = [:] + + void initConversionMap(String extFrom, String extTo) { + if (sourceFiles instanceof FileTree) { + // For file trees, we will try and preserve structure on output + FileTree tree = (FileTree) sourceFiles + tree.visit {element -> + if (element.file.isFile()) { + convertMap.put(element.file, swapExtension(element.relativePath.toString(), extFrom, extTo)) + } + } + } else { + // For simple collections, output will be flattened + sourceFiles.each {File file -> + if (file.isFile()) { + convertMap.put(file, swapExtension(file.name, extFrom, extTo)) + } + } + } + } + + String swapExtension(String path, String extFrom, String extTo) { + return (path.endsWith(extFrom)) ? path.take(path.lastIndexOf('.')) + extTo + : path + extTo + } + + void convertAll() { + convertMap.each{entry -> convert(entry.key, new File(outDir, entry.value))} + } + + abstract void convert(File fin, File fout) +} + +public class YamlToJson extends ConverterTask { + + @TaskAction + void perform() { + initConversionMap(yamlExt, jsonExt) + convertAll() + } + + void convert(File fin, File fout) { + doIt(fin, fout) + } + + static void doIt (File fin, File fout) { + def yamlText = fin.text + def yamlMap = new YamlSlurper().parseText(yamlText) + def factory = new JsonFactory() + def mapper = new ObjectMapper(factory) + + fout.parentFile.mkdirs() + mapper.writeValue(fout, yamlMap) + } +} + + +public class JsonToYaml extends ConverterTask { + + @Input + boolean minimizeQuote = true + + @TaskAction + void perform() { + initConversionMap(jsonExt, yamlExt) + convertAll() + } + + void convert(File fin, File fout) { + doIt(fin, fout, minimizeQuote) + } + + static void doIt (File fin, File fout, boolean minQuote) { + def jsonText = fin.text + def jSonMap = new JsonSlurper().parseText(jsonText) + def factory = new YAMLFactory() + if (minQuote){ + factory.enable(Feature.MINIMIZE_QUOTES) + } + def mapper = new ObjectMapper(factory) + + fout.parentFile.mkdirs() + mapper.writeValue(fout, jSonMap) + } +} + + diff --git a/src/test/groovy/dev/castocolina/gradle/plugin/Json2YamlPluginTest.groovy b/src/test/groovy/dev/castocolina/gradle/plugin/Json2YamlPluginTest.groovy index 71e58a0..9fc95cd 100644 --- a/src/test/groovy/dev/castocolina/gradle/plugin/Json2YamlPluginTest.groovy +++ b/src/test/groovy/dev/castocolina/gradle/plugin/Json2YamlPluginTest.groovy @@ -19,7 +19,9 @@ public class Json2YamlPluginTest extends Specification { project.plugins.apply("dev.castocolina.json2yaml") then: - project.tasks.findByName("json2yaml") != null - + (project.tasks.findByName("json2yaml") != null && + project.tasks.findByName("yaml2json") != null && + project.tasks.findByName("JsonToYaml") != null && + project.tasks.findByName("YamlToJson") != null) } }