summaryrefslogtreecommitdiff
path: root/scalalib/src/test/resource/better-files/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'scalalib/src/test/resource/better-files/README.md')
-rw-r--r--scalalib/src/test/resource/better-files/README.md637
1 files changed, 637 insertions, 0 deletions
diff --git a/scalalib/src/test/resource/better-files/README.md b/scalalib/src/test/resource/better-files/README.md
new file mode 100644
index 00000000..9877c3bc
--- /dev/null
+++ b/scalalib/src/test/resource/better-files/README.md
@@ -0,0 +1,637 @@
+# better-files [![License][licenseImg]][licenseLink] [![CircleCI][circleCiImg]][circleCiLink] [![Codacy][codacyImg]][codacyLink]
+
+`better-files` is a [dependency-free](project/Dependencies.scala) *pragmatic* [thin Scala wrapper](core/src/main/scala/better/files/File.scala) around [Java NIO](https://docs.oracle.com/javase/tutorial/essential/io/fileio.html).
+
+## Talks [![Gitter][gitterImg]][gitterLink]
+ - [ScalaDays NYC 2016][scalaDaysNyc2016Event] ([slides][scalaDaysNyc2016Slides])
+
+ <a href="http://www.youtube.com/watch?feature=player_embedded&v=uaYKkpqs6CE" target="_blank">
+ <img src="site/tech_talk_preview.png" alt="ScalaDays NYC 2016: Introduction to better-files" width="480" height="360" border="10" />
+ </a>
+
+ - [ScalaDays Berlin 2016][scalaDaysBerlin2016Event] ([video][scalaDaysBerlin2016Video], [slides][scalaDaysBerlin2016Slides])
+ - [Scalæ by the Bay 2016][scalæByTheBay2016Event] ([video][scalæByTheBay2016Video], [slides][scalæByTheBay2016Slides])
+
+## Tutorial [![Scaladoc][scaladocImg]][scaladocLink]
+ 0. [Instantiation](#instantiation)
+ 0. [Simple I/O](#file-readwrite)
+ 0. [Streams](#streams)
+ 0. [Encodings](#encodings)
+ 0. [Java serialization utils](#java-serialization-utils)
+ 0. [Java compatibility](#java-interoperability)
+ 0. [Globbing](#globbing)
+ 0. [File system operations](#file-system-operations)
+ 0. [Temporary files](#temporary-files)
+ 0. [UNIX DSL](#unix-dsl)
+ 0. [File attributes](#file-attributes)
+ 0. [File comparison](#file-comparison)
+ 0. [Zip/Unzip](#zip-apis)
+ 0. [Automatic Resource Management](#lightweight-arm)
+ 0. [Scanner](#scanner)
+ 0. [File Monitoring](#file-monitoring)
+ 0. [Reactive File Watcher](#akka-file-watcher)
+
+## sbt [![UpdateImpact][updateImpactImg]][updateImpactLink]
+In your `build.sbt`, add this:
+```scala
+libraryDependencies += "com.github.pathikrit" %% "better-files" % version
+```
+To use the [Akka based file monitor](akka), also add this:
+```scala
+libraryDependencies ++= Seq(
+ "com.github.pathikrit" %% "better-files-akka" % version,
+ "com.typesafe.akka" %% "akka-actor" % "2.5.6"
+)
+```
+Latest `version`: [![Maven][mavenImg]][mavenLink] [![Scaladex][scaladexImg]][scaladexLink]
+
+Although this library is currently only actively developed for Scala 2.12 and 2.13,
+you can find reasonably recent versions of this library for Scala 2.10 and 2.11 [here](https://oss.sonatype.org/#nexus-search;quick~better-files).
+
+## Tests [![codecov][codecovImg]][codecovLink]
+* [FileSpec](core/src/test/scala/better/files/FileSpec.scala)
+* [FileWatcherSpec](akka/src/test/scala/better/files/FileWatcherSpec.scala)
+* [Benchmarks](benchmarks/)
+
+[licenseImg]: https://img.shields.io/github/license/pathikrit/better-files.svg
+[licenseImg2]: https://img.shields.io/:license-mit-blue.svg
+[licenseLink]: LICENSE
+
+[circleCiImg]: https://img.shields.io/circleci/project/pathikrit/better-files/master.svg
+[circleCiImg2]: https://circleci.com/gh/pathikrit/better-files/tree/master.svg
+[circleCiLink]: https://circleci.com/gh/pathikrit/better-files
+
+[codecovImg]: https://img.shields.io/codecov/c/github/pathikrit/better-files/master.svg
+[codecovImg2]: https://codecov.io/github/pathikrit/better-files/coverage.svg?branch=master
+[codecovLink]: http://codecov.io/github/pathikrit/better-files?branch=master
+
+[codacyImg]: https://img.shields.io/codacy/0e2aeb7949bc49e6802afcc43a7a1aa1.svg
+[codacyImg2]: https://api.codacy.com/project/badge/grade/0e2aeb7949bc49e6802afcc43a7a1aa1
+[codacyLink]: https://www.codacy.com/app/pathikrit/better-files/dashboard
+
+[mavenImg]: https://img.shields.io/maven-central/v/com.github.pathikrit/better-files_2.12.svg
+[mavenImg2]: https://maven-badges.herokuapp.com/maven-central/com.github.pathikrit/better-files_2.12/badge.svg
+[mavenLink]: http://search.maven.org/#search%7Cga%7C1%7Cbetter-files
+
+[gitterImg]: https://img.shields.io/gitter/room/pathikrit/better-files.svg
+[gitterImg2]: https://badges.gitter.im/Join%20Chat.svg
+[gitterLink]: https://gitter.im/pathikrit/better-files
+
+[scaladexImg]: https://index.scala-lang.org/pathikrit/better-files/better-files/latest.svg
+[scaladexLink]: https://index.scala-lang.org/pathikrit/better-files
+
+[scaladocImg]: https://www.javadoc.io/badge/com.github.pathikrit/better-files_2.12.svg?color=blue&label=scaladocs
+<!--[scaladocLink]: https://www.javadoc.io/page/com.github.pathikrit/better-files_2.12/latest/better/files/File.html-->
+[scaladocLink]: http://pathikrit.github.io/better-files/latest/api/better/files/File.html
+
+[updateImpactImg]: https://app.updateimpact.com/badge/704376701047672832/root.svg?config=compile
+[updateImpactLink]: https://app.updateimpact.com/latest/704376701047672832/root
+
+[scalaDaysNyc2016Event]: http://event.scaladays.org/scaladays-nyc-2016/#!#schedulePopupExtras-7664
+[scalaDaysNyc2016Video]: https://www.youtube.com/watch?v=uaYKkpqs6CE
+<!--[scalaDaysNyc2016VideoPreview]: http://img.youtube.com/vi/uaYKkpqs6CE/0.jpg-->
+[scalaDaysNyc2016VideoPreview]: site/tech_talk_preview.png
+[scalaDaysNyc2016Slides]: https://slides.com/pathikrit/better-files/
+
+[scalaDaysBerlin2016Event]: http://event.scaladays.org/scaladays-berlin-2016#!#schedulePopupExtras-7668
+[scalaDaysBerlin2016Video]: https://www.youtube.com/watch?v=m2YsD5cgnzI
+[scalaDaysBerlin2016Slides]: https://slides.com/pathikrit/better-files/
+
+[scalæByTheBay2016Event]: http://sched.co/7iUn
+[scalæByTheBay2016Video]: https://www.youtube.com/watch?v=bLiCE6NGjrk&t=251s
+[scalæByTheBay2016Slides]: https://slides.com/pathikrit/better-files/
+
+-------
+### Instantiation
+The following are all equivalent:
+```scala
+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 = "/User"/'johndoe/'Documents // same as above but using Symbols instead of Strings
+val f8: File = home/"Documents"/"presentations"/`..` // use `..` to navigate up to parent
+```
+
+**Note**: Rename the import if you think the usage of the class `File` may confuse your teammates:
+```scala
+import better.files.{File => ScalaFile, _}
+import java.io.File
+```
+I personally prefer renaming the Java crap instead:
+```scala
+import better.files._
+import java.io.{File => JFile}
+```
+
+### File Read/Write
+Dead simple I/O:
+```scala
+val file = root/"tmp"/"test.txt"
+file.overwrite("hello")
+file.appendLine().append("world")
+assert(file.contentAsString == "hello\nworld")
+```
+If you are someone who likes symbols, then the above code can also be written as:
+```scala
+import better.files.Dsl.SymbolicOperations
+
+file < "hello" // same as file.overwrite("hello")
+file << "world" // same as file.appendLines("world")
+assert(file! == "hello\nworld")
+```
+Or even, right-associatively:
+```scala
+import better.files.Dsl.SymbolicOperations
+
+"hello" `>:` file
+"world" >>: file
+val bytes: Array[Byte] = file.loadBytes
+```
+[Fluent Interface](https://en.wikipedia.org/wiki/Fluent_interface):
+```scala
+ (root/"tmp"/"diary.txt")
+ .createIfNotExists()
+ .appendLine()
+ .appendLines("My name is", "Inigo Montoya")
+ .moveToDirectory(home/"Documents")
+ .renameTo("princess_diary.txt")
+ .changeExtensionTo(".md")
+ .lines
+```
+
+### Streams
+Various ways to slurp a file without loading the contents into memory:
+ ```scala
+val bytes : Iterator[Byte] = file.bytes
+val chars : Iterator[Char] = file.chars
+val lines : Iterator[String] = file.lineIterator //file.lines loads all lines in memory
+```
+Note: The above APIs can be traversed at most once e.g. `file.bytes` is a `Iterator[Byte]` which only allows `TraversableOnce`.
+To traverse it multiple times without creating a new iterator instance, convert it into some other collection e.g. `file.bytes.toStream`
+
+You can write an `Iterator[Byte]` or an `Iterator[String]` back to a file:
+```scala
+file.writeBytes(bytes)
+file.printLines(lines)
+```
+
+### Encodings
+You can supply your own charset too for anything that does a read/write (it assumes `java.nio.charset.Charset.defaultCharset()` if you don't provide one):
+```scala
+val content: String = file.contentAsString // default charset
+
+// custom charset:
+import java.nio.charset.Charset
+file.contentAsString(charset = Charset.forName("US-ASCII"))
+
+//or simply using implicit conversion from Strings
+file.write("hello world")(charset = "US-ASCII")
+ ```
+
+Note: By default, `better-files` [correctly handles BOMs while decoding](core/src/main/scala/better/files/UnicodeCharset.scala).
+If you wish to have the [incorrect JDK behaviour](http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4508058),
+you would need to supply Java's UTF-8 charset e.g.:
+```scala
+file.contentAsString(charset = Charset.forName("UTF-8")) // Default incorrect JDK behaviour for UTF-8 (see: JDK-4508058)
+```
+
+If you also wish to write BOMs while encoding, you would need to supply it as:
+```scala
+file.write("hello world")(charset = UnicodeCharset("UTF-8", writeByteOrderMarkers = true))
+```
+
+### Java serialization utils
+Some common utils to serialize/deserialize using Java's serialization util
+```scala
+case class Person(name: String, age: Int)
+val person = new Person("Chris", 24)
+
+// Write
+file.newOutputStream.buffered.asObjectOutputStream.serialize(obj).flush()
+
+// Read
+val person2 = file.newInputStream.buffered.asObjectInputStream.readObject().asInstanceOf[Person]
+assert(person == person2)
+```
+
+The above can be simply written as:
+```scala
+val person2: Person = file.writeSerialized(person).readDeserialized[Person]
+assert(person == person2)
+```
+
+### Java interoperability
+You can always access the Java I/O classes:
+```scala
+val file: File = tmp / "hello.txt"
+val javaFile : java.io.File = file.toJava
+val uri : java.net.URI = file.uri
+val url : java.net.URL = file.url
+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
+```
+The library also adds some useful [implicits](http://pathikrit.github.io/better-files/latest/api/better/files/Implicits.html) to above classes e.g.:
+```scala
+file1.reader > file2.writer // pipes a reader to a writer
+System.in > file2.out // pipes an inputstream to an outputstream
+src.pipeTo(sink) // if you don't like symbols
+
+val bytes : Iterator[Byte] = inputstream.bytes
+val bis : BufferedInputStream = inputstream.buffered
+val bos : BufferedOutputStream = outputstream.buffered
+val reader : InputStreamReader = inputstream.reader
+val writer : OutputStreamWriter = outputstream.writer
+val printer : PrintWriter = outputstream.printWriter
+val br : BufferedReader = reader.buffered
+val bw : BufferedWriter = writer.buffered
+val mm : MappedByteBuffer = fileChannel.toMappedByteBuffer
+val str : String = inputstream.asString //Read a string from an InputStream
+```
+`better-files` also supports [certain conversions that are not supported out of the box by the JDK](https://stackoverflow.com/questions/62241/how-to-convert-a-reader-to-inputstream-and-a-writer-to-outputstream)
+
+[`tee`](http://stackoverflow.com/questions/7987395/) multiple outputstreams:
+```scala
+val s3 = s1 tee s2
+s3.printWriter.println(s"Hello world") // gets written to both s1 and s2
+```
+
+### Globbing
+No need to port [this](http://docs.oracle.com/javase/tutorial/essential/io/find.html) to Scala:
+```scala
+val dir = "src"/"test"
+val matches: Iterator[File] = dir.glob("*.{java,scala}")
+// above code is equivalent to:
+dir.listRecursively.filter(f => f.extension == Some(".java") || f.extension == Some(".scala"))
+```
+
+You can even use more advanced regex syntax instead of [glob syntax](http://docs.oracle.com/javase/tutorial/essential/io/fileOps.html#glob):
+```scala
+val matches = dir.globRegex("^\\w*$".r) //equivalent to dir.glob("^\\w*$")(syntax = File.PathMatcherSyntax.regex)
+```
+
+By default, glob syntax in `better-files` is [different from](https://github.com/pathikrit/better-files/issues/114)
+the default JDK glob behaviour since it always includes path. To use the default behaviour:
+```scala
+dir.glob("**/*.txt", includePath = false) // JDK default
+//OR
+dir.glob("*.txt", includePath = true) // better-files default
+```
+You can also extend the `File.PathMatcherSyntax` to create your own matchers.
+
+For custom cases:
+```scala
+dir.collectChildren(_.isSymbolicLink) // collect all symlinks in a directory
+```
+For simpler cases, you can always use `dir.list` or `dir.walk(maxDepth: Int)`
+
+### File system operations
+Utilities to `ls`, `cp`, `rm`, `mv`, `ln`, `md5`, `touch`, `cat` etc:
+```scala
+file.touch()
+file.delete() // unlike the Java API, also works on directories as expected (deletes children recursively)
+file.clear() // If directory, deletes all children; if file clears contents
+file.renameTo(newName: String)
+file.moveTo(destination)
+file.moveToDirectory(destination)
+file.copyTo(destination) // unlike the default API, also works on directories (copies recursively)
+file.copyToDirectory(destination)
+file.linkTo(destination) // ln destination file
+file.symbolicLinkTo(destination) // ln -s destination file
+file.{checksum, md5, sha1, sha256, sha512, digest} // also works for directories
+file.setOwner(user: String) // chown user file
+file.setGroup(group: String) // chgrp group file
+Seq(file1, file2) `>:` file3 // same as cat file1 file2 > file3 (must import import better.files.Dsl.SymbolicOperations)
+Seq(file1, file2) >>: file3 // same as cat file1 file2 >> file3 (must import import better.files.Dsl.SymbolicOperations)
+file.isReadLocked; file.isWriteLocked; file.isLocked
+File.numberOfOpenFileDescriptors // number of open file descriptors
+```
+You can also load resources from your classpath using `File.resource` or `File.copyResource`.
+
+### Temporary files
+Utils to create temporary files:
+```scala
+File.newTemporaryDirectory()
+File.newTemporaryFile()
+```
+The above APIs allow optional specifications of `prefix`, `suffix` and `parentDir`.
+These files are [not deleted automatically on exit by the JVM](http://stackoverflow.com/questions/16691437/when-are-java-temporary-files-deleted) (you have to set `deleteOnExit` which adds to `shutdownHook`).
+
+A cleaner alternative is to use self-deleting file contexts which deletes the file immediately when done:
+```scala
+for {
+ tempFile <- File.temporaryFile()
+} doSomething(tempFile) // tempFile is auto deleted at the end of this block - even if an exception happens
+```
+
+OR equivalently:
+```scala
+File.usingTemporaryFile() {tempFile =>
+ //do something
+} // tempFile is auto deleted at the end of this block - even if an exception happens
+```
+
+You can make any files temporary (i.e. delete after use) by doing this:
+```scala
+val foo = File.home / "Downloads" / "foo.txt"
+
+for {
+ temp <- foo.toTemporary
+} doSomething(temp) // foo is deleted at the end of this block - even if an exception happens
+```
+
+### UNIX DSL
+All the above can also be expressed using [methods](http://pathikrit.github.io/better-files/latest/api/better/files/Dsl$.html) reminiscent of the command line:
+```scala
+import better.files._
+import better.files.Dsl._ // must import Dsl._ to bring in these utils
+
+pwd / cwd // current dir
+cp(file1, file2)
+mv(file1, file2)
+rm(file) /*or*/ del(file)
+ls(file) /*or*/ dir(file)
+ln(file1, file2) // hard link
+ln_s(file1, file2) // soft link
+cat(file1)
+cat(file1) >>: file
+touch(file)
+mkdir(file)
+mkdirs(file) // mkdir -p
+chown(owner, file)
+chgrp(owner, file)
+chmod_+(permission, files) // add permission
+chmod_-(permission, files) // remove permission
+md5(file); sha1(file); sha256(file); sha512(file)
+unzip(zipFile)(targetDir)
+zip(file*)(targetZipFile)
+```
+
+### File attributes
+Query various file attributes e.g.:
+```scala
+file.name // simpler than java.io.File#getName
+file.extension
+file.contentType
+file.lastModifiedTime // returns JSR-310 time
+file.owner
+file.group
+file.isDirectory; file.isSymbolicLink; file.isRegularFile
+file.isHidden
+file.hide(); file.unhide()
+file.isOwnerExecutable; file.isGroupReadable // etc. see file.permissions
+file.size // for a directory, computes the directory size
+file.posixAttributes; file.dosAttributes // see file.attributes
+file.isEmpty // true if file has no content (or no children if directory) or does not exist
+file.isParentOf; file.isChildOf; file.isSiblingOf; file.siblings
+file("dos:system") = true // set custom meta-data for file (similar to Files.setAttribute)
+```
+All the above APIs let you specify the [`LinkOption`](http://docs.oracle.com/javase/8/docs/api/java/nio/file/LinkOption.html) either directly:
+```scala
+file.isDirectory(LinkOption.NOFOLLOW_LINKS)
+```
+Or using the [`File.LinkOptions`](http://pathikrit.github.io/better-files/latest/api/better/files/File$$LinkOptions$.html) helper:
+```scala
+file.isDirectory(File.LinkOptions.noFollow)
+```
+
+`chmod`:
+```scala
+import java.nio.file.attribute.PosixFilePermission
+file.addPermission(PosixFilePermission.OWNER_EXECUTE) // chmod +X file
+file.removePermission(PosixFilePermission.OWNER_WRITE) // chmod -w file
+assert(file.permissionsAsString == "rw-r--r--")
+
+// The following are all equivalent:
+assert(file.permissions contains PosixFilePermission.OWNER_EXECUTE)
+assert(file.testPermission(PosixFilePermission.OWNER_EXECUTE))
+assert(file.isOwnerExecutable)
+```
+
+### File comparison
+Use `==` to check for path-based equality and `===` for content-based equality:
+```scala
+file1 == file2 // equivalent to `file1.isSamePathAs(file2)`
+file1 === file2 // equivalent to `file1.isSameContentAs(file2)` (works for regular-files and directories)
+file1 != file2 // equivalent to `!file1.isSamePathAs(file2)`
+file1 !== file2 // equivalent to `!file1.isSameContentAs(file2)`
+```
+There are also various [`Ordering[File]` instances](http://pathikrit.github.io/better-files/latest/api/better/files/File$$Order$.html) included, e.g.:
+```scala
+val files = myDir.list.toSeq
+files.sorted(File.Order.byName)
+files.max(File.Order.bySize)
+files.min(File.Order.byDepth)
+files.max(File.Order.byModificationTime)
+files.sorted(File.Order.byDirectoriesFirst)
+```
+
+### Zip APIs
+You don't have to lookup on StackOverflow "[How to zip/unzip in Java/Scala?](http://stackoverflow.com/questions/9324933/)":
+```scala
+// Unzipping:
+val zipFile: File = file"path/to/research.zip"
+val research: File = zipFile.unzipTo(destination = home/"Documents"/"research")
+
+// Zipping:
+val zipFile: File = directory.zipTo(destination = home/"Desktop"/"toEmail.zip")
+
+// Zipping in:
+val zipFile = File("countries.zip").zipIn(file"usa.txt", file"russia.txt")
+
+// Zipping/Unzipping to temporary files/directories:
+val someTempZipFile: File = directory.zip()
+val someTempDir: File = zipFile.unzip()
+assert(directory === someTempDir)
+
+// Gzip handling:
+File("countries.gz").newInputStream.gzipped.lines.take(10).foreach(println)
+```
+
+### Lightweight ARM
+Auto-close Java closeables:
+```scala
+for {
+ in <- file1.newInputStream.autoClosed
+ out <- file2.newOutputStream.autoClosed
+} in.pipeTo(out)
+// The input and output streams are auto-closed once out of scope
+```
+`better-files` provides convenient managed versions of all the Java closeables e.g. instead of writing:
+```scala
+for {
+ reader <- file.newBufferedReader.autoClosed
+} foo(reader)
+```
+You can write:
+```scala
+for {
+ reader <- file.bufferedReader // returns ManagedResource[BufferedReader]
+} foo(reader)
+
+// or simply:
+file.bufferedReader.foreach(foo)
+```
+
+You can also define your own custom disposable resources e.g.:
+```scala
+trait Shutdownable {
+ def shutdown(): Unit = ()
+}
+
+object Shutdownable {
+ implicit val disposable: Disposable[Shutdownable] = Disposable(_.shutdown())
+}
+
+val s: Shutdownable = ....
+
+for {
+ instance <- new ManagedResource(s)
+} doSomething(s) // s is disposed after this
+```
+
+### Scanner
+Although [`java.util.Scanner`](http://docs.oracle.com/javase/8/docs/api/java/util/Scanner.html) has a feature-rich API, it only allows parsing primitives.
+It is also [notoriously slow](https://www.cpe.ku.ac.th/~jim/java-io.html) since it uses regexes and does un-Scala things like returns nulls and throws exceptions.
+
+`better-files` provides a [faster](benchmarks#benchmarks), richer, safer, more idiomatic and compossible [Scala replacement](http://pathikrit.github.io/better-files/latest/api/better/files/Scanner.html)
+that [does not use regexes](core/src/main/scala/better/files/Scanner.scala), allows peeking, accessing line numbers, returns `Option`s whenever possible and lets the user mixin custom parsers:
+```scala
+val data = t1 << s"""
+ | Hello World
+ | 1 true 2 3
+""".stripMargin
+val scanner: Scanner = data.newScanner()
+assert(scanner.next[String] == "Hello")
+assert(scanner.lineNumber == 1)
+assert(scanner.next[String] == "World")
+assert(scanner.next[(Int, Boolean)] == (1, true))
+assert(scanner.tillEndOfLine() == " 2 3")
+assert(!scanner.hasNext)
+```
+If you are simply interested in tokens, you can use `file.tokens()`
+
+Writing your own custom scanners:
+```scala
+sealed trait Animal
+case class Dog(name: String) extends Animal
+case class Cat(name: String) extends Animal
+
+implicit val animalParser: Scannable[Animal] = Scannable {scanner =>
+ val name = scanner.next[String]
+ if (name == "Garfield") Cat(name) else Dog(name)
+}
+
+val scanner = file.newScanner()
+println(scanner.next[Animal])
+```
+
+The [shapeless-scanner](shapeless/src/main/scala/better/files/ShapelessScanner.scala) module lets you scan [`HList`s](https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/hlists.scala):
+```scala
+val in = Scanner("""
+ 12 Bob True
+ 13 Mary False
+ 26 Rick True
+""")
+
+import shapeless._
+
+type Row = Int :: String :: Boolean :: HNil
+
+val out = Seq.fill(3)(in.next[Row])
+assert(out == Seq(
+ 12 :: "Bob" :: true :: HNil,
+ 13 :: "Mary" :: false :: HNil,
+ 26 :: "Rick" :: true :: HNil
+))
+```
+
+[and case-classes](https://meta.plasm.us/posts/2015/11/08/type-classes-and-generic-derivation/):
+
+```scala
+case class Person(id: Int, name: String, isMale: Boolean)
+val out2 = Seq.fill(3)(in.next[Person])
+```
+
+Simple CSV reader:
+```scala
+val file = """
+ 23,foo
+ 42,bar
+"""
+val csvScanner = file.newScanner(StringSpliiter.on(','))
+csvScanner.next[Int] //23
+csvScanner.next[String] //foo
+```
+
+### File Monitoring
+Vanilla Java watchers:
+```scala
+import java.nio.file.{StandardWatchEventKinds => EventType}
+val service: java.nio.file.WatchService = myDir.newWatchService
+myDir.register(service, events = Seq(EventType.ENTRY_CREATE, EventType.ENTRY_DELETE))
+```
+The above APIs are [cumbersome to use](https://docs.oracle.com/javase/tutorial/essential/io/notification.html#process) (involves a lot of type-casting and null-checking),
+are based on a blocking [polling-based model](http://docs.oracle.com/javase/8/docs/api/java/nio/file/WatchKey.html),
+does not easily allow [recursive watching of directories](https://docs.oracle.com/javase/tutorial/displayCode.html?code=https://docs.oracle.com/javase/tutorial/essential/io/examples/WatchDir.java)
+and nor does it easily allow [watching regular files](http://stackoverflow.com/questions/16251273/) without writing a lot of Java boilerplate.
+
+`better-files` abstracts all the above ugliness behind a [simple interface](core/src/main/scala/better/files/File.scala#1100):
+```scala
+val watcher = new FileMonitor(myDir, recursive = true) {
+ override def onCreate(file: File, count: Int) = println(s"$file got created")
+ override def onModify(file: File, count: Int) = println(s"$file got modified $count times")
+ override def onDelete(file: File, count: Int) = println(s"$file got deleted")
+}
+watcher.start()
+```
+Sometimes, instead of overwriting each of the 3 methods above, it is more convenient to override the dispatcher itself:
+```scala
+import java.nio.file.{Path, StandardWatchEventKinds => EventType, WatchEvent}
+
+val watcher = new FileMonitor(myDir, recursive = true) {
+ override def onEvent(eventType: WatchEvent.Kind[Path], file: File, count: Int) = eventType match {
+ case EventType.ENTRY_CREATE => println(s"$file got created")
+ case EventType.ENTRY_MODIFY => println(s"$file got modified $count")
+ case EventType.ENTRY_DELETE => println(s"$file got deleted")
+ }
+}
+```
+
+### Akka File Watcher
+`better-files` also provides a powerful yet concise [reactive file watcher](akka/src/main/scala/better/files/FileWatcher.scala)
+based on [Akka actors](http://doc.akka.io/docs/akka/snapshot/scala/actors.html) that supports dynamic dispatches:
+ ```scala
+import akka.actor.{ActorRef, ActorSystem}
+import better.files._, FileWatcher._
+
+implicit val system = ActorSystem("mySystem")
+
+val watcher: ActorRef = (home/"Downloads").newWatcher(recursive = true)
+
+// register partial function for an event
+watcher ! on(EventType.ENTRY_DELETE) {
+ case file if file.isDirectory => println(s"$file got deleted")
+}
+
+// watch for multiple events
+watcher ! when(events = EventType.ENTRY_CREATE, EventType.ENTRY_MODIFY) {
+ case (EventType.ENTRY_CREATE, file, count) => println(s"$file got created")
+ case (EventType.ENTRY_MODIFY, file, count) => println(s"$file got modified $count times")
+}
+```