This is a proposal to add better-files to the Scala Platform.

Introduction

better-files is a dependency-free pragmatic thin Scala wrapper around Java NIO. It is an alternative to the IO interface present in the Scala standard library and defines idiomatic helpers to handle IO in a sane and elegant way. It is licensed under the MIT license.

Features

Java interoperability

Easy to use

Efficient NIO wrappers

Zero external dependencies (aside from JVM-bundled dependencies)

Merits

better-files has got a lot of traffic lately:

Gave couple of well received talks on it at Scala Days and Scala by the Bay in 2016

2000 Maven downloads. Met a lot of people who use the library at their job/personal use at conferences.

700+ stars on Github

Fairly small and self-contained: <500 LOC and no external dependencies

Search on sbt files in Github has >200 hits

Example usage

better-files allow users to work with IO in different styles. Here, I only describe the most common. For more information on the supported use cases and explanations on the syntax, have a look at the README.

File instantiation

import better.files._ import java.io.{File => JFile} val f = File("/User/johndoe/Documents") // using constructor val f1: File = file"/User/johndoe/Documents" // using string interpolator val f2: File = "/User/johndoe/Documents".toFile // convert a string path to a file val f3: File = new JFile("/User/johndoe/Documents").toScala // convert a Java file to Scala val f4: File = root/"User"/"johndoe"/"Documents" // using root helper to start from root val f5: File = `~` / "Documents" // also equivalent to `home / "Documents"` val f6: File = "/User"/"johndoe"/"Documents" // using file separator DSL val f7: File = home/"Documents"/"presentations"/`..` // Use `..` to navigate up to parent

File read/write

val file = root/"tmp"/"test.txt" file.overwrite("hello") file.appendLine().append("world") assert(file.contentAsString == "hello

world")

Streams and codecs

Various ways to slurp a file without loading the contents into memory:

val bytes : Iterator[Byte] = file.bytes val chars : Iterator[Char] = file.chars val lines : Iterator[String] = file.lines val source : scala.io.BufferedSource = file.newBufferedSource // needs to be closed, unlike the above APIs which auto closes when iterator ends

Java interoperability

We can go from better-files wrappers to Java wrapper and the other way around at any time.

val file: File = tmp / "hello.txt" val javaFile : java.io.File = file.toJava val uri : java.net.uri = file.uri val reader : java.io.BufferedReader = file.newBufferedReader val outputstream : java.io.OutputStream = file.newOutputStream val writer : java.io.BufferedWriter = file.newBufferedWriter val inputstream : java.io.InputStream = file.newInputStream val path : java.nio.file.Path = file.path val fs : java.nio.file.FileSystem = file.fileSystem val channel : java.nio.channel.FileChannel = file.newFileChannel val ram : java.io.RandomAccessFile = file.newRandomAccess val fr : java.io.FileReader = file.newFileReader val fw : java.io.FileWriter = file.newFileWriter(append = true) val printer : java.io.PrintWriter = file.newPrintWriter

Better pattern matching

better-files defines extractor objects that help pattern matching on files and avoiding if-else expressions.

/** * @return true if file is a directory with no children or a file with no contents */ def isEmpty(file: File): Boolean = file match { case File.Type.SymbolicLink(to) => isEmpty(to) // this must be first case statement if you want to handle symlinks specially; else will follow link case File.Type.Directory(files) => files.isEmpty case File.Type.RegularFile(content) => content.isEmpty case _ => file.notExists // a file may not be one of the above e.g. UNIX pipes, sockets, devices etc } // or as extractors on LHS: val File.Type.Directory(researchDocs) = home/"Downloads"/"research"

File system operations

Utilities like ls, cp, rm, mv, ln, md5, diff, touch, cat are easy to use. See this.

Implementation

The implementation has no extra dependencies aside from the NIO module bundled into the JDK8.

Alternatives

There are other good libraries out there e.g. Apache Commons IO, Guava but they are in Java

In Scala world, only Li Haoyi’s ammonite-ops comes close.

This is the biggest: By no means, this is side-effect free purely functional library. There are purer idioms we can use (IOMonads) that better-files does not touch. better-files is simply a wrapper around Java NIO and happily does side-effects and throws Exceptions that Java throws. This may not be palatable to many Scala programmers.

Bibliography

Previous discussion: https://github.com/scala/slip/issues/19