Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port fm-common Resource/Input&OutputStream helpers to better.files.resources #431

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,29 @@ lazy val akka = (project in file("akka"))
)
.dependsOn(core % "test->test;compile->compile")

lazy val resources = (project in file("resources"))
.settings(commonSettings: _*)
.settings(
name := s"$repo-resources",
description := "Use I/O in close-safe resources",
libraryDependencies ++= Dependencies.resources,
baseDirectory in (Test) := file("./resources"),
fork in Test := true
)
.dependsOn(core % "test->test;compile->compile")

lazy val root = (project in file("."))
.settings(name := s"$repo-root")
.settings(commonSettings: _*)
.settings(docSettings: _*)
.settings(skip in publish := true)
.enablePlugins(ScalaUnidocPlugin)
.enablePlugins(GhpagesPlugin)
.aggregate(core, akka)
.aggregate(core, akka, resources)

lazy val docSettings = Seq(
autoAPIMappings := true,
unidocProjectFilter in (ScalaUnidoc, unidoc) := inProjects(core, akka),
unidocProjectFilter in (ScalaUnidoc, unidoc) := inProjects(core, akka, resources),
siteSourceDirectory := baseDirectory.value / "site",
siteSubdirName in ScalaUnidoc := "latest/api",
addMappingsToSiteDir(mappings in (ScalaUnidoc, packageDoc), siteSubdirName in ScalaUnidoc),
Expand Down
7 changes: 7 additions & 0 deletions core/src/main/scala/better/files/File.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ class File private (val path: Path)(implicit val fileSystem: FileSystem = path.g
def pathAsString: String =
path.toString

/**
* getResource[...](path) always uses "/" for separator
* https://docs.oracle.com/javase/8/docs/api/java/lang/ClassLoader.html#getResource(java.lang.String)
*/
def toResourcePathAsString: String =
pathAsString.replace(JFile.separatorChar, '/')

def toJava: JFile =
new JFile(path.toAbsolutePath.toString)

Expand Down
160 changes: 133 additions & 27 deletions core/src/main/scala/better/files/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,21 @@ import java.util.zip._

import scala.annotation.tailrec
import scala.collection.JavaConverters._
import java.net.URI
import java.net.URL

/**
* Container for various implicits
*/
trait Implicits extends Dispose.FlatMap.Implicits with Scanner.Read.Implicits with Scanner.Source.Implicits {

object Implicits {
//TODO: Rename all Ops to Extensions

implicit class StringInterpolations(sc: StringContext) {
final class StringInterpolations(val sc: StringContext) extends AnyVal {
def file(args: Any*): File =
value(args).toFile

private[this] def value(args: Seq[Any]) =
sc.s(args: _*)
}

implicit class StringExtensions(str: String) {
final class StringExtensions(val str: String) extends AnyVal {
def toFile: File =
File(str)

Expand All @@ -42,25 +40,53 @@ trait Implicits extends Dispose.FlatMap.Implicits with Scanner.Read.Implicits wi
new StringReader(str)
}

implicit class FileExtensions(file: JFile) {
final class FileExtensions(val file: JFile) extends AnyVal {
def toScala: File =
File(file.getPath)
}

implicit class SymbolExtensions(symbol: Symbol) {
final class URLExtensions(val url: URL) extends AnyVal {
def isFile: Boolean = {
if (null == url) return false
val uri: URI = url.toURI
uri.getScheme == "file"
}

def toFile: File = {
require(isFile, s"Not a file: $url")
File(url)
}

def toFileOption: Option[File] = if (isFile) Some(File(url)) else None
}

final class URIExtensions(val uri: URI) extends AnyVal {
def isFile: Boolean =
if (null == uri) false
else uri.getScheme() == "file"

def toFile: File = {
require(isFile, s"Not a file: $uri")
File(uri)
}

def toFileOption: Option[File] = if (isFile) Some(File(uri)) else None
}

final class SymbolExtensions(val symbol: Symbol) extends AnyVal {
def /(child: Symbol): File =
File(symbol.name) / child
}

implicit class IteratorExtensions[A](it: Iterator[A]) {
final class IteratorExtensions[A](val it: Iterator[A]) extends AnyVal {
def withHasNext(f: => Boolean): Iterator[A] =
new Iterator[A] {
override def hasNext = f && it.hasNext
override def next() = it.next()
}
}

implicit class InputStreamExtensions(in: InputStream) {
final class InputStreamExtensions(val in: InputStream) extends AnyVal {
def pipeTo(out: OutputStream, bufferSize: Int = DefaultBufferSize): out.type =
pipeTo(out, Array.ofDim[Byte](bufferSize))

Expand Down Expand Up @@ -173,7 +199,7 @@ trait Implicits extends Dispose.FlatMap.Implicits with Scanner.Read.Implicits wi
}.get()
}

implicit class DigestInputStreamExtensions(in: DigestInputStream) {
final class DigestInputStreamExtensions(val in: DigestInputStream) extends AnyVal {

/** Exhausts the stream and computes the digest and closes the stream */
def digest(drainTo: OutputStream = NullOutputStream): Array[Byte] = {
Expand All @@ -186,7 +212,7 @@ trait Implicits extends Dispose.FlatMap.Implicits with Scanner.Read.Implicits wi
toHex(digest(drainTo))
}

implicit class OutputStreamExtensions(val out: OutputStream) {
final class OutputStreamExtensions(val out: OutputStream) extends AnyVal {
def buffered: BufferedOutputStream =
new BufferedOutputStream(out)

Expand Down Expand Up @@ -250,14 +276,14 @@ trait Implicits extends Dispose.FlatMap.Implicits with Scanner.Read.Implicits wi
new ZipOutputStream(out, charset)
}

implicit class PrintWriterExtensions(pw: PrintWriter) {
final class PrintWriterExtensions(val pw: PrintWriter) extends AnyVal {
def printLines(lines: TraversableOnce[_]): PrintWriter = {
lines.foreach(pw.println)
pw
}
}

implicit class ReaderExtensions(reader: Reader) {
final class ReaderExtensions(val reader: Reader) extends AnyVal {
def buffered: BufferedReader =
new BufferedReader(reader)

Expand All @@ -268,42 +294,42 @@ trait Implicits extends Dispose.FlatMap.Implicits with Scanner.Read.Implicits wi
new Dispose(reader).flatMap(res => eofReader(res.read()).map(_.toChar))
}

implicit class BufferedReaderExtensions(reader: BufferedReader) {
final class BufferedReaderExtensions(val reader: BufferedReader) extends AnyVal {
def tokens(splitter: StringSplitter = StringSplitter.Default): Iterator[String] =
reader.lines().toAutoClosedIterator.flatMap(splitter.split)
}

implicit class WriterExtensions(writer: Writer) {
final class WriterExtensions(val writer: Writer) extends AnyVal {
def buffered: BufferedWriter =
new BufferedWriter(writer)

def outputstream(implicit charset: Charset = DefaultCharset): OutputStream =
new WriterOutputStream(writer)(charset)
}

implicit class FileChannelExtensions(fc: FileChannel) {
final class FileChannelExtensions(val fc: FileChannel) extends AnyVal {
def toMappedByteBuffer: MappedByteBuffer =
fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size())
}

implicit class PathMatcherExtensions(matcher: PathMatcher) {
final class PathMatcherExtensions(val matcher: PathMatcher) extends AnyVal {
def matches(file: File, maxDepth: Int)(implicit visitOptions: File.VisitOptions = File.VisitOptions.default) =
file.collectChildren(child => matcher.matches(child.path), maxDepth)(visitOptions)
}

implicit class ObjectInputStreamExtensions(ois: ObjectInputStream) {
final class ObjectInputStreamExtensions(val ois: ObjectInputStream) extends AnyVal {
def deserialize[A]: A =
ois.readObject().asInstanceOf[A]
}

implicit class ObjectOutputStreamExtensions(val oos: ObjectOutputStream) {
final class ObjectOutputStreamExtensions(val oos: ObjectOutputStream) extends AnyVal {
def serialize(obj: Serializable): oos.type = {
oos.writeObject(obj)
oos
}
}

implicit class ZipOutputStreamExtensions(val out: ZipOutputStream) {
final class ZipOutputStreamExtensions(val out: ZipOutputStream) extends AnyVal {

/**
* Correctly set the compression level
Expand Down Expand Up @@ -332,7 +358,7 @@ trait Implicits extends Dispose.FlatMap.Implicits with Scanner.Read.Implicits wi
add(file, file.name)
}

implicit class ZipInputStreamExtensions(val in: ZipInputStream) {
final class ZipInputStreamExtensions(val in: ZipInputStream) extends AnyVal {

/**
* Apply `f` on each ZipEntry in the archive, closing the entry after `f` has been applied.
Expand Down Expand Up @@ -368,7 +394,7 @@ trait Implicits extends Dispose.FlatMap.Implicits with Scanner.Read.Implicits wi
mapEntries(_ => f(in))
}

implicit class ZipEntryExtensions(val entry: ZipEntry) {
final class ZipEntryExtensions(val entry: ZipEntry) extends AnyVal {

/**
* Extract this ZipEntry under this rootDir
Expand All @@ -385,7 +411,7 @@ trait Implicits extends Dispose.FlatMap.Implicits with Scanner.Read.Implicits wi
}
}

implicit class DisposeableExtensions[A: Disposable](resource: A) {
final class DisposeableExtensions[A: Disposable](val resource: A) {

/**
* Lightweight automatic resource management
Expand All @@ -403,7 +429,7 @@ trait Implicits extends Dispose.FlatMap.Implicits with Scanner.Read.Implicits wi
new Dispose(resource)
}

implicit class JStreamExtensions[A](stream: JStream[A]) {
final class JStreamExtensions[A](val stream: JStream[A]) extends AnyVal {

/**
* Closes this stream when iteration is complete
Expand All @@ -415,11 +441,91 @@ trait Implicits extends Dispose.FlatMap.Implicits with Scanner.Read.Implicits wi
stream.autoClosed.flatMap(_.iterator().asScala)
}

private[files] implicit class OrderingExtensions[A](order: Ordering[A]) {
final private[files] class OrderingExtensions[A](val order: Ordering[A]) extends AnyVal {
def andThenBy(order2: Ordering[A]): Ordering[A] =
Ordering.comparatorToOrdering(order.thenComparing(order2))
}

}

/**
* Container for various implicits
*/
trait Implicits extends Dispose.FlatMap.Implicits with Scanner.Read.Implicits with Scanner.Source.Implicits {
import Implicits._

implicit def toStringInterpolations(sc: StringContext): StringInterpolations =
new StringInterpolations(sc)

implicit def toStringExtensions(str: String): StringExtensions =
new StringExtensions(str)

implicit def toFileExtensions(file: JFile): FileExtensions =
new FileExtensions(file)

implicit def toURLExtensions(url: URL): URLExtensions =
new URLExtensions(url)

implicit def toURIExtensions(uri: URI): URIExtensions =
new URIExtensions(uri)

implicit def toSymbolExtensions(symbol: Symbol): SymbolExtensions =
new SymbolExtensions(symbol)

implicit def toIteratorExtensions[A](it: Iterator[A]): IteratorExtensions[A] =
new IteratorExtensions(it)

implicit def toInputStreamExtensions(in: InputStream): InputStreamExtensions =
new InputStreamExtensions(in)

implicit def toDigestInputStreamExtensions(in: DigestInputStream): DigestInputStreamExtensions =
new DigestInputStreamExtensions(in)

implicit def toOutputStreamExtensions(out: OutputStream): OutputStreamExtensions =
new OutputStreamExtensions(out)

implicit def toPrintWriterExtensions(writer: PrintWriter): PrintWriterExtensions =
new PrintWriterExtensions(writer)

implicit def toReaderExtensions(reader: Reader): ReaderExtensions =
new ReaderExtensions(reader)

implicit def toBufferedReaderExtensions(reader: BufferedReader): BufferedReaderExtensions =
new BufferedReaderExtensions(reader)

implicit def toWriterExtensions(writer: Writer): WriterExtensions =
new WriterExtensions(writer)

implicit def toFileChannelExtensions(fc: FileChannel): FileChannelExtensions =
new FileChannelExtensions(fc)

implicit def toPathMatcherExtensions(matcher: PathMatcher): PathMatcherExtensions =
new PathMatcherExtensions(matcher)

implicit def toObjectInputStreamExtensions(ois: ObjectInputStream): ObjectInputStreamExtensions =
new ObjectInputStreamExtensions(ois)

implicit def toObjectOutputStreamExtensions(oos: ObjectOutputStream): ObjectOutputStreamExtensions =
new ObjectOutputStreamExtensions(oos)

implicit def toZipOutputStreamExtensions(out: ZipOutputStream): ZipOutputStreamExtensions =
new ZipOutputStreamExtensions(out)

implicit def toZipInputStreamExtensions(in: ZipInputStream): ZipInputStreamExtensions =
new ZipInputStreamExtensions(in)

implicit def toZipEntryExtensions(entry: ZipEntry): ZipEntryExtensions =
new ZipEntryExtensions(entry)

implicit def toDisposeableExtensions[A: Disposable](resource: A): DisposeableExtensions[A] =
new DisposeableExtensions(resource)

implicit def toJStreamExtensions[A](stream: JStream[A]): JStreamExtensions[A] =
new JStreamExtensions(stream)

private[files] implicit def toOrderingExtensions[A](order: Ordering[A]): OrderingExtensions[A] =
new OrderingExtensions(order)

implicit def stringToMessageDigest(algorithmName: String): MessageDigest =
MessageDigest.getInstance(algorithmName)

Expand Down
9 changes: 8 additions & 1 deletion core/src/main/scala/better/files/UnicodeCharset.scala
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@ object UnicodeCharset {
}
.ensuring(_.nonEmpty, "No unicode charset detected")

def isUnicodeCharset(charset: Charset): Boolean = bomTable.contains(charset)

def isUnicodeCharset(charsetName: String): Boolean =
if (Charset.isSupported(charsetName)) bomTable.contains(Charset.forName(charsetName))
else false

def apply(charset: Charset, writeByteOrderMarkers: Boolean = false): Charset =
if (bomTable.contains(charset)) new UnicodeCharset(charset, writeByteOrderMarkers) else charset
if (isUnicodeCharset(charset)) new UnicodeCharset(charset, writeByteOrderMarkers)
else charset
}
14 changes: 14 additions & 0 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,18 @@ object Dependencies {
val commonsio = "commons-io" % "commons-io" % "2.6" % Test // Benchmarks
val fastjavaio =
"fastjavaio" % "fastjavaio" % "1.0" % Test from "https://github.com/williamfiset/FastJavaIO/releases/download/v1.0/fastjavaio.jar" //Benchmarks

val resources = Seq(
"io.github.er1c" %% "scala-typesafeequals" % "1.0.0-RC1" % Compile,
"com.fasterxml.woodstox" % "woodstox-core" % "5.1.0",
"com.github.albfernandez" % "juniversalchardet" % "2.3.2",
"com.typesafe.scala-logging" %% "scala-logging" % "3.9.2",
"org.apache.commons" % "commons-compress" % "1.9",
// Used by commons-compress and should be synced up with whatever version commons-compress requires
"org.tukaani" % "xz" % "1.8",
"org.apache.commons" % "commons-lang3" % "3.4",
"commons-codec" % "commons-codec" % "1.10",
"commons-io" % "commons-io" % "2.4",
"org.xerial.snappy" % "snappy-java" % "1.1.1" // SnappyOutputStream might be messed up in 1.1.1.3
)
}
4 changes: 4 additions & 0 deletions project/metals.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// DO NOT EDIT! This file is auto-generated.
// This file enables sbt-bloop to create bloop config files.

addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.4.3")
Loading