mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-04-04 10:29:34 +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
|
||||
|
||||
// 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 =
|
||||
OrExpr(Nel.of(expr0, exprs: _*))
|
||||
|
||||
|
@ -5,8 +5,8 @@ import docspell.query.ItemQuery._
|
||||
|
||||
object ExprUtil {
|
||||
|
||||
/** Does some basic transformation, like unfolding deeply nested and
|
||||
* trees containing one value etc.
|
||||
/** Does some basic transformation, like unfolding nested and trees
|
||||
* containing one value etc.
|
||||
*/
|
||||
def reduce(expr: Expr): Expr =
|
||||
expr match {
|
||||
@ -30,6 +30,9 @@ object ExprUtil {
|
||||
expr
|
||||
}
|
||||
|
||||
case m: MacroExpr =>
|
||||
reduce(m.body)
|
||||
|
||||
case DirectionExpr(_) =>
|
||||
expr
|
||||
|
||||
|
@ -5,61 +5,29 @@ import cats.parse.{Parser => P}
|
||||
import docspell.query.ItemQuery._
|
||||
|
||||
object MacroParser {
|
||||
private[this] val macroDef: P[String] =
|
||||
P.char('$') *> BasicParser.identParser <* P.char(':')
|
||||
private def macroDef(name: String): P[Unit] =
|
||||
P.char('$').soft.with1 *> P.string(name) <* P.char(':')
|
||||
|
||||
def parser[A](macros: Map[String, P[A]]): P[A] = {
|
||||
val p: P[P[A]] = macroDef.map { name =>
|
||||
macros
|
||||
.get(name)
|
||||
.getOrElse(P.failWith(s"Unknown macro: $name"))
|
||||
private def dateRangeMacroImpl(
|
||||
name: String,
|
||||
attr: Attr.DateAttr
|
||||
): P[Expr.DateRangeMacro] =
|
||||
(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) =>
|
||||
pexpr
|
||||
.parseAll(str)
|
||||
.left
|
||||
.map(err => err.copy(failedAtOffset = err.failedAtOffset + index))
|
||||
}
|
||||
val namesMacro: P[Expr.NamesMacro] =
|
||||
(macroDef("names") *> BasicParser.singleString).map(Expr.NamesMacro.apply)
|
||||
|
||||
P.select(px)(P.Fail)
|
||||
}
|
||||
val dateRangeMacro: P[Expr.DateRangeMacro] =
|
||||
dateRangeMacroImpl("datein", Attr.Date)
|
||||
|
||||
// --- definitions of available macros
|
||||
|
||||
/** 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)
|
||||
)
|
||||
}
|
||||
val dueDateRangeMacro: P[Expr.DateRangeMacro] =
|
||||
dateRangeMacroImpl("duein", Attr.DueDate)
|
||||
|
||||
// --- all macro parser
|
||||
|
||||
val allMacros: Map[String, P[Expr]] =
|
||||
Map(
|
||||
"names" -> names,
|
||||
"datein" -> dateRange(Attr.Date),
|
||||
"duein" -> dateRange(Attr.DueDate)
|
||||
)
|
||||
|
||||
val all: P[Expr] =
|
||||
parser(allMacros)
|
||||
P.oneOf(List(namesMacro, dateRangeMacro, dueDateRangeMacro))
|
||||
|
||||
}
|
||||
|
@ -1,19 +1,15 @@
|
||||
package docspell.query.internal
|
||||
|
||||
import munit._
|
||||
import cats.parse.{Parser => P}
|
||||
//import cats.parse.{Parser => P}
|
||||
import docspell.query.ItemQuery.Expr
|
||||
|
||||
class MacroParserTest extends FunSuite {
|
||||
|
||||
test("fail with unkown macro names") {
|
||||
val p = MacroParser.parser(Map.empty)
|
||||
assert(p.parseAll("$bla:blup").isLeft) // TODO check error message
|
||||
test("start with $") {
|
||||
val p = MacroParser.namesMacro
|
||||
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) =>
|
||||
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) =>
|
||||
tables.item.id.in(itemsWithCustomField(_.id ==== field)(coll, makeOp(op), value))
|
||||
@ -153,6 +155,9 @@ object ItemQueryGenerator {
|
||||
case Expr.Fulltext(_) =>
|
||||
// not supported here
|
||||
Condition.unit
|
||||
|
||||
case _: Expr.MacroExpr =>
|
||||
Condition.unit
|
||||
}
|
||||
|
||||
private def dateToTimestamp(today: LocalDate)(date: Date): Timestamp =
|
||||
@ -233,7 +238,7 @@ object ItemQueryGenerator {
|
||||
}
|
||||
|
||||
private def itemsWithCustomField(
|
||||
sel: RCustomField.Table => Condition
|
||||
sel: RCustomField.Table => Condition
|
||||
)(coll: Ident, op: QOp, value: String): Select = {
|
||||
val cf = RCustomField.as("cf")
|
||||
val cfv = RCustomFieldValue.as("cfv")
|
||||
|
Loading…
x
Reference in New Issue
Block a user