Scala File Glob Parser

Posted Feb 21, 2021

Years ago I worked for two days on writing a file glob parser function in Scala. I still think about that function every now and then.

I don’t really ever write in Scala, but I did a whole project in it once toexperience what that would be like. There were a lot of things I learned and took away to other languages I use.

Scala has a lot of incredible features that can make code safe and succinct. However, if you’re not using it regularly the results of using those features can be code that is very difficult to understand.

I wrote a file glob parser in Scala that I no longer understand once I look away from it for too long. I think about it occasionally go back to study it. It is one of my favorite functions in how short it became and how opaque its function is.

I think this is a good example needing to balance the “best” way of solving a problem in a particular language or domain, with finding the most maintainable solution. This may be a great way to write this function for a team working in Scala using FP patterns every day. But a more procedural implementation would be more accessible to developers who may, for example, be spending most of their time on Java code.

Tests

Here are the tests that is passes:

@Test
def testNoAccumulator() {
    assertTrue(globMatch("testFile.js", "testFile.js"))
    assertFalse(globMatch("testFile.js", "testOther.js"))
}

@Test
def testSimpleAccumulator() {
    assertTrue(globMatch("*.js", "testFile.js"))
    assertFalse(globMatch("*.js", "testFile.jsp"))
    assertTrue(globMatch("Test*.scala", "TestDescriptorUtil.scala"))
    assertFalse(globMatch("Test*.scala", "TestConnector.java"))
}

@Test
def testComplexAccumulator() {
    assertTrue(globMatch("*.js", "testFile.js.js"))
    assertTrue(globMatch("Test*.Fn*.js", "TestCtrlPass.FnBox.js"))
    assertTrue(globMatch("Test*.Fn*.js", "TestCtrlPass.Fx.FnBox.js"))
}

And Here is the Function

def globMatch(glob: String, name: String): Boolean = {
    def chM(n: List[Char], g: List[Char]): Boolean =
        n.isEmpty || g.nonEmpty && (
            (n.head == g.head && chM(n.tail, g.tail)) ||
            (g.head == '*' &&
                (chM(n.tail, g.tail)) ||
                (n.length > g.length && chM(n.tail, g))))
    chM(name.toList, glob.toList)
}