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)