scala/ScalaBook/chapter-10/src/main/scala/scalabook/file/matcher/FileMatcher.scala

package scalabook.file.matcher

import _root_.scalabook.file.VFile
import _root_.scalabook.file.VFS.AnyFile
import _root_.scalabook.path.Path
import util.matching.Regex

trait Matcher[T] { outer =>
  def matches(t: T): Boolean

  def binop(other: Matcher[T],
           op: (Boolean, Boolean) => Boolean) =
    new Matcher[T] {
      def matches(t: T) = op(outer.matches(t), other.matches(t))
    }

  def &&(other: Matcher[T]) = binop(other, _ && _)

  def ||(other: Matcher[T]) = binop(other, _ || _)

  def unary_! = new Matcher[T] {
    def matches(t: T) = !outer.matches(t)
  }
}

trait FilePathMatcher extends VFileWithStar.FileMatcher {
  
  def matches(file: AnyFile) = matchesPath(file.path)

  def matchesPath(path: Path): Boolean
}

class ExtensionMatcher(ext: String) extends FilePathMatcher {
  def matchesPath(path: Path) =
    path.extension.toLowerCase == ext.toLowerCase
}

// This is called "Weak" because we consider only filename matching, not path matching
class WeakGlobMatcher(glob: String) extends FilePathMatcher {
  val globRE = new Regex("^(?i)" + glob
    .replace("\\", "\\\\")
    .replace(".", "\\.")
    .replace("[", "\\[").replace("]", "\\]")
    .replace("(", "\\(").replace(")", "\\)")
    .replace("*", ".+")
    .replace("?", ".?")
    .replace("$", "\\$")
    .replace("^", "\\^") + "$")

  // A nice optimization (and not widely adopted, not even here) is to precalculate the
  // matcher and reset() it every time before using it
  def matchesPath(path: Path) =
    //globRE.pattern.matcher(path.name).matches
        globRE.findFirstIn(path.name).isDefined

  def toREString = globRE.pattern.toString

  override def toString = glob
}

class VFileWithStar[T <: VFile[T]](file: T) {
  import VFileWithStar.FileMatcher

  def *(matcher: FileMatcher): Iterable[T] =
    file.children filter matcher.matches

  def **(matcher: FileMatcher): Iterable[T] = {
    def deep(f: T): Iterable[T] =
      f.children ++ f.children.flatMap(deep(_))

    deep(file) filter matcher.matches
  }
}

object VFileWithStar {
  type FileMatcher = Matcher[AnyFile]


  implicit def file2fileWithStar[T <: VFile[T]](f: T) = new VFileWithStar(f)
  implicit def glob2Matcher(glob: String) = new WeakGlobMatcher(glob)
}