losizm / little-io   9.0.0

Apache License 2.0 Website GitHub

The Scala library that provides extension methods for java.io and java.nio.

Scala versions: 3.x

little-io

Maven Central

The Scala library that provides extension methods for java.io and java.nio.

Getting Started

To get started, add little-io as a dependency to your project:

libraryDependencies += "com.github.losizm" %% "little-io" % "9.0.0"

NOTE: Starting with 5.0, little-io is written for Scala 3. See previous releases for compatibility with Scala 2.12 and Scala 2.13.

A Taste of little-io

Here's a taste of what little-io offers.

Getting and Setting File Content

If you have a File, you can easily get and set its content.

import little.io.{ FileMethods, IoStringMethods }

val file = "greeting.txt".toFile

// Set file content to string
file.setText("Hello, world!")

// Get file content as string
println(file.getText()) // Hello, world!

And, if you need to append content, here's how you would do that:

import little.io.{ FileMethods, IoStringMethods }

// Append text and return reference to file
val file = "greeting.txt".toFile << "Hello, world!"

println(file.getText())

The same applies to java.nio.file.Path.

import little.io.{ PathMethods, IoStringMethods }

val path = "greeting.txt".toPath << "Hello, world!"
println(path.getText())

And, if you prefer working with bytes, there are extension methods for those too.

import little.io.{ FileMethods, IoStringMethods }

val file = "greeting.txt".toFile
val data = "Hello, world!".getBytes("utf-8")

file.setBytes(data)
file << "\n" << data.reverse

println(String(file.getBytes(), "utf-8"))

Reading and Writing File Content

If you have a File or Path, you can open an OutputStream or Writer to write its content, and open an InputStream or Reader to read its content, all with automatic resource management.

import little.io.{ FileMethods, IoStringMethods, WriterMethods }

val file = "numbers.txt".toFile

// Manages writer resource
file.withWriter { out =>
  out write "One\n"
  out writeLine "Two"
  out << "Three\n"
}

// Manages reader resource
file.withReader { in =>
  println(in.readLine())
  println(in.readLine())
  println(in.readLine())
}

If you'll be reading file content line by line, there's an even simpler way.

import little.io.{ FileMethods, IoStringMethods }

// Open file, print each line, and close file
"numbers.txt".toFile.forEachLine(println)

Filtering, Mapping, and Folding Lines in File

There are other methods for processing files line by line. You can filter and map the lines in a file to build a collection, or you can fold them to a single value.

import little.io.*

val file = "test.txt".toFile << "abc\n123\nxyz\n789"

// Filter lines containing digits only
val filtered = file.filterLines(_.matches("\\d+"))
assert { filtered == Seq("123", "789") }

// Map lines to uppercase
val mapped = file.mapLines(_.toUpperCase)
assert { mapped == Seq("ABC", "123", "XYZ", "789") }

// Fold lines to single, concatenated string
val folded = file.foldLines("") { _ + _ }
assert(folded == "abc123xyz789")

Mapping and Folding Files in Directory

If you have a File or Path to a directory, you can map the files in the directory. Or you can fold them to generate a single value.

import little.io.{ FileMethods, IoStringMethods }

val home = sys.props("user.home").toFile

// Get file names
val fileNames = home.mapFiles(_.getName)

// Calculate disk usage
val totalSize = home.foldFiles(0L) { _ + _.length }

Traversing File Directories

A feature available since Java 7 is builtin library support for walking a file tree starting at a particular Path. This is carried out by specifying a FileVisitor as a callback to handle a set of events.

little-io makes this feature a little more Scala-like. You make a method call to a Path extension method, passing in a PartialFunction to handle events of interest.

import java.nio.file.FileVisitResult
import little.io.{ FileVisitEvent, IoStringMethods, PathMethods }
import FileVisitEvent.{ PreVisitDirectory, VisitFile }

// Traverse directories starting at 'src'
"./src".toPath.withVisitor {
  // Go deeper if not 'test' directory
  case PreVisitDirectory(dir, attrs) =>
    if dir.getFileName.toString == "test" then
      FileVisitResult.SKIP_SUBTREE
    else
      println(s"Listing files in ${dir.getFileName} directory...")
      FileVisitResult.CONTINUE

  // Print file name and size
  case VisitFile(file, attrs) =>
    println(s"${file.getFileName} is ${attrs.size} bytes.")
    FileVisitResult.CONTINUE
}

Watching File Events

Another feature available since Java 7 is WatchService, which allows you to monitor a directory for changes. You can poll the service to check for new, modified, and deleted files.

With pure Java, you create a Path to a directory, create a WatchService using a reference to the path's FileSystem, and then register the path with the service while specifying the kinds of WatchEvent you wish to track. A WatchKey is returned when the path is registered, and you use this key to poll for file events.

With little-io, it's straight to the point.

import java.nio.file.StandardWatchEventKinds.ENTRY_CREATE
import little.io.{ PathMethods, IoStringMethods }

val dir = "/tmp".toPath

// Print message when file is created
val handle = dir.withWatcher(ENTRY_CREATE) { evt =>
  println(s"${evt.context} was created.")
}

try
  Thread.sleep(60 * 1000)
finally
  handle.close() // Close handle when finished

File Compression

The Compressor object provides various compression methods. For example, you can compress a file using gzip.

import java.io.File
import little.io.BufferSize
import little.io.Compressor.gzip

// Specify buffer size for I/O operations
given BufferSize = BufferSize(1024)

// Specify input and output files
val in  = File("/path/to/file.txt")
val out = File("/path/to/file.txt.gz")

// Gzip input to output
gzip(in, out)

And decompress it with gunzip.

import java.io.File
import little.io.BufferSize
import little.io.Compressor.gunzip

// Specify buffer size for I/O operations
given BufferSize = BufferSize(1024)

// Specify input and output files
val in  = File("/path/to/file.txt.gz")
val out = File("/path/to/file.txt")

// Gunzip input to output
gunzip(in, out)

Or, to build an archive, you can zip a directory.

import java.io.{ File, FileFilter }
import little.io.Compressor.zip

// Specify input directory and output file
val in  = File("./src")
val out = File("/tmp/src.zip")

// Set file filter
given FileFilter with
  def accept(f: File) =
    f.isDirectory || f.getName.endsWith(".scala")

// Zip .scala files in all directories
zip(in, out)

And extract the files to a directory using unzip.

import java.io.File
import little.io.AcceptAnyFile
import little.io.Compressor.unzip

// Specify input file and output directory
val in  = File("/tmp/src.zip")
val out = File("/tmp/src")

// Unzip all files
unzip(in, out)(using AcceptAnyFile)

API Documentation

See scaladoc for additional details.

License

little-io is licensed under the Apache License, Version 2. See LICENSE for more information.