Skip to content

Commit 920559f

Browse files
committed
fix bugs related to bactopia workflows
1 parent 5f46774 commit 920559f

File tree

8 files changed

+318
-35
lines changed

8 files changed

+318
-35
lines changed

.vscode/settings.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"java.compile.nullAnalysis.mode": "automatic",
3+
"cSpell.words": [
4+
"Appender",
5+
"bakta",
6+
"blastdb",
7+
"fastani",
8+
"fofn",
9+
"phix",
10+
"staphopia"
11+
]
12+
}

build.gradle

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
plugins {
2-
id 'io.nextflow.nextflow-plugin' version '0.0.1-alpha4'
2+
id 'io.nextflow.nextflow-plugin' version '1.0.0-beta.9'
33
}
44

55
dependencies {
@@ -20,11 +20,4 @@ nextflowPlugin {
2020
'bactopia.plugin.BactopiaExtension',
2121
'bactopia.plugin.BactopiaFactory'
2222
]
23-
24-
publishing {
25-
registry {
26-
url = 'https://nf-plugins-registry.dev-tower.net/api'
27-
authToken = project.findProperty('pluginRegistry.accessToken')
28-
}
29-
}
3023
}

src/main/groovy/bactopia/plugin/BactopiaExtension.groovy

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ import bactopia.plugin.BactopiaSchema
3434
import bactopia.plugin.nfschema.HelpMessageCreator
3535
import bactopia.plugin.nfschema.SummaryCreator
3636

37-
import static bactopia.plugin.inputs.BactopiaTools.collectInputs
37+
import static bactopia.plugin.inputs.Bactopia.collectBactopiaInputs
38+
import static bactopia.plugin.inputs.BactopiaTools.collectBactopiaToolInputs
3839
import static bactopia.plugin.BactopiaTemplate.dashedLine
3940
import static bactopia.plugin.BactopiaTemplate.getLogColors
4041
import static bactopia.plugin.BactopiaTemplate.getLogo
@@ -69,6 +70,21 @@ class BactopiaExtension extends PluginExtensionPoint {
6970
}
7071
}
7172

73+
//
74+
// Collect Bactopia inputs
75+
//
76+
@Function
77+
public Map bactopiaInputs(String runType) {
78+
def Map params = session.params
79+
def List samples = collectBactopiaInputs(params, runType)
80+
def Map logs = BactopiaLoggerFactory.captureAndClearLogs()
81+
return [
82+
hasErrors: logs.hasErrors,
83+
error: logs.error,
84+
logs: logs.logs,
85+
samples: samples
86+
]
87+
}
7288

7389
//
7490
// Collect Bactopia Tool inputs
@@ -80,7 +96,7 @@ class BactopiaExtension extends PluginExtensionPoint {
8096
String includeFile,
8197
String excludeFile
8298
) {
83-
def List samples = collectInputs(bactopiaDir, extension, includeFile, excludeFile)
99+
def List samples = collectBactopiaToolInputs(bactopiaDir, extension, includeFile, excludeFile)
84100
def Map logs = BactopiaLoggerFactory.captureAndClearLogs()
85101
return [
86102
hasErrors: logs.hasErrors,
@@ -205,13 +221,13 @@ class BactopiaExtension extends PluginExtensionPoint {
205221
Boolean isBactopiaTool = false
206222
) {
207223
def BactopiaSchema validator = new BactopiaSchema(config)
208-
validator.validateParameters(
224+
def String result = validator.validateParameters(
209225
options,
210226
session.params,
211227
session.baseDir.toString(),
212228
isBactopiaTool
213229
)
214-
return BactopiaLoggerFactory.captureAndClearLogs()
230+
return BactopiaLoggerFactory.captureAndClearLogs(result)
215231
}
216232

217233
/*
@@ -220,7 +236,8 @@ class BactopiaExtension extends PluginExtensionPoint {
220236
@Function
221237
String getCapturedLogs(Boolean withColors = true) {
222238
// Get all global logs directly from the factory
223-
return BactopiaLoggerFactory.captureAndClearLogs(withColors)
239+
def Map result = BactopiaLoggerFactory.captureAndClearLogs("", withColors)
240+
return result.logs
224241
}
225242

226243
/*

src/main/groovy/bactopia/plugin/BactopiaLogger.groovy

Whitespace-only changes.

src/main/groovy/bactopia/plugin/BactopiaLoggerFactory.groovy

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,12 @@ class BactopiaLoggerFactory {
180180
/**
181181
* Get the logs for using getCapturedLogsAsString and clear them
182182
*/
183-
static Map captureAndClearLogs(Boolean withColors = true) {
183+
static Map captureAndClearLogs(String data = "", Boolean withColors = true) {
184184
Boolean hasErrors = hasErrors()
185185
String logs = getCapturedLogsAsString(withColors)
186186
clearLogs()
187187
return [
188+
data: data,
188189
logs: logs,
189190
hasErrors: hasErrors,
190191
error: hasErrors ? "${colors.red}!! ERROR !!${colors.reset}\n\n" + logs : ""

src/main/groovy/bactopia/plugin/BactopiaSchema.groovy

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,15 @@ class BactopiaSchema {
283283
} else {
284284
missing_required << "--eggnog_db"
285285
}
286+
} else if (params.workflow.name == "fastani") {
287+
if (!params.fastani_pairwise && !(params.fastani_reference || params.accession || params.accessions || params.species)) {
288+
log.error("Either '--fastani_reference', '--accession', '--accessions', or '--species' is required unless using --fastani_pairwise")
289+
error += 1
290+
}
291+
292+
if (params.fastani_reference) {
293+
error += fileNotFound(params.fastani_reference, "fastani_reference")
294+
}
286295
} else if (params.workflow.name == "gamma") {
287296
if (params.gamma_db) {
288297
error += fileNotFound(params.gamma_db, "gamma_db")
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
package bactopia.plugin.inputs
2+
3+
import groovy.util.logging.Slf4j
4+
import java.nio.file.Path
5+
import java.nio.file.Files
6+
7+
import static bactopia.plugin.BactopiaUtils.fileExists
8+
import static bactopia.plugin.BactopiaTemplate.dashedLine
9+
10+
@Slf4j
11+
class Bactopia {
12+
//
13+
// Create input channel data structure based on runtype
14+
//
15+
public static List collectBactopiaInputs(Map params, String runtype) {
16+
if (runtype == "is_fofn") {
17+
return processFOFN(params)
18+
} else if (runtype == "is_accessions") {
19+
return processAccessions(params)
20+
} else if (runtype == "is_accession") {
21+
return processAccession(params)
22+
} else {
23+
def Map meta = [:]
24+
meta.id = params.sample
25+
meta.name = params.sample
26+
meta.runtype = runtype
27+
meta.genome_size = params.genome_size
28+
meta.species = params.species
29+
30+
if (runtype == "paired-end") {
31+
return [[meta, [params.r1], [params.r2], params.empty_extra]]
32+
} else if (runtype == "hybrid" || runtype == "short_polish") {
33+
return [[meta, [params.r1], [params.r2], params.ont]]
34+
} else if (runtype == "assembly") {
35+
return [[meta, [params.empty_r1], [params.empty_r2], params.assembly]]
36+
} else if (runtype == "ont") {
37+
return [[meta, [params.ont], [params.empty_r2], params.empty_extra]]
38+
} else {
39+
return [[meta, [params.se], [params.empty_r2], params.empty_extra]]
40+
}
41+
}
42+
43+
44+
}
45+
46+
//
47+
// Handle multiple FASTQ files for merging
48+
//
49+
public static List<String> handleMultipleFqs(String readSet) {
50+
def List<String> fqs = []
51+
def String[] reads = readSet.split(",")
52+
reads.each { fq ->
53+
fqs << fq
54+
}
55+
return fqs
56+
}
57+
58+
59+
//
60+
// Process FOFN file and determine input type for each row
61+
//
62+
public static List processFOFN(Map params) {
63+
def results = []
64+
def headers = null
65+
def isFirstLine = true
66+
67+
// Read the samples file (TSV format)
68+
new File(params.samples).splitEachLine('\t') { columns ->
69+
// Strip whitespace from each column
70+
columns = columns.collect { it.trim() }
71+
72+
if (isFirstLine) {
73+
headers = columns
74+
isFirstLine = false
75+
} else if (columns.size() > 0 && columns[0]) { // Skip empty lines
76+
// Create map from headers and values
77+
def line = [:]
78+
headers.eachWithIndex { header, index ->
79+
if (index < columns.size()) {
80+
line[header] = columns[index]
81+
}
82+
}
83+
84+
// Process and collect the result
85+
results << _processFOFNLine(line, params)
86+
}
87+
}
88+
89+
return results
90+
}
91+
92+
93+
//
94+
// Process a single FOFN line and determine input type
95+
//
96+
private static List _processFOFNLine(Map line, Map params) {
97+
/* Parse line and determine if single end or paired reads*/
98+
def Map meta = [:]
99+
meta.id = line.sample
100+
meta.name = params.sample
101+
meta.runtype = line.runtype
102+
103+
if (params.genome_size) {
104+
// user provided via --genome_size, use it
105+
meta.genome_size = params.genome_size
106+
} else {
107+
// use size available in FOFN
108+
meta.genome_size = line.genome_size
109+
}
110+
111+
if (params.species) {
112+
// user provided via --species, use it
113+
meta.species = params.species
114+
} else {
115+
// use species available in FOFN
116+
meta.species = line.species
117+
}
118+
119+
if (line.sample) {
120+
if (line.runtype == 'single-end' || line.runtype == 'ont') {
121+
return [meta, [line.r1], [params.empty_r2], params.empty_extra]
122+
} else if (line.runtype == 'paired-end') {
123+
return [meta, [line.r1], [line.r2], params.empty_extra]
124+
} else if (line.runtype == 'hybrid' || line.runtype == 'short_polish') {
125+
return [meta, [line.r1], [line.r2], line.extra]
126+
} else if (line.runtype == 'assembly') {
127+
return [meta, [params.empty_r1], [params.empty_r2], line.extra]
128+
} else if (line.runtype == 'merge-pe') {
129+
return [meta, handleMultipleFqs(line.r1), handleMultipleFqs(line.r2), params.empty_extra]
130+
} else if (line.runtype == 'hybrid-merge-pe' || line.runtype == 'short_polish-merge-pe') {
131+
return [meta, handleMultipleFqs(line.r1), handleMultipleFqs(line.r2), line.extra]
132+
} else if (line.runtype == 'merge-se') {
133+
return [meta, handleMultipleFqs(line.r1), [params.empty_r2], params.empty_extra]
134+
} else {
135+
log.error(
136+
"Invalid runtype ${line.runtype} found, please correct to continue. " +
137+
"Expected: single-end, paired-end, hybrid, short_polish, merge-pe, hybrid-merge-pe, short_polish-merge-pe, merge-se, or assembly"
138+
)
139+
}
140+
} else {
141+
log.error("Sample name cannot be null: ${line}")
142+
}
143+
}
144+
145+
146+
//
147+
// Process accessions from CSV file
148+
//
149+
public static List processAccessions(Map params) {
150+
def results = []
151+
def headers = null
152+
def isFirstLine = true
153+
154+
// Read the accessions file (TSV format)
155+
new File(params.accessions).splitEachLine('\t') { columns ->
156+
// Strip whitespace from each column
157+
columns = columns.collect { it.trim() }
158+
159+
if (isFirstLine) {
160+
headers = columns
161+
isFirstLine = false
162+
} else if (columns.size() > 0 && columns[0]) { // Skip empty lines
163+
// Create map from headers and values
164+
def row = [:]
165+
headers.eachWithIndex { header, index ->
166+
if (index < columns.size()) {
167+
row[header] = columns[index]
168+
}
169+
}
170+
171+
// Process and collect the result
172+
results << _processAccessionsLine(row, params)
173+
}
174+
}
175+
176+
return results
177+
}
178+
179+
180+
//
181+
// Process accessions from FOFN
182+
//
183+
public static List _processAccessionsLine(Map line, Map params) {
184+
/* Parse line and determine if single end or paired reads*/
185+
def Map meta = [:]
186+
187+
if (line.accession.startsWith('GCF') || line.accession.startsWith('GCA')) {
188+
meta.id = line.accession.split(/\./)[0]
189+
meta.name = params.sample
190+
meta.runtype = "assembly_accession"
191+
meta.genome_size = params.genome_size
192+
meta.species = params.species
193+
return [meta, [params.empty_r1], [params.empty_r2], params.empty_extra]
194+
} else if (line.accession.startsWith('DRX') || line.accession.startsWith('ERX') || line.accession.startsWith('SRX')) {
195+
meta.id = line.accession
196+
meta.name = params.sample
197+
meta.runtype = line.runtype == 'ont' ? "sra_accession_ont" : "sra_accession"
198+
199+
// if genome_size is provided, use it, otherwise use the genome_size from the FOFN
200+
meta.genome_size = params.genome_size ? params.genome_size : line.genome_size
201+
202+
// if species is provided, use it, otherwise use the species from the FOFN
203+
meta.species = params.species ? params.species : line.species
204+
return [meta, [params.empty_r1], [params.empty_r2], params.empty_extra]
205+
} else {
206+
log.error(
207+
"Invalid accession: ${line.accession} is not an accepted accession type. Accessions must " +
208+
"be Assembly (GCF_*, GCA*) or Experiment (DRX*, ERX*, SRX*) accessions. Please correct to " +
209+
"continue.\n\nYou can use 'bactopia search' to convert BioProject, BioSample, or Run " +
210+
"accessions into an Experiment accession."
211+
)
212+
}
213+
}
214+
215+
216+
//
217+
// Process single accession
218+
//
219+
public static List processAccession(Map params) {
220+
String accession = params.accession
221+
def Map meta = [:]
222+
meta.genome_size = params.genome_size
223+
meta.species = params.species
224+
225+
if (accession.length() > 0) {
226+
if (accession.startsWith('GCF') || accession.startsWith('GCA')) {
227+
meta.id = accession.split(/\./)[0]
228+
meta.name = params.sample
229+
meta.runtype = "assembly_accession"
230+
} else if (accession.startsWith('DRX') || accession.startsWith('ERX') || accession.startsWith('SRX')) {
231+
meta.id = accession
232+
meta.name = params.sample
233+
meta.runtype = params.ont ? "sra_accession_ont" : "sra_accession"
234+
} else {
235+
log.error(
236+
"Invalid accession: ${accession} is not an accepted accession type. Accessions must be " +
237+
"Assembly (GCF_*, GCA*) or Experiment (DRX*, ERX*, SRX*) accessions. Please correct to " +
238+
"continue.\n\nYou can use 'bactopia search' to convert BioProject, BioSample, or Run " +
239+
"accessions into an Experiment accession."
240+
)
241+
}
242+
return [[meta, [params.empty_r1], [params.empty_r2], params.empty_extra]]
243+
}
244+
log.error("Accession cannot be empty, please provide a valid accession to continue.")
245+
}
246+
}

0 commit comments

Comments
 (0)