diff --git a/.gitignore b/.gitignore
index 1e3ada7b..3615157f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,5 +14,5 @@ project/target/
/gui/bin/
/gui/.cache-main
/gui/.cache-tests
-/testCases/*/out.*
-*.idea/
+/testCases/**/out.*
+/**/.idea/
diff --git a/base/src/main/scala/co/uproot/abandon/Ast.scala b/base/src/main/scala/co/uproot/abandon/Ast.scala
index 5087ea27..5e301414 100644
--- a/base/src/main/scala/co/uproot/abandon/Ast.scala
+++ b/base/src/main/scala/co/uproot/abandon/Ast.scala
@@ -118,6 +118,134 @@ case class Date(year: Int, month: Int, day: Int) {
}
}
+/**
+ * Trait for stackable Transaction filters
+ * These filters operate on "raw" AST level,
+ * so all filtering decisions are based on unprocessed
+ * information.
+ *
+ * TxnFilterStack is used to glue these filters together.
+ */
+sealed trait TransactionFilter {
+ def filter(txn: Transaction): Boolean
+ def description(): String
+ def xmlDescription(): xml.Node
+}
+
+/*
+ * Txn:Time filters
+ * To create filter for time span, stack "onOrAfter" and "before" filters.
+ */
+case class BeforeDateTxnFilter(before: Date) extends TransactionFilter {
+ override def filter(txn: Transaction) = { txn.date.toInt < before.toInt }
+ override def description() = { "before: Transaction date is before: " + before.formatISO8601Ext }
+ override def xmlDescription() = { }
+}
+case class OnOrAfterDateTxnFilter(onOrAfter: Date) extends TransactionFilter {
+ override def filter(txn: Transaction) = { onOrAfter.toInt <= txn.date.toInt }
+ override def description() = { "onOrAfter: Transaction date is on or after: " + onOrAfter.formatISO8601Ext }
+ override def xmlDescription() = { }
+}
+
+case class PayeeTxnFilter(regex: String) extends TransactionFilter {
+ val pattern = java.util.regex.Pattern.compile(regex)
+
+ override def filter(txn: Transaction) = {
+ pattern.matcher(txn.payeeOpt match {
+ case Some(payee) => payee
+ case None => ""
+ }).matches
+ }
+ override def description() = { "payee: Payee must match \"" + pattern.toString + "\""}
+ override def xmlDescription() = { }
+}
+
+/**
+ * Annotation Txn filter
+ * - returns all transactions with matching annotation
+ */
+case class AnnotationTxnFilter(regex: String) extends TransactionFilter {
+ val pattern = java.util.regex.Pattern.compile(regex)
+
+ override def filter(txn: Transaction) = {
+ pattern.matcher(txn.annotationOpt match {
+ case Some(ann) => ann
+ case None => ""
+ }).matches
+ }
+ override def description() = { "annotation: Annotation must match \"" + pattern.toString + "\"" }
+ override def xmlDescription() = { }
+}
+
+/**
+ * Account Name Txn filter
+ * Returns all transactions which have at least one matching account name
+ */
+case class AccountNameTxnFilter(regex: String) extends TransactionFilter {
+ val pattern = java.util.regex.Pattern.compile(regex)
+
+ override def filter(txn: Transaction) = {
+ txn.posts.exists { post =>
+ pattern.matcher(post.accName.toString).matches
+ }
+ }
+ override def description() = { "account: At least one of transaction's accounts must match \"" + pattern.toString + "\"" }
+ override def xmlDescription() = { }
+}
+
+// TODO Txn comment filter
+// TODO Txn:Post comment filter
+
+object FilterStackHelper {
+ def getFilterWarnings(txnFilters: Option[TxnFilterStack], indent: String): List[String] = {
+ txnFilters match {
+ case Some(txnFilters) => {
+ indent + txnFilters.description() ::
+ txnFilters.filterDescriptions().map { desc =>
+ indent * 2 + desc
+ }.toList
+ }
+ case None => Nil
+ }
+ }
+}
+
+/**
+ * Trait for Transaction filter stacks
+ * Filter stack defines relationship between filters
+ * (e.g. f1 && f2, f1 || f2 or some other, specialized logic)
+ */
+sealed trait TxnFilterStack {
+ def filter(txn: Transaction): Boolean
+ def description(): String
+ def filterDescriptions(): Seq[String]
+ def xmlDescription(): xml.Node
+}
+
+/**
+ * AND- TxnFilterStack (e.g. f1 && f2 && ..)
+ */
+case class ANDTxnFilterStack(filterStack: Seq[TransactionFilter]) extends TxnFilterStack {
+ override def filter(txn: Transaction): Boolean = {
+ filterStack.forall { f => f.filter(txn) }
+ }
+ override def description() = {
+ assert(!filterStack.isEmpty)
+ "All following conditions must be true:"
+ }
+ override def filterDescriptions() = {
+ assert(!filterStack.isEmpty)
+ filterStack.map({case f => f.description})
+ }
+ override def xmlDescription(): xml.Node = {
+ assert(!filterStack.isEmpty)
+
+ {
+ filterStack.map({ filt => filt.xmlDescription })
+ }
+
+ }
+}
object DateOrdering extends Ordering[Date] {
def compare(x: Date, y: Date) = {
diff --git a/base/src/main/scala/co/uproot/abandon/Config.scala b/base/src/main/scala/co/uproot/abandon/Config.scala
index fcbe554f..e8b107cb 100644
--- a/base/src/main/scala/co/uproot/abandon/Config.scala
+++ b/base/src/main/scala/co/uproot/abandon/Config.scala
@@ -16,6 +16,7 @@ class AbandonCLIConf(arguments: Seq[String]) extends ScallopConf(arguments) {
val inputs = opt[List[String]]("input", short = 'i')
val reports = opt[List[String]]("report", short = 'r')
val config = opt[String]("config", short = 'c')
+ val filters = propsLong[String]("filter", descr="Transaction filters", keyName=" name")
val unversioned = opt[Boolean]("unversioned", short = 'X')
val quiet = opt[Boolean]("quiet", short = 'q')
// val trail = trailArg[String]()
@@ -33,19 +34,57 @@ object SettingsHelper {
}
}
+ def createTxnFilter(key: String, value: String): TransactionFilter = {
+ def makeDate(date: String) = {
+ val jDate = java.time.LocalDate.parse(date,
+ java.time.format.DateTimeFormatter.ISO_DATE)
+ Date(jDate.getYear, jDate.getMonthValue, jDate.getDayOfMonth)
+ }
+
+ (key, value) match {
+ case (key, value) if (key == "onOrAfter") => {
+ OnOrAfterDateTxnFilter(makeDate(value))
+ }
+ case (key, value) if (key == "before") => {
+ BeforeDateTxnFilter(makeDate(value))
+ }
+ case (key, value) if (key == "payee") => {
+ PayeeTxnFilter(value)
+ }
+ case (key, value) if (key == "account") => {
+ AccountNameTxnFilter(value)
+ }
+ case (key, value) if (key == "annotation") => {
+ AnnotationTxnFilter(value)
+ }
+ case _ => {
+ throw new RuntimeException("Unknown filter: " + key)
+ }
+ }
+ }
+
def getCompleteSettings(args: Seq[String]): Either[String, Settings] = {
val cliConf = new AbandonCLIConf(args)
cliConf.verify()
val configOpt = cliConf.config.toOption
val withoutVersion = cliConf.unversioned.getOrElse(false)
val quiet = cliConf.quiet.getOrElse(false)
+ val txnFilters =
+ if (cliConf.filters.isEmpty) {
+ None
+ }else {
+ val txnfs: Seq[TransactionFilter] =
+ cliConf.filters.map({case (k,v) => createTxnFilter(k, v)}).toSeq
+ Option(ANDTxnFilterStack(txnfs))
+ }
+
configOpt match {
case Some(configFileName) =>
- makeSettings(configFileName, withoutVersion, quiet)
+ makeSettings(configFileName, withoutVersion, quiet, txnFilters)
case _ =>
val inputs = cliConf.inputs.toOption.getOrElse(Nil)
val allReport = BalanceReportSettings("All Balances", None, Nil, true)
- Right(Settings(inputs, Nil, Nil, Seq(allReport), ReportOptions(Nil), Nil, None, quiet))
+ Right(Settings(inputs, Nil, Nil, Seq(allReport), ReportOptions(Nil), Nil, None, quiet, txnFilters))
}
}
@@ -61,7 +100,7 @@ object SettingsHelper {
}
}
- def makeSettings(configFileName: String, withoutVersion: Boolean, quiet: Boolean) = {
+ def makeSettings(configFileName: String, withoutVersion: Boolean, quiet: Boolean, txnFiltersCLI: Option[TxnFilterStack]) = {
def handleInput(input: String, confPath: String): List[String] = {
val parentPath = Processor.mkParentDirPath(confPath)
if (input.startsWith("glob:")) {
@@ -86,8 +125,26 @@ object SettingsHelper {
val accountConfigs = config.optConfigList("accounts").getOrElse(Nil)
val accounts = accountConfigs.map(makeAccountSettings)
val eodConstraints = config.optConfigList("eodConstraints").getOrElse(Nil).map(makeEodConstraints(_))
+
+ /*
+ * filters, precedence
+ * - conf none, cli none => None
+ * - conf none, cli some => cli
+ * - conf some, cli some => cli
+ */
+ val txnFilters = txnFiltersCLI match {
+ case Some(txnfs) => Option(txnfs)
+ case None =>
+ try {
+ val txnfs = config.getStringList("filters").asScala.map(s => s.split("=", 2)).
+ map({ case Array(k, v) => createTxnFilter(k, v) })
+ Option(ANDTxnFilterStack(txnfs))
+ } catch {
+ case e: ConfigException.Missing => None
+ }
+ }
val dateConstraints = config.optConfigList("dateConstraints").getOrElse(Nil).map(makeDateRangeConstraint(_))
- Right(Settings(inputs, eodConstraints ++ dateConstraints, accounts, reports, ReportOptions(isRight), exports, Some(file), quiet))
+ Right(Settings(inputs, eodConstraints ++ dateConstraints, accounts, reports, ReportOptions(isRight), exports, Some(file), quiet, txnFilters))
} catch {
case e: ConfigException => Left(e.getMessage)
}
@@ -277,7 +334,8 @@ case class Settings(
reportOptions: ReportOptions,
exports: Seq[ExportSettings],
configFileOpt: Option[java.io.File],
- quiet: Boolean) {
+ quiet: Boolean,
+ txnFilters: Option[TxnFilterStack]) {
def getConfigRelativePath(path: String) = {
configFileOpt.map(configFile => Processor.mkRelativeFileName(path, configFile.getAbsolutePath)).getOrElse(path)
}
diff --git a/base/src/main/scala/co/uproot/abandon/Process.scala b/base/src/main/scala/co/uproot/abandon/Process.scala
index a2ae0e2d..3815834b 100644
--- a/base/src/main/scala/co/uproot/abandon/Process.scala
+++ b/base/src/main/scala/co/uproot/abandon/Process.scala
@@ -201,9 +201,14 @@ object Processor {
(new java.io.File(path)).getCanonicalPath
}
- def process(scope: Scope, accountSettings: Seq[AccountSettings]) = {
+ def process(scope: Scope, accountSettings: Seq[AccountSettings], txnFilters: Option[TxnFilterStack]) = {
scope.checkDupes()
- val transactions = scope.allTransactions
+ val transactions = (filterByType[ScopedTxn](scope.allTransactions)).filter { scopeTxn =>
+ txnFilters match {
+ case Some(filterStack) => filterStack.filter(scopeTxn.txn)
+ case None => { true }
+ }
+ }
val sortedTxns = transactions.sortBy(_.txn.date)(DateOrdering)
val accState = new AccountState()
val aliasMap = accountSettings.collect{ case AccountSettings(name, Some(alias)) => alias -> name }.toMap
diff --git a/base/src/main/scala/co/uproot/abandon/Report.scala b/base/src/main/scala/co/uproot/abandon/Report.scala
index 7502d131..0f0cb27b 100644
--- a/base/src/main/scala/co/uproot/abandon/Report.scala
+++ b/base/src/main/scala/co/uproot/abandon/Report.scala
@@ -252,9 +252,19 @@ object Reports {
}
}
- def xmlBalanceExport(state: AppState, exportSettings: XmlExportSettings): xml.Node = {
+ def xmlBalanceExport(state: AppState, exportSettings: XmlExportSettings, filterXML: Option[xml.Node]): xml.Node = {
val balance: Elem =
+ {
+ filterXML match {
+ case Some(xml) => {
+
+ { xml }
+
+ }
+ case None => ;
+ }
+ }
{state.accState.mkTree(exportSettings.isAccountMatching).toXML}
@@ -267,9 +277,19 @@ object Reports {
}
}
- def xmlJournalExport(state: AppState, exportSettings: XmlExportSettings): xml.Node = {
+ def xmlJournalExport(state: AppState, exportSettings: XmlExportSettings, filterXML: Option[xml.Node]): xml.Node = {
val journal: Elem =
+ {
+ filterXML match {
+ case Some(xml) => {
+
+ { xml }
+
+ }
+ case None => ;
+ }
+ }
{
val sortedGroups = state.accState.postGroups.sortBy(_.date.toInt)
@@ -299,10 +319,15 @@ object Reports {
def addAttribute(n: Elem, k: String, v: String) = n % new xml.UnprefixedAttribute(k, v, xml.Null)
- def xmlExport(state: AppState, exportSettings: XmlExportSettings): xml.Node = {
+ def xmlExport(state: AppState, exportSettings: XmlExportSettings, txnFilters: Option[TxnFilterStack]): xml.Node = {
+ val filterXML: Option[xml.Node] = txnFilters match {
+ case Some(filters) => Option(filters.xmlDescription)
+ case None => None
+ }
+
exportSettings.exportType match {
- case JournalType => xmlJournalExport(state, exportSettings)
- case BalanceType => xmlBalanceExport(state, exportSettings)
+ case JournalType => xmlJournalExport(state, exportSettings, filterXML)
+ case BalanceType => xmlBalanceExport(state, exportSettings, filterXML)
}
}
}
diff --git a/base/src/test/scala/co/uproot/abandon/ComplexProcessTest.scala b/base/src/test/scala/co/uproot/abandon/ComplexProcessTest.scala
index 6dc4996d..061baaa7 100644
--- a/base/src/test/scala/co/uproot/abandon/ComplexProcessTest.scala
+++ b/base/src/test/scala/co/uproot/abandon/ComplexProcessTest.scala
@@ -20,13 +20,13 @@ class ComplexProcessTest extends FlatSpec with Matchers with Inside {
val xmlBalSettings = XmlExportSettings(BalanceType, None, Seq("not-used.xml"), true)
val xmlTxnSettings = XmlExportSettings(JournalType, None, Seq("not-used.xml"), true)
- val settings = Settings(Nil, Nil, Nil, Nil, ReportOptions(Nil), Seq(xmlBalSettings), None, quiet)
+ val settings = Settings(Nil, Nil, Nil, Nil, ReportOptions(Nil), Seq(xmlBalSettings), None, quiet, None)
- val appState = Processor.process(scope, settings.accounts)
+ val appState = Processor.process(scope,settings.accounts, None)
//TODO: Processor.checkConstaints(appState, settings.eodConstraints)
- val xmlBalance = Reports.xmlExport(appState, xmlBalSettings)
- val xmlJournal = Reports.xmlExport(appState, xmlTxnSettings)
+ val xmlBalance = Reports.xmlExport(appState, xmlBalSettings, settings.txnFilters)
+ val xmlJournal = Reports.xmlExport(appState, xmlTxnSettings, settings.txnFilters)
val refXMLBalance = scala.xml.XML.loadFile("testCases/refSmallBalance.xml")
val refXMLJournal = scala.xml.XML.loadFile("testCases/refSmallJournal.xml")
diff --git a/base/src/test/scala/co/uproot/abandon/ProcessorTest.scala b/base/src/test/scala/co/uproot/abandon/ProcessorTest.scala
index 239a5a7f..db32ae4b 100644
--- a/base/src/test/scala/co/uproot/abandon/ProcessorTest.scala
+++ b/base/src/test/scala/co/uproot/abandon/ProcessorTest.scala
@@ -21,10 +21,10 @@ class ProcessorTest extends FlatSpec with Matchers with Inside {
val parseResult = parser.abandon(scanner(testInput))
inside(parseResult) {
case parser.Success(scope, _) =>
- val appState = Processor.process(scope, Nil)
+ val appState = Processor.process(scope, Nil, None)
val balSettings = LedgerExportSettings(None, Seq("balSheet12.txt"), false, Nil)
- val settings = Settings(Nil, Nil, Nil, Nil, ReportOptions(Nil), Seq(balSettings), None, false)
+ val settings = Settings(Nil, Nil, Nil, Nil, ReportOptions(Nil), Seq(balSettings), None, false, None)
val ledgerRep = Reports.ledgerExport(appState, settings, balSettings)
inside(ledgerRep) {
@@ -51,10 +51,10 @@ class ProcessorTest extends FlatSpec with Matchers with Inside {
val parseResult = parser.abandon(scanner(testInput))
inside(parseResult) {
case parser.Success(scope, _) =>
- val appState = Processor.process(scope, Nil)
+ val appState = Processor.process(scope, Nil, None)
val balSettings = LedgerExportSettings(None, Seq("balSheet12.txt"), false, Nil)
- val settings = Settings(Nil, Nil, Nil, Nil, ReportOptions(Nil), Seq(balSettings), None, false)
+ val settings = Settings(Nil, Nil, Nil, Nil, ReportOptions(Nil), Seq(balSettings), None, false, None)
val ledgerRep = Reports.ledgerExport(appState, settings, balSettings)
inside(ledgerRep) {
@@ -81,10 +81,10 @@ class ProcessorTest extends FlatSpec with Matchers with Inside {
val parseResult = parser.abandon(scanner(testInput))
inside(parseResult) {
case parser.Success(scope, _) =>
- val appState = Processor.process(scope, Nil)
+ val appState = Processor.process(scope, Nil, None)
val balSettings = LedgerExportSettings(None, Seq("balSheet12.txt"), true, Nil)
- val settings = Settings(Nil, Nil, Nil, Nil, ReportOptions(Nil), Seq(balSettings), None, false)
+ val settings = Settings(Nil, Nil, Nil, Nil, ReportOptions(Nil), Seq(balSettings), None, false, None)
val ledgerRep = Reports.ledgerExport(appState, settings, balSettings)
inside(ledgerRep) {
@@ -109,10 +109,10 @@ class ProcessorTest extends FlatSpec with Matchers with Inside {
val parseResult = parser.abandon(scanner(testInput))
inside(parseResult) {
case parser.Success(scope, _) =>
- val appState = Processor.process(scope, Nil)
+ val appState = Processor.process(scope, Nil, None)
val balSettings = LedgerExportSettings(None, Seq("balSheet12.txt"), false, Nil)
- val settings = Settings(Nil, Nil, Nil, Nil, ReportOptions(Nil), Seq(balSettings), None, false)
+ val settings = Settings(Nil, Nil, Nil, Nil, ReportOptions(Nil), Seq(balSettings), None, false, None)
val ledgerRep = Reports.ledgerExport(appState, settings, balSettings)
ledgerRep should be (Nil)
@@ -130,13 +130,13 @@ class ProcessorTest extends FlatSpec with Matchers with Inside {
val parseResult = parser.abandon(scanner(testInput))
inside(parseResult) {
case parser.Success(scope, _) =>
- val appState = Processor.process(scope, Nil)
+ val appState = Processor.process(scope, Nil, None)
val source = Seq("Income", "Expense")
val destination = "Equity"
val closure = Seq(ClosureExportSettings(source, destination))
val balSettings = LedgerExportSettings(None, Seq("balSheet12.txt"), false, closure)
- val settings = Settings(Nil, Nil, Nil, Nil, ReportOptions(Nil), Seq(balSettings), None, false)
+ val settings = Settings(Nil, Nil, Nil, Nil, ReportOptions(Nil), Seq(balSettings), None, false, None)
val ledgerRep = Reports.ledgerExport(appState, settings, balSettings)
inside(ledgerRep) {
@@ -180,7 +180,7 @@ class ProcessorTest extends FlatSpec with Matchers with Inside {
val parseResult = parser.abandon(scanner(testInput))
inside(parseResult) {
case parser.Success(scope, _) =>
- val appState = Processor.process(scope, Nil)
+ val appState = Processor.process(scope, Nil, None)
val source = Seq("Income", "Expense")
val destination = "Equity"
val source1 = Seq("Income", "Expense")
@@ -190,7 +190,7 @@ class ProcessorTest extends FlatSpec with Matchers with Inside {
val closure = closure1 ++ closure2
val balSettings = LedgerExportSettings(None, Seq("balSheet12.txt"), false, closure)
- val settings = Settings(Nil, Nil, Nil, Nil, ReportOptions(Nil), Seq(balSettings), None, false)
+ val settings = Settings(Nil, Nil, Nil, Nil, ReportOptions(Nil), Seq(balSettings), None, false, None)
val ledgerRep = intercept[InputError] {
Reports.ledgerExport(appState, settings, balSettings)
@@ -209,13 +209,13 @@ class ProcessorTest extends FlatSpec with Matchers with Inside {
val parseResult = parser.abandon(scanner(testInput))
inside(parseResult) {
case parser.Success(scope, _) =>
- val appState = Processor.process(scope, Nil)
+ val appState = Processor.process(scope, Nil, None)
val source = Seq("Income", "Expense")
val destination = "Equity"
val closure = Seq(ClosureExportSettings(source, destination))
val balSettings = LedgerExportSettings(None, Seq("balSheet12.txt"), false, closure)
- val settings = Settings(Nil, Nil, Nil, Nil, ReportOptions(Nil), Seq(balSettings), None, false)
+ val settings = Settings(Nil, Nil, Nil, Nil, ReportOptions(Nil), Seq(balSettings), None, false, None)
val ledgerRep = intercept[MissingDestinationError] {
Reports.ledgerExport(appState, settings, balSettings)
@@ -234,13 +234,13 @@ class ProcessorTest extends FlatSpec with Matchers with Inside {
val parseResult = parser.abandon(scanner(testInput))
inside(parseResult) {
case parser.Success(scope, _) =>
- val appState = Processor.process(scope, Nil)
+ val appState = Processor.process(scope, Nil, None)
val source = Seq("Income", "Equity")
val destination = "Equity"
val closure = Seq(ClosureExportSettings(source, destination))
val balSettings = LedgerExportSettings(None, Seq("balSheet12.txt"), false, closure)
- val settings = Settings(Nil, Nil, Nil, Nil, ReportOptions(Nil), Seq(balSettings), None, false)
+ val settings = Settings(Nil, Nil, Nil, Nil, ReportOptions(Nil), Seq(balSettings), None, false, None)
val ledgerRep = intercept[SourceDestinationClashError] {
Reports.ledgerExport(appState, settings, balSettings)
@@ -265,9 +265,9 @@ class ProcessorTest extends FlatSpec with Matchers with Inside {
val accounts = Seq(AccountSettings(name, Some(alias)))
val balSettings = LedgerExportSettings(None, Seq("balSheet12.txt"), false, Nil)
- val settings = Settings(Nil, Nil, accounts, Nil, ReportOptions(Nil), Seq(balSettings), None, false)
+ val settings = Settings(Nil, Nil, accounts, Nil, ReportOptions(Nil), Seq(balSettings), None, false, None)
- val appState = Processor.process(scope, settings.accounts)
+ val appState = Processor.process(scope, settings.accounts, None)
val ledgerRep = Reports.ledgerExport(appState, settings, balSettings)
inside(ledgerRep) {
diff --git a/cli/src/main/scala/co/uproot/abandon/App.scala b/cli/src/main/scala/co/uproot/abandon/App.scala
index 5ac514a2..329b9ccb 100644
--- a/cli/src/main/scala/co/uproot/abandon/App.scala
+++ b/cli/src/main/scala/co/uproot/abandon/App.scala
@@ -93,7 +93,14 @@ object CLIMain {
}
}
- def exportAsLedger(reportWriter: ReportWriter, ledgerRep: Seq[LedgerExportData]) = {
+ def exportAsLedger(reportWriter: ReportWriter, ledgerRep: Seq[LedgerExportData], txnFilterTxt: List[String]) = {
+
+ if (txnFilterTxt.nonEmpty) {
+ reportWriter.println("; ACTIVE FILTER")
+ txnFilterTxt.foreach { line => reportWriter.println("; " + line) }
+ reportWriter.println(";")
+ }
+
ledgerRep foreach { reportGroup =>
reportWriter.println(reportGroup.date.formatCompactYYYYMMDD)
val formatStr = "%-" + (reportGroup.maxNameLength + 4) + "s %s"
@@ -150,7 +157,8 @@ object CLIMain {
case Right(settings) =>
val (parseError, astEntries, processedFiles) = Processor.parseAll(settings.inputs, settings.quiet)
if (!parseError) {
- val appState = Processor.process(astEntries,settings.accounts)
+ val txnFilters = None
+ val appState = Processor.process(astEntries,settings.accounts, settings.txnFilters)
Processor.checkConstaints(appState, settings.constraints)
settings.exports.foreach { exportSettings =>
val reportWriter = new ReportWriter(settings, exportSettings.outFiles)
@@ -163,9 +171,9 @@ object CLIMain {
exportSettings match {
case balSettings: LedgerExportSettings =>
val ledgerRep = Reports.ledgerExport(appState, settings, balSettings)
- exportAsLedger(reportWriter, ledgerRep)
+ exportAsLedger(reportWriter, ledgerRep, FilterStackHelper.getFilterWarnings(settings.txnFilters, " "))
case xmlSettings: XmlExportSettings =>
- val xmlData = Reports.xmlExport(appState, xmlSettings)
+ val xmlData = Reports.xmlExport(appState, xmlSettings, settings.txnFilters)
reportWriter.printXml(xmlData)
case _ => ???
}
@@ -185,6 +193,12 @@ object CLIMain {
reportWriter.printHeading(reportSettings.title)
+ if (settings.txnFilters.nonEmpty) {
+ reportWriter.println("ACTIVE FILTER")
+ FilterStackHelper.getFilterWarnings(settings.txnFilters, " ").foreach { line => reportWriter.println(line)}
+ reportWriter.println("")
+ }
+
reportSettings match {
case balSettings: BalanceReportSettings =>
val balanceReport = Reports.balanceReport(appState, settings, balSettings)
diff --git a/doc/abandon.md b/doc/abandon.md
index bf5e934c..b764a4d3 100644
--- a/doc/abandon.md
+++ b/doc/abandon.md
@@ -5,11 +5,32 @@
- '-g' := Start graphical GUI
- '-c' := path to journal's configuration file
- '-i' := input file, could be specified inside conf-file
+ - '--filter' := Transaction filter, please Transaction filters for further info
+
+## Transaction filters
+
+With transaction filters, you can select which transactions are used for reports and exports.
+Only those transactions which pass or match filter criteria, will be processed.
+
+Currently there are following filters available
+ - `onOrAfter=ISO-DATE`, include transactions on or after `ISO-DATE`
+ - `before=ISO-DATE`, include transactions before `ISO-DATE`
+ - `annotation=REGEX`, include all transactions which have `annotation` with matching `REGEX`
+ - `payee=REGEX`, include all transactions which have `payee` description with matching `REGEX`
+ - `account=REGEX`, include all transactions which have an `account` with matching `REGEX`
+
+For example to query all transactions for Feb 2016 (which is leap year), you could use following filter:
+ `--filter onOrAfter=2016-02-01 before=2016-03-01`. Notice that date 2016-03-01 is excluded from result set.
+
+There are many functional tests for filters, those tests could be also used as an example.
+Please see [readme.md](testCases/sclT0005-filters/readme.md) for filter tests, which provides more
+information about various use cases.
+
## Configuration file
-### Input -directive
+### Inputs -directive
Input directive defines primary inputs for this particular journal.
There could be other "include" directives inside these primary inputs.
@@ -103,3 +124,18 @@ Regex based path matching is activated by prefixing input path with "regex:".
Abandon regex system supports also basepath cooked form of regex, please see
documentation of glob syntax for further info how cooked mode works.
+### Filters -directive
+
+Filters could be also defined in configuration file, with same way as on command line.
+
+For example:
+```
+filters += "onOrAfter=2015-01-01"
+filters += "before=2015-12-31"
+```
+
+Or same as single line:
+
+`filters = ["onOrAfter=2012-02-01", "before=2016-01-02"]`
+
+For full information about filters, please see "Transaction filters" section.
diff --git a/gui/src/main/scala/co/uproot/abandon/UI.scala b/gui/src/main/scala/co/uproot/abandon/UI.scala
index 7c8d0f43..112b9df4 100644
--- a/gui/src/main/scala/co/uproot/abandon/UI.scala
+++ b/gui/src/main/scala/co/uproot/abandon/UI.scala
@@ -59,7 +59,15 @@ object AbandonUI extends JFXApp {
}
def updateInfo(appState: AppState, settings: Settings, processedFiles: Set[String]) = {
- infoTab.content = new Label("Processed files:\n" + processedFiles.mkString("\n"))
+ val warnings = FilterStackHelper.getFilterWarnings(settings.txnFilters, " ")
+ val warningsTxt =
+ if (warnings.isEmpty) {
+ ""
+ } else {
+ "ACTIVE FILTER\n" + warnings.mkString("\n") + "\n\n"
+ }
+
+ infoTab.content = new Label(warningsTxt + "Processed files:\n" + processedFiles.mkString("\n"))
}
val tabPane = new TabPane {
@@ -129,7 +137,7 @@ object AbandonUI extends JFXApp {
def createReportTabs(firstRun: Boolean, settings: Settings) = {
val (parseError, astEntries, processedFiles) = Processor.parseAll(settings.inputs, settings.quiet)
if (!parseError) {
- val appState = Processor.process(astEntries, settings.accounts)
+ val appState = Processor.process(astEntries, settings.accounts, settings.txnFilters)
if (firstRun) {
settings.reports.foreach(CurrReports.addReport(appState, settings, _))
} else {
@@ -151,7 +159,12 @@ object AbandonUI extends JFXApp {
def updateReports(firstRun: Boolean): Unit = {
val processedFiles = createReportTabs(firstRun, settings)
Platform.runLater {
- StatusBar.setText("Report generated on " + new java.util.Date)
+ val statusBarTxt = (settings.txnFilters match {
+ case Some(txnfs) => "ACTIVE FILTER! "
+ case None => ""
+ }) + "Report generated on " + new java.util.Date
+
+ StatusBar.setText(statusBarTxt)
}
inputFileWatcher.watch(processedFiles, () => {
Platform.runLater({ updateReports(false) })
diff --git a/testCases/README.md b/testCases/README.md
index e5dc64a4..ed92c640 100644
--- a/testCases/README.md
+++ b/testCases/README.md
@@ -30,8 +30,8 @@ Inside a test directory, there must be:
For example filter-arguments:
```
--filter
- begin=2013-01-01
- end=2013-12-31
+ onOrAfter=2013-01-01
+ before=2013-12-31
```
Each argument line is trimmed, so whitespace is removed
from begining and at end.
diff --git a/testCases/sclT0005-filters/f001.args b/testCases/sclT0005-filters/f001.args
new file mode 100644
index 00000000..b0c23c3f
--- /dev/null
+++ b/testCases/sclT0005-filters/f001.args
@@ -0,0 +1,2 @@
+--filter
+onOrAfter=2016-02-01
diff --git a/testCases/sclT0005-filters/f001.conf b/testCases/sclT0005-filters/f001.conf
new file mode 100644
index 00000000..871facb2
--- /dev/null
+++ b/testCases/sclT0005-filters/f001.conf
@@ -0,0 +1,20 @@
+inputs += filter-targets.ledger
+
+reports += {
+ title = "Balance Sheet"
+ type = balance
+ outFiles = ["-"]
+ accountMatch = ["^Assets.*", "^Expenses.*"]
+}
+
+exports += {
+ type = journal
+ format = xml
+ outFiles = [out.f001.journal.xml]
+}
+
+exports += {
+ type = balance
+ format = xml
+ outFiles = [out.f001.balance.xml]
+}
diff --git a/testCases/sclT0005-filters/f001.ref.balance.xml b/testCases/sclT0005-filters/f001.ref.balance.xml
new file mode 100644
index 00000000..d489cdd5
--- /dev/null
+++ b/testCases/sclT0005-filters/f001.ref.balance.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/testCases/sclT0005-filters/f001.ref.journal.xml b/testCases/sclT0005-filters/f001.ref.journal.xml
new file mode 100644
index 00000000..e174c8d2
--- /dev/null
+++ b/testCases/sclT0005-filters/f001.ref.journal.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+ begin off by one
+
+
+
+
+ begin off by one
+
+
+
+
+ foobar
+ matchme
+
+
+
+
+ matchme
+ ann
+
+
+
+
+ account
+
+
+
+
+ by account
+
+
+
+
+ by notme account
+
+
+
+
+ by account
+ ann
+
+
+
+
+ end off by one
+
+
+
+
+ end off by one
+
+
+
+
+ end off by one
+
+
+
+
+
+
\ No newline at end of file
diff --git a/testCases/sclT0005-filters/f002.args b/testCases/sclT0005-filters/f002.args
new file mode 100644
index 00000000..038fd1a4
--- /dev/null
+++ b/testCases/sclT0005-filters/f002.args
@@ -0,0 +1,2 @@
+--filter
+before=2016-03-02
diff --git a/testCases/sclT0005-filters/f002.conf b/testCases/sclT0005-filters/f002.conf
new file mode 100644
index 00000000..59d15b2a
--- /dev/null
+++ b/testCases/sclT0005-filters/f002.conf
@@ -0,0 +1,20 @@
+inputs += filter-targets.ledger
+
+reports += {
+ title = "Balance Sheet"
+ type = balance
+ outFiles = ["-"]
+ accountMatch = ["^Assets.*", "^Expenses.*"]
+}
+
+exports += {
+ type = journal
+ format = xml
+ outFiles = [out.f002.journal.xml]
+}
+
+exports += {
+ type = balance
+ format = xml
+ outFiles = [out.f002.balance.xml]
+}
diff --git a/testCases/sclT0005-filters/f002.ref.balance.xml b/testCases/sclT0005-filters/f002.ref.balance.xml
new file mode 100644
index 00000000..0cde277e
--- /dev/null
+++ b/testCases/sclT0005-filters/f002.ref.balance.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testCases/sclT0005-filters/f002.ref.journal.xml b/testCases/sclT0005-filters/f002.ref.journal.xml
new file mode 100644
index 00000000..dc3504f4
--- /dev/null
+++ b/testCases/sclT0005-filters/f002.ref.journal.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+ begin off by one
+
+
+
+
+ begin off by one
+
+
+
+
+ begin off by one
+
+
+
+
+ foobar
+ matchme
+
+
+
+
+ matchme
+ ann
+
+
+
+
+ account
+
+
+
+
+ by account
+
+
+
+
+ by notme account
+
+
+
+
+ by account
+ ann
+
+
+
+
+ end off by one
+
+
+
+
+ end off by one
+
+
+
+
+
+
diff --git a/testCases/sclT0005-filters/f003.args b/testCases/sclT0005-filters/f003.args
new file mode 100644
index 00000000..25afee0b
--- /dev/null
+++ b/testCases/sclT0005-filters/f003.args
@@ -0,0 +1,3 @@
+--filter
+onOrAfter=2016-02-01
+before=2016-03-02
diff --git a/testCases/sclT0005-filters/f003.conf b/testCases/sclT0005-filters/f003.conf
new file mode 100644
index 00000000..74b729cf
--- /dev/null
+++ b/testCases/sclT0005-filters/f003.conf
@@ -0,0 +1,20 @@
+inputs += filter-targets.ledger
+
+reports += {
+ title = "Balance Sheet"
+ type = balance
+ outFiles = ["-"]
+ accountMatch = ["^Assets.*", "^Expenses.*"]
+}
+
+exports += {
+ type = journal
+ format = xml
+ outFiles = [out.f003.journal.xml]
+}
+
+exports += {
+ type = balance
+ format = xml
+ outFiles = [out.f003.balance.xml]
+}
diff --git a/testCases/sclT0005-filters/f003.ref.balance.xml b/testCases/sclT0005-filters/f003.ref.balance.xml
new file mode 100644
index 00000000..d7f1062b
--- /dev/null
+++ b/testCases/sclT0005-filters/f003.ref.balance.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testCases/sclT0005-filters/f003.ref.journal.xml b/testCases/sclT0005-filters/f003.ref.journal.xml
new file mode 100644
index 00000000..3707d992
--- /dev/null
+++ b/testCases/sclT0005-filters/f003.ref.journal.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+ begin off by one
+
+
+
+
+ begin off by one
+
+
+
+
+ foobar
+ matchme
+
+
+
+
+ matchme
+ ann
+
+
+
+
+ account
+
+
+
+
+ by account
+
+
+
+
+ by notme account
+
+
+
+
+ by account
+ ann
+
+
+
+
+ end off by one
+
+
+
+
+ end off by one
+
+
+
+
+
+
diff --git a/testCases/sclT0005-filters/f010.args b/testCases/sclT0005-filters/f010.args
new file mode 100644
index 00000000..b99de775
--- /dev/null
+++ b/testCases/sclT0005-filters/f010.args
@@ -0,0 +1,2 @@
+--filter
+payee=.*matchme
diff --git a/testCases/sclT0005-filters/f010.conf b/testCases/sclT0005-filters/f010.conf
new file mode 100644
index 00000000..66242423
--- /dev/null
+++ b/testCases/sclT0005-filters/f010.conf
@@ -0,0 +1,20 @@
+inputs += filter-targets.ledger
+
+reports += {
+ title = "Balance Sheet"
+ type = balance
+ outFiles = ["-"]
+ accountMatch = ["^Assets.*", "^Expenses.*"]
+}
+
+exports += {
+ type = journal
+ format = xml
+ outFiles = [out.f010.journal.xml]
+}
+
+exports += {
+ type = balance
+ format = xml
+ outFiles = [out.f010.balance.xml]
+}
diff --git a/testCases/sclT0005-filters/f010.ref.balance.xml b/testCases/sclT0005-filters/f010.ref.balance.xml
new file mode 100644
index 00000000..48103bb2
--- /dev/null
+++ b/testCases/sclT0005-filters/f010.ref.balance.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/testCases/sclT0005-filters/f010.ref.journal.xml b/testCases/sclT0005-filters/f010.ref.journal.xml
new file mode 100644
index 00000000..e0a32bcd
--- /dev/null
+++ b/testCases/sclT0005-filters/f010.ref.journal.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+ matchme
+ ann
+
+
+
+
+
+
\ No newline at end of file
diff --git a/testCases/sclT0005-filters/f011.args b/testCases/sclT0005-filters/f011.args
new file mode 100644
index 00000000..5837737a
--- /dev/null
+++ b/testCases/sclT0005-filters/f011.args
@@ -0,0 +1,2 @@
+--filter
+payee=.*one
diff --git a/testCases/sclT0005-filters/f011.conf b/testCases/sclT0005-filters/f011.conf
new file mode 100644
index 00000000..75e8ffc1
--- /dev/null
+++ b/testCases/sclT0005-filters/f011.conf
@@ -0,0 +1,20 @@
+inputs += filter-targets.ledger
+
+reports += {
+ title = "Balance Sheet"
+ type = balance
+ outFiles = ["-"]
+ accountMatch = ["^Assets.*", "^Expenses.*"]
+}
+
+exports += {
+ type = journal
+ format = xml
+ outFiles = [out.f011.journal.xml]
+}
+
+exports += {
+ type = balance
+ format = xml
+ outFiles = [out.f011.balance.xml]
+}
diff --git a/testCases/sclT0005-filters/f011.ref.balance.xml b/testCases/sclT0005-filters/f011.ref.balance.xml
new file mode 100644
index 00000000..5143c962
--- /dev/null
+++ b/testCases/sclT0005-filters/f011.ref.balance.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/testCases/sclT0005-filters/f011.ref.journal.xml b/testCases/sclT0005-filters/f011.ref.journal.xml
new file mode 100644
index 00000000..6a16fc64
--- /dev/null
+++ b/testCases/sclT0005-filters/f011.ref.journal.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+ begin off by one
+
+
+
+
+ begin off by one
+
+
+
+
+ begin off by one
+
+
+
+
+ end off by one
+
+
+
+
+ end off by one
+
+
+
+
+ end off by one
+
+
+
+
+
+
\ No newline at end of file
diff --git a/testCases/sclT0005-filters/f020.args b/testCases/sclT0005-filters/f020.args
new file mode 100644
index 00000000..799da6ce
--- /dev/null
+++ b/testCases/sclT0005-filters/f020.args
@@ -0,0 +1,2 @@
+--filter
+annotation=(ann)|(matchme)
diff --git a/testCases/sclT0005-filters/f020.conf b/testCases/sclT0005-filters/f020.conf
new file mode 100644
index 00000000..3a435a11
--- /dev/null
+++ b/testCases/sclT0005-filters/f020.conf
@@ -0,0 +1,20 @@
+inputs += filter-targets.ledger
+
+reports += {
+ title = "Balance Sheet"
+ type = balance
+ outFiles = ["-"]
+ accountMatch = ["^Assets.*", "^Expenses.*"]
+}
+
+exports += {
+ type = journal
+ format = xml
+ outFiles = [out.f020.journal.xml]
+}
+
+exports += {
+ type = balance
+ format = xml
+ outFiles = [out.f020.balance.xml]
+}
diff --git a/testCases/sclT0005-filters/f020.ref.balance.xml b/testCases/sclT0005-filters/f020.ref.balance.xml
new file mode 100644
index 00000000..291577ce
--- /dev/null
+++ b/testCases/sclT0005-filters/f020.ref.balance.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/testCases/sclT0005-filters/f020.ref.journal.xml b/testCases/sclT0005-filters/f020.ref.journal.xml
new file mode 100644
index 00000000..a95367b0
--- /dev/null
+++ b/testCases/sclT0005-filters/f020.ref.journal.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+ foobar
+ matchme
+
+
+
+
+ matchme
+ ann
+
+
+
+
+ by account
+ ann
+
+
+
+
+
+
\ No newline at end of file
diff --git a/testCases/sclT0005-filters/f030.args b/testCases/sclT0005-filters/f030.args
new file mode 100644
index 00000000..7230e40a
--- /dev/null
+++ b/testCases/sclT0005-filters/f030.args
@@ -0,0 +1,2 @@
+--filter
+account=.*matchme
diff --git a/testCases/sclT0005-filters/f030.conf b/testCases/sclT0005-filters/f030.conf
new file mode 100644
index 00000000..9182860b
--- /dev/null
+++ b/testCases/sclT0005-filters/f030.conf
@@ -0,0 +1,20 @@
+inputs += filter-targets.ledger
+
+reports += {
+ title = "Balance Sheet"
+ type = balance
+ outFiles = ["-"]
+ accountMatch = ["^Assets.*", "^Expenses.*"]
+}
+
+exports += {
+ type = journal
+ format = xml
+ outFiles = [out.f030.journal.xml]
+}
+
+exports += {
+ type = balance
+ format = xml
+ outFiles = [out.f030.balance.xml]
+}
diff --git a/testCases/sclT0005-filters/f030.ref.balance.xml b/testCases/sclT0005-filters/f030.ref.balance.xml
new file mode 100644
index 00000000..e0b1170b
--- /dev/null
+++ b/testCases/sclT0005-filters/f030.ref.balance.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/testCases/sclT0005-filters/f030.ref.journal.xml b/testCases/sclT0005-filters/f030.ref.journal.xml
new file mode 100644
index 00000000..6d867fcf
--- /dev/null
+++ b/testCases/sclT0005-filters/f030.ref.journal.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+ account
+
+
+
+
+
+
\ No newline at end of file
diff --git a/testCases/sclT0005-filters/f031.args b/testCases/sclT0005-filters/f031.args
new file mode 100644
index 00000000..9f835f38
--- /dev/null
+++ b/testCases/sclT0005-filters/f031.args
@@ -0,0 +1,2 @@
+--filter
+account=.*:MatchSubAccount:.*
diff --git a/testCases/sclT0005-filters/f031.conf b/testCases/sclT0005-filters/f031.conf
new file mode 100644
index 00000000..3b08901d
--- /dev/null
+++ b/testCases/sclT0005-filters/f031.conf
@@ -0,0 +1,20 @@
+inputs += filter-targets.ledger
+
+reports += {
+ title = "Balance Sheet"
+ type = balance
+ outFiles = ["-"]
+ accountMatch = ["^Assets.*", "^Expenses.*"]
+}
+
+exports += {
+ type = journal
+ format = xml
+ outFiles = [out.f031.journal.xml]
+}
+
+exports += {
+ type = balance
+ format = xml
+ outFiles = [out.f031.balance.xml]
+}
diff --git a/testCases/sclT0005-filters/f031.ref.balance.xml b/testCases/sclT0005-filters/f031.ref.balance.xml
new file mode 100644
index 00000000..1aae77fb
--- /dev/null
+++ b/testCases/sclT0005-filters/f031.ref.balance.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/testCases/sclT0005-filters/f031.ref.journal.xml b/testCases/sclT0005-filters/f031.ref.journal.xml
new file mode 100644
index 00000000..6059de42
--- /dev/null
+++ b/testCases/sclT0005-filters/f031.ref.journal.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+ by account
+
+
+
+
+ by notme account
+
+
+
+
+ by account
+ ann
+
+
+
+
+
+
\ No newline at end of file
diff --git a/testCases/sclT0005-filters/f040.args b/testCases/sclT0005-filters/f040.args
new file mode 100644
index 00000000..46b4c168
--- /dev/null
+++ b/testCases/sclT0005-filters/f040.args
@@ -0,0 +1,4 @@
+--filter
+annotation=(^$)|(ann)
+onOrAfter=2016-01-31
+before=2016-03-01
diff --git a/testCases/sclT0005-filters/f040.conf b/testCases/sclT0005-filters/f040.conf
new file mode 100644
index 00000000..eea30449
--- /dev/null
+++ b/testCases/sclT0005-filters/f040.conf
@@ -0,0 +1,20 @@
+inputs += filter-targets.ledger
+
+reports += {
+ title = "Balance Sheet"
+ type = balance
+ outFiles = ["-"]
+ accountMatch = ["^Assets.*", "^Expenses.*"]
+}
+
+exports += {
+ type = journal
+ format = xml
+ outFiles = [out.f040.journal.xml]
+}
+
+exports += {
+ type = balance
+ format = xml
+ outFiles = [out.f040.balance.xml]
+}
diff --git a/testCases/sclT0005-filters/f040.ref.balance.xml b/testCases/sclT0005-filters/f040.ref.balance.xml
new file mode 100644
index 00000000..bbf53134
--- /dev/null
+++ b/testCases/sclT0005-filters/f040.ref.balance.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testCases/sclT0005-filters/f040.ref.journal.xml b/testCases/sclT0005-filters/f040.ref.journal.xml
new file mode 100644
index 00000000..a31fdaec
--- /dev/null
+++ b/testCases/sclT0005-filters/f040.ref.journal.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+ begin off by one
+
+
+
+
+ begin off by one
+
+
+
+
+ begin off by one
+
+
+
+
+ matchme
+ ann
+
+
+
+
+ account
+
+
+
+
+ by account
+
+
+
+
+ by notme account
+
+
+
+
+ by account
+ ann
+
+
+
+
+ end off by one
+
+
+
+
+
+
diff --git a/testCases/sclT0005-filters/f041.args b/testCases/sclT0005-filters/f041.args
new file mode 100644
index 00000000..403353e0
--- /dev/null
+++ b/testCases/sclT0005-filters/f041.args
@@ -0,0 +1,3 @@
+--filter
+payee=by account
+account=.*:MatchSubAccount:.*
diff --git a/testCases/sclT0005-filters/f041.conf b/testCases/sclT0005-filters/f041.conf
new file mode 100644
index 00000000..84673935
--- /dev/null
+++ b/testCases/sclT0005-filters/f041.conf
@@ -0,0 +1,20 @@
+inputs += filter-targets.ledger
+
+reports += {
+ title = "Balance Sheet"
+ type = balance
+ outFiles = ["-"]
+ accountMatch = ["^Assets.*", "^Expenses.*"]
+}
+
+exports += {
+ type = journal
+ format = xml
+ outFiles = [out.f041.journal.xml]
+}
+
+exports += {
+ type = balance
+ format = xml
+ outFiles = [out.f041.balance.xml]
+}
diff --git a/testCases/sclT0005-filters/f041.ref.balance.xml b/testCases/sclT0005-filters/f041.ref.balance.xml
new file mode 100644
index 00000000..0e9b5d77
--- /dev/null
+++ b/testCases/sclT0005-filters/f041.ref.balance.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/testCases/sclT0005-filters/f041.ref.journal.xml b/testCases/sclT0005-filters/f041.ref.journal.xml
new file mode 100644
index 00000000..195e48dd
--- /dev/null
+++ b/testCases/sclT0005-filters/f041.ref.journal.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+ by account
+
+
+
+
+ by account
+ ann
+
+
+
+
+
+
\ No newline at end of file
diff --git a/testCases/sclT0005-filters/f042.args b/testCases/sclT0005-filters/f042.args
new file mode 100644
index 00000000..f4f8b381
--- /dev/null
+++ b/testCases/sclT0005-filters/f042.args
@@ -0,0 +1,4 @@
+--filter
+onOrAfter=2016-02-19
+before=2016-03-01
+account=.*:MatchSubAccount:.*
diff --git a/testCases/sclT0005-filters/f042.conf b/testCases/sclT0005-filters/f042.conf
new file mode 100644
index 00000000..dd26a90e
--- /dev/null
+++ b/testCases/sclT0005-filters/f042.conf
@@ -0,0 +1,20 @@
+inputs += filter-targets.ledger
+
+reports += {
+ title = "Balance Sheet"
+ type = balance
+ outFiles = ["-"]
+ accountMatch = ["^Assets.*", "^Expenses.*"]
+}
+
+exports += {
+ type = journal
+ format = xml
+ outFiles = [out.f042.journal.xml]
+}
+
+exports += {
+ type = balance
+ format = xml
+ outFiles = [out.f042.balance.xml]
+}
diff --git a/testCases/sclT0005-filters/f042.ref.balance.xml b/testCases/sclT0005-filters/f042.ref.balance.xml
new file mode 100644
index 00000000..8d229f0e
--- /dev/null
+++ b/testCases/sclT0005-filters/f042.ref.balance.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testCases/sclT0005-filters/f042.ref.journal.xml b/testCases/sclT0005-filters/f042.ref.journal.xml
new file mode 100644
index 00000000..8f4424f7
--- /dev/null
+++ b/testCases/sclT0005-filters/f042.ref.journal.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+ by notme account
+
+
+
+
+ by account
+ ann
+
+
+
+
+
+
diff --git a/testCases/sclT0005-filters/f050.conf b/testCases/sclT0005-filters/f050.conf
new file mode 100644
index 00000000..595ba998
--- /dev/null
+++ b/testCases/sclT0005-filters/f050.conf
@@ -0,0 +1,24 @@
+inputs += filter-targets.ledger
+
+filters += "onOrAfter=2016-02-19"
+filters += "before=2016-03-01"
+filters += "account=.*:MatchSubAccount:.*"
+
+reports += {
+ title = "Balance Sheet"
+ type = balance
+ outFiles = ["-"]
+ accountMatch = ["^Assets.*", "^Expenses.*"]
+}
+
+exports += {
+ type = journal
+ format = xml
+ outFiles = [out.f050.journal.xml]
+}
+
+exports += {
+ type = balance
+ format = xml
+ outFiles = [out.f050.balance.xml]
+}
diff --git a/testCases/sclT0005-filters/f050.ref.balance.xml b/testCases/sclT0005-filters/f050.ref.balance.xml
new file mode 100644
index 00000000..8d229f0e
--- /dev/null
+++ b/testCases/sclT0005-filters/f050.ref.balance.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testCases/sclT0005-filters/f050.ref.journal.xml b/testCases/sclT0005-filters/f050.ref.journal.xml
new file mode 100644
index 00000000..8f4424f7
--- /dev/null
+++ b/testCases/sclT0005-filters/f050.ref.journal.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+ by notme account
+
+
+
+
+ by account
+ ann
+
+
+
+
+
+
diff --git a/testCases/sclT0005-filters/f051.args b/testCases/sclT0005-filters/f051.args
new file mode 100644
index 00000000..25afee0b
--- /dev/null
+++ b/testCases/sclT0005-filters/f051.args
@@ -0,0 +1,3 @@
+--filter
+onOrAfter=2016-02-01
+before=2016-03-02
diff --git a/testCases/sclT0005-filters/f051.conf b/testCases/sclT0005-filters/f051.conf
new file mode 100644
index 00000000..d32c4d8c
--- /dev/null
+++ b/testCases/sclT0005-filters/f051.conf
@@ -0,0 +1,24 @@
+inputs += filter-targets.ledger
+
+filters += "onOrAfter=2016-02-19"
+filters += "before=2016-02-29"
+filters += "account=.*:MatchSubAccount:.*"
+
+reports += {
+ title = "Balance Sheet"
+ type = balance
+ outFiles = ["-"]
+ accountMatch = ["^Assets.*", "^Expenses.*"]
+}
+
+exports += {
+ type = journal
+ format = xml
+ outFiles = [out.f051.journal.xml]
+}
+
+exports += {
+ type = balance
+ format = xml
+ outFiles = [out.f051.balance.xml]
+}
diff --git a/testCases/sclT0005-filters/f051.ref.balance.xml b/testCases/sclT0005-filters/f051.ref.balance.xml
new file mode 100644
index 00000000..d7f1062b
--- /dev/null
+++ b/testCases/sclT0005-filters/f051.ref.balance.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testCases/sclT0005-filters/f051.ref.journal.xml b/testCases/sclT0005-filters/f051.ref.journal.xml
new file mode 100644
index 00000000..3707d992
--- /dev/null
+++ b/testCases/sclT0005-filters/f051.ref.journal.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+ begin off by one
+
+
+
+
+ begin off by one
+
+
+
+
+ foobar
+ matchme
+
+
+
+
+ matchme
+ ann
+
+
+
+
+ account
+
+
+
+
+ by account
+
+
+
+
+ by notme account
+
+
+
+
+ by account
+ ann
+
+
+
+
+ end off by one
+
+
+
+
+ end off by one
+
+
+
+
+
+
diff --git a/testCases/sclT0005-filters/f060.conf b/testCases/sclT0005-filters/f060.conf
new file mode 100644
index 00000000..86232177
--- /dev/null
+++ b/testCases/sclT0005-filters/f060.conf
@@ -0,0 +1,47 @@
+inputs += filter-targets.ledger
+
+# each one of these is cutting away something
+# and result is combination of all
+filters += "onOrAfter=2016-02-03"
+filters += "before=2016-02-23"
+filters += "annotation=(^$)|(ann)"
+filters += "payee=(.*by.*)|(matchme)"
+filters += "account=(.*:MatchSubAccount:.*)|(.*:E11)"
+
+reports += {
+ title = "Balance Sheet"
+ type = balance
+ outFiles = ["-", "out.f060.report.bal.txt"]
+ accountMatch = ["^Assets.*", "^Expenses.*"]
+}
+
+reports += {
+ title = "Register"
+ type = register
+ outFiles = [out.f060.register.txt]
+}
+
+reports += {
+ title = "Asset Book"
+ type = book
+ outFiles = [out.f060.book.txt]
+ account = "Assets"
+}
+
+exports += {
+ type = journal
+ format = ledger
+ outFiles = [out.f060.closing.ledger]
+}
+
+exports += {
+ type = journal
+ format = xml
+ outFiles = [out.f060.journal.xml]
+}
+
+exports += {
+ type = balance
+ format = xml
+ outFiles = [out.f060.balance.xml]
+}
diff --git a/testCases/sclT0005-filters/f060.ref.balance.xml b/testCases/sclT0005-filters/f060.ref.balance.xml
new file mode 100644
index 00000000..2f42c676
--- /dev/null
+++ b/testCases/sclT0005-filters/f060.ref.balance.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testCases/sclT0005-filters/f060.ref.book.txt b/testCases/sclT0005-filters/f060.ref.book.txt
new file mode 100644
index 00000000..ec4c2c62
--- /dev/null
+++ b/testCases/sclT0005-filters/f060.ref.book.txt
@@ -0,0 +1,33 @@
+Asset Book
+
+ACTIVE FILTER
+ All following conditions must be true:
+ onOrAfter: Transaction date is on or after: 2016-02-03
+ before: Transaction date is before: 2016-02-23
+ annotation: Annotation must match "(^$)|(ann)"
+ payee: Payee must match "(.*by.*)|(matchme)"
+ account: At least one of transaction's accounts must match "(.*:MatchSubAccount:.*)|(.*:E11)"
+
+Account Name: Assets
+
+```
+2016 / Feb
+ -11.00 -11.00 2016 feb 11 (ann) matchme
+ Expenses:E11 11.00
+
+ -17.00 -17.00 2016 feb 17 by account
+ Expenses:MatchSubAccount:E17 17.00
+
+ -19.00 -19.00 2016 feb 19 by notme account
+ Expenses:MatchSubAccount:E19 19.00
+
+ 11.00 11.00 2016 feb 11 (ann) matchme
+ Assets:A11 -11.00
+
+ 17.00 17.00 2016 feb 17 by account
+ Assets:A17 -17.00
+
+ 19.00 19.00 2016 feb 19 by notme account
+ Assets:A19 -19.00
+
+```
diff --git a/testCases/sclT0005-filters/f060.ref.closing.ledger b/testCases/sclT0005-filters/f060.ref.closing.ledger
new file mode 100644
index 00000000..cc5764bd
--- /dev/null
+++ b/testCases/sclT0005-filters/f060.ref.closing.ledger
@@ -0,0 +1,16 @@
+; ACTIVE FILTER
+; All following conditions must be true:
+; onOrAfter: Transaction date is on or after: 2016-02-03
+; before: Transaction date is before: 2016-02-23
+; annotation: Annotation must match "(^$)|(ann)"
+; payee: Payee must match "(.*by.*)|(matchme)"
+; account: At least one of transaction's accounts must match "(.*:MatchSubAccount:.*)|(.*:E11)"
+;
+2016/2/19
+ Assets:A11 -11
+ Assets:A17 -17
+ Assets:A19 -19
+ Expenses:E11 11
+ Expenses:MatchSubAccount:E17 17
+ Expenses:MatchSubAccount:E19 19
+
diff --git a/testCases/sclT0005-filters/f060.ref.journal.xml b/testCases/sclT0005-filters/f060.ref.journal.xml
new file mode 100644
index 00000000..dd9c83cf
--- /dev/null
+++ b/testCases/sclT0005-filters/f060.ref.journal.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ matchme
+ ann
+
+
+
+
+ by account
+
+
+
+
+ by notme account
+
+
+
+
+
+
diff --git a/testCases/sclT0005-filters/f060.ref.register.txt b/testCases/sclT0005-filters/f060.ref.register.txt
new file mode 100644
index 00000000..5b602f36
--- /dev/null
+++ b/testCases/sclT0005-filters/f060.ref.register.txt
@@ -0,0 +1,17 @@
+Register
+
+ACTIVE FILTER
+ All following conditions must be true:
+ onOrAfter: Transaction date is on or after: 2016-02-03
+ before: Transaction date is before: 2016-02-23
+ annotation: Annotation must match "(^$)|(ann)"
+ payee: Payee must match "(.*by.*)|(matchme)"
+ account: At least one of transaction's accounts must match "(.*:MatchSubAccount:.*)|(.*:E11)"
+
+2016 / Feb
+ Assets:A11 -11.00 -11.00
+ Assets:A17 -17.00 -17.00
+ Assets:A19 -19.00 -19.00
+ Expenses:E11 11.00 11.00
+ Expenses:MatchSubAccount:E17 17.00 17.00
+ Expenses:MatchSubAccount:E19 19.00 19.00
diff --git a/testCases/sclT0005-filters/f060.ref.report.bal.txt b/testCases/sclT0005-filters/f060.ref.report.bal.txt
new file mode 100644
index 00000000..0e240331
--- /dev/null
+++ b/testCases/sclT0005-filters/f060.ref.report.bal.txt
@@ -0,0 +1,21 @@
+Balance Sheet
+
+ACTIVE FILTER
+ All following conditions must be true:
+ onOrAfter: Transaction date is on or after: 2016-02-03
+ before: Transaction date is before: 2016-02-23
+ annotation: Annotation must match "(^$)|(ann)"
+ payee: Payee must match "(.*by.*)|(matchme)"
+ account: At least one of transaction's accounts must match "(.*:MatchSubAccount:.*)|(.*:E11)"
+
+ -47.00 Assets
+ -11.00 ├╴A11
+ -17.00 ├╴A17
+ -19.00 └╴A19
+ 47.00 Expenses
+ 11.00 ├╴E11
+ 36.00 └╴MatchSubAccount
+ 17.00 ├╴E17
+ 19.00 └╴E19
+────────────────────────────────────────────
+ 0.00 0.00 = Zero
diff --git a/testCases/sclT0005-filters/filter-targets.ledger b/testCases/sclT0005-filters/filter-targets.ledger
new file mode 100644
index 00000000..d917456b
--- /dev/null
+++ b/testCases/sclT0005-filters/filter-targets.ledger
@@ -0,0 +1,49 @@
+
+2016-01-31 begin off by one
+ Expenses:E02 2
+ Assets:A02
+
+2016-02-01 begin off by one
+ Expenses:E03 3
+ Assets:A03
+
+2016-02-02 begin off by one
+ Expenses:E05 5
+ Assets:A05
+
+2016-02-05 (matchme) foobar
+ Expenses:E07 7
+ Assets:A07
+
+2016-02-11 (ann) matchme
+ Expenses:E11 11
+ Assets:A11
+
+2016-02-13 account
+ Expenses:E13:matchme 13
+ Assets:A13
+
+2016-02-17 by account
+ Expenses:MatchSubAccount:E17 17
+ Assets:A17
+
+2016-02-19 by notme account
+ Expenses:MatchSubAccount:E19 19
+ Assets:A19
+
+2016-02-23 (ann) by account
+ Expenses:E23 23
+ Assets:MatchSubAccount:A23
+
+2016-02-29 end off by one
+ Expenses:E29 29
+ Assets:A29
+
+2016-03-01 end off by one
+ Expenses:E31 31
+ Assets:A31
+
+2016-03-02 end off by one
+ Expenses:E37 37
+ Assets:A37
+
diff --git a/testCases/sclT0005-filters/readme.md b/testCases/sclT0005-filters/readme.md
new file mode 100644
index 00000000..80eb91c8
--- /dev/null
+++ b/testCases/sclT0005-filters/readme.md
@@ -0,0 +1,37 @@
+# tests for filters
+
+## testcases
+
+ - date
+ - [001] begin
+ - [002] end
+ - [003] span (e.g. begin - end)
+
+ - payee
+ - [010] Single txn
+ - [011] multiple txn
+
+ - [020] annotation
+
+ - account
+ - [030] single account
+ - [031] multiple accounts
+
+ - [04X] combinations
+ - [040] txn:attr multiple times (e.g. time + annotation, payee)
+ - txn:attr + post:attr
+ - [041] txn:attr + post:account
+ - [042] txn:time + post:account
+
+ - filter definitions precedence + conf-file
+ - filters from conf-file
+ - [050] one test with combined filter (multiple filter definitions) should cover it for now
+
+ - filters from conf-file + cli
+ - [051] test that cli overrides conf
+
+## Special things to test
+
+ - [010, 020, 030] correct usage of txn:attr (e.g. two different txn, where payee and annotation would match)
+ - [010, 030] txn:attr vs. post:account (e.g. two different txn, where txn:attr and post:attr would match)
+ - [060] active filter notices (this is also testing combinations of all filters by conf-file)