Skip to content

Commit dca68bd

Browse files
committed
Untangle dependencies between CLI and runtime code
Signed-off-by: Ben Sherman <[email protected]>
1 parent eff6796 commit dca68bd

File tree

17 files changed

+399
-360
lines changed

17 files changed

+399
-360
lines changed

modules/nextflow/src/main/groovy/nextflow/cli/CmdClone.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import nextflow.scm.AssetManager
3030
@Slf4j
3131
@CompileStatic
3232
@Parameters(commandDescription = "Clone a project into a folder")
33-
class CmdClone extends CmdBase implements HubOptions {
33+
class CmdClone extends CmdBase implements HubAware {
3434

3535
static final public NAME = 'clone'
3636

@@ -52,7 +52,7 @@ class CmdClone extends CmdBase implements HubOptions {
5252
Plugins.init()
5353
// the pipeline name
5454
String pipeline = args[0]
55-
final manager = new AssetManager(pipeline, this)
55+
final manager = new AssetManager(pipeline, toHubOptions())
5656

5757
// the target directory is the second parameter
5858
// otherwise default the current pipeline name

modules/nextflow/src/main/groovy/nextflow/cli/CmdLint.groovy

Lines changed: 3 additions & 245 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import com.beust.jcommander.Parameters
2525
import com.beust.jcommander.ParameterException
2626
import groovy.json.JsonOutput
2727
import groovy.transform.CompileStatic
28-
import groovy.transform.Memoized
2928
import groovy.util.logging.Slf4j
3029
import nextflow.config.control.ConfigParser
3130
import nextflow.config.formatter.ConfigFormattingVisitor
@@ -35,13 +34,14 @@ import nextflow.script.control.ParanoidWarning
3534
import nextflow.script.control.ScriptParser
3635
import nextflow.script.formatter.FormattingOptions
3736
import nextflow.script.formatter.ScriptFormattingVisitor
37+
import nextflow.script.parser.v2.ErrorListener
38+
import nextflow.script.parser.v2.ErrorSummary
39+
import nextflow.script.parser.v2.StandardErrorListener
3840
import nextflow.util.PathUtils
3941
import org.codehaus.groovy.control.SourceUnit
4042
import org.codehaus.groovy.control.messages.SyntaxErrorMessage
4143
import org.codehaus.groovy.control.messages.WarningMessage
4244
import org.codehaus.groovy.syntax.SyntaxException
43-
import org.fusesource.jansi.Ansi
44-
import org.fusesource.jansi.AnsiConsole
4545
/**
4646
* CLI sub-command LINT
4747
*
@@ -266,248 +266,6 @@ class CmdLint extends CmdBase {
266266
}
267267

268268

269-
class ErrorSummary {
270-
int errors = 0
271-
int filesWithErrors = 0
272-
int filesWithoutErrors = 0
273-
int filesFormatted = 0
274-
}
275-
276-
277-
interface ErrorListener {
278-
void beforeAll()
279-
void beforeFile(File file)
280-
void beforeErrors()
281-
void onError(SyntaxException error, String filename, SourceUnit source)
282-
void onWarning(WarningMessage warning, String filename, SourceUnit source)
283-
void afterErrors()
284-
void beforeFormat(File file)
285-
void afterAll(ErrorSummary summary)
286-
}
287-
288-
289-
@CompileStatic
290-
class StandardErrorListener implements ErrorListener {
291-
private String mode
292-
private boolean ansiLog
293-
294-
StandardErrorListener(String mode, boolean ansiLog) {
295-
this.mode = mode
296-
this.ansiLog = ansiLog
297-
}
298-
299-
private Ansi ansi() {
300-
final ansi = Ansi.ansi()
301-
ansi.setEnabled(ansiLog)
302-
return ansi
303-
}
304-
305-
@Override
306-
void beforeAll() {
307-
final line = ansi().a("Linting Nextflow code..").newline()
308-
AnsiConsole.out.print(line)
309-
AnsiConsole.out.flush()
310-
}
311-
312-
@Override
313-
void beforeFile(File file) {
314-
final line = ansi()
315-
.cursorUp(1).eraseLine()
316-
.a(Ansi.Attribute.INTENSITY_FAINT).a("Linting: ${file}")
317-
.reset().newline().toString()
318-
AnsiConsole.out.print(line)
319-
AnsiConsole.out.flush()
320-
}
321-
322-
private Ansi term
323-
324-
@Override
325-
void beforeErrors() {
326-
term = ansi().cursorUp(1).eraseLine()
327-
}
328-
329-
@Override
330-
void onError(SyntaxException error, String filename, SourceUnit source) {
331-
term.bold().a(filename).reset()
332-
term.a(":${error.getStartLine()}:${error.getStartColumn()}: ")
333-
term = highlightString(error.getOriginalMessage(), term)
334-
if( mode != 'concise' ) {
335-
term.newline()
336-
term = printCodeBlock(source, Range.of(error), term, Ansi.Color.RED)
337-
}
338-
term.newline()
339-
}
340-
341-
@Override
342-
void onWarning(WarningMessage warning, String filename, SourceUnit source) {
343-
final token = warning.getContext().getRoot()
344-
term.bold().a(filename).reset()
345-
term.a(":${token.getStartLine()}:${token.getStartColumn()}: ")
346-
term.fg(Ansi.Color.YELLOW).a(warning.getMessage()).fg(Ansi.Color.DEFAULT)
347-
if( mode != 'concise' ) {
348-
term.newline()
349-
term = printCodeBlock(source, Range.of(warning), term, Ansi.Color.YELLOW)
350-
}
351-
term.newline()
352-
}
353-
354-
private Ansi highlightString(String str, Ansi term) {
355-
final matcher = str =~ /^(.*)([`'][^`']+[`'])(.*)$/
356-
if( matcher.find() ) {
357-
term.a(matcher.group(1))
358-
.fg(Ansi.Color.CYAN).a(matcher.group(2)).fg(Ansi.Color.DEFAULT)
359-
.a(matcher.group(3))
360-
}
361-
else {
362-
term.a(str)
363-
}
364-
return term
365-
}
366-
367-
private Ansi printCodeBlock(SourceUnit source, Range range, Ansi term, Ansi.Color color) {
368-
final startLine = range.startLine()
369-
final startColumn = range.startColumn()
370-
final endLine = range.endLine()
371-
final endColumn = range.endColumn()
372-
final lines = getSourceText(source)
373-
374-
// get context window (up to 5 lines)
375-
int padding = mode == 'extended' ? 2 : 0
376-
int fromLine = Math.max(1, startLine - padding)
377-
int toLine = Math.min(lines.size(), endLine + padding)
378-
if( toLine - fromLine + 1 > 5 ) {
379-
if( startLine <= 3 ) {
380-
toLine = fromLine + 4
381-
}
382-
else if( endLine >= lines.size() - 2 ) {
383-
fromLine = toLine - 4
384-
}
385-
else {
386-
fromLine = startLine - 2
387-
toLine = startLine + 2
388-
}
389-
}
390-
391-
for( int i = fromLine; i <= toLine; i++ ) {
392-
String fullLine = lines[i - 1]
393-
int start = (i == startLine) ? startColumn - 1 : 0
394-
int end = (i == endLine) ? endColumn - 1 : fullLine.length()
395-
396-
// Truncate to max 70 characters
397-
int maxLen = 70
398-
int lineLen = fullLine.length()
399-
int windowStart = 0
400-
if( lineLen > maxLen ) {
401-
if( start < maxLen - 10 )
402-
windowStart = 0
403-
else if( end > lineLen - 10 )
404-
windowStart = lineLen - maxLen
405-
else
406-
windowStart = start - 30
407-
}
408-
409-
String line = fullLine.substring(windowStart, Math.min(lineLen, windowStart + maxLen))
410-
int adjStart = Math.max(0, start - windowStart)
411-
int adjEnd = Math.max(adjStart + 1, Math.min(end - windowStart, line.length()))
412-
413-
// Line number
414-
term.fg(Ansi.Color.BLUE).a(String.format("%3d | ", i)).reset()
415-
416-
if( i == startLine ) {
417-
// Print line with range highlighted
418-
term.a(Ansi.Attribute.INTENSITY_FAINT).a(line.substring(0, adjStart)).reset()
419-
term.fg(color).a(line.substring(adjStart, adjEnd)).reset()
420-
term.a(Ansi.Attribute.INTENSITY_FAINT).a(line.substring(adjEnd)).reset().newline()
421-
422-
// Print carets underneath the range
423-
String marker = ' ' * adjStart
424-
String carets = '^' * Math.max(1, adjEnd - adjStart)
425-
term.a(" | ")
426-
.fg(color).bold().a(marker + carets).reset().newline()
427-
}
428-
else {
429-
term.a(Ansi.Attribute.INTENSITY_FAINT).a(line).reset().newline()
430-
}
431-
}
432-
433-
return term
434-
}
435-
436-
@Memoized
437-
private List<String> getSourceText(SourceUnit source) {
438-
return source.getSource().getReader().readLines()
439-
}
440-
441-
@Override
442-
void afterErrors() {
443-
// print extra newline since next file status will chomp back one
444-
term.fg(Ansi.Color.DEFAULT).newline()
445-
AnsiConsole.out.print(term)
446-
AnsiConsole.out.flush()
447-
}
448-
449-
@Override
450-
void beforeFormat(File file) {
451-
final line = ansi()
452-
.cursorUp(1).eraseLine()
453-
.a(Ansi.Attribute.INTENSITY_FAINT).a("Formatting: ${file}")
454-
.reset().newline().toString()
455-
AnsiConsole.out.print(line)
456-
AnsiConsole.out.flush()
457-
}
458-
459-
@Override
460-
void afterAll(ErrorSummary summary) {
461-
final term = ansi()
462-
term.cursorUp(1).eraseLine().cursorUp(1).eraseLine()
463-
// print extra newline if no code is being shown
464-
if( mode == 'concise' )
465-
term.newline()
466-
term.bold().a("Nextflow linting complete!").reset().newline()
467-
if( summary.filesWithErrors > 0 ) {
468-
term.fg(Ansi.Color.RED).a("${summary.filesWithErrors} file${summary.filesWithErrors==1 ? '' : 's'} had ${summary.errors} error${summary.errors==1 ? '' : 's'}").newline()
469-
}
470-
if( summary.filesWithoutErrors > 0 ) {
471-
term.fg(Ansi.Color.GREEN).a("${summary.filesWithoutErrors} file${summary.filesWithoutErrors==1 ? '' : 's'} had no errors")
472-
if( summary.filesFormatted > 0 )
473-
term.fg(Ansi.Color.BLUE).a(" (${summary.filesFormatted} formatted)")
474-
term.newline()
475-
}
476-
if( summary.filesWithErrors == 0 && summary.filesWithoutErrors == 0 ) {
477-
term.a(" No files found to process").newline()
478-
}
479-
AnsiConsole.out.print(term)
480-
AnsiConsole.out.flush()
481-
}
482-
483-
private static record Range(
484-
int startLine,
485-
int startColumn,
486-
int endLine,
487-
int endColumn
488-
) {
489-
public static Range of(SyntaxException error) {
490-
return new Range(
491-
error.getStartLine(),
492-
error.getStartColumn(),
493-
error.getEndLine(),
494-
error.getEndColumn(),
495-
)
496-
}
497-
498-
public static Range of(WarningMessage warning) {
499-
final token = warning.getContext().getRoot()
500-
return new Range(
501-
token.getStartLine(),
502-
token.getStartColumn(),
503-
token.getStartLine(),
504-
token.getStartColumn() + token.getText().length(),
505-
)
506-
}
507-
}
508-
}
509-
510-
511269
@CompileStatic
512270
class JsonErrorListener implements ErrorListener {
513271

modules/nextflow/src/main/groovy/nextflow/cli/CmdPull.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import nextflow.scm.AssetManager
3030
@Slf4j
3131
@CompileStatic
3232
@Parameters(commandDescription = "Download or update a project")
33-
class CmdPull extends CmdBase implements HubOptions {
33+
class CmdPull extends CmdBase implements HubAware {
3434

3535
static final public NAME = 'pull'
3636

@@ -74,7 +74,7 @@ class CmdPull extends CmdBase implements HubOptions {
7474

7575
list.each {
7676
log.info "Checking $it ..."
77-
def manager = new AssetManager(it, this)
77+
def manager = new AssetManager(it, toHubOptions())
7878

7979
def result = manager.download(revision,deep)
8080
manager.updateModules()

modules/nextflow/src/main/groovy/nextflow/cli/CmdRun.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ import org.yaml.snakeyaml.Yaml
5858
@Slf4j
5959
@CompileStatic
6060
@Parameters(commandDescription = "Execute a pipeline project")
61-
class CmdRun extends CmdBase implements HubOptions {
61+
class CmdRun extends CmdBase implements HubAware {
6262

6363
static final public Pattern RUN_NAME_PATTERN = Pattern.compile(/^[a-z](?:[a-z\d]|[-_](?=[a-z\d])){0,79}$/, Pattern.CASE_INSENSITIVE)
6464

@@ -579,7 +579,7 @@ class CmdRun extends CmdBase implements HubOptions {
579579
/*
580580
* try to look for a pipeline in the repository
581581
*/
582-
def manager = new AssetManager(pipelineName, this)
582+
def manager = new AssetManager(pipelineName, toHubOptions())
583583
def repo = manager.getProject()
584584

585585
boolean checkForUpdate = true

modules/nextflow/src/main/groovy/nextflow/cli/HubOptions.groovy renamed to modules/nextflow/src/main/groovy/nextflow/cli/HubAware.groovy

Lines changed: 5 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -18,51 +18,22 @@ package nextflow.cli
1818

1919
import com.beust.jcommander.Parameter
2020
import groovy.transform.CompileStatic
21+
import nextflow.scm.HubOptions
2122
/**
22-
* Defines the command line parameters for command that need to interact with a pipeline service hub i.e. GitHub or BitBucket
23+
* Command line parameters for commands that interact with a git repository provider i.e. GitHub or BitBucket
2324
*
2425
* @author Paolo Di Tommaso <[email protected]>
2526
*/
26-
2727
@CompileStatic
28-
trait HubOptions {
28+
interface HubAware {
2929

3030
@Parameter(names=['-hub'], description = "Service hub where the project is hosted")
3131
String hubProvider
3232

3333
@Parameter(names='-user', description = 'Private repository user name')
3434
String hubUser
3535

36-
/**
37-
* Return the password provided on the command line or stop allowing the user to enter it on the console
38-
*
39-
* @return The password entered or {@code null} if no user has been entered
40-
*/
41-
String getHubPassword() {
42-
43-
if( !hubUser )
44-
return null
45-
46-
def p = hubUser.indexOf(':')
47-
if( p != -1 )
48-
return hubUser.substring(p+1)
49-
50-
def console = System.console()
51-
if( !console )
52-
return null
53-
54-
print "Enter your $hubProvider password: "
55-
char[] pwd = console.readPassword()
56-
new String(pwd)
36+
default HubOptions toHubOptions() {
37+
return new HubOptions(hubProvider, hubUser)
5738
}
58-
59-
String getHubUser() {
60-
if(!hubUser) {
61-
return hubUser
62-
}
63-
64-
def p = hubUser.indexOf(':')
65-
return p != -1 ? hubUser.substring(0,p) : hubUser
66-
}
67-
6839
}

0 commit comments

Comments
 (0)