Commit 85621e54 authored by Michał Góral's avatar Michał Góral

<sr> with correct implementation of markup processing

parent 5f760677
Pipeline #56835734 failed with stage
in 1 minute and 31 seconds
......@@ -103,7 +103,7 @@ def _create_default_blocks(cfg):
'default',
name='Next tasks',
filter='status:pending',
fmt=' {description} <surr beg=1 end=2><surr beg="(" end=")"><m class=warning>{priority}</m></surr></surr> <m class=info>{tags:>}</m>', # pylint: disable=line-too-long
fmt=' {description}<sr beg=" "><m name=warning>{priority}</m></sr><sr beg=" "><m name=info>{tags:>}</m></sr>', # pylint: disable=line-too-long
sort='priority,urgency-',
height='70%')
......
......@@ -15,40 +15,65 @@
# You should have received a copy of the GNU General Public License
# along with TWC. If not, see <http://www.gnu.org/licenses/>.
'''Custom markup processor.
Each tag should be implemented as a separate class which has the following
properties:
1. implements process(data : <list, str, tuple>) -> <list, str, tuple>
2. defines static __tagname__ : str, which defines handled tag name
3. is added to _tag_types tuple
Markup element is either a string or a tuple of (potentially nested) of other
markup elements.
process() should accept either a list of markup elements or a single markup
element (string or tuple).
'''
import itertools
from collections import deque
from html.parser import HTMLParser
import attr
class _Tag:
def __init__(self, attrs):
for attr, val in attrs:
setattr(self, attr, val)
from twc.utils import eprint
from twc.locale import _
def markup(self, data):
'''Re-implement in child classes. Returns a non-nested markup object or
objects. If multiple objects are returned via a list), it markup will
be extended'''
raise NotImplemented('markup() not implemented')
@attr.s
class _M:
'''Elements markup: <m name=text>'''
name = attr.ib('text')
class _M(_Tag):
def markup(self, data):
if data:
cl = getattr(self, 'class')
return (cl, data)
__tagname__ = 'm'
def process(self, data):
# wrap only string and lists, because wrapping another tuple (another
# <m>) won't have any effect because URWID will only process the
# inner-most markup element. Plus my tests showed that URWID quickly
# starts bugging with markup nested multiple times.
if isinstance(data, str) or isinstance(data, list):
return (self.name, data)
return data
class _Surr(_Tag):
def markup(self, data):
if data:
# TODO: extract data lists
return [self.beg, data, self.end]
@attr.s
class _Sr:
'''Elements surround: <sr beg=[ end=]>'''
beg = attr.ib('')
end = attr.ib('')
_tag_classes = {
'm': _M,
'surr': _Surr,
}
__tagname__ = 'sr'
def process(self, data):
if isinstance(data, str):
return '{0}{1}{2}'.format(self.beg, data, self.end)
elif isinstance(data, list):
return [self.beg] + data + [self.end]
return [self.beg, data, self.end]
_tag_types = (_M, _Sr)
_tag_handlers = {cls.__tagname__: cls for cls in _tag_types}
# pylint: disable=abstract-method
......@@ -64,26 +89,29 @@ class Parser(HTMLParser):
super().__init__()
def handle_starttag(self, tag, attrs):
ctor = _tag_classes.get(tag)
ctor = _tag_handlers.get(tag)
if ctor:
self.tags.append(ctor(attrs))
kwds = {}
kwds.update(attrs)
self.tags.append(ctor(**kwds))
else:
eprint(_('Tag not supported: <{}>'.format(tag)))
def handle_endtag(self, tag):
ctor = _tag_classes.get(tag)
if ctor and self.tags and isinstance(self.tags[-1], ctor):
tag_obj = self.tags.pop()
newdata = tag_obj.markup(self._data)
self._data = newdata
if self._data and not self.tags:
if isinstance(self._data, list):
self.markup.extend(self._data)
else:
self.markup.append(self._data)
self._data = None
ctor = _tag_handlers.get(tag)
if not ctor:
return
if not self.tags:
return eprint(_('Unexpected end tag: </{}>'.format(tag)))
if not isinstance(self.tags[-1], ctor):
return eprint(_('End tag in incorrect order: </{}>'.format(tag)))
self.tags.pop()
def handle_data(self, data):
if self.tags:
self._data = data
for tag in reversed(self.tags):
data = tag.process(data)
if isinstance(data, list):
self.markup.extend(data)
else:
self.markup.append(data)
......@@ -6,13 +6,60 @@ import twc.markup as markup
@pytest.mark.parametrize('fmt,expected', [
('', []),
('foo bar', ['foo bar']),
('foo <m class=highlight>bar baz</m> blah',
('foo <m name=highlight>bar baz</m> blah',
['foo ', ('highlight', 'bar baz'), ' blah']),
('foo <m class="highlight">bar baz</m> blah',
('foo <m name="highlight">bar baz</m> blah',
['foo ', ('highlight', 'bar baz'), ' blah']),
('foo <sr beg=, end=?>bar </sr> baz', ['foo ', ',bar ?', ' baz'])
])
def test_markup(fmt, expected):
p = markup.Parser()
p.feed(fmt)
assert p.markup == expected
@pytest.mark.parametrize('fmt,expected', [
('<m>a</m>', [('text', 'a')]),
('<sr>a</sr>', ['a']),
])
def test_default_attrs(fmt, expected):
p = markup.Parser()
p.feed(fmt)
assert p.markup == expected
@pytest.mark.parametrize('fmt', [
('<m></m>'),
('<sr></sr>'),
('<m><sr></sr></m>'),
('<sr><m></m></sr>'),
])
def test_empty(fmt):
p = markup.Parser()
p.feed(fmt)
assert not p.markup
@pytest.mark.parametrize('fmt,expected', [
('<sr beg=[ end=]><sr beg=[ end=]><m>foo</m></sr></sr>',
['[', '[', ('text', 'foo'), ']', ']']),
('<m><sr beg=[ end=]>foo</sr></m> bar', [('text', '[foo]'), ' bar']),
('<m><m><sr beg=[ end=]>foo</sr></m> bar</m>',
[('text', '[foo]'), ('text', ' bar')]),
('<sr beg=[ end=]><m>foo</m></sr>', ['[', ('text', 'foo'), ']']),
('<sr><m name=out><sr beg=; end=;><m name=in>foo</m></sr></m></sr>',
['', ('out', [';', ('in', 'foo'), ';']), ''])
])
def test_nested(fmt, expected):
p = markup.Parser()
p.feed(fmt)
assert p.markup == expected
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment