mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-22 10:28:27 +00:00
Improve glob and filter archive entries
This commit is contained in:
@ -1,27 +1,81 @@
|
||||
package docspell.common
|
||||
|
||||
import cats.implicits._
|
||||
import cats.data.NonEmptyList
|
||||
import cats.implicits._
|
||||
|
||||
import io.circe.{Decoder, Encoder}
|
||||
|
||||
/** A very simple glob supporting only `*` and `?`. */
|
||||
final case class Glob(pattern: Glob.Pattern) {
|
||||
def matches(in: String): Boolean =
|
||||
pattern.parts
|
||||
.zipWith(Glob.split(in, Glob.separator))(_.matches(_))
|
||||
.forall(identity)
|
||||
trait Glob {
|
||||
|
||||
def asString: String =
|
||||
pattern.asString
|
||||
/** Matches the input string against this glob. */
|
||||
def matches(in: String): Boolean
|
||||
|
||||
/** If this glob consists of multiple segments, it is the same as
|
||||
* `matches`. If it is only a single segment, it is matched against
|
||||
* the last segment of the input string that is assumed to be a
|
||||
* pathname separated by slash.
|
||||
*
|
||||
* Example:
|
||||
* test.* <> "/a/b/test.txt" => true
|
||||
* /test.* <> "/a/b/test.txt" => false
|
||||
*/
|
||||
def matchFilenameOrPath(in: String): Boolean
|
||||
|
||||
def asString: String
|
||||
}
|
||||
|
||||
object Glob {
|
||||
private val separator = '/'
|
||||
private val anyChar = '|'
|
||||
|
||||
def apply(str: String): Glob =
|
||||
Glob(Pattern(split(str, separator).map(makeSegment)))
|
||||
val all = new Glob {
|
||||
def matches(in: String) = true
|
||||
def matchFilenameOrPath(in: String) = true
|
||||
val asString = "*"
|
||||
}
|
||||
|
||||
case class Pattern(parts: NonEmptyList[Segment]) {
|
||||
def pattern(pattern: Pattern): Glob =
|
||||
PatternGlob(pattern)
|
||||
|
||||
/** A simple glob supporting `*` and `?`. */
|
||||
final private case class PatternGlob(pattern: Pattern) extends Glob {
|
||||
def matches(in: String): Boolean =
|
||||
pattern.parts
|
||||
.zipWith(Glob.split(in, Glob.separator))(_.matches(_))
|
||||
.forall(identity)
|
||||
|
||||
def matchFilenameOrPath(in: String): Boolean =
|
||||
if (pattern.parts.tail.isEmpty) matches(split(in, separator).last)
|
||||
else matches(in)
|
||||
|
||||
def asString: String =
|
||||
pattern.asString
|
||||
}
|
||||
|
||||
final private case class AnyGlob(globs: NonEmptyList[Glob]) extends Glob {
|
||||
def matches(in: String) =
|
||||
globs.exists(_.matches(in))
|
||||
def matchFilenameOrPath(in: String) =
|
||||
globs.exists(_.matchFilenameOrPath(in))
|
||||
def asString =
|
||||
globs.toList.map(_.asString).mkString(anyChar.toString)
|
||||
}
|
||||
|
||||
def apply(in: String): Glob = {
|
||||
def single(str: String) =
|
||||
PatternGlob(Pattern(split(str, separator).map(makeSegment)))
|
||||
|
||||
if (in == "*") all
|
||||
else
|
||||
split(in, anyChar) match {
|
||||
case NonEmptyList(_, Nil) =>
|
||||
single(in)
|
||||
case nel =>
|
||||
AnyGlob(nel.map(_.trim).map(single))
|
||||
}
|
||||
}
|
||||
|
||||
case class Pattern(parts: NonEmptyList[Segment]) {
|
||||
def asString =
|
||||
parts.map(_.asString).toList.mkString(separator.toString)
|
||||
}
|
||||
|
@ -6,13 +6,15 @@ import Glob._
|
||||
object GlobTest extends SimpleTestSuite {
|
||||
|
||||
test("literals") {
|
||||
assert(Glob(Pattern(Segment(Token.Literal("hello")))).matches("hello"))
|
||||
assert(!Glob(Pattern(Segment(Token.Literal("hello")))).matches("hello1"))
|
||||
assert(Glob.pattern(Pattern(Segment(Token.Literal("hello")))).matches("hello"))
|
||||
assert(!Glob.pattern(Pattern(Segment(Token.Literal("hello")))).matches("hello1"))
|
||||
}
|
||||
|
||||
test("single wildcards 1") {
|
||||
val glob =
|
||||
Glob(Pattern(Segment(Token.Literal("s"), Token.Until("p"), Token.Until("t"))))
|
||||
Glob.pattern(
|
||||
Pattern(Segment(Token.Literal("s"), Token.Until("p"), Token.Until("t")))
|
||||
)
|
||||
|
||||
assert(glob.matches("snapshot"))
|
||||
assert(!glob.matches("snapshots"))
|
||||
@ -20,7 +22,7 @@ object GlobTest extends SimpleTestSuite {
|
||||
|
||||
test("single wildcards 2") {
|
||||
val glob =
|
||||
Glob(Pattern(Segment(Token.Literal("test."), Token.Until(""))))
|
||||
Glob.pattern(Pattern(Segment(Token.Literal("test."), Token.Until(""))))
|
||||
|
||||
assert(glob.matches("test.txt"))
|
||||
assert(glob.matches("test.pdf"))
|
||||
@ -32,28 +34,29 @@ object GlobTest extends SimpleTestSuite {
|
||||
test("single parsing") {
|
||||
assertEquals(
|
||||
Glob("s*p*t"),
|
||||
Glob(Pattern(Segment(Token.Literal("s"), Token.Until("p"), Token.Until("t"))))
|
||||
Glob.pattern(
|
||||
Pattern(Segment(Token.Literal("s"), Token.Until("p"), Token.Until("t")))
|
||||
)
|
||||
)
|
||||
assertEquals(
|
||||
Glob("s***p*t"),
|
||||
Glob(Pattern(Segment(Token.Literal("s"), Token.Until("p"), Token.Until("t"))))
|
||||
Glob.pattern(
|
||||
Pattern(Segment(Token.Literal("s"), Token.Until("p"), Token.Until("t")))
|
||||
)
|
||||
)
|
||||
assertEquals(
|
||||
Glob("test.*"),
|
||||
Glob(Pattern(Segment(Token.Literal("test."), Token.Until(""))))
|
||||
Glob.pattern(Pattern(Segment(Token.Literal("test."), Token.Until(""))))
|
||||
)
|
||||
assertEquals(
|
||||
Glob("stop"),
|
||||
Glob(Pattern(Segment(Token.Literal("stop"))))
|
||||
Glob.pattern(Pattern(Segment(Token.Literal("stop"))))
|
||||
)
|
||||
assertEquals(
|
||||
Glob("*stop"),
|
||||
Glob(Pattern(Segment(Token.Until("stop"))))
|
||||
)
|
||||
assertEquals(
|
||||
Glob("*"),
|
||||
Glob(Pattern(Segment(Token.Until(""))))
|
||||
Glob.pattern(Pattern(Segment(Token.Until("stop"))))
|
||||
)
|
||||
assertEquals(Glob("*"), Glob.all)
|
||||
}
|
||||
|
||||
test("with splitting") {
|
||||
@ -71,5 +74,38 @@ object GlobTest extends SimpleTestSuite {
|
||||
assertEquals(Glob("stop").asString, "stop")
|
||||
assertEquals(Glob("*stop").asString, "*stop")
|
||||
assertEquals(Glob("/a/b/*").asString, "/a/b/*")
|
||||
assertEquals(Glob("*").asString, "*")
|
||||
assertEquals(Glob.all.asString, "*")
|
||||
}
|
||||
|
||||
test("simple matches") {
|
||||
assert(Glob("/test.*").matches("/test.pdf"))
|
||||
assert(!Glob("/test.*").matches("test.pdf"))
|
||||
assert(!Glob("test.*").matches("/test.pdf"))
|
||||
}
|
||||
|
||||
test("matchFilenameOrPath") {
|
||||
assert(Glob("test.*").matchFilenameOrPath("/a/b/test.pdf"))
|
||||
assert(!Glob("/test.*").matchFilenameOrPath("/a/b/test.pdf"))
|
||||
assert(Glob("s*p*t").matchFilenameOrPath("snapshot"))
|
||||
assert(Glob("s*p*t").matchFilenameOrPath("/tmp/snapshot"))
|
||||
assert(Glob("/tmp/s*p*t").matchFilenameOrPath("/tmp/snapshot"))
|
||||
|
||||
assert(Glob("a/b/*").matchFilenameOrPath("a/b/hello"))
|
||||
assert(!Glob("a/b/*").matchFilenameOrPath("/a/b/hello"))
|
||||
assert(Glob("/a/b/*").matchFilenameOrPath("/a/b/hello"))
|
||||
assert(!Glob("/a/b/*").matchFilenameOrPath("a/b/hello"))
|
||||
assert(!Glob("*/a/b/*").matchFilenameOrPath("a/b/hello"))
|
||||
assert(Glob("*/a/b/*").matchFilenameOrPath("test/a/b/hello"))
|
||||
}
|
||||
|
||||
test("anyglob") {
|
||||
assert(Glob("*.pdf|*.txt").matches("test.pdf"))
|
||||
assert(Glob("*.pdf|*.txt").matches("test.txt"))
|
||||
assert(!Glob("*.pdf|*.txt").matches("test.xls"))
|
||||
assert(Glob("*.pdf | *.txt").matches("test.pdf"))
|
||||
assert(Glob("*.pdf | mail.html").matches("test.pdf"))
|
||||
assert(Glob("*.pdf | mail.html").matches("mail.html"))
|
||||
assert(!Glob("*.pdf | mail.html").matches("test.docx"))
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user