Use a better representation for macros

This commit is contained in:
Eike Kettner 2021-03-02 22:14:26 +01:00
parent a48504debb
commit 71985244f1
5 changed files with 53 additions and 61 deletions

View File

@ -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: _*))

View File

@ -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

View File

@ -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))
} }

View File

@ -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))
}
} }

View File

@ -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 =