Skip to content

Commit

Permalink
Add more out formats to config command
Browse files Browse the repository at this point in the history
Signed-off-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com>
  • Loading branch information
pditommaso committed Oct 11, 2024
1 parent 8eb0c39 commit bf3a441
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 10 deletions.
56 changes: 46 additions & 10 deletions modules/nextflow/src/main/groovy/nextflow/cli/CmdConfig.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class CmdConfig extends CmdBase {

static final public NAME = 'config'

static final List<String> FORMATS = ['flat','properties','canonical','json','yaml']

@Parameter(description = 'project name')
List<String> args = []

Expand All @@ -50,10 +52,12 @@ class CmdConfig extends CmdBase {
@Parameter(names=['-profile'], description = 'Choose a configuration profile')
String profile

@Parameter(names = '-properties', description = 'Prints config using Java properties notation')
@Deprecated
@Parameter(names = '-properties', description = 'Prints config using Java properties notation (deprecated: use `-o properties` instead)')
boolean printProperties

@Parameter(names = '-flat', description = 'Print config using flat notation')
@Deprecated
@Parameter(names = '-flat', description = 'Print config using flat notation (deprecated: use `-o flat` instead)')
boolean printFlatten

@Parameter(names = '-sort', description = 'Sort config attributes')
Expand All @@ -62,6 +66,9 @@ class CmdConfig extends CmdBase {
@Parameter(names = '-value', description = 'Print the value of a config option, or fail if the option is not defined')
String printValue

@Parameter(names = ['-o','-output'], description = 'Print the config using the specified format: canonical,properties,flat,json,yaml')
String outputFormat

@Override
String getName() { NAME }

Expand All @@ -79,13 +86,22 @@ class CmdConfig extends CmdBase {
}

if( printProperties && printFlatten )
throw new AbortOperationException("Option `-flat` and `-properties` conflicts")
throw new AbortOperationException("Option `-flat` and `-properties` conflicts each other")

if ( printValue && printFlatten )
throw new AbortOperationException("Option `-value` and `-flat` conflicts")
throw new AbortOperationException("Option `-value` and `-flat` conflicts each other")

if ( printValue && printProperties )
throw new AbortOperationException("Option `-value` and `-properties` conflicts")
throw new AbortOperationException("Option `-value` and `-properties` conflicts each other")

if( printValue && outputFormat )
throw new AbortOperationException("Option `-value` and `-output` conflicts each other")

if( printFlatten )
outputFormat = 'flat'

if( printProperties )
outputFormat = 'properties'

final builder = new ConfigBuilder()
.setShowClosures(true)
Expand All @@ -97,18 +113,31 @@ class CmdConfig extends CmdBase {

final config = builder.buildConfigObject()

if( printProperties ) {
if( printValue ) {
printValue0(config, printValue, stdout)
}
else if( outputFormat=='properties' ) {
printProperties0(config, stdout)
}
else if( printFlatten ) {
else if( outputFormat=='flat' ) {
printFlatten0(config, stdout)
}
else if( printValue ) {
printValue0(config, printValue, stdout)
else if( outputFormat=='yaml' ) {
printYaml0(config, stdout)
}
else {
else if( outputFormat=='json') {
printJson0(config, stdout)
}
else if( !outputFormat || outputFormat=='canonical' ) {
printCanonical0(config, stdout)
}
else {
def msg = "Unknown output format: $outputFormat"
def suggest = FORMATS.closest(outputFormat)
if( suggest )
msg += " - did you mean '${suggest.first()}' instead?"
throw new AbortOperationException(msg)
}

for( String msg : builder.warnings )
log.warn(msg)
Expand Down Expand Up @@ -172,6 +201,13 @@ class CmdConfig extends CmdBase {
config.writeTo( writer )
}

@PackageScope void printJson0(ConfigObject config, OutputStream output) {
output << ConfigHelper.toJsonString(config, sort) << '\n'
}

@PackageScope void printYaml0(ConfigObject config, OutputStream output) {
output << ConfigHelper.toYamlString(config, sort)
}

Path getBaseDir(String path) {

Expand Down
37 changes: 37 additions & 0 deletions modules/nextflow/src/main/groovy/nextflow/util/ConfigHelper.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ package nextflow.util

import java.nio.file.Path

import groovy.json.JsonOutput
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import groovy.util.logging.Slf4j
import org.codehaus.groovy.runtime.InvokerHelper
import org.yaml.snakeyaml.DumperOptions
import org.yaml.snakeyaml.Yaml

/**
* Helper method to handle configuration object
*
Expand Down Expand Up @@ -301,6 +305,19 @@ class ConfigHelper {
flattenFormat(map.toConfigObject(), sort)
}

static String toJsonString(ConfigObject config, boolean sort=false) {
final copy = normaliseConfig(config)
JsonOutput.prettyPrint(JsonOutput.toJson(sort ? deepSort(copy) : copy))
}

static String toYamlString(ConfigObject config, boolean sort=false) {
final copy = normaliseConfig(config)
final options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); // Use block style instead of inline
final yaml = new Yaml(options)
return yaml.dump(sort ? deepSort(copy) : copy);
}

static boolean isValidIdentifier(String s) {
// an empty or null string cannot be a valid identifier
if (s == null || s.length() == 0) {
Expand All @@ -321,7 +338,27 @@ class ConfigHelper {
return true;
}

private static Map<String, Object> deepSort(Map<String, Object> map) {
Map<String, Object> sortedMap = new TreeMap<>(map); // Sort current map

for (Map.Entry<String, Object> entry : sortedMap.entrySet()) {
Object value = entry.getValue();
if (value instanceof Map) {
// Recursively sort nested map
sortedMap.put(entry.getKey(), deepSort((Map<String, Object>) value));
}
}
return sortedMap;
}

private static Map<String,Object> normaliseConfig(ConfigObject config) {
final result = new LinkedHashMap()
for( Map.Entry it : config ) {
if( it.value instanceof Map && !it.value )
continue
result.put(it.key, it.value)
}
return result
}
}

81 changes: 81 additions & 0 deletions modules/nextflow/src/test/groovy/nextflow/cli/CmdConfigTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ class CmdConfigTest extends Specification {
.stripIndent().leftTrim()

}

def 'should canonical notation' () {

given:
Expand Down Expand Up @@ -196,6 +197,86 @@ class CmdConfigTest extends Specification {

}

def 'should print config using json' () {
given:
ByteArrayOutputStream buffer
ConfigObject config
def cmd = new CmdConfig(sort: true)

when:
buffer = new ByteArrayOutputStream()

config = new ConfigObject()
config.process.executor = 'slurm'
config.process.queue = 'long'
config.docker.enabled = true
config.dummy = new ConfigObject() // <-- empty config object should not be print
config.mail.from = 'yo@mail.com'
config.mail.smtp.host = 'mail.com'
config.mail.smtp.port = 25
config.mail.smtp.user = 'yo'

cmd.printJson0(config, buffer)
then:
buffer.toString() == '''\
{
"docker": {
"enabled": true
},
"mail": {
"from": "yo@mail.com",
"smtp": {
"host": "mail.com",
"port": 25,
"user": "yo"
}
},
"process": {
"executor": "slurm",
"queue": "long"
}
}
'''
.stripIndent()
}

def 'should print config using yaml' () {
given:
ByteArrayOutputStream buffer
ConfigObject config
def cmd = new CmdConfig(sort: true)

when:
buffer = new ByteArrayOutputStream()

config = new ConfigObject()
config.process.executor = 'slurm'
config.process.queue = 'long'
config.docker.enabled = true
config.dummy = new ConfigObject() // <-- empty config object should not be print
config.mail.from = 'yo@mail.com'
config.mail.smtp.host = 'mail.com'
config.mail.smtp.port = 25
config.mail.smtp.user = 'yo'

cmd.printYaml0(config, buffer)
then:
buffer.toString() == '''\
docker:
enabled: true
mail:
from: yo@mail.com
smtp:
host: mail.com
port: 25
user: yo
process:
executor: slurm
queue: long
'''
.stripIndent()
}

def 'should parse config file' () {
given:
def folder = Files.createTempDirectory('test')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,94 @@ class ConfigHelperTest extends Specification {

}

def 'should render config as json' () {
given:
def config = new ConfigObject()
config.process.queue = 'long'
config.process.executor = 'slurm'
config.docker.enabled = true
config.zeta.'quoted-attribute'.foo = 1

when:
def result = ConfigHelper.toJsonString(config, true)
then:
result == '''
{
"docker": {
"enabled": true
},
"process": {
"executor": "slurm",
"queue": "long"
},
"zeta": {
"quoted-attribute": {
"foo": 1
}
}
}
'''.stripIndent().trim()


when:
result = ConfigHelper.toJsonString(config, false)
then:
result == '''
{
"process": {
"queue": "long",
"executor": "slurm"
},
"docker": {
"enabled": true
},
"zeta": {
"quoted-attribute": {
"foo": 1
}
}
}
'''.stripIndent().trim()
}

def 'should render config as yaml' () {
given:
def config = new ConfigObject()
config.process.queue = 'long'
config.process.executor = 'slurm'
config.docker.enabled = true
config.zeta.'quoted-attribute'.foo = 1

when:
def result = ConfigHelper.toYamlString(config, true)
then:
result == '''\
docker:
enabled: true
process:
executor: slurm
queue: long
zeta:
quoted-attribute:
foo: 1
'''.stripIndent()


when:
result = ConfigHelper.toYamlString(config, false)
then:
result == '''\
process:
queue: long
executor: slurm
docker:
enabled: true
zeta:
quoted-attribute:
foo: 1
'''.stripIndent()
}

def 'should verify valid identifiers' () {

expect:
Expand Down

0 comments on commit bf3a441

Please sign in to comment.