mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-05 22:55:58 +00:00
Refactoring parser
- put all used strings in one place to have it easier to track - don't use `$` for shortcuts, it's a detail not interesting to a user; now names must not clash (which is a good idea anyways) - Added two more shortcuts `conc` and `corr`
This commit is contained in:
parent
e681ffa96f
commit
77a87782b7
@ -123,23 +123,40 @@ object ItemQuery {
|
|||||||
sealed trait MacroExpr extends Expr {
|
sealed trait MacroExpr extends Expr {
|
||||||
def body: Expr
|
def body: Expr
|
||||||
}
|
}
|
||||||
case class NamesMacro(searchTerm: String) extends MacroExpr {
|
final case class NamesMacro(searchTerm: String) extends MacroExpr {
|
||||||
val body =
|
val body =
|
||||||
Expr.or(
|
Expr.or(
|
||||||
like(Attr.ItemName, searchTerm),
|
like(Attr.ItemName, searchTerm),
|
||||||
like(Attr.ItemNotes, searchTerm),
|
|
||||||
like(Attr.Correspondent.OrgName, searchTerm),
|
like(Attr.Correspondent.OrgName, searchTerm),
|
||||||
like(Attr.Correspondent.PersonName, searchTerm),
|
like(Attr.Correspondent.PersonName, searchTerm),
|
||||||
like(Attr.Concerning.PersonName, searchTerm),
|
like(Attr.Concerning.PersonName, searchTerm),
|
||||||
like(Attr.Concerning.EquipName, searchTerm)
|
like(Attr.Concerning.EquipName, searchTerm)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
case class DateRangeMacro(attr: DateAttr, left: Date, right: Date) extends MacroExpr {
|
|
||||||
|
final case class CorrMacro(term: String) extends MacroExpr {
|
||||||
val body =
|
val body =
|
||||||
and(date(Operator.Gte, attr, left), date(Operator.Lte, attr, right))
|
Expr.or(
|
||||||
|
like(Attr.Correspondent.OrgName, term),
|
||||||
|
like(Attr.Correspondent.PersonName, term)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
case class YearMacro(attr: DateAttr, year: Int) extends MacroExpr {
|
final case class ConcMacro(term: String) extends MacroExpr {
|
||||||
|
val body =
|
||||||
|
Expr.or(
|
||||||
|
like(Attr.Concerning.PersonName, term),
|
||||||
|
like(Attr.Concerning.EquipName, term)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
final case class DateRangeMacro(attr: DateAttr, left: Date, right: Date)
|
||||||
|
extends MacroExpr {
|
||||||
|
val body =
|
||||||
|
and(date(Operator.Gte, attr, left), date(Operator.Lt, attr, right))
|
||||||
|
}
|
||||||
|
|
||||||
|
final case class YearMacro(attr: DateAttr, year: Int) extends MacroExpr {
|
||||||
val body =
|
val body =
|
||||||
DateRangeMacro(attr, date(year), date(year + 1))
|
DateRangeMacro(attr, date(year), date(year + 1))
|
||||||
|
|
||||||
|
@ -31,9 +31,10 @@ object ParseFailure {
|
|||||||
}
|
}
|
||||||
final case class SimpleMessage(offset: Int, msg: String) extends Message {
|
final case class SimpleMessage(offset: Int, msg: String) extends Message {
|
||||||
def render: String =
|
def render: String =
|
||||||
s"Failed at $offset: $msg"
|
s"Failed at $offset: $msg"
|
||||||
}
|
}
|
||||||
final case class ExpectMessage(offset: Int, expected: List[String], exhaustive: Boolean) extends Message {
|
final case class ExpectMessage(offset: Int, expected: List[String], exhaustive: Boolean)
|
||||||
|
extends Message {
|
||||||
def render: String = {
|
def render: String = {
|
||||||
val opts = expected.mkString(", ")
|
val opts = expected.mkString(", ")
|
||||||
val dots = if (exhaustive) "" else "…"
|
val dots = if (exhaustive) "" else "…"
|
||||||
@ -50,7 +51,8 @@ object ParseFailure {
|
|||||||
|
|
||||||
private[query] def packMsg(msg: Nel[Message]): Nel[Message] = {
|
private[query] def packMsg(msg: Nel[Message]): Nel[Message] = {
|
||||||
val expectMsg = combineExpected(msg.collect({ case em: ExpectMessage => em }))
|
val expectMsg = combineExpected(msg.collect({ case em: ExpectMessage => em }))
|
||||||
.sortBy(_.offset).headOption
|
.sortBy(_.offset)
|
||||||
|
.headOption
|
||||||
|
|
||||||
val simpleMsg = msg.collect({ case sm: SimpleMessage => sm })
|
val simpleMsg = msg.collect({ case sm: SimpleMessage => sm })
|
||||||
|
|
||||||
@ -58,9 +60,16 @@ object ParseFailure {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private[query] def combineExpected(msg: List[ExpectMessage]): List[ExpectMessage] =
|
private[query] def combineExpected(msg: List[ExpectMessage]): List[ExpectMessage] =
|
||||||
msg.groupBy(_.offset).map({ case (offset, es) =>
|
msg
|
||||||
ExpectMessage(offset, es.flatMap(_.expected).distinct.sorted, es.forall(_.exhaustive))
|
.groupBy(_.offset)
|
||||||
}).toList
|
.map({ case (offset, es) =>
|
||||||
|
ExpectMessage(
|
||||||
|
offset,
|
||||||
|
es.flatMap(_.expected).distinct.sorted,
|
||||||
|
es.forall(_.exhaustive)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.toList
|
||||||
|
|
||||||
private[query] def expectationToMsg(e: Parser.Expectation): Message =
|
private[query] def expectationToMsg(e: Parser.Expectation): Message =
|
||||||
e match {
|
e match {
|
||||||
|
@ -3,67 +3,68 @@ package docspell.query.internal
|
|||||||
import cats.parse.{Parser => P}
|
import cats.parse.{Parser => P}
|
||||||
|
|
||||||
import docspell.query.ItemQuery.Attr
|
import docspell.query.ItemQuery.Attr
|
||||||
|
import docspell.query.internal.{Constants => C}
|
||||||
|
|
||||||
object AttrParser {
|
object AttrParser {
|
||||||
|
|
||||||
val name: P[Attr.StringAttr] =
|
val name: P[Attr.StringAttr] =
|
||||||
P.ignoreCase("name").as(Attr.ItemName)
|
P.ignoreCase(C.name).as(Attr.ItemName)
|
||||||
|
|
||||||
val source: P[Attr.StringAttr] =
|
val source: P[Attr.StringAttr] =
|
||||||
P.ignoreCase("source").as(Attr.ItemSource)
|
P.ignoreCase(C.source).as(Attr.ItemSource)
|
||||||
|
|
||||||
val id: P[Attr.StringAttr] =
|
val id: P[Attr.StringAttr] =
|
||||||
P.ignoreCase("id").as(Attr.ItemId)
|
P.ignoreCase(C.id).as(Attr.ItemId)
|
||||||
|
|
||||||
val date: P[Attr.DateAttr] =
|
val date: P[Attr.DateAttr] =
|
||||||
P.ignoreCase("date").as(Attr.Date)
|
P.ignoreCase(C.date).as(Attr.Date)
|
||||||
|
|
||||||
val notes: P[Attr.StringAttr] =
|
val notes: P[Attr.StringAttr] =
|
||||||
P.ignoreCase("notes").as(Attr.ItemNotes)
|
P.ignoreCase(C.notes).as(Attr.ItemNotes)
|
||||||
|
|
||||||
val dueDate: P[Attr.DateAttr] =
|
val dueDate: P[Attr.DateAttr] =
|
||||||
P.stringIn(List("dueDate", "due", "due-date")).as(Attr.DueDate)
|
P.ignoreCase(C.due).as(Attr.DueDate)
|
||||||
|
|
||||||
val corrOrgId: P[Attr.StringAttr] =
|
val corrOrgId: P[Attr.StringAttr] =
|
||||||
P.stringIn(List("correspondent.org.id", "corr.org.id"))
|
P.ignoreCase(C.corrOrgId)
|
||||||
.as(Attr.Correspondent.OrgId)
|
.as(Attr.Correspondent.OrgId)
|
||||||
|
|
||||||
val corrOrgName: P[Attr.StringAttr] =
|
val corrOrgName: P[Attr.StringAttr] =
|
||||||
P.stringIn(List("correspondent.org.name", "corr.org.name"))
|
P.ignoreCase(C.corrOrgName)
|
||||||
.as(Attr.Correspondent.OrgName)
|
.as(Attr.Correspondent.OrgName)
|
||||||
|
|
||||||
val corrPersId: P[Attr.StringAttr] =
|
val corrPersId: P[Attr.StringAttr] =
|
||||||
P.stringIn(List("correspondent.person.id", "corr.pers.id"))
|
P.ignoreCase(C.corrPersId)
|
||||||
.as(Attr.Correspondent.PersonId)
|
.as(Attr.Correspondent.PersonId)
|
||||||
|
|
||||||
val corrPersName: P[Attr.StringAttr] =
|
val corrPersName: P[Attr.StringAttr] =
|
||||||
P.stringIn(List("correspondent.person.name", "corr.pers.name"))
|
P.ignoreCase(C.corrPersName)
|
||||||
.as(Attr.Correspondent.PersonName)
|
.as(Attr.Correspondent.PersonName)
|
||||||
|
|
||||||
val concPersId: P[Attr.StringAttr] =
|
val concPersId: P[Attr.StringAttr] =
|
||||||
P.stringIn(List("concerning.person.id", "conc.pers.id"))
|
P.ignoreCase(C.concPersId)
|
||||||
.as(Attr.Concerning.PersonId)
|
.as(Attr.Concerning.PersonId)
|
||||||
|
|
||||||
val concPersName: P[Attr.StringAttr] =
|
val concPersName: P[Attr.StringAttr] =
|
||||||
P.stringIn(List("concerning.person.name", "conc.pers.name"))
|
P.ignoreCase(C.concPersName)
|
||||||
.as(Attr.Concerning.PersonName)
|
.as(Attr.Concerning.PersonName)
|
||||||
|
|
||||||
val concEquipId: P[Attr.StringAttr] =
|
val concEquipId: P[Attr.StringAttr] =
|
||||||
P.stringIn(List("concerning.equip.id", "conc.equip.id"))
|
P.ignoreCase(C.concEquipId)
|
||||||
.as(Attr.Concerning.EquipId)
|
.as(Attr.Concerning.EquipId)
|
||||||
|
|
||||||
val concEquipName: P[Attr.StringAttr] =
|
val concEquipName: P[Attr.StringAttr] =
|
||||||
P.stringIn(List("concerning.equip.name", "conc.equip.name"))
|
P.ignoreCase(C.concEquipName)
|
||||||
.as(Attr.Concerning.EquipName)
|
.as(Attr.Concerning.EquipName)
|
||||||
|
|
||||||
val folderId: P[Attr.StringAttr] =
|
val folderId: P[Attr.StringAttr] =
|
||||||
P.ignoreCase("folder.id").as(Attr.Folder.FolderId)
|
P.ignoreCase(C.folderId).as(Attr.Folder.FolderId)
|
||||||
|
|
||||||
val folderName: P[Attr.StringAttr] =
|
val folderName: P[Attr.StringAttr] =
|
||||||
P.ignoreCase("folder").as(Attr.Folder.FolderName)
|
P.ignoreCase(C.folder).as(Attr.Folder.FolderName)
|
||||||
|
|
||||||
val attachCountAttr: P[Attr.IntAttr] =
|
val attachCountAttr: P[Attr.IntAttr] =
|
||||||
P.ignoreCase("attach.count").as(Attr.AttachCount)
|
P.ignoreCase(C.attachCount).as(Attr.AttachCount)
|
||||||
|
|
||||||
// combining grouped by type
|
// combining grouped by type
|
||||||
|
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
package docspell.query.internal
|
||||||
|
|
||||||
|
object Constants {
|
||||||
|
|
||||||
|
val attachCount = "attach.count"
|
||||||
|
val attachId = "attach.id"
|
||||||
|
val cat = "cat"
|
||||||
|
val checksum = "checksum"
|
||||||
|
val conc = "conc"
|
||||||
|
val concEquipId = "conc.equip.id"
|
||||||
|
val concEquipName = "conc.equip.name"
|
||||||
|
val concPersId = "conc.pers.id"
|
||||||
|
val concPersName = "conc.pers.name"
|
||||||
|
val content = "content"
|
||||||
|
val corr = "corr"
|
||||||
|
val corrOrgId = "corr.org.id"
|
||||||
|
val corrOrgName = "corr.org.name"
|
||||||
|
val corrPersId = "corr.pers.id"
|
||||||
|
val corrPersName = "corr.pers.name"
|
||||||
|
val customField = "f"
|
||||||
|
val customFieldId = "f.id"
|
||||||
|
val date = "date"
|
||||||
|
val dateIn = "dateIn"
|
||||||
|
val due = "due"
|
||||||
|
val dueIn = "dueIn"
|
||||||
|
val exist = "exist"
|
||||||
|
val folder = "folder"
|
||||||
|
val folderId = "folder.id"
|
||||||
|
val id = "id"
|
||||||
|
val inbox = "inbox"
|
||||||
|
val incoming = "incoming"
|
||||||
|
val name = "name"
|
||||||
|
val names = "names"
|
||||||
|
val notPrefix = '!'
|
||||||
|
val notes = "notes"
|
||||||
|
val source = "source"
|
||||||
|
val tag = "tag"
|
||||||
|
val tagId = "tag.id"
|
||||||
|
val year = "year"
|
||||||
|
|
||||||
|
// operators
|
||||||
|
val eqs = '='
|
||||||
|
val gt = '>'
|
||||||
|
val gte = ">="
|
||||||
|
val in = "~="
|
||||||
|
val like = ':'
|
||||||
|
val lt = '<'
|
||||||
|
val lte = "<="
|
||||||
|
val neq = "!="
|
||||||
|
}
|
@ -4,6 +4,7 @@ import cats.parse.{Parser => P}
|
|||||||
|
|
||||||
import docspell.query.ItemQuery
|
import docspell.query.ItemQuery
|
||||||
import docspell.query.ItemQuery._
|
import docspell.query.ItemQuery._
|
||||||
|
import docspell.query.internal.{Constants => C}
|
||||||
|
|
||||||
object ExprParser {
|
object ExprParser {
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ object ExprParser {
|
|||||||
.map(Expr.OrExpr.apply)
|
.map(Expr.OrExpr.apply)
|
||||||
|
|
||||||
def not(inner: P[Expr]): P[Expr] =
|
def not(inner: P[Expr]): P[Expr] =
|
||||||
(P.char('!') *> inner).map(_.negate)
|
(P.char(C.notPrefix) *> inner).map(_.negate)
|
||||||
|
|
||||||
val exprParser: P[Expr] =
|
val exprParser: P[Expr] =
|
||||||
P.recursive[Expr] { recurse =>
|
P.recursive[Expr] { recurse =>
|
||||||
@ -28,7 +29,7 @@ object ExprParser {
|
|||||||
val orP = or(recurse)
|
val orP = or(recurse)
|
||||||
val notP = not(recurse)
|
val notP = not(recurse)
|
||||||
val macros = MacroParser.all
|
val macros = MacroParser.all
|
||||||
P.oneOf(SimpleExprParser.simpleExpr :: macros :: andP :: orP :: notP :: Nil)
|
P.oneOf(macros :: SimpleExprParser.simpleExpr :: andP :: orP :: notP :: Nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
def parseQuery(input: String): Either[P.Error, ItemQuery] = {
|
def parseQuery(input: String): Either[P.Error, ItemQuery] = {
|
||||||
|
@ -3,10 +3,11 @@ package docspell.query.internal
|
|||||||
import cats.parse.{Parser => P}
|
import cats.parse.{Parser => P}
|
||||||
|
|
||||||
import docspell.query.ItemQuery._
|
import docspell.query.ItemQuery._
|
||||||
|
import docspell.query.internal.{Constants => C}
|
||||||
|
|
||||||
object MacroParser {
|
object MacroParser {
|
||||||
private def macroDef(name: String): P[Unit] =
|
private def macroDef(name: String): P[Unit] =
|
||||||
P.char('$').soft.with1 *> P.string(name) <* P.char(':')
|
P.ignoreCase(name).soft.with1 <* P.char(':')
|
||||||
|
|
||||||
private def dateRangeMacroImpl(
|
private def dateRangeMacroImpl(
|
||||||
name: String,
|
name: String,
|
||||||
@ -20,20 +21,35 @@ object MacroParser {
|
|||||||
(macroDef(name) *> DateParser.yearOnly).map(year => Expr.YearMacro(attr, year))
|
(macroDef(name) *> DateParser.yearOnly).map(year => Expr.YearMacro(attr, year))
|
||||||
|
|
||||||
val namesMacro: P[Expr.NamesMacro] =
|
val namesMacro: P[Expr.NamesMacro] =
|
||||||
(macroDef("names") *> BasicParser.singleString).map(Expr.NamesMacro.apply)
|
(macroDef(C.names) *> BasicParser.singleString).map(Expr.NamesMacro.apply)
|
||||||
|
|
||||||
val dateRangeMacro: P[Expr.DateRangeMacro] =
|
val dateRangeMacro: P[Expr.DateRangeMacro] =
|
||||||
dateRangeMacroImpl("datein", Attr.Date)
|
dateRangeMacroImpl(C.dateIn, Attr.Date)
|
||||||
|
|
||||||
val dueDateRangeMacro: P[Expr.DateRangeMacro] =
|
val dueDateRangeMacro: P[Expr.DateRangeMacro] =
|
||||||
dateRangeMacroImpl("duein", Attr.DueDate)
|
dateRangeMacroImpl(C.dueIn, Attr.DueDate)
|
||||||
|
|
||||||
val yearDateMacro: P[Expr.YearMacro] =
|
val yearDateMacro: P[Expr.YearMacro] =
|
||||||
yearMacroImpl("year", Attr.Date)
|
yearMacroImpl(C.year, Attr.Date)
|
||||||
|
|
||||||
|
val corrMacro: P[Expr.CorrMacro] =
|
||||||
|
(macroDef(C.corr) *> BasicParser.singleString).map(Expr.CorrMacro.apply)
|
||||||
|
|
||||||
|
val concMacro: P[Expr.ConcMacro] =
|
||||||
|
(macroDef(C.conc) *> BasicParser.singleString).map(Expr.ConcMacro.apply)
|
||||||
|
|
||||||
// --- all macro parser
|
// --- all macro parser
|
||||||
|
|
||||||
val all: P[Expr] =
|
val all: P[Expr] =
|
||||||
P.oneOf(List(namesMacro, dateRangeMacro, dueDateRangeMacro, yearDateMacro))
|
P.oneOf(
|
||||||
|
List(
|
||||||
|
namesMacro,
|
||||||
|
dateRangeMacro,
|
||||||
|
dueDateRangeMacro,
|
||||||
|
yearDateMacro,
|
||||||
|
corrMacro,
|
||||||
|
concMacro
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,37 +3,38 @@ package docspell.query.internal
|
|||||||
import cats.parse.{Parser => P}
|
import cats.parse.{Parser => P}
|
||||||
|
|
||||||
import docspell.query.ItemQuery._
|
import docspell.query.ItemQuery._
|
||||||
|
import docspell.query.internal.{Constants => C}
|
||||||
|
|
||||||
object OperatorParser {
|
object OperatorParser {
|
||||||
private[this] val Eq: P[Operator] =
|
private[this] val Eq: P[Operator] =
|
||||||
P.char('=').as(Operator.Eq)
|
P.char(C.eqs).as(Operator.Eq)
|
||||||
|
|
||||||
private[this] val Neq: P[Operator] =
|
private[this] val Neq: P[Operator] =
|
||||||
P.string("!=").as(Operator.Neq)
|
P.string(C.neq).as(Operator.Neq)
|
||||||
|
|
||||||
private[this] val Like: P[Operator] =
|
private[this] val Like: P[Operator] =
|
||||||
P.char(':').as(Operator.Like)
|
P.char(C.like).as(Operator.Like)
|
||||||
|
|
||||||
private[this] val Gt: P[Operator] =
|
private[this] val Gt: P[Operator] =
|
||||||
P.char('>').as(Operator.Gt)
|
P.char(C.gt).as(Operator.Gt)
|
||||||
|
|
||||||
private[this] val Lt: P[Operator] =
|
private[this] val Lt: P[Operator] =
|
||||||
P.char('<').as(Operator.Lt)
|
P.char(C.lt).as(Operator.Lt)
|
||||||
|
|
||||||
private[this] val Gte: P[Operator] =
|
private[this] val Gte: P[Operator] =
|
||||||
P.string(">=").as(Operator.Gte)
|
P.string(C.gte).as(Operator.Gte)
|
||||||
|
|
||||||
private[this] val Lte: P[Operator] =
|
private[this] val Lte: P[Operator] =
|
||||||
P.string("<=").as(Operator.Lte)
|
P.string(C.lte).as(Operator.Lte)
|
||||||
|
|
||||||
val op: P[Operator] =
|
val op: P[Operator] =
|
||||||
P.oneOf(List(Like, Eq, Neq, Gte, Lte, Gt, Lt))
|
P.oneOf(List(Like, Eq, Neq, Gte, Lte, Gt, Lt))
|
||||||
|
|
||||||
private[this] val anyOp: P[TagOperator] =
|
private[this] val anyOp: P[TagOperator] =
|
||||||
P.char(':').as(TagOperator.AnyMatch)
|
P.char(C.like).as(TagOperator.AnyMatch)
|
||||||
|
|
||||||
private[this] val allOp: P[TagOperator] =
|
private[this] val allOp: P[TagOperator] =
|
||||||
P.char('=').as(TagOperator.AllMatch)
|
P.char(C.eqs).as(TagOperator.AllMatch)
|
||||||
|
|
||||||
val tagOp: P[TagOperator] =
|
val tagOp: P[TagOperator] =
|
||||||
P.oneOf(List(anyOp, allOp))
|
P.oneOf(List(anyOp, allOp))
|
||||||
|
@ -4,6 +4,7 @@ import cats.parse.Numbers
|
|||||||
import cats.parse.{Parser => P}
|
import cats.parse.{Parser => P}
|
||||||
|
|
||||||
import docspell.query.ItemQuery._
|
import docspell.query.ItemQuery._
|
||||||
|
import docspell.query.internal.{Constants => C}
|
||||||
|
|
||||||
object SimpleExprParser {
|
object SimpleExprParser {
|
||||||
|
|
||||||
@ -11,7 +12,7 @@ object SimpleExprParser {
|
|||||||
OperatorParser.op.surroundedBy(BasicParser.ws0)
|
OperatorParser.op.surroundedBy(BasicParser.ws0)
|
||||||
|
|
||||||
private[this] val inOp: P[Unit] =
|
private[this] val inOp: P[Unit] =
|
||||||
P.string("~=").surroundedBy(BasicParser.ws0)
|
P.string(C.in).surroundedBy(BasicParser.ws0)
|
||||||
|
|
||||||
private[this] val inOrOpStr =
|
private[this] val inOrOpStr =
|
||||||
P.eitherOr(op ~ BasicParser.singleString, inOp *> BasicParser.stringOrMore)
|
P.eitherOr(op ~ BasicParser.singleString, inOp *> BasicParser.stringOrMore)
|
||||||
@ -44,52 +45,63 @@ object SimpleExprParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val existsExpr: P[Expr.Exists] =
|
val existsExpr: P[Expr.Exists] =
|
||||||
(P.ignoreCase("exists:") *> AttrParser.anyAttr).map(attr => Expr.Exists(attr))
|
(P.ignoreCase(C.exist) *> P.char(C.like) *> AttrParser.anyAttr).map(attr =>
|
||||||
|
Expr.Exists(attr)
|
||||||
|
)
|
||||||
|
|
||||||
val fulltextExpr: P[Expr.Fulltext] =
|
val fulltextExpr: P[Expr.Fulltext] =
|
||||||
(P.ignoreCase("content:") *> BasicParser.singleString).map(q => Expr.Fulltext(q))
|
(P.ignoreCase(C.content) *> P.char(C.like) *> BasicParser.singleString).map(q =>
|
||||||
|
Expr.Fulltext(q)
|
||||||
|
)
|
||||||
|
|
||||||
val tagIdExpr: P[Expr.TagIdsMatch] =
|
val tagIdExpr: P[Expr.TagIdsMatch] =
|
||||||
(P.ignoreCase("tag.id") *> OperatorParser.tagOp ~ BasicParser.stringOrMore).map {
|
(P.ignoreCase(C.tagId) *> OperatorParser.tagOp ~ BasicParser.stringOrMore).map {
|
||||||
case (op, values) =>
|
case (op, values) =>
|
||||||
Expr.TagIdsMatch(op, values)
|
Expr.TagIdsMatch(op, values)
|
||||||
}
|
}
|
||||||
|
|
||||||
val tagExpr: P[Expr.TagsMatch] =
|
val tagExpr: P[Expr.TagsMatch] =
|
||||||
(P.ignoreCase("tag") *> OperatorParser.tagOp ~ BasicParser.stringOrMore).map {
|
(P.ignoreCase(C.tag) *> OperatorParser.tagOp ~ BasicParser.stringOrMore).map {
|
||||||
case (op, values) =>
|
case (op, values) =>
|
||||||
Expr.TagsMatch(op, values)
|
Expr.TagsMatch(op, values)
|
||||||
}
|
}
|
||||||
|
|
||||||
val catExpr: P[Expr.TagCategoryMatch] =
|
val catExpr: P[Expr.TagCategoryMatch] =
|
||||||
(P.ignoreCase("cat") *> OperatorParser.tagOp ~ BasicParser.stringOrMore).map {
|
(P.ignoreCase(C.cat) *> OperatorParser.tagOp ~ BasicParser.stringOrMore).map {
|
||||||
case (op, values) =>
|
case (op, values) =>
|
||||||
Expr.TagCategoryMatch(op, values)
|
Expr.TagCategoryMatch(op, values)
|
||||||
}
|
}
|
||||||
|
|
||||||
val customFieldExpr: P[Expr.CustomFieldMatch] =
|
val customFieldExpr: P[Expr.CustomFieldMatch] =
|
||||||
(P.string("f:") *> BasicParser.identParser ~ op ~ BasicParser.singleString).map {
|
(P.string(C.customField) *> P.char(
|
||||||
case ((name, op), value) =>
|
C.like
|
||||||
|
) *> BasicParser.identParser ~ op ~ BasicParser.singleString)
|
||||||
|
.map { case ((name, op), value) =>
|
||||||
Expr.CustomFieldMatch(name, op, value)
|
Expr.CustomFieldMatch(name, op, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
val customFieldIdExpr: P[Expr.CustomFieldIdMatch] =
|
val customFieldIdExpr: P[Expr.CustomFieldIdMatch] =
|
||||||
(P.string("f.id:") *> BasicParser.identParser ~ op ~ BasicParser.singleString).map {
|
(P.string(C.customFieldId) *> P.char(
|
||||||
case ((name, op), value) =>
|
C.like
|
||||||
|
) *> BasicParser.identParser ~ op ~ BasicParser.singleString)
|
||||||
|
.map { case ((name, op), value) =>
|
||||||
Expr.CustomFieldIdMatch(name, op, value)
|
Expr.CustomFieldIdMatch(name, op, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
val inboxExpr: P[Expr.InboxExpr] =
|
val inboxExpr: P[Expr.InboxExpr] =
|
||||||
(P.string("inbox:") *> BasicParser.bool).map(Expr.InboxExpr.apply)
|
(P.string(C.inbox) *> P.char(C.like) *> BasicParser.bool).map(Expr.InboxExpr.apply)
|
||||||
|
|
||||||
val dirExpr: P[Expr.DirectionExpr] =
|
val dirExpr: P[Expr.DirectionExpr] =
|
||||||
(P.string("incoming:") *> BasicParser.bool).map(Expr.DirectionExpr.apply)
|
(P.string(C.incoming) *> P.char(C.like) *> BasicParser.bool)
|
||||||
|
.map(Expr.DirectionExpr.apply)
|
||||||
|
|
||||||
val checksumExpr: P[Expr.ChecksumMatch] =
|
val checksumExpr: P[Expr.ChecksumMatch] =
|
||||||
(P.string("checksum:") *> BasicParser.singleString).map(Expr.ChecksumMatch.apply)
|
(P.string(C.checksum) *> P.char(C.like) *> BasicParser.singleString)
|
||||||
|
.map(Expr.ChecksumMatch.apply)
|
||||||
|
|
||||||
val attachIdExpr: P[Expr.AttachId] =
|
val attachIdExpr: P[Expr.AttachId] =
|
||||||
(P.ignoreCase("attach.id:") *> BasicParser.singleString).map(Expr.AttachId.apply)
|
(P.ignoreCase(C.attachId) *> P.char(C.eqs) *> BasicParser.singleString)
|
||||||
|
.map(Expr.AttachId.apply)
|
||||||
|
|
||||||
val simpleExpr: P[Expr] =
|
val simpleExpr: P[Expr] =
|
||||||
P.oneOf(
|
P.oneOf(
|
||||||
|
@ -42,9 +42,9 @@ class FulltextExtractTest extends FunSuite {
|
|||||||
|
|
||||||
test("find fulltext within and") {
|
test("find fulltext within and") {
|
||||||
assertFtsSuccess("content:what name:test", "what".some)
|
assertFtsSuccess("content:what name:test", "what".some)
|
||||||
assertFtsSuccess("$names:marc* content:what name:test", "what".some)
|
assertFtsSuccess("names:marc* content:what name:test", "what".some)
|
||||||
assertFtsSuccess(
|
assertFtsSuccess(
|
||||||
"$names:marc* date:2021-02 content:\"what else\" name:test",
|
"names:marc* date:2021-02 content:\"what else\" name:test",
|
||||||
"what else".some
|
"what else".some
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -13,10 +13,8 @@ class AttrParserTest extends FunSuite {
|
|||||||
assertEquals(p.parseAll("id"), Right(Attr.ItemId))
|
assertEquals(p.parseAll("id"), Right(Attr.ItemId))
|
||||||
assertEquals(p.parseAll("corr.org.id"), Right(Attr.Correspondent.OrgId))
|
assertEquals(p.parseAll("corr.org.id"), Right(Attr.Correspondent.OrgId))
|
||||||
assertEquals(p.parseAll("corr.org.name"), Right(Attr.Correspondent.OrgName))
|
assertEquals(p.parseAll("corr.org.name"), Right(Attr.Correspondent.OrgName))
|
||||||
assertEquals(p.parseAll("correspondent.org.name"), Right(Attr.Correspondent.OrgName))
|
|
||||||
assertEquals(p.parseAll("conc.pers.id"), Right(Attr.Concerning.PersonId))
|
assertEquals(p.parseAll("conc.pers.id"), Right(Attr.Concerning.PersonId))
|
||||||
assertEquals(p.parseAll("conc.pers.name"), Right(Attr.Concerning.PersonName))
|
assertEquals(p.parseAll("conc.pers.name"), Right(Attr.Concerning.PersonName))
|
||||||
assertEquals(p.parseAll("concerning.person.name"), Right(Attr.Concerning.PersonName))
|
|
||||||
assertEquals(p.parseAll("folder"), Right(Attr.Folder.FolderName))
|
assertEquals(p.parseAll("folder"), Right(Attr.Folder.FolderName))
|
||||||
assertEquals(p.parseAll("folder.id"), Right(Attr.Folder.FolderId))
|
assertEquals(p.parseAll("folder.id"), Right(Attr.Folder.FolderId))
|
||||||
}
|
}
|
||||||
@ -24,14 +22,12 @@ class AttrParserTest extends FunSuite {
|
|||||||
test("date attributes") {
|
test("date attributes") {
|
||||||
val p = AttrParser.dateAttr
|
val p = AttrParser.dateAttr
|
||||||
assertEquals(p.parseAll("date"), Right(Attr.Date))
|
assertEquals(p.parseAll("date"), Right(Attr.Date))
|
||||||
assertEquals(p.parseAll("dueDate"), Right(Attr.DueDate))
|
|
||||||
assertEquals(p.parseAll("due"), Right(Attr.DueDate))
|
assertEquals(p.parseAll("due"), Right(Attr.DueDate))
|
||||||
}
|
}
|
||||||
|
|
||||||
test("all attributes parser") {
|
test("all attributes parser") {
|
||||||
val p = AttrParser.anyAttr
|
val p = AttrParser.anyAttr
|
||||||
assertEquals(p.parseAll("date"), Right(Attr.Date))
|
assertEquals(p.parseAll("date"), Right(Attr.Date))
|
||||||
assertEquals(p.parseAll("dueDate"), Right(Attr.DueDate))
|
|
||||||
assertEquals(p.parseAll("name"), Right(Attr.ItemName))
|
assertEquals(p.parseAll("name"), Right(Attr.ItemName))
|
||||||
assertEquals(p.parseAll("source"), Right(Attr.ItemSource))
|
assertEquals(p.parseAll("source"), Right(Attr.ItemSource))
|
||||||
assertEquals(p.parseAll("id"), Right(Attr.ItemId))
|
assertEquals(p.parseAll("id"), Right(Attr.ItemId))
|
||||||
|
@ -6,10 +6,10 @@ import docspell.query.ItemQuery.Expr
|
|||||||
|
|
||||||
class MacroParserTest extends FunSuite {
|
class MacroParserTest extends FunSuite {
|
||||||
|
|
||||||
test("start with $") {
|
test("recognize names shortcut") {
|
||||||
val p = MacroParser.namesMacro
|
val p = MacroParser.namesMacro
|
||||||
assertEquals(p.parseAll("$names:test"), Right(Expr.NamesMacro("test")))
|
assertEquals(p.parseAll("names:test"), Right(Expr.NamesMacro("test")))
|
||||||
assert(p.parseAll("names:test").isLeft)
|
assert(p.parseAll("$names:test").isLeft)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -77,10 +77,10 @@ class SimpleExprParserTest extends FunSuite with ValueHelper {
|
|||||||
|
|
||||||
test("exists expr") {
|
test("exists expr") {
|
||||||
val p = SimpleExprParser.existsExpr
|
val p = SimpleExprParser.existsExpr
|
||||||
assertEquals(p.parseAll("exists:name"), Right(Expr.Exists(Attr.ItemName)))
|
assertEquals(p.parseAll("exist:name"), Right(Expr.Exists(Attr.ItemName)))
|
||||||
assert(p.parseAll("exists:blabla").isLeft)
|
assert(p.parseAll("exist:blabla").isLeft)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
p.parseAll("exists:conc.pers.id"),
|
p.parseAll("exist:conc.pers.id"),
|
||||||
Right(Expr.Exists(Attr.Concerning.PersonId))
|
Right(Expr.Exists(Attr.Concerning.PersonId))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -153,7 +153,7 @@ class SimpleExprParserTest extends FunSuite with ValueHelper {
|
|||||||
Right(dateExpr(Operator.Lt, Attr.DueDate, ld(2021, 3, 14)))
|
Right(dateExpr(Operator.Lt, Attr.DueDate, ld(2021, 3, 14)))
|
||||||
)
|
)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
p.parseAll("exists:conc.pers.id"),
|
p.parseAll("exist:conc.pers.id"),
|
||||||
Right(Expr.Exists(Attr.Concerning.PersonId))
|
Right(Expr.Exists(Attr.Concerning.PersonId))
|
||||||
)
|
)
|
||||||
assertEquals(p.parseAll("content:test"), Right(Expr.Fulltext("test")))
|
assertEquals(p.parseAll("content:test"), Right(Expr.Fulltext("test")))
|
||||||
|
@ -35,7 +35,8 @@ object ItemQueryGeneratorTest extends SimpleTestSuite {
|
|||||||
val cond = ItemQueryGenerator(now, tables, Ident.unsafe("coll"))(q)
|
val cond = ItemQueryGenerator(now, tables, Ident.unsafe("coll"))(q)
|
||||||
val expect =
|
val expect =
|
||||||
tables.item.name.like("hello") &&
|
tables.item.name.like("hello") &&
|
||||||
tables.item.itemDate >= mkTimestamp(2020, 2, 1) &&
|
coalesce(tables.item.itemDate.s, tables.item.created.s) >=
|
||||||
|
mkTimestamp(2020, 2, 1) &&
|
||||||
(tables.item.source.like("expense%") || tables.folder.name === "test")
|
(tables.item.source.like("expense%") || tables.folder.name === "test")
|
||||||
|
|
||||||
assertEquals(cond, expect)
|
assertEquals(cond, expect)
|
||||||
|
@ -140,16 +140,16 @@ render q =
|
|||||||
"folder.id" ++ attrMatch m ++ quoteStr id
|
"folder.id" ++ attrMatch m ++ quoteStr id
|
||||||
|
|
||||||
CorrOrgId m id ->
|
CorrOrgId m id ->
|
||||||
"correspondent.org.id" ++ attrMatch m ++ quoteStr id
|
"corr.org.id" ++ attrMatch m ++ quoteStr id
|
||||||
|
|
||||||
CorrPersId m id ->
|
CorrPersId m id ->
|
||||||
"correspondent.person.id" ++ attrMatch m ++ quoteStr id
|
"corr.pers.id" ++ attrMatch m ++ quoteStr id
|
||||||
|
|
||||||
ConcPersId m id ->
|
ConcPersId m id ->
|
||||||
"concerning.person.id" ++ attrMatch m ++ quoteStr id
|
"conc.pers.id" ++ attrMatch m ++ quoteStr id
|
||||||
|
|
||||||
ConcEquipId m id ->
|
ConcEquipId m id ->
|
||||||
"concerning.equip.id" ++ attrMatch m ++ quoteStr id
|
"conc.equip.id" ++ attrMatch m ++ quoteStr id
|
||||||
|
|
||||||
CustomField m kv ->
|
CustomField m kv ->
|
||||||
"f:" ++ kv.field ++ attrMatch m ++ quoteStr kv.value
|
"f:" ++ kv.field ++ attrMatch m ++ quoteStr kv.value
|
||||||
|
Loading…
x
Reference in New Issue
Block a user