Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion modules/nextflow/src/main/groovy/nextflow/cli/CmdLint.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ class CmdLint extends CmdBase {
}
}

@Parameter(names = ['-q', '-quiet'], description = 'Suppress progress messages (file-by-file status and summary); errors are still shown')
boolean quiet

@Parameter(names = ['-format'], description = 'Format scripts and config files that have no errors')
boolean formatting

Expand Down Expand Up @@ -127,7 +130,7 @@ class CmdLint extends CmdBase {
? new JsonErrorListener()
: outputMode == 'markdown'
? new MarkdownErrorListener()
: new StandardErrorListener(outputMode, launcher.options.ansiLog)
: new StandardErrorListener(outputMode, launcher.options.ansiLog, quiet || launcher.options.quiet)
formattingOptions = new FormattingOptions(spaces, !tabs, harhsilAlignment, false, sortDeclarations)

errorListener.beforeAll()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,40 +55,66 @@ class ErrorSummary {
class StandardErrorListener implements ErrorListener {
private String mode
private boolean ansiLog
/** When true, suppress progress/summary messages (file-by-file status and
* the final summary). Errors and warnings are always shown.
* Automatically set when ansiLog is false (non-interactive output) or
* when the global --quiet / -q flag is active. */
private boolean suppressProgress

StandardErrorListener(String mode, boolean ansiLog) {
StandardErrorListener(String mode, boolean ansiLog, boolean quiet=false) {
this.mode = mode
this.ansiLog = ansiLog
// Suppress the animated progress lines whenever ANSI is off (they
// rely on cursor-up/erase-line sequences) or when the caller has
// explicitly requested quiet mode.
this.suppressProgress = !ansiLog || quiet
}

private Ansi ansi() {
final ansi = Ansi.ansi()
ansi.setEnabled(ansiLog)
return ansi
// Ansi.setEnabled() is static-only in Jansi 2.x: codes are baked into
// the Ansi buffer when fg()/bold() etc. are called, not at toString()
// time. Set the global flag here so every Ansi object created by this
// listener appends codes only when ansiLog=true.
Ansi.setEnabled(ansiLog)
return Ansi.ansi()
}

private void print(Object text) {
final str = text.toString()
if( ansiLog ) {
// AnsiConsole.out handles TTY detection: renders ANSI on a terminal,
// strips codes when output is piped.
AnsiConsole.out.print(str)
AnsiConsole.out.flush()
}
else {
System.out.print(str)
System.out.flush()
}
}

@Override
void beforeAll() {
final line = ansi().a("Linting Nextflow code..").newline()
AnsiConsole.out.print(line)
AnsiConsole.out.flush()
if( suppressProgress )
return
print(ansi().a("Linting Nextflow code..").newline())
}

@Override
void beforeFile(File file) {
final line = ansi()
if( suppressProgress )
return
print(ansi()
.cursorUp(1).eraseLine()
.a(Ansi.Attribute.INTENSITY_FAINT).a("Linting: ${file}")
.reset().newline().toString()
AnsiConsole.out.print(line)
AnsiConsole.out.flush()
.reset().newline())
}

private Ansi term

@Override
void beforeErrors() {
term = ansi().cursorUp(1).eraseLine()
term = suppressProgress ? ansi() : ansi().cursorUp(1).eraseLine()
}

@Override
Expand Down Expand Up @@ -215,26 +241,28 @@ class StandardErrorListener implements ErrorListener {

@Override
void afterErrors() {
// print extra newline since next file status will chomp back one
term.fg(Ansi.Color.DEFAULT).newline()
AnsiConsole.out.print(term)
AnsiConsole.out.flush()
if( !suppressProgress ) {
// print extra newline since next file status will chomp back one
term.fg(Ansi.Color.DEFAULT).newline()
}
print(term)
}

@Override
void beforeFormat(File file) {
final line = ansi()
if( suppressProgress )
return
print(ansi()
.cursorUp(1).eraseLine()
.a(Ansi.Attribute.INTENSITY_FAINT).a("Formatting: ${file}")
.reset().newline().toString()
AnsiConsole.out.print(line)
AnsiConsole.out.flush()
.reset().newline())
}

@Override
void afterAll(ErrorSummary summary) {
final term = ansi()
term.cursorUp(1).eraseLine()
if( !suppressProgress )
term.cursorUp(1).eraseLine()
// print extra newline if no code is being shown
if( mode == 'concise' )
term.newline()
Expand All @@ -254,8 +282,7 @@ class StandardErrorListener implements ErrorListener {
if( summary.filesWithErrors == 0 && summary.filesWithoutErrors == 0 ) {
term.a(" No files found to process").newline()
}
AnsiConsole.out.print(term)
AnsiConsole.out.flush()
print(term)
}

private static record Range(
Expand Down
147 changes: 147 additions & 0 deletions modules/nextflow/src/test/groovy/nextflow/cli/CmdLintTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,151 @@ class CmdLintTest extends Specification {
dir?.deleteDir()
}

def 'should suppress progress with nextflow -q lint (global quiet flag)'() {

given:
def dir = Files.createTempDirectory('test')
dir.resolve('main.nf').text = '''\
process HELLO {
script:
"echo hello"
}
'''

when:
def cmd = new CmdLint()
cmd.args = [dir.toFile().toString()]
cmd.launcher = Mock(Launcher) {
getOptions() >> Mock(CliOptions) {
isQuiet() >> true
getAnsiLog() >> false
}
}
cmd.run()

then:
noExceptionThrown()
and:
!capture.toString().contains("Linting Nextflow code")
!capture.toString().contains("Linting:")
and:
capture.toString().contains("Nextflow linting complete")

cleanup:
dir?.deleteDir()
}

def 'should suppress progress when ansiLog is false (non-interactive output)'() {

given:
def dir = Files.createTempDirectory('test')
dir.resolve('main.nf').text = '''\
process HELLO {
script:
"echo hello"
}
'''

when:
def cmd = new CmdLint()
cmd.args = [dir.toFile().toString()]
cmd.launcher = Mock(Launcher) {
getOptions() >> Mock(CliOptions) {
isQuiet() >> false
getAnsiLog() >> false
}
}
cmd.run()

then:
noExceptionThrown()
and:
!capture.toString().contains("Linting Nextflow code")
!capture.toString().contains("Linting:")
and:
capture.toString().contains("Nextflow linting complete")

cleanup:
dir?.deleteDir()
}

def 'should suppress progress with lint -q flag (lint-level quiet, keeps ANSI)'() {

given:
def dir = Files.createTempDirectory('test')
dir.resolve('main.nf').text = '''\
process HELLO {
script:
"echo hello"
}
'''

when:
def cmd = new CmdLint()
cmd.quiet = true
cmd.args = [dir.toFile().toString()]
cmd.launcher = Mock(Launcher) {
getOptions() >> Mock(CliOptions) {
isQuiet() >> false
getAnsiLog() >> false
}
}
cmd.run()

then:
noExceptionThrown()
and:
!capture.toString().contains("Linting Nextflow code")
!capture.toString().contains("Linting:")
and:
capture.toString().contains("Nextflow linting complete")

cleanup:
dir?.deleteDir()
}

def 'should still show errors when progress is suppressed' () {

given:
def dir = Files.createTempDirectory('test')

dir.resolve('main.nf').text = '''\
process HELLO {

script:
"""
${
params.is_paired_end
? "..."
: "..."
}
"""
}
'''

when:
def cmd = new CmdLint()
cmd.args = [dir.toFile().toString()]
cmd.launcher = Mock(Launcher) {
getOptions() >> Mock(CliOptions) {
isQuiet() >> true
getAnsiLog() >> false
}
}
cmd.run()

then:
thrown(AbortOperationException)
and:
!capture.toString().contains("Linting Nextflow code")
!capture.toString().contains("Linting:")
and:
capture.toString().contains("Error")
capture.toString().contains("Unexpected input")
capture.toString().contains("Nextflow linting complete")

cleanup:
dir?.deleteDir()
}

}
Loading