Skip to content

Commit 56f0f00

Browse files
authored
Add listDirectory() to Path type and deprecate listFiles() (#6581)
Signed-off-by: Ben Sherman <[email protected]>
1 parent 55f6ff5 commit 56f0f00

File tree

5 files changed

+76
-90
lines changed

5 files changed

+76
-90
lines changed

docs/reference/stdlib-namespaces.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ The global namespace contains globally available constants and functions.
8383
This function returns a collection if the glob pattern yields zero or multiple files. Use `files()` to get a collection of files.
8484
:::
8585

86-
`files( filePattern: String, [options] ) -> List<Path>`
86+
`files( filePattern: String, [options] ) -> Iterable<Path>`
8787
: Get a collection of files from a file name or glob pattern. Supports the same options as `file()`.
8888
: See also: {ref}`channel.fromPath <channel-path>`.
8989

docs/reference/stdlib-types.md

Lines changed: 31 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -509,30 +509,32 @@ The following operations are supported for paths:
509509
`<< : (Path, String)`
510510
: Appends text to a file without replacing existing content. Equivalent to `append()`.
511511

512-
<h3>Getting attributes</h3>
512+
<h3>Filesystem attributes</h3>
513513

514-
The following methods are useful for getting attributes of a path:
514+
The following properties are available:
515515

516-
`exists() -> Boolean`
517-
: Returns `true` if the path exists.
516+
`baseName: String`
517+
: The path name without its extension, e.g. `/some/path/file.tar.gz` -> `file.tar`.
518518

519-
`getBaseName() -> String`
520-
: Gets the path name without its extension, e.g. `/some/path/file.tar.gz` -> `file.tar`.
519+
`extension: String`
520+
: The path extension, e.g. `/some/path/file.txt` -> `txt`.
521521

522-
`getExtension() -> String`
523-
: Gets the path extension, e.g. `/some/path/file.txt` -> `txt`.
522+
`name: String`
523+
: The path name, e.g. `/some/path/file.txt` -> `file.txt`.
524524

525-
`getName() -> String`
526-
: Gets the path name, e.g. `/some/path/file.txt` -> `file.txt`.
525+
`parent: Path`
526+
: The path parent path, e.g. `/some/path/file.txt` -> `/some/path`.
527527

528-
`getSimpleName() -> String`
529-
: Gets the path name without any extension, e.g. `/some/path/file.tar.gz` -> `file`.
528+
`scheme: String`
529+
: The path URI scheme, e.g. `s3://some-bucket/hello.txt` -> `s3`.
530530

531-
`getParent() -> Path`
532-
: Gets the path parent path, e.g. `/some/path/file.txt` -> `/some/path`.
531+
`simpleName: String`
532+
: The path name without any extension, e.g. `/some/path/file.tar.gz` -> `file`.
533533

534-
`getScheme() -> String`
535-
: Gets the path URI scheme, e.g. `s3://some-bucket/hello.txt` -> `s3`.
534+
The following methods are available for getting filesystem attributes:
535+
536+
`exists() -> Boolean`
537+
: Returns `true` if the path exists.
536538

537539
`isDirectory() -> Boolean`
538540
: Returns `true` if the path is a directory.
@@ -578,32 +580,17 @@ The following methods are useful for getting attributes of a path:
578580

579581
The following methods are available for reading files:
580582

581-
`eachByte( action: (byte) -> () )`
582-
: Iterates over the file, applying the specified closure to each byte.
583-
584583
`eachLine( action: (String) -> () )`
585584
: Iterates over the file, applying the specified closure to each line.
586585

587-
`getBytes() -> byte[]`
588-
: Returns the file content as a byte array.
589-
590586
`getText() -> String`
591587
: Returns the file content as a string.
592588

593-
`newInputStream() -> InputStream`
594-
: Returns an [InputStream](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/InputStream.html) object to read a binary file.
595-
596-
`newReader() -> Reader`
597-
: Returns a [Reader](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/Reader.html) object to read a text file.
598-
599589
`readLines() -> List<String>`
600590
: Reads the file line by line and returns the content as a list of strings.
601591

602-
`withInputStream( action: (InputStream) -> () )`
603-
: Opens a file for reading and lets you access it with an [InputStream](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/InputStream.html) object.
604-
605-
`withReader( action: (Reader) -> () )`
606-
: Opens a file for reading and lets you access it with a [Reader](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/Reader.html) object.
592+
`withReader( action: (BufferedReader) -> () )`
593+
: Invokes the given closure with a [BufferedReader](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/BufferedReader.html), which can be used to read the file one line at a time using the `readLine()` method.
607594

608595
<h3>Writing</h3>
609596

@@ -612,30 +599,9 @@ The following methods are available for writing to files:
612599
`append( text: String )`
613600
: Appends text to a file without replacing existing content.
614601

615-
`newOutputStream() -> OutputStream`
616-
: Creates an [OutputStream](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/OutputStream.html) object that allows you to write binary data to a file.
617-
618-
`newPrintWriter() -> PrintWriter`
619-
: Creates a [PrintWriter](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/PrintWriter.html) object that allows you to write formatted text to a file.
620-
621-
`newWriter() -> Writer`
622-
: Creates a [Writer](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/Writer.html) object that allows you to save text data to a file.
623-
624-
`setBytes( bytes: byte[] )`
625-
: Writes a byte array to a file. Equivalent to setting the `bytes` property.
626-
627602
`setText( text: String )`
628603
: Writes text to a file. Equivalent to setting the `text` property.
629604

630-
`withOutputStream( action: (OutputStream) -> () )`
631-
: Applies the specified closure to an [OutputStream](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/OutputStream.html) object, closing it when finished.
632-
633-
`withPrintWriter( action: (PrintWriter) -> () )`
634-
: Applies the specified closure to a [PrintWriter](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/PrintWriter.html) object, closing it when finished.
635-
636-
`withWriter( action: (Writer) -> () )`
637-
: Applies the specified closure to a [Writer](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/Writer.html) object, closing it when finished.
638-
639605
`write( text: String )`
640606
: Writes a string to a file, replacing any existing content.
641607

@@ -691,12 +657,6 @@ The following methods are available for manipulating files and directories in a
691657
`getPermissions() -> String`
692658
: Returns a file's permissions using the [symbolic notation](http://en.wikipedia.org/wiki/File_system_permissions#Symbolic_notation), e.g. `'rw-rw-r--'`.
693659

694-
`list() -> List<String>`
695-
: Returns the first-level elements (files and directories) of a directory as a list of strings.
696-
697-
`listFiles() -> List<Path>`
698-
: Returns the first-level elements (files and directories) of a directory as a list of Paths.
699-
700660
`mkdir() -> Boolean`
701661
: Creates a directory at the given path, returning `true` if the directory is created successfully, and `false` otherwise:
702662

@@ -759,24 +719,23 @@ The following methods are available for manipulating files and directories in a
759719

760720
The following methods are available for listing and traversing directories:
761721

762-
`eachDir( action: (Path) -> () )`
763-
: Iterates through first-level directories only.
764-
765-
`eachDirMatch( nameFilter: String, action: (Path) -> () )`
766-
: Iterates through directories whose names match the given filter.
767-
768-
`eachDirRecurse( action: (Path) -> () )`
769-
: Iterates through directories depth-first (regular files are ignored).
770-
771722
`eachFile( action: (Path) -> () )`
772723
: Iterates through first-level files and directories.
773724

774-
`eachFileMatch( nameFilter: String, action: (Path) -> () )`
775-
: Iterates through files and directories whose names match the given filter.
776-
777725
`eachFileRecurse( action: (Path) -> () )`
778726
: Iterates through files and directories depth-first.
779727

728+
`listDirectory() -> Iterable<Path>`
729+
: :::{versionadded} 26.04.0
730+
:::
731+
: Returns the first-level elements (files and directories) in a directory.
732+
733+
`listFiles() -> Iterable<Path>`
734+
: :::{deprecated}
735+
Use `listDirectory()` instead.
736+
:::
737+
: Returns the first-level elements (files and directories) in a directory.
738+
780739
<h3>Splitting files</h3>
781740

782741
The following methods are available for splitting and counting the records in files:

modules/nextflow/src/main/groovy/nextflow/processor/TaskPath.groovy

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,11 +334,14 @@ final class TaskPath implements Path, PathEscapeAware {
334334
FilesEx.or(getFileName(),other)
335335
}
336336

337-
338337
String getPermissions() {
339338
FilesEx.getPermissions(target)
340339
}
341340

341+
Collection<Path> listDirectory() {
342+
FilesEx.listDirectory(target)
343+
}
344+
342345
boolean setPermissions( String permissions ) {
343346
throw new UnsupportedOperationException()
344347
}

modules/nextflow/src/test/groovy/nextflow/processor/TaskPathTest.groovy

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ class TaskPathTest extends Specification {
8787

8888
cleanup:
8989
folder.deleteDir()
90-
9190
}
9291

9392
def 'should validate equals method' () {
@@ -111,10 +110,8 @@ class TaskPathTest extends Specification {
111110
new TaskPath(p1, 'foo.txt').equals(t2)
112111

113112
!t1.equals(t3)
114-
115113
}
116114

117-
118115
def 'should validate operator equality' () {
119116
given:
120117
def p1 = Paths.get('/foo/bar/baz.txt')
@@ -138,7 +135,6 @@ class TaskPathTest extends Specification {
138135

139136
expect:
140137
new TaskPath(Paths.get('/foo')).size() == 0
141-
142138
}
143139

144140
@Ignore
@@ -159,10 +155,8 @@ class TaskPathTest extends Specification {
159155

160156
cleanup:
161157
folder.deleteDir()
162-
163158
}
164159

165-
166160
def 'should serialised task path' () {
167161

168162
given:
@@ -174,7 +168,22 @@ class TaskPathTest extends Specification {
174168
copy.equals(p)
175169
}
176170

177-
}
171+
def 'should support directory listing' () {
178172

173+
given:
174+
def folder = Files.createTempDirectory('test')
175+
folder.resolve('hello.txt').text = 'Hello world'
179176

177+
when:
178+
def path = new TaskPath(folder, 'test')
179+
def result = path.listDirectory()
180+
181+
then:
182+
result.size() == 1
183+
result[0].name == 'hello.txt'
184+
185+
cleanup:
186+
folder.deleteDir()
187+
}
180188

189+
}

modules/nf-commons/src/main/nextflow/extension/FilesEx.groovy

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,7 @@ class FilesEx {
626626
* @return A list of strings or {@code null} if the path is not a folder
627627
*/
628628
static String[] list(Path self) {
629-
listFiles(self).collect { getName(it) } as String[]
629+
return listFiles0(self).collect { getName(it) } as String[]
630630
}
631631

632632
/**
@@ -637,33 +637,48 @@ class FilesEx {
637637
* @return A list of {@code Path} or {@code null} if the path is not a folder
638638
*/
639639
static Path[] listFiles(Path self, @ClosureParams(value = FromString.class, options = ["java.nio.file.Path", "java.nio.file.Path,java.nio.file.attribute.BasicFileAttributes"]) Closure<Boolean> filter=null) {
640+
return listFiles0(self, filter) as Path[]
641+
}
642+
643+
/**
644+
* List the files in a directory.
645+
*
646+
* This method is preferred over `listFiles()` in the Nextflow language
647+
* because it returns a Collection<Path> instead of a Path[].
648+
*
649+
* @param self
650+
*/
651+
static Collection<Path> listDirectory(Path self) {
652+
return listFiles0(self)
653+
}
654+
655+
private static Collection<Path> listFiles0(Path self, Closure<Boolean> filter=null) {
640656

641657
if( !self.isDirectory() )
642658
return null
643659

644-
def result = []
660+
final result = []
661+
645662
Files.walkFileTree(self, new SimpleFileVisitor<Path>() {
646663

647664
FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
648-
if( filter==null || invokeFilter(filter,file,attrs) )
649-
result.add( file )
665+
if( filter == null || invokeFilter(filter, file, attrs) )
666+
result.add(file)
650667
FileVisitResult.CONTINUE
651668
}
652669

653670
FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
654-
if( self == dir )
671+
if( self == dir ) {
655672
FileVisitResult.CONTINUE
656-
673+
}
657674
else {
658675
result.add(dir)
659676
FileVisitResult.SKIP_SUBTREE
660677
}
661678
}
662-
663679
} )
664680

665-
return result as Path[]
666-
681+
return result
667682
}
668683

669684
@CompileStatic

0 commit comments

Comments
 (0)