diff --git a/modules/nextflow/src/main/groovy/nextflow/cli/CmdLint.groovy b/modules/nextflow/src/main/groovy/nextflow/cli/CmdLint.groovy index cc1461fea1..1f463227b2 100644 --- a/modules/nextflow/src/main/groovy/nextflow/cli/CmdLint.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/cli/CmdLint.groovy @@ -17,6 +17,7 @@ package nextflow.cli import java.nio.file.Path +import java.nio.file.Files import java.time.Instant import java.time.ZoneOffset import java.time.format.DateTimeFormatter @@ -58,6 +59,12 @@ class CmdLint extends CmdBase { @Parameter(description = 'List of paths to lint') List args = [] + @Parameter( + names = ['-files-from'], + description = 'Read list of paths to lint from file (one per line, use - for stdin)' + ) + String filesFrom + @Parameter( names = ['-exclude'], description = 'File pattern to exclude from error checking (can be specified multiple times)' @@ -112,6 +119,20 @@ class CmdLint extends CmdBase { @Override void run() { + // Read paths from --files-from if specified + if( filesFrom ) { + if( args == null ) + args = [] + final lines = filesFrom == '-' + ? new BufferedReader(new InputStreamReader(System.in)).readLines() + : Files.readAllLines(Path.of(filesFrom)) + for( final line : lines ) { + final trimmed = line.trim() + if( trimmed ) + args.add(trimmed) + } + } + if( !args ) throw new AbortOperationException("Error: No input files were specified") diff --git a/modules/nextflow/src/test/groovy/nextflow/cli/CmdLintTest.groovy b/modules/nextflow/src/test/groovy/nextflow/cli/CmdLintTest.groovy index f62b58e113..8f8a40576a 100644 --- a/modules/nextflow/src/test/groovy/nextflow/cli/CmdLintTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/cli/CmdLintTest.groovy @@ -81,4 +81,110 @@ class CmdLintTest extends Specification { dir?.deleteDir() } + def 'should read files from --files-from option' () { + + given: + def dir = Files.createTempDirectory('test') + + dir.resolve('main.nf').text = '''\ + process HELLO { + + script: + """ + ${ + params.is_paired_end + ? "..." + : "..." + } + """ + } + ''' + + def fileList = dir.resolve('filelist.txt') + fileList.text = dir.resolve('main.nf').toString() + '\n' + + when: + def cmd = new CmdLint() + cmd.filesFrom = fileList.toFile().toString() + cmd.launcher = Mock(Launcher) { + getOptions() >> Mock(CliOptions) + } + cmd.run() + + then: + thrown(AbortOperationException) + + cleanup: + dir?.deleteDir() + } + + def 'should combine --files-from with positional args' () { + + given: + def dir = Files.createTempDirectory('test') + + dir.resolve('main.nf').text = '''\ + process HELLO { + + script: + """ + ${ + params.is_paired_end + ? "..." + : "..." + } + """ + } + ''' + + dir.resolve('nextflow.config').text = '''\ + process { + withLabel: + 'bambino' { + container = "..." + } + } + ''' + + def fileList = dir.resolve('filelist.txt') + fileList.text = dir.resolve('nextflow.config').toString() + '\n' + + when: + def cmd = new CmdLint() + cmd.args = [dir.resolve('main.nf').toString()] + cmd.filesFrom = fileList.toFile().toString() + cmd.launcher = Mock(Launcher) { + getOptions() >> Mock(CliOptions) + } + cmd.run() + + then: + thrown(AbortOperationException) + + cleanup: + dir?.deleteDir() + } + + def 'should throw error when --files-from file is empty' () { + + given: + def dir = Files.createTempDirectory('test') + def fileList = dir.resolve('filelist.txt') + fileList.text = '\n' + + when: + def cmd = new CmdLint() + cmd.filesFrom = fileList.toFile().toString() + cmd.launcher = Mock(Launcher) { + getOptions() >> Mock(CliOptions) + } + cmd.run() + + then: + thrown(AbortOperationException) + + cleanup: + dir?.deleteDir() + } + }