forked from kenkeiras/org-rw
Add base support for markup tests.
This commit is contained in:
parent
d23ee1adba
commit
0dab7e4703
@ -1 +1,2 @@
|
|||||||
from .org_dom import *
|
from .org_dom import *
|
||||||
|
from .utils import *
|
||||||
|
@ -55,12 +55,14 @@ INACTIVE_TIME_STAMP_RE = re.compile(r'\[{}\]'.format(BASE_TIME_STAMP_RE))
|
|||||||
# r'(?P<end_year>\d{4})-(?P<end_month>\d{2})-(?P<end_day>\d{2}) (?P<end_dow>[^ ]+)((?P<end_hour>\d{1,2}):(?P<end_minute>\d{1,2}))?')
|
# r'(?P<end_year>\d{4})-(?P<end_month>\d{2})-(?P<end_day>\d{2}) (?P<end_dow>[^ ]+)((?P<end_hour>\d{1,2}):(?P<end_minute>\d{1,2}))?')
|
||||||
|
|
||||||
Headline = collections.namedtuple('Headline', ('start_line', 'depth',
|
Headline = collections.namedtuple('Headline', ('start_line', 'depth',
|
||||||
'keyword_start', 'keyword',
|
'orig',
|
||||||
|
'properties', 'keywords',
|
||||||
'priority_start', 'priority',
|
'priority_start', 'priority',
|
||||||
'title_start', 'title',
|
'title_start', 'title',
|
||||||
'tags_start', 'tags',
|
'tags_start', 'tags',
|
||||||
'content',
|
'contents',
|
||||||
'children',
|
'children',
|
||||||
|
'structural',
|
||||||
))
|
))
|
||||||
|
|
||||||
RawLine = collections.namedtuple('RawLine', ('linenum', 'line'))
|
RawLine = collections.namedtuple('RawLine', ('linenum', 'line'))
|
||||||
@ -107,9 +109,43 @@ def timestamp_to_string(ts):
|
|||||||
else:
|
else:
|
||||||
return '[{}]'.format(base)
|
return '[{}]'.format(base)
|
||||||
|
|
||||||
|
def parse_headline(hl) -> Headline:
|
||||||
|
# 'linenum': linenum,
|
||||||
|
# 'orig': match,
|
||||||
|
# 'title': match.group('line'),
|
||||||
|
# 'contents': [],
|
||||||
|
# 'children': [],
|
||||||
|
# 'keywords': [],
|
||||||
|
# 'properties': [],
|
||||||
|
# 'structural': [],
|
||||||
|
# HEADLINE_RE = re.compile(r'^(?P<stars>\*+) (?P<spacing>\s*)(?P<line>.*)$')
|
||||||
|
stars = hl['orig'].group('stars')
|
||||||
|
depth = len(stars)
|
||||||
|
|
||||||
|
# TODO: Parse line for priority, cookies and tags
|
||||||
|
line = hl['orig'].group('line')
|
||||||
|
title = line.strip()
|
||||||
|
|
||||||
|
return Headline(start_line=hl['linenum'],
|
||||||
|
depth=depth,
|
||||||
|
orig=hl['orig'],
|
||||||
|
title=title,
|
||||||
|
contents=hl['contents'],
|
||||||
|
children=[parse_headline(child) for child in hl['children']],
|
||||||
|
keywords=hl['keywords'],
|
||||||
|
properties=hl['properties'],
|
||||||
|
structural=hl['structural'],
|
||||||
|
title_start=None,
|
||||||
|
priority=None,
|
||||||
|
priority_start=None,
|
||||||
|
tags_start=None,
|
||||||
|
tags=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class OrgDom:
|
class OrgDom:
|
||||||
def __init__(self, headlines, keywords, contents):
|
def __init__(self, headlines, keywords, contents):
|
||||||
self.headlines: List[Headline] = headlines
|
self.headlines: List[Headline] = list(map(parse_headline, headlines))
|
||||||
self.keywords: List[Property] = keywords
|
self.keywords: List[Property] = keywords
|
||||||
self.contents: List[RawLine] = contents
|
self.contents: List[RawLine] = contents
|
||||||
|
|
||||||
@ -162,23 +198,23 @@ class OrgDom:
|
|||||||
return (structural[0], structural[1])
|
return (structural[0], structural[1])
|
||||||
|
|
||||||
def dump_headline(self, headline):
|
def dump_headline(self, headline):
|
||||||
yield headline['orig'].group('stars') + ' ' + headline['orig'].group('spacing') + headline['orig'].group('line')
|
yield '*' * headline.depth + ' ' + headline.orig.group('spacing') + headline.title
|
||||||
|
|
||||||
lines = []
|
lines = []
|
||||||
KW_T = 0
|
KW_T = 0
|
||||||
CONTENT_T = 1
|
CONTENT_T = 1
|
||||||
PROPERTIES_T = 2
|
PROPERTIES_T = 2
|
||||||
STRUCTURAL_T = 3
|
STRUCTURAL_T = 3
|
||||||
for keyword in headline['keywords']:
|
for keyword in headline.keywords:
|
||||||
lines.append((KW_T, self.dump_kw(keyword)))
|
lines.append((KW_T, self.dump_kw(keyword)))
|
||||||
|
|
||||||
for content in headline['contents']:
|
for content in headline.contents:
|
||||||
lines.append((CONTENT_T, self.dump_contents(content)))
|
lines.append((CONTENT_T, self.dump_contents(content)))
|
||||||
|
|
||||||
for prop in headline['properties']:
|
for prop in headline.properties:
|
||||||
lines.append((PROPERTIES_T, self.dump_property(prop)))
|
lines.append((PROPERTIES_T, self.dump_property(prop)))
|
||||||
|
|
||||||
for struct in headline['structural']:
|
for struct in headline.structural:
|
||||||
lines.append((STRUCTURAL_T, self.dump_structural(struct)))
|
lines.append((STRUCTURAL_T, self.dump_structural(struct)))
|
||||||
|
|
||||||
lines = sorted(lines, key=lambda x: x[1][0])
|
lines = sorted(lines, key=lambda x: x[1][0])
|
||||||
@ -204,7 +240,7 @@ class OrgDom:
|
|||||||
|
|
||||||
yield from structured_lines
|
yield from structured_lines
|
||||||
|
|
||||||
for child in headline['children']:
|
for child in headline.children:
|
||||||
yield from self.dump_headline(child)
|
yield from self.dump_headline(child)
|
||||||
|
|
||||||
def dump(self):
|
def dump(self):
|
||||||
@ -235,7 +271,7 @@ class OrgDomReader:
|
|||||||
def add_headline(self, linenum: int, match: re.Match) -> int:
|
def add_headline(self, linenum: int, match: re.Match) -> int:
|
||||||
# Position reader on the proper headline
|
# Position reader on the proper headline
|
||||||
stars = match.group('stars')
|
stars = match.group('stars')
|
||||||
depth = len(stars) - 1
|
depth = len(stars)
|
||||||
|
|
||||||
headline = {
|
headline = {
|
||||||
'linenum': linenum,
|
'linenum': linenum,
|
||||||
@ -248,13 +284,13 @@ class OrgDomReader:
|
|||||||
'structural': [],
|
'structural': [],
|
||||||
}
|
}
|
||||||
|
|
||||||
while (depth - 1) > len(self.headline_hierarchy):
|
while (depth - 2) > len(self.headline_hierarchy):
|
||||||
# Introduce structural headlines
|
# Introduce structural headlines
|
||||||
self.headline_hierarchy.append(None)
|
self.headline_hierarchy.append(None)
|
||||||
while depth < len(self.headline_hierarchy):
|
while depth < len(self.headline_hierarchy):
|
||||||
self.headline_hierarchy.pop()
|
self.headline_hierarchy.pop()
|
||||||
|
|
||||||
if depth == 0:
|
if depth == 1:
|
||||||
self.headlines.append(headline)
|
self.headlines.append(headline)
|
||||||
else:
|
else:
|
||||||
self.headline_hierarchy[-1]['children'].append(headline)
|
self.headline_hierarchy[-1]['children'].append(headline)
|
||||||
|
22
org_dom/utils.py
Normal file
22
org_dom/utils.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
from .org_dom import Headline, RawLine
|
||||||
|
|
||||||
|
|
||||||
|
def get_hl_raw_contents(doc: Headline) -> str:
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
for content in doc.contents:
|
||||||
|
lines.append(get_raw_contents(content))
|
||||||
|
|
||||||
|
return '\n'.join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def get_rawline_contents(doc: RawLine) -> str:
|
||||||
|
return doc.line
|
||||||
|
|
||||||
|
|
||||||
|
def get_raw_contents(doc) -> str:
|
||||||
|
if isinstance(doc, Headline):
|
||||||
|
return get_hl_raw_contents(doc)
|
||||||
|
if isinstance(doc, RawLine):
|
||||||
|
return get_rawline_contents(doc)
|
||||||
|
raise NotImplementedError('Unhandled type: ' + str(doc))
|
21
tests/02-markup.org
Normal file
21
tests/02-markup.org
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#+TITLE: 02-Markup
|
||||||
|
#+DESCRIPTION: Simple org file to test markup
|
||||||
|
#+TODO: TODO(t) PAUSED(p) | DONE(d)
|
||||||
|
|
||||||
|
|
||||||
|
* First level
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: 02-markup-first-level-id
|
||||||
|
:CREATED: [2020-01-01 Wed 01:01]
|
||||||
|
:END:
|
||||||
|
This is a *bold phrase*.
|
||||||
|
|
||||||
|
This is a =verbatim phrase=.
|
||||||
|
|
||||||
|
This is a /italic phrase/.
|
||||||
|
|
||||||
|
This is a +strike-through phrase+.
|
||||||
|
|
||||||
|
This is a _underlined phrase_.
|
||||||
|
|
||||||
|
This is a ~code phrase~.
|
@ -4,7 +4,8 @@ import unittest
|
|||||||
from datetime import datetime as DT
|
from datetime import datetime as DT
|
||||||
|
|
||||||
from org_dom import dumps, load, loads
|
from org_dom import dumps, load, loads
|
||||||
from utils.dom_assertions import HL, Dom
|
from utils.dom_assertions import (BOLD, CODE, HL, ITALIC, SPAN, STRIKE,
|
||||||
|
UNDERLINED, VERBATIM, Dom)
|
||||||
|
|
||||||
DIR = os.path.dirname(os.path.abspath(__file__))
|
DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
@ -23,15 +24,15 @@ class TestSerde(unittest.TestCase):
|
|||||||
('ID', '01-simple-first-level-id'),
|
('ID', '01-simple-first-level-id'),
|
||||||
('CREATED', DT(2020, 1, 1, 1, 1)),
|
('CREATED', DT(2020, 1, 1, 1, 1)),
|
||||||
],
|
],
|
||||||
content='First level content',
|
content=' First level content\n',
|
||||||
children=[
|
children=[
|
||||||
HL('Second level',
|
HL('Second level',
|
||||||
props=[('ID', '01-simple-second-level-id')],
|
props=[('ID', '01-simple-second-level-id')],
|
||||||
content='Second level content',
|
content='\n Second level content\n',
|
||||||
children=[
|
children=[
|
||||||
HL('Third level',
|
HL('Third level',
|
||||||
props=[('ID', '01-simple-third-level-id')],
|
props=[('ID', '01-simple-third-level-id')],
|
||||||
content='Third level content')
|
content='\n Third level content\n')
|
||||||
])
|
])
|
||||||
])))
|
])))
|
||||||
|
|
||||||
@ -44,3 +45,38 @@ class TestSerde(unittest.TestCase):
|
|||||||
doc = loads(orig)
|
doc = loads(orig)
|
||||||
|
|
||||||
self.assertEqual(dumps(doc), orig)
|
self.assertEqual(dumps(doc), orig)
|
||||||
|
|
||||||
|
def test_markup_file_02(self):
|
||||||
|
with open(os.path.join(DIR, '02-markup.org')) as f:
|
||||||
|
doc = load(f)
|
||||||
|
|
||||||
|
ex = Dom(props=[('TITLE', '02-Markup'),
|
||||||
|
('DESCRIPTION', 'Simple org file to test markup'),
|
||||||
|
('TODO', 'TODO(t) PAUSED(p) | DONE(d)')],
|
||||||
|
children=(HL('First level',
|
||||||
|
props=[
|
||||||
|
('ID', '02-markup-first-level-id'),
|
||||||
|
('CREATED', DT(2020, 1, 1, 1, 1)),
|
||||||
|
],
|
||||||
|
content=[
|
||||||
|
SPAN(" This is a ", BOLD("bold phrase"),
|
||||||
|
"."),
|
||||||
|
SPAN(""),
|
||||||
|
SPAN(" This is a ",
|
||||||
|
VERBATIM("verbatim phrase"), "."),
|
||||||
|
SPAN(""),
|
||||||
|
SPAN(" This is a ", ITALIC("italic phrase"),
|
||||||
|
"."),
|
||||||
|
SPAN(""),
|
||||||
|
SPAN(" This is a ",
|
||||||
|
STRIKE("strike-through phrase"), "."),
|
||||||
|
SPAN(""),
|
||||||
|
SPAN(" This is a ",
|
||||||
|
UNDERLINED("underlined phrase"), "."),
|
||||||
|
SPAN(""),
|
||||||
|
SPAN(" This is a ", CODE("code phrase"),
|
||||||
|
"."),
|
||||||
|
SPAN(""),
|
||||||
|
])))
|
||||||
|
|
||||||
|
ex.assert_matches(self, doc)
|
||||||
|
@ -2,6 +2,8 @@ import collections
|
|||||||
import unittest
|
import unittest
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
from org_dom import get_raw_contents
|
||||||
|
|
||||||
|
|
||||||
def timestamp_to_datetime(ts):
|
def timestamp_to_datetime(ts):
|
||||||
return datetime(ts.year, ts.month, ts.day, ts.hour, ts.minute)
|
return datetime(ts.year, ts.month, ts.day, ts.hour, ts.minute)
|
||||||
@ -48,13 +50,13 @@ class HL:
|
|||||||
self.children = children
|
self.children = children
|
||||||
|
|
||||||
def assert_matches(self, test_case: unittest.TestCase, doc):
|
def assert_matches(self, test_case: unittest.TestCase, doc):
|
||||||
test_case.assertEqual(self.title, doc['title'])
|
test_case.assertEqual(self.title, doc.title)
|
||||||
|
|
||||||
# Check properties
|
# Check properties
|
||||||
if self.props is None:
|
if self.props is None:
|
||||||
test_case.assertEqual(len(doc['properties']), 0)
|
test_case.assertEqual(len(doc.properties), 0)
|
||||||
else:
|
else:
|
||||||
doc_props = doc['properties']
|
doc_props = doc.properties
|
||||||
test_case.assertEqual(len(doc_props), len(self.props))
|
test_case.assertEqual(len(doc_props), len(self.props))
|
||||||
|
|
||||||
for i, prop in enumerate(self.props):
|
for i, prop in enumerate(self.props):
|
||||||
@ -63,15 +65,84 @@ class HL:
|
|||||||
test_case.assertEqual(
|
test_case.assertEqual(
|
||||||
timestamp_to_datetime(doc_props[i].value), prop[1])
|
timestamp_to_datetime(doc_props[i].value), prop[1])
|
||||||
|
|
||||||
# @TODO: Check properties
|
if isinstance(self.content, str):
|
||||||
|
test_case.assertEqual(get_raw_contents(doc), self.content)
|
||||||
|
else:
|
||||||
|
test_case.assertEqual(len(doc.contents), len(self.content))
|
||||||
|
for i, content in enumerate(self.content):
|
||||||
|
test_case.assertEqual(get_raw_contents(doc.contents[i]),
|
||||||
|
content.to_raw())
|
||||||
|
|
||||||
# Check children
|
# Check children
|
||||||
if self.children is None:
|
if self.children is None:
|
||||||
test_case.assertEqual(len(doc['children']), 0)
|
test_case.assertEqual(len(doc.children), 0)
|
||||||
else:
|
else:
|
||||||
doc_headlines = doc['children']
|
doc_headlines = doc.children
|
||||||
test_case.assertEqual(len(doc_headlines), len(self.children),
|
test_case.assertEqual(len(doc_headlines), len(self.children),
|
||||||
self.title)
|
self.title)
|
||||||
|
|
||||||
for i, children in enumerate(self.children):
|
for i, children in enumerate(self.children):
|
||||||
children.assert_matches(test_case, doc_headlines[i])
|
children.assert_matches(test_case, doc_headlines[i])
|
||||||
|
|
||||||
|
|
||||||
|
class SPAN:
|
||||||
|
def __init__(self, *kwargs):
|
||||||
|
self.contents = kwargs
|
||||||
|
|
||||||
|
def to_raw(self):
|
||||||
|
chunks = []
|
||||||
|
for section in self.contents:
|
||||||
|
if isinstance(section, str):
|
||||||
|
chunks.append(section)
|
||||||
|
else:
|
||||||
|
chunks.append(section.to_raw())
|
||||||
|
|
||||||
|
return ''.join(chunks)
|
||||||
|
|
||||||
|
|
||||||
|
class BOLD:
|
||||||
|
def __init__(self, text):
|
||||||
|
self.text = text
|
||||||
|
|
||||||
|
def to_raw(self):
|
||||||
|
return '*{}*'.format(self.text)
|
||||||
|
|
||||||
|
|
||||||
|
class CODE:
|
||||||
|
def __init__(self, text):
|
||||||
|
self.text = text
|
||||||
|
|
||||||
|
def to_raw(self):
|
||||||
|
return '~{}~'.format(self.text)
|
||||||
|
|
||||||
|
|
||||||
|
class ITALIC:
|
||||||
|
def __init__(self, text):
|
||||||
|
self.text = text
|
||||||
|
|
||||||
|
def to_raw(self):
|
||||||
|
return '/{}/'.format(self.text)
|
||||||
|
|
||||||
|
|
||||||
|
class STRIKE:
|
||||||
|
def __init__(self, text):
|
||||||
|
self.text = text
|
||||||
|
|
||||||
|
def to_raw(self):
|
||||||
|
return '+{}+'.format(self.text)
|
||||||
|
|
||||||
|
|
||||||
|
class UNDERLINED:
|
||||||
|
def __init__(self, text):
|
||||||
|
self.text = text
|
||||||
|
|
||||||
|
def to_raw(self):
|
||||||
|
return '_{}_'.format(self.text)
|
||||||
|
|
||||||
|
|
||||||
|
class VERBATIM:
|
||||||
|
def __init__(self, text):
|
||||||
|
self.text = text
|
||||||
|
|
||||||
|
def to_raw(self):
|
||||||
|
return '={}='.format(self.text)
|
||||||
|
Loading…
Reference in New Issue
Block a user