Beispiel #1
0
    def test_compile_nested(self):
        """Expand a Rule into actionable steps"""

        rule = yaml.load("""
        match:
            narration: AirBnB(.*)
        match-narration:
            - Air BnB(.*)
            - BnB Payments(.*)
        set-account: Income:AirBnB
        set-tags: passive-income
        """, Loader=yaml.FullLoader)

        r = Rule(rule)
        r.add_directives(rule)
        expected_rule = MatchRule(
            directive='transaction',
            parameter='narration',
            meta_key=None,
            regular_expressions={
                re.compile("AirBnB(.*)"),
                re.compile("Air BnB(.*)"),
                re.compile("BnB Payments(.*)")
            }
        )

        self.assertEqual(
            {'transaction-narration': expected_rule},
            r.match_requirements
        )
Beispiel #2
0
    def test_match_1(self):
        entry = parser.parse_one("""
2020-04-08 ! "AMZN Mktp US*L08746BB3"
  match-key: "2020040824692160098100992944500"
  ofx-type: "DEBIT"
  * Liabilities:CreditCard:Chase:Amazon  -39.98 USD
  ! Expenses:FIXME                        39.98 USD
""")
        assert entry.flag == "!"
        ruler = Rule({
            'match-narration': "AMZN Mktp *.",
            'set-account': 'Expenses:Shopping'
        })
        result = ruler.check(entry)
        self.assertIsNotNone(result)
        new_entry = ruler.modify_entry(entry, result)
        self.assertEqual(new_entry.flag, '*')
Beispiel #3
0
    def test_add_rule_meta_acct(self):
        rules = yaml.load("""
- match-narration: E*TRADE DES:ACH.*
  match-account: Assets:Banking:.*
  set-posting-account: Assets:Transfer
  set-posting-account-meta-account: Assets:Banking:ETrade:Cash
""", Loader=yaml.FullLoader)
        for rule in rules:
            Rule(rule)
Beispiel #4
0
def rule_from_meta(entry: data.Transaction) -> Rule:
    """We use the Entry as a template to the Rule
    Copy the Narration, Payee, Tags, Expense Account etc.
    """
    rs = {}
    if entry.tags:
        rs['set-tags'] = entry.tags
    if entry.payee:
        rs['set-payee'] = entry.payee

    for posting in entry.postings:
        if posting.account.startswith('Expenses'):
            rs['set-posting-account'] = posting.account

    for k, v in entry.meta.items():
        if k.startswith('match-') or k.startswith('set-'):
            rs[k] = v
    r = Rule(rs)

    logger.info(f"Created a Fancy Rule: {rs} -> {repr(r)}")
    return r
Beispiel #5
0
    def test_compile_basic(self):
        """Expand a Rule into actionable steps"""

        rule = yaml.load("""
        match:
            narration: AirBnB(.*)
        """, Loader=yaml.FullLoader)

        r = Rule(rule)

        expected_rule = MatchRule(
            directive='transaction',
            parameter='narration',
            meta_key=None,
            regular_expressions={
                re.compile("AirBnB(.*)")
            }
        )

        self.assertEqual(
            {'transaction-narration': expected_rule},
            r.match_requirements
        )
Beispiel #6
0
def match_directives(entries, options_map, *args):
    """Modify any entries that Match existing Rules"""
    MATCH_CHECK.clear()
    rules = []

    # Make sure to have run the apply_coolbeans_settings_plugin
    settings = options_map['coolbeans']

    # Load a rules.yaml type file
    if 'rules-file' in settings:
        # We support multiple Rules Files
        for file_path in settings['rules-file']:
            file = pathlib.Path(file_path)
            if not file.exists():
                logger.warning(f"Unable to find Rules File {file}")
                continue

            # Read the YAML file
            with file.open("r") as stream:
                new_rules = yaml.full_load(stream)

            for rule in new_rules:
                rules.append(Rule(rule))
    output_file = settings.get('output-file', ['matched.bean'])[0]
    output_file = pathlib.Path(output_file)

    new_entries = []
    mod_entries = []
    no_match_entries = []
    possible_rules = {}

    # Now, see what we can actually Match:
    for entry in entries:

        # We're only interested in Pending Entries
        if getattr(entry, 'flag', None) != '!':
            # Pass through to new_entries
            new_entries.append(entry)
            continue

        # Check against all the Rules:
        modified = False
        for rule in rules:
            match_values = rule.check(entry)
            if entry.narration.lower().startswith('airbnb') and match_values:
                logger.info(
                    f"{entry.narration.lower()}: {rule.match_requirements}: {match_values}"
                )
            if match_values is None:
                continue
            entry = rule.modify_entry(entry, match_values)
            modified = True

        # Always pass it to our output stream
        new_entries.append(entry)

        if modified:
            mod_entries.append(entry)
        else:
            no_match_entries.append(entry)

    # We update the "suggestions" file

    dcontext = DisplayContext()
    dcontext.set_commas(True)
    with output_file.open("w") as outstream:
        try:
            print_entries(mod_entries, file=outstream, dcontext=dcontext)
            logger.info(
                f"cached: wrote {len(mod_entries)} entries to {output_file}")
        except Exception as exc:
            logger.exception('while printing entries.  ')
            try:
                for entry in mod_entries:
                    print_entries([entry], file=sys.stderr)
            except Exception:
                logger.exception(f"{entry}")
                raise

    return new_entries, []
Beispiel #7
0
 def add_rules(self, rules: List[dict]):
     for rule_dict in rules:
         self.rules.append(Rule(rule_dict))