As I slowly learn my way around the Scala APIs, I'm enjoying the expressiveness that is possible. You can do a lot with just a little bit of code.
Operating on Files
For example, I needed a way to perform an operation on every file in a directory tree.
/** * Call proc(f) for each file in the directory tree rooted at dir. * * proc will not be called for directories, just for files. */ def traverse(dir: File, proc: File => Unit): Unit = { dir.listFiles foreach { f => if(f.isDirectory) traverse(f, proc) else proc(f) } }
That File
is just a java.io.File, and its listFiles
method
returns an Array
of File
.
Scala's foreach
calls the given anonymous function for each element in the array.
Unit
is Scala's name for void
.
Operating on Strings, Experimenting in the REPL
Related to files, but operating on strings, I needed to take a file, say
content/topic/subtopic/post.md
and generate the name where the output
would land, in this case output/topic/subtopic/post.html
.
This means splitting the filename by "/", dropping the first directory, prepending the output directory, and changing the expression from ".md" to ".html".
I had to experiment in the REPL (along with heavy use of ddg) to figure this one out, but it ends up being a (long) one-liner.
scala> val f = new java.io.File("content/topic/subtopic/post.md") f: java.io.File = content/topic/subtopic/post.md
First, the File
object gives us the parent directory of the file,
and splitting on the separator gives an array of Strings representing
the path.
scala> f.getParent split java.io.File.separator res37: Array[java.lang.String] = Array(content, topic, subtopic)
I want to drop
the first directory component:
scala> f.getParent split java.io.File.separator drop 1 res38: Array[java.lang.String] = Array(topic, subtopic)
and put the output directory at the front of the list:
scala> val out = new java.io.File("output") out: java.io.File = output scala> out.getPath :: ((f.getParent split java.io.File.separator).toList drop 1) res42: List[java.lang.String] = List(output, topic, subtopic)
[If you're paying close attention to the REPL output, you'll notice that I'm leaving out my mistakes, i.e. res39 through res41.]
I had to convert the Array
returned by split
to a list, drop the
first element of the list, and then prepend the output directory using
Scala List
::
function.
Now I can join the strings in the list together with a slash to get the output directory that the file belongs in:
scala> out.getPath :: ((f.getParent split java.io.File.separator).toList drop 1) mkString java.io.File.separator res44: String = output/topic/subtopic
That java.io.File
is kind of a hassle to keep typing out:
scala> import java.io.File import java.io.File scala> out.getPath :: ((f.getParent split File.separator).toList drop 1) mkString File.separator res45: String = output/topic/subtopic
That's a little better.
Now let's take care of the filename, which is a trivial replacement:
scala> f.getName.replaceAll(".md", ".html") res46: java.lang.String = post.html
and append it to the path we just generated:
cala> out.getPath :: ((f.getParent split File.separator).toList drop 1) mkString File.separator + f.getName.replaceAll(".md", ".html") res47: String = output/post.htmltopic/post.htmlsubtopic
Whoops, that's not right -- it needs parentheses so that the mkString happens first, and an extra path separator:
scala> (out.getPath :: ((f.getParent split File.separator).toList drop 1) mkString File.separator) + File.separator + f.getName.replaceAll(".md", ".html") res49: java.lang.String = output/topic/subtopic/post.html
So the function that I added to my class is (note that the out
from
above is called outDir
here):
def outputName(f: File): String = { val sep = File.separator (outDir.getPath :: ((f.getParent split sep).toList drop 1) mkString sep) + sep + f.getName.replaceAll(".md", ".html") }
Doesn't exactly look like a one-liner, but it could be if I didn't break it up for readability.