@@ -24,6 +24,7 @@ import groovyx.gpars.dataflow.DataflowReadChannel
24
24
import nextflow.Global
25
25
import nextflow.Session
26
26
import nextflow.processor.PublishDir
27
+ import nextflow.util.CsvWriter
27
28
/**
28
29
* Publish files from a source channel.
29
30
*
@@ -37,13 +38,22 @@ class PublishOp {
37
38
38
39
private PublishDir publisher
39
40
41
+ private Path targetDir
42
+
43
+ private IndexOpts indexOpts
44
+
45
+ private List indexRecords = []
46
+
40
47
private volatile boolean complete
41
48
42
49
private Session getSession () { Global . session as Session }
43
50
44
51
PublishOp (DataflowReadChannel source , Map opts ) {
45
52
this . source = source
46
53
this . publisher = PublishDir . create(opts)
54
+ this . targetDir = opts. path as Path
55
+ if ( opts. index )
56
+ this . indexOpts = new IndexOpts (targetDir, opts. index as Map )
47
57
}
48
58
49
59
boolean getComplete () { complete }
@@ -64,13 +74,32 @@ class PublishOp {
64
74
final files = entry. value
65
75
publisher. apply(files, sourceDir)
66
76
}
77
+
78
+ if ( indexOpts ) {
79
+ final record = indexOpts. mapper != null ? indexOpts. mapper. call(value) : value
80
+ final normalized = normalizePaths(record)
81
+ log. trace " Normalized record for index file: ${ normalized} "
82
+ indexRecords << normalized
83
+ }
67
84
}
68
85
69
86
protected void onComplete (nope ) {
87
+ if ( indexOpts && indexRecords. size() > 0 ) {
88
+ log. trace " Saving records to index file: ${ indexRecords} "
89
+ new CsvWriter (header : indexOpts. header, sep : indexOpts. sep). apply(indexRecords, indexOpts. path)
90
+ session. notifyFilePublish(indexOpts. path)
91
+ }
92
+
70
93
log. trace " Publish operator complete"
71
94
this . complete = true
72
95
}
73
96
97
+ /**
98
+ * Extract files from a received value for publishing.
99
+ *
100
+ * @param result
101
+ * @param value
102
+ */
74
103
protected Map<Path ,Set<Path > > collectFiles (Map<Path ,Set<Path > > result , value ) {
75
104
if ( value instanceof Path ) {
76
105
final sourceDir = getTaskDir(value)
@@ -86,9 +115,47 @@ class PublishOp {
86
115
}
87
116
88
117
/**
89
- * Given a path try to infer the task directory to which the path below
90
- * ie. the directory starting with a workflow work dir and having at lest
91
- * two sub-directories eg work-dir/xx/yyyyyy/etc
118
+ * Normalize the paths in a record by converting
119
+ * work directory paths to publish paths.
120
+ *
121
+ * @param value
122
+ */
123
+ protected Object normalizePaths (value ) {
124
+ if ( value instanceof Path )
125
+ return List . of(normalizePath(value))
126
+
127
+ if ( value instanceof Collection ) {
128
+ return value. collect { el ->
129
+ if ( el instanceof Path )
130
+ return normalizePath(el)
131
+ if ( el instanceof Collection<Path > )
132
+ return normalizePaths(el)
133
+ return el
134
+ }
135
+ }
136
+
137
+ if ( value instanceof Map ) {
138
+ return value. collectEntries { k , v ->
139
+ if ( v instanceof Path )
140
+ return List . of(k, normalizePath(v))
141
+ if ( v instanceof Collection<Path > )
142
+ return List . of(k, normalizePaths(v))
143
+ return List . of(k, v)
144
+ }
145
+ }
146
+
147
+ throw new IllegalArgumentException (" Index file record must be a list, map, or file: ${ value} [${ value.class.simpleName} ]" )
148
+ }
149
+
150
+ private Path normalizePath (Path path ) {
151
+ final sourceDir = getTaskDir(path)
152
+ return targetDir. resolve(sourceDir. relativize(path))
153
+ }
154
+
155
+ /**
156
+ * Try to infer the parent task directory to which a path belongs. It
157
+ * should be a directory starting with a session work dir and having
158
+ * at lest two sub-directories, e.g. work/ab/cdef/etc
92
159
*
93
160
* @param path
94
161
*/
@@ -111,4 +178,22 @@ class PublishOp {
111
178
return null
112
179
}
113
180
181
+ static class IndexOpts {
182
+ Path path
183
+ Closure mapper
184
+ def /* boolean | List<String> */ header = false
185
+ String sep = ' ,'
186
+
187
+ IndexOpts (Path targetDir , Map opts ) {
188
+ this . path = targetDir. resolve(opts. path as String )
189
+
190
+ if ( opts. mapper )
191
+ this . mapper = opts. mapper as Closure
192
+ if ( opts. header != null )
193
+ this . header = opts. header
194
+ if ( opts. sep )
195
+ this . sep = opts. sep as String
196
+ }
197
+ }
198
+
114
199
}
0 commit comments