Skip to content

Instantly share code, notes, and snippets.

@runiq
Last active December 14, 2016 14:13
Show Gist options
  • Save runiq/49a42a237299581455bbcfc128eb7231 to your computer and use it in GitHub Desktop.
Save runiq/49a42a237299581455bbcfc128eb7231 to your computer and use it in GitHub Desktop.
import re
tags = { 'otag': re.escape('{{'), 'ctag': re.escape('}}') }
# Taken from anki.template.template.Template.compile_regexps()
# section = r"%(otag)s[#^]([^}]*)%(ctag)s((?s).+?)%(otag)s/\1%(ctag)s" % tags
# tag = r"%(otag)s([=&!>{]?)((?![#^/]).+?)\1?%(ctag)s+"
sectionAndTag = r"""%(otag)s # Section regex first
([#^]) # Separates section from tag (#1)
([^}]*) # Section tag name (#2)
%(ctag)s
((?s).+?) # Section contents (#3)
%(otag)s
/\2 # Closing tag with section name
%(ctag)s
| # Tag regex now
%(otag)s
([=&!>{]?) # Modifiers according to Anki (#4)
((?![#^/]).+?)\4? # Tag name (must not be preceded by [#^/]) (#5)
%(ctag)s
+"""
both_re = re.compile(sectionAndTag % tags, re.VERBOSE)
def myReqForTemplate(self, m, flds, t):
"""
Creates the cached code object for every template in a model.
m: Model (dict)
flds: Field names (list)
t: Template (dict)
"""
# Maps field names -> list indices in field string because
# I don't want to deal with string handling in eval()
fMap = {f: i for (i, f) in enumerate(flds)}
# t['qfmt'] is the front template of the Anki card (string)
codetxt = _parseSec(fMap, t['qfmt'])
code = compile(codetxt, '<string>', 'eval')
# This will be saved in m['req'] by another function
return codetxt, code
def _parseSec(fMap, cardQuestionTemplate):
"""
Builds the expression to be evaluated later. Recursive,
therefore expensive.
There are three kinds of values in the card question template that are
of interest to us. This function maps them to their corresponding code:
{{fld}} -> flds[idx]
{{#fld}}...{{/fld}} -> flds[idx] and any([...])
{{^fld}}....{{/fld}} -> not flds[idx] and any([...])
The expression is therefore a string in this form (example):
"any([flds[0], flds[2], flds[1] and any([flds[5], flds[6]]), flds[3]])".
"flds" is a list containing strings, so this expression checks if
all of the strings are empty or not. "flds" is not known at the
construction time of this expression, only on evaluation (because it
contains the field values of the card that is being checked).
"""
codetxt = []
tags = set()
for match in both_re.finditer(cardQuestionTemplate):
# See sectionAndTag regex for match group indices
# #2: field name for {{#fld}} or {{^fld}}
fld = match.group(2)
if fld:
# #1: '#' or '^'
pred = '' if match.group(1) == '#' else 'not '
try:
# #3: Section content (the '...' in '{{#fld}}...{{/fld}}'
codetxt.append(pred + 'flds[' + str(fMap[fld]) + '] and any([' + _parseSec(fMap, match.group(3)) + '])')
except KeyError:
# Invalid field names evaluate to False
codetxt.append('False')
else:
# #5: field name for {{fld}}
fld = match.group(5)
try:
codetxt.append('flds[' + str(fMap[fld]) + ']')
except KeyError:
# Invalid field names evaluate to False
codetxt.append('False')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment