mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-06 15:15:58 +00:00
Use a better representation for macros
This commit is contained in:
parent
a48504debb
commit
71985244f1
@ -108,6 +108,26 @@ object ItemQuery {
|
|||||||
|
|
||||||
final case class Fulltext(query: String) extends Expr
|
final case class Fulltext(query: String) extends Expr
|
||||||
|
|
||||||
|
// things that can be expressed with terms above
|
||||||
|
sealed trait MacroExpr extends Expr {
|
||||||
|
def body: Expr
|
||||||
|
}
|
||||||
|
case class NamesMacro(searchTerm: String) extends MacroExpr {
|
||||||
|
val body =
|
||||||
|
Expr.or(
|
||||||
|
like(Attr.ItemName, searchTerm),
|
||||||
|
like(Attr.ItemNotes, searchTerm),
|
||||||
|
like(Attr.Correspondent.OrgName, searchTerm),
|
||||||
|
like(Attr.Correspondent.PersonName, searchTerm),
|
||||||
|
like(Attr.Concerning.PersonName, searchTerm),
|
||||||
|
like(Attr.Concerning.EquipName, searchTerm)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case class DateRangeMacro(attr: DateAttr, left: Date, right: Date) extends MacroExpr {
|
||||||
|
val body =
|
||||||
|
and(date(Operator.Gte, attr, left), date(Operator.Lte, attr, right))
|
||||||
|
}
|
||||||
|
|
||||||
def or(expr0: Expr, exprs: Expr*): OrExpr =
|
def or(expr0: Expr, exprs: Expr*): OrExpr =
|
||||||
OrExpr(Nel.of(expr0, exprs: _*))
|
OrExpr(Nel.of(expr0, exprs: _*))
|
||||||
|
|
||||||
|
@ -5,8 +5,8 @@ import docspell.query.ItemQuery._
|
|||||||
|
|
||||||
object ExprUtil {
|
object ExprUtil {
|
||||||
|
|
||||||
/** Does some basic transformation, like unfolding deeply nested and
|
/** Does some basic transformation, like unfolding nested and trees
|
||||||
* trees containing one value etc.
|
* containing one value etc.
|
||||||
*/
|
*/
|
||||||
def reduce(expr: Expr): Expr =
|
def reduce(expr: Expr): Expr =
|
||||||
expr match {
|
expr match {
|
||||||
@ -30,6 +30,9 @@ object ExprUtil {
|
|||||||
expr
|
expr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case m: MacroExpr =>
|
||||||
|
reduce(m.body)
|
||||||
|
|
||||||
case DirectionExpr(_) =>
|
case DirectionExpr(_) =>
|
||||||
expr
|
expr
|
||||||
|
|
||||||
|
@ -5,61 +5,29 @@ import cats.parse.{Parser => P}
|
|||||||
import docspell.query.ItemQuery._
|
import docspell.query.ItemQuery._
|
||||||
|
|
||||||
object MacroParser {
|
object MacroParser {
|
||||||
private[this] val macroDef: P[String] =
|
private def macroDef(name: String): P[Unit] =
|
||||||
P.char('$') *> BasicParser.identParser <* P.char(':')
|
P.char('$').soft.with1 *> P.string(name) <* P.char(':')
|
||||||
|
|
||||||
def parser[A](macros: Map[String, P[A]]): P[A] = {
|
private def dateRangeMacroImpl(
|
||||||
val p: P[P[A]] = macroDef.map { name =>
|
name: String,
|
||||||
macros
|
attr: Attr.DateAttr
|
||||||
.get(name)
|
): P[Expr.DateRangeMacro] =
|
||||||
.getOrElse(P.failWith(s"Unknown macro: $name"))
|
(macroDef(name) *> DateParser.dateRange).map { case (left, right) =>
|
||||||
|
Expr.DateRangeMacro(attr, left, right)
|
||||||
}
|
}
|
||||||
|
|
||||||
val px = (p ~ P.index ~ BasicParser.singleString).map { case ((pexpr, index), str) =>
|
val namesMacro: P[Expr.NamesMacro] =
|
||||||
pexpr
|
(macroDef("names") *> BasicParser.singleString).map(Expr.NamesMacro.apply)
|
||||||
.parseAll(str)
|
|
||||||
.left
|
|
||||||
.map(err => err.copy(failedAtOffset = err.failedAtOffset + index))
|
|
||||||
}
|
|
||||||
|
|
||||||
P.select(px)(P.Fail)
|
val dateRangeMacro: P[Expr.DateRangeMacro] =
|
||||||
}
|
dateRangeMacroImpl("datein", Attr.Date)
|
||||||
|
|
||||||
// --- definitions of available macros
|
val dueDateRangeMacro: P[Expr.DateRangeMacro] =
|
||||||
|
dateRangeMacroImpl("duein", Attr.DueDate)
|
||||||
/** Expands in an OR expression that matches name fields of item and
|
|
||||||
* correspondent/concerning metadata.
|
|
||||||
*/
|
|
||||||
val names: P[Expr] =
|
|
||||||
P.string(P.anyChar.rep.void).map { input =>
|
|
||||||
Expr.or(
|
|
||||||
Expr.like(Attr.ItemName, input),
|
|
||||||
Expr.like(Attr.ItemNotes, input),
|
|
||||||
Expr.like(Attr.Correspondent.OrgName, input),
|
|
||||||
Expr.like(Attr.Correspondent.PersonName, input),
|
|
||||||
Expr.like(Attr.Concerning.PersonName, input),
|
|
||||||
Expr.like(Attr.Concerning.EquipName, input)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
def dateRange(attr: Attr.DateAttr): P[Expr] =
|
|
||||||
DateParser.dateRange.map { case (left, right) =>
|
|
||||||
Expr.and(
|
|
||||||
Expr.date(Operator.Gte, attr, left),
|
|
||||||
Expr.date(Operator.Lte, attr, right)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- all macro parser
|
// --- all macro parser
|
||||||
|
|
||||||
val allMacros: Map[String, P[Expr]] =
|
|
||||||
Map(
|
|
||||||
"names" -> names,
|
|
||||||
"datein" -> dateRange(Attr.Date),
|
|
||||||
"duein" -> dateRange(Attr.DueDate)
|
|
||||||
)
|
|
||||||
|
|
||||||
val all: P[Expr] =
|
val all: P[Expr] =
|
||||||
parser(allMacros)
|
P.oneOf(List(namesMacro, dateRangeMacro, dueDateRangeMacro))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,15 @@
|
|||||||
package docspell.query.internal
|
package docspell.query.internal
|
||||||
|
|
||||||
import munit._
|
import munit._
|
||||||
import cats.parse.{Parser => P}
|
//import cats.parse.{Parser => P}
|
||||||
|
import docspell.query.ItemQuery.Expr
|
||||||
|
|
||||||
class MacroParserTest extends FunSuite {
|
class MacroParserTest extends FunSuite {
|
||||||
|
|
||||||
test("fail with unkown macro names") {
|
test("start with $") {
|
||||||
val p = MacroParser.parser(Map.empty)
|
val p = MacroParser.namesMacro
|
||||||
assert(p.parseAll("$bla:blup").isLeft) // TODO check error message
|
assertEquals(p.parseAll("$names:test"), Right(Expr.NamesMacro("test")))
|
||||||
|
assert(p.parseAll("names:test").isLeft)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("select correct parser") {
|
|
||||||
val p =
|
|
||||||
MacroParser.parser[Int](Map("one" -> P.anyChar.as(1), "two" -> P.anyChar.as(2)))
|
|
||||||
assertEquals(p.parseAll("$one:y"), Right(1))
|
|
||||||
assertEquals(p.parseAll("$two:y"), Right(2))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -145,7 +145,9 @@ object ItemQueryGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case Expr.CustomFieldMatch(field, op, value) =>
|
case Expr.CustomFieldMatch(field, op, value) =>
|
||||||
tables.item.id.in(itemsWithCustomField(_.name ==== field)(coll, makeOp(op), value))
|
tables.item.id.in(
|
||||||
|
itemsWithCustomField(_.name ==== field)(coll, makeOp(op), value)
|
||||||
|
)
|
||||||
|
|
||||||
case Expr.CustomFieldIdMatch(field, op, value) =>
|
case Expr.CustomFieldIdMatch(field, op, value) =>
|
||||||
tables.item.id.in(itemsWithCustomField(_.id ==== field)(coll, makeOp(op), value))
|
tables.item.id.in(itemsWithCustomField(_.id ==== field)(coll, makeOp(op), value))
|
||||||
@ -153,6 +155,9 @@ object ItemQueryGenerator {
|
|||||||
case Expr.Fulltext(_) =>
|
case Expr.Fulltext(_) =>
|
||||||
// not supported here
|
// not supported here
|
||||||
Condition.unit
|
Condition.unit
|
||||||
|
|
||||||
|
case _: Expr.MacroExpr =>
|
||||||
|
Condition.unit
|
||||||
}
|
}
|
||||||
|
|
||||||
private def dateToTimestamp(today: LocalDate)(date: Date): Timestamp =
|
private def dateToTimestamp(today: LocalDate)(date: Date): Timestamp =
|
||||||
|
Loading…
x
Reference in New Issue
Block a user