Merge branch 'dev/render-as-dom' into develop

This commit is contained in:
Sergio Martínez Portela 2023-10-07 13:14:51 +02:00
commit 99e44fd8b2
7 changed files with 433 additions and 92 deletions

View File

@ -27,7 +27,7 @@ class PropertyNode:
self.value = value self.value = value
def __repr__(self): def __repr__(self):
return "{{{}: {}}".format(self.key, self.value) return "{{{}: {}}}".format(self.key, self.value)
class ListGroupNode: class ListGroupNode:
@ -89,11 +89,12 @@ class BlockNode:
class CodeBlock(BlockNode): class CodeBlock(BlockNode):
def __init__(self, header, subtype): def __init__(self, header, subtype, arguments):
super().__init__() super().__init__()
self.header = header self.header = header
self.lines = None self.lines = None
self.subtype = subtype self.subtype = subtype
self.arguments = arguments
def set_lines(self, lines): def set_lines(self, lines):
self.lines = lines self.lines = lines

View File

@ -92,10 +92,10 @@ LIST_ITEM_RE = re.compile(
) )
# Org-Babel # Org-Babel
BEGIN_BLOCK_RE = re.compile(r"^\s*#\+BEGIN_(?P<subtype>[^ ]+)(?P<content>.*)$", re.I) BEGIN_BLOCK_RE = re.compile(r"^\s*#\+BEGIN_(?P<subtype>[^ ]+)(?P<arguments>.*)$", re.I)
END_BLOCK_RE = re.compile(r"^\s*#\+END_(?P<subtype>[^ ]+)\s*$", re.I) END_BLOCK_RE = re.compile(r"^\s*#\+END_(?P<subtype>[^ ]+)\s*$", re.I)
RESULTS_DRAWER_RE = re.compile(r"^\s*:results:\s*$", re.I) RESULTS_DRAWER_RE = re.compile(r"^\s*:results:\s*$", re.I)
CodeSnippet = collections.namedtuple("CodeSnippet", ("name", "content", "result")) CodeSnippet = collections.namedtuple("CodeSnippet", ("name", "content", "result", "arguments"))
# Groupings # Groupings
NON_FINISHED_GROUPS = (type(None), dom.ListGroupNode, dom.ResultsDrawerNode, dom.PropertyDrawerNode) NON_FINISHED_GROUPS = (type(None), dom.ListGroupNode, dom.ResultsDrawerNode, dom.PropertyDrawerNode)
@ -154,6 +154,26 @@ class RangeInRaw:
contents.insert(start_idx + i + 1, element) contents.insert(start_idx + i + 1, element)
def unescape_block_lines(lines: str) -> str:
"""
Remove leading ',' from block_lines if they escape `*` characters.
"""
i = 0
lines = lines.split('\n')
while i < len(lines):
line = lines[i]
if (line.lstrip(' ').startswith(',')
and line.lstrip(' ,').startswith('*')
):
# Remove leading ','
lead_pos = line.index(',')
line = line[:lead_pos] + line[lead_pos + 1:]
lines[i] = line
i += 1
return '\n'.join(lines)
def get_links_from_content(content): def get_links_from_content(content):
in_link = False in_link = False
in_description = False in_description = False
@ -356,7 +376,7 @@ class Headline:
end = line.linenum end = line.linenum
lines = self.get_lines_between(start + 1, end) lines = self.get_lines_between(start + 1, end)
contents = "\n".join(lines) contents = unescape_block_lines("\n".join(lines))
if contents.endswith("\n"): if contents.endswith("\n"):
# This is not ideal, but to avoid having to do this maybe # This is not ideal, but to avoid having to do this maybe
# the content parsing must be re-thinked # the content parsing must be re-thinked
@ -376,18 +396,32 @@ class Headline:
current_node.append(dom.PropertyNode(line.key, line.value)) current_node.append(dom.PropertyNode(line.key, line.value))
elif isinstance(line, Text): elif isinstance(line, Text):
if isinstance(current_node, dom.BlockNode): tree_up = list(indentation_tree)
current_node.append(dom.Text(line)) while len(tree_up) > 0:
elif isinstance(current_node, dom.DrawerNode): node = tree_up[-1]
current_node.append(dom.Text(line)) if (isinstance(node, dom.BlockNode)
or isinstance(node, dom.DrawerNode)
):
node.append(dom.Text(line))
current_node = node
contents = []
break
elif ((not isinstance(node, dom.TableNode)) and
(type(node) not in NON_FINISHED_GROUPS)
):
raise NotImplementedError('Not implemented node type: {} (headline_id={}, line={}, doc={})'.format(
node,
self.id,
line.linenum,
self.doc.path,
))
else:
tree_up.pop(-1)
else: else:
if isinstance(current_node, dom.TableNode):
pass # No problem here
elif type(current_node) not in NON_FINISHED_GROUPS:
raise NotImplementedError('Not implemented node type: {}'.format(current_node))
current_node = None current_node = None
contents = [] contents = []
tree.append(dom.Text(text_to_dom(line.contents, line))) tree.append(dom.Text(text_to_dom(line.contents, line)))
indentation_tree = tree_up
elif isinstance(line, ListItem): elif isinstance(line, ListItem):
if (current_node is None if (current_node is None
@ -395,9 +429,12 @@ class Headline:
or isinstance(current_node, dom.BlockNode) or isinstance(current_node, dom.BlockNode)
or isinstance(current_node, dom.DrawerNode) or isinstance(current_node, dom.DrawerNode)
): ):
was_node = current_node
current_node = dom.ListGroupNode() current_node = dom.ListGroupNode()
if current_node is None: if was_node is None:
tree.append(current_node) tree.append(current_node)
else:
was_node.append(current_node)
indentation_tree.append(current_node) indentation_tree.append(current_node)
if not isinstance(current_node, dom.ListGroupNode): if not isinstance(current_node, dom.ListGroupNode):
if not isinstance(current_node, dom.ListGroupNode): if not isinstance(current_node, dom.ListGroupNode):
@ -420,18 +457,22 @@ class Headline:
current_node = sublist current_node = sublist
indentation_tree.append(current_node) indentation_tree.append(current_node)
while len(indentation_tree) > 0 and ( while len(indentation_tree) > 0:
(len(indentation_tree[-1].children) > 0) list_children = [
and len( c
[ for c in indentation_tree[-1].children
c if isinstance(c, dom.ListItem)
for c in indentation_tree[-1].children ]
if isinstance(c, dom.ListItem)
][-1].orig.indentation if (len(list_children) == 0):
) break
> len(line.indentation) if ((len(list_children[-1].orig.indentation)
): <= len(line.indentation))):
rem = indentation_tree.pop() # No more breaking out of lists, it's indentation
# is less than ours
break
rem = indentation_tree.pop(-1)
if len(indentation_tree) == 0: if len(indentation_tree) == 0:
indentation_tree.append(rem) indentation_tree.append(rem)
current_node = rem current_node = rem
@ -448,9 +489,15 @@ class Headline:
tree.append(current_node) tree.append(current_node)
# TODO: Allow indentation of this element inside others # TODO: Allow indentation of this element inside others
indentation_tree = [current_node] indentation_tree = [current_node]
if not isinstance(current_node, dom.TableNode): elif not isinstance(current_node, dom.TableNode):
if not isinstance(current_node, dom.TableNode): if isinstance(current_node, dom.ListGroupNode):
logging.warning("Expected a {}, found: {} on line {}".format(dom.TableNode, current_node, line.linenum)) # As an item inside a list
list_node = current_node
current_node = dom.TableNode()
list_node.append(current_node)
indentation_tree.append(current_node)
else:
logging.debug("Expected a {}, found: {} on line {}".format(dom.TableNode, current_node, line.linenum))
# This can happen. Frequently inside a LogDrawer # This can happen. Frequently inside a LogDrawer
if len(line.cells) > 0 and len(line.cells[0]) > 0 and line.cells[0][0] == '-': if len(line.cells) > 0 and len(line.cells[0]) > 0 and line.cells[0][0] == '-':
@ -464,7 +511,7 @@ class Headline:
and line.delimiter_type == DelimiterLineType.BEGIN_BLOCK and line.delimiter_type == DelimiterLineType.BEGIN_BLOCK
): ):
assert type(current_node) in NON_FINISHED_GROUPS assert type(current_node) in NON_FINISHED_GROUPS
current_node = dom.CodeBlock(line, line.type_data.subtype) current_node = dom.CodeBlock(line, line.type_data.subtype, line.arguments)
elif isinstance(line, Keyword): elif isinstance(line, Keyword):
logging.warning("Keywords not implemented on `as_dom()`") logging.warning("Keywords not implemented on `as_dom()`")
@ -512,8 +559,6 @@ class Headline:
tree_up.pop(-1) tree_up.pop(-1)
else: else:
raise Exception('Unexpected node ({}) on headline (id={}), line {}'.format(current_node, self.id, linenum)) raise Exception('Unexpected node ({}) on headline (id={}), line {}'.format(current_node, self.id, linenum))
if self.id == 'd07fcf27-d6fc-41e3-a9d0-b2e2902aec23':
print("Found node:", current_node)
current_node = None current_node = None
elif content.strip().upper() == ":RESULTS:": elif content.strip().upper() == ":RESULTS:":
assert current_node is None assert current_node is None
@ -534,14 +579,26 @@ class Headline:
last_line = None last_line = None
for li in self.list_items: for li in self.list_items:
if last_line == li.linenum - 1: if last_line is None:
lists[-1].append(li)
else:
lists.append([li]) lists.append([li])
else:
num_lines = li.linenum - (last_line + 1)
lines_between = ''.join(['\n' + l
for l in self.get_lines_between(last_line + 1, li.linenum)]
)
last_line = li.linenum # Only empty lines
if ((num_lines == lines_between.count('\n'))
and (len(lines_between.strip()) == 0)
):
lists[-1].append(li)
else:
lists.append([li])
last_line = li.linenum + sum(c.count('\n') for c in li.content)
return lists return lists
# @DEPRECATED: use `get_lists`
def getLists(self): def getLists(self):
return self.get_lists() return self.get_lists()
@ -623,6 +680,9 @@ class Headline:
else: else:
return list(self.shallow_tags) + self.parent.tags return list(self.shallow_tags) + self.parent.tags
def add_tag(self, tag: str):
self.shallow_tags.append(tag)
def get_property(self, name: str, default=None): def get_property(self, name: str, default=None):
for prop in self.properties: for prop in self.properties:
if prop.key == name: if prop.key == name:
@ -676,18 +736,14 @@ class Headline:
for lst in self.get_lists(): for lst in self.get_lists():
for item in lst: for item in lst:
if item.tag:
yield from get_links_from_content(item.tag)
yield from get_links_from_content(item.content) yield from get_links_from_content(item.content)
def get_lines_between(self, start, end): def get_lines_between(self, start, end):
for line in self.contents: for line in self.contents:
if start <= line.linenum < end: if start <= line.linenum < end:
text = [] yield "".join(line.get_raw())
for item in line.contents:
if isinstance(item, str):
text.append(item)
elif isinstance(item, MarkerType):
text.append(ModeToMarker[item])
yield "".join(text)
def get_contents(self, format): def get_contents(self, format):
if format == "raw": if format == "raw":
@ -737,17 +793,19 @@ class Headline:
inside_code = False inside_code = False
sections = [] sections = []
arguments = None
for delimiter in self.delimiters: for delimiter in self.delimiters:
if delimiter.delimiter_type == DelimiterLineType.BEGIN_BLOCK and delimiter.type_data.subtype.lower() == "src": if delimiter.delimiter_type == DelimiterLineType.BEGIN_BLOCK and delimiter.type_data.subtype.lower() == "src":
line_start = delimiter.linenum line_start = delimiter.linenum
inside_code = True inside_code = True
arguments = delimiter.arguments
elif delimiter.delimiter_type == DelimiterLineType.END_BLOCK and delimiter.type_data.subtype.lower() == "src": elif delimiter.delimiter_type == DelimiterLineType.END_BLOCK and delimiter.type_data.subtype.lower() == "src":
inside_code = False inside_code = False
start, end = line_start, delimiter.linenum start, end = line_start, delimiter.linenum
lines = self.get_lines_between(start + 1, end) lines = self.get_lines_between(start + 1, end)
contents = "\n".join(lines) contents = unescape_block_lines("\n".join(lines))
if contents.endswith("\n"): if contents.endswith("\n"):
# This is not ideal, but to avoid having to do this maybe # This is not ideal, but to avoid having to do this maybe
# the content parsing must be re-thinked # the content parsing must be re-thinked
@ -758,8 +816,10 @@ class Headline:
"line_first": start + 1, "line_first": start + 1,
"line_last": end - 1, "line_last": end - 1,
"content": contents, "content": contents,
"arguments": arguments,
} }
) )
arguments = None
line_start = None line_start = None
for kword in self.keywords: for kword in self.keywords:
@ -810,10 +870,40 @@ class Headline:
name = None name = None
content = section["content"] content = section["content"]
code_result = section.get("result", None) code_result = section.get("result", None)
results.append(CodeSnippet(name=name, content=content, result=code_result)) arguments = section.get("arguments", None)
results.append(CodeSnippet(name=name, content=content, result=code_result, arguments=arguments))
return results return results
def create_headline_at_end(self) -> Headline:
headline = Headline(
start_line=1,
depth=self.depth + 1,
orig=None,
properties=[],
keywords=[],
priority_start=None,
priority=None,
title_start=None,
title="",
state="",
tags_start=None,
tags=[],
contents=[],
children=[],
structural=[],
delimiters=[],
list_items=[],
table_rows=[],
parent=self,
is_todo=False,
is_done=False,
spacing=" ",
)
self.children.append(headline)
return headline
RawLine = collections.namedtuple("RawLine", ("linenum", "line")) RawLine = collections.namedtuple("RawLine", ("linenum", "line"))
Keyword = collections.namedtuple( Keyword = collections.namedtuple(
@ -823,22 +913,34 @@ Property = collections.namedtuple(
"Property", ("linenum", "match", "key", "value", "options") "Property", ("linenum", "match", "key", "value", "options")
) )
ListItem = collections.namedtuple( class ListItem:
"ListItem", def __init__(self,
( linenum, match,
"linenum", indentation,
"match", bullet, counter, counter_sep,
"indentation", checkbox_indentation, checkbox_value,
"bullet", tag_indentation, tag,
"counter", content,
"counter_sep", ):
"checkbox_indentation", self.linenum = linenum
"checkbox_value", self.match = match
"tag_indentation", self.indentation = indentation
"tag", self.bullet = bullet
"content", self.counter = counter
), self.counter_sep = counter_sep
) self.checkbox_indentation = checkbox_indentation
self.checkbox_value = checkbox_value
self.tag_indentation = tag_indentation
self.tag = tag
self.content = content
@property
def text_start_pos(self):
return len(self.indentation) + 1 # Indentation + bullet
def append_line(self, line):
self.content += parse_content_block('\n' + line).contents
TableRow = collections.namedtuple( TableRow = collections.namedtuple(
"TableRow", "TableRow",
( (
@ -952,7 +1054,7 @@ BlockDelimiterTypeData = collections.namedtuple(
) )
DelimiterLine = collections.namedtuple( DelimiterLine = collections.namedtuple(
"DelimiterLine", ("linenum", "line", "delimiter_type", "type_data") "DelimiterLine", ("linenum", "line", "delimiter_type", "type_data", "arguments")
) )
@ -1000,6 +1102,8 @@ def token_from_type(tok_type):
class TimeRange: class TimeRange:
def __init__(self, start_time: OrgTime, end_time: OrgTime): def __init__(self, start_time: OrgTime, end_time: OrgTime):
assert start_time is not None
assert end_time is not None
self.start_time = start_time self.start_time = start_time
self.end_time = end_time self.end_time = end_time
@ -1540,14 +1644,20 @@ def parse_contents(raw_contents: List[RawLine]):
return [parse_content_block(block) for block in blocks] return [parse_content_block(block) for block in blocks]
def parse_content_block(raw_contents: List[RawLine]): def parse_content_block(raw_contents: Union[List[RawLine],str]):
contents_buff = [] contents_buff = []
for line in raw_contents: if isinstance(raw_contents, str):
contents_buff.append(line.line) contents_buff.append(raw_contents)
else:
for line in raw_contents:
contents_buff.append(line.line)
contents = "\n".join(contents_buff) contents = "\n".join(contents_buff)
tokens = tokenize_contents(contents) tokens = tokenize_contents(contents)
current_line = raw_contents[0].linenum if isinstance(raw_contents, str):
current_line = None
else:
current_line = raw_contents[0].linenum
contents = [] contents = []
# Use tokens to tag chunks of text with it's container type # Use tokens to tag chunks of text with it's container type
@ -1574,7 +1684,9 @@ def dump_contents(raw):
elif isinstance(raw, ListItem): elif isinstance(raw, ListItem):
bullet = raw.bullet if raw.bullet else raw.counter + raw.counter_sep bullet = raw.bullet if raw.bullet else raw.counter + raw.counter_sep
content = token_list_to_raw(raw.content) content_full = token_list_to_raw(raw.content)
content_lines = content_full.split('\n')
content = '\n'.join(content_lines)
checkbox = f"[{raw.checkbox_value}]" if raw.checkbox_value else "" checkbox = f"[{raw.checkbox_value}]" if raw.checkbox_value else ""
tag = f"{raw.tag_indentation}{token_list_to_raw(raw.tag or '')}::" if raw.tag or raw.tag_indentation else "" tag = f"{raw.tag_indentation}{token_list_to_raw(raw.tag or '')}::" if raw.tag or raw.tag_indentation else ""
return ( return (
@ -1804,7 +1916,12 @@ class OrgDoc:
if headline.state: if headline.state:
state = headline.state + " " state = headline.state + " "
yield "*" * headline.depth + headline.spacing + state + token_list_to_raw(headline.title.contents) + tags raw_title = token_list_to_raw(headline.title.contents)
tags_padding = ""
if not raw_title.endswith(" ") and tags:
tags_padding = " "
yield "*" * headline.depth + headline.spacing + state + raw_title + tags_padding + tags
planning = headline.get_planning_line() planning = headline.get_planning_line()
if planning is not None: if planning is not None:
@ -1963,19 +2080,19 @@ class OrgDocReader:
def add_list_item_line(self, linenum: int, match: re.Match) -> int: def add_list_item_line(self, linenum: int, match: re.Match) -> int:
li = ListItem( li = ListItem(
linenum, linenum=linenum,
match, match=match,
match.group("indentation"), indentation=match.group("indentation"),
match.group("bullet"), bullet=match.group("bullet"),
match.group("counter"), counter=match.group("counter"),
match.group("counter_sep"), counter_sep=match.group("counter_sep"),
match.group("checkbox_indentation"), checkbox_indentation=match.group("checkbox_indentation"),
match.group("checkbox_value"), checkbox_value=match.group("checkbox_value"),
match.group("tag_indentation"), tag_indentation=match.group("tag_indentation"),
parse_content_block( tag=parse_content_block(
[RawLine(linenum=linenum, line=match.group("tag"))] [RawLine(linenum=linenum, line=match.group("tag"))]
).contents if match.group("tag") else None, ).contents if match.group("tag") else None,
parse_content_block( content=parse_content_block(
[RawLine(linenum=linenum, line=match.group("content"))] [RawLine(linenum=linenum, line=match.group("content"))]
).contents, ).contents,
) )
@ -1984,6 +2101,7 @@ class OrgDocReader:
self.list_items.append(li) self.list_items.append(li)
else: else:
self.headline_hierarchy[-1]["list_items"].append(li) self.headline_hierarchy[-1]["list_items"].append(li)
return li
def add_table_line(self, linenum: int, line: str) -> int: def add_table_line(self, linenum: int, line: str) -> int:
chunks = line.split('|') chunks = line.split('|')
@ -2033,7 +2151,7 @@ class OrgDocReader:
def add_begin_block_line(self, linenum: int, match: re.Match): def add_begin_block_line(self, linenum: int, match: re.Match):
line = DelimiterLine(linenum, match.group(0), DelimiterLineType.BEGIN_BLOCK, line = DelimiterLine(linenum, match.group(0), DelimiterLineType.BEGIN_BLOCK,
BlockDelimiterTypeData(match.group("subtype"))) BlockDelimiterTypeData(match.group("subtype")), match.group('arguments'))
if len(self.headline_hierarchy) == 0: if len(self.headline_hierarchy) == 0:
self.delimiters.append(line) self.delimiters.append(line)
else: else:
@ -2041,7 +2159,7 @@ class OrgDocReader:
def add_end_block_line(self, linenum: int, match: re.Match): def add_end_block_line(self, linenum: int, match: re.Match):
line = DelimiterLine(linenum, match.group(0), DelimiterLineType.END_BLOCK, line = DelimiterLine(linenum, match.group(0), DelimiterLineType.END_BLOCK,
BlockDelimiterTypeData(match.group("subtype"))) BlockDelimiterTypeData(match.group("subtype")), None)
if len(self.headline_hierarchy) == 0: if len(self.headline_hierarchy) == 0:
self.delimiters.append(line) self.delimiters.append(line)
else: else:
@ -2093,6 +2211,25 @@ class OrgDocReader:
reader = enumerate(lines) reader = enumerate(lines)
in_drawer = False in_drawer = False
in_block = False in_block = False
list_item_indentation = None
list_item = None
def add_raw_line_with_possible_indentation(linenum, line):
added = False
nonlocal list_item
nonlocal list_item_indentation
if list_item:
if ((line[:list_item.text_start_pos].strip() == '')
or (len(line.strip()) == 0)
):
list_item.append_line(line)
added = True
else:
list_item = None
list_item_indentation = None
if not added:
self.add_raw_line(linenum, line)
for lnum, line in reader: for lnum, line in reader:
linenum = lnum + 1 linenum = lnum + 1
@ -2101,41 +2238,58 @@ class OrgDocReader:
if m := END_BLOCK_RE.match(line): if m := END_BLOCK_RE.match(line):
self.add_end_block_line(linenum, m) self.add_end_block_line(linenum, m)
in_block = False in_block = False
list_item_indentation = None
list_item = None
else: else:
self.add_raw_line(linenum, line) add_raw_line_with_possible_indentation(linenum, line)
elif m := HEADLINE_RE.match(line): elif m := HEADLINE_RE.match(line):
list_item_indentation = None
list_item = None
self.add_headline(linenum, m) self.add_headline(linenum, m)
elif m := LIST_ITEM_RE.match(line): elif m := LIST_ITEM_RE.match(line):
self.add_list_item_line(linenum, m) list_item = self.add_list_item_line(linenum, m)
list_item_indentation = m.group("indentation")
elif m := RAW_LINE_RE.match(line): elif m := RAW_LINE_RE.match(line):
self.add_raw_line(linenum, line) add_raw_line_with_possible_indentation(linenum, line)
# Org-babel # Org-babel
elif m := BEGIN_BLOCK_RE.match(line): elif m := BEGIN_BLOCK_RE.match(line):
self.add_begin_block_line(linenum, m) self.add_begin_block_line(linenum, m)
in_block = True in_block = True
list_item_indentation = None
list_item = None
elif m := END_BLOCK_RE.match(line): elif m := END_BLOCK_RE.match(line):
self.add_end_block_line(linenum, m) self.add_end_block_line(linenum, m)
in_block = False in_block = False
list_item_indentation = None
list_item = None
# Generic properties # Generic properties
elif m := KEYWORDS_RE.match(line): elif m := KEYWORDS_RE.match(line):
self.add_keyword_line(linenum, m) self.add_keyword_line(linenum, m)
elif m := DRAWER_END_RE.match(line): elif m := DRAWER_END_RE.match(line):
self.add_drawer_end_line(linenum, line, m) self.add_drawer_end_line(linenum, line, m)
in_drawer = False in_drawer = False
list_item_indentation = None
list_item = None
elif (not in_drawer) and (m := DRAWER_START_RE.match(line)): elif (not in_drawer) and (m := DRAWER_START_RE.match(line)):
self.add_property_drawer_line(linenum, line, m) self.add_property_drawer_line(linenum, line, m)
in_drawer = True in_drawer = True
list_item_indentation = None
list_item = None
elif (not in_drawer) and (m := RESULTS_DRAWER_RE.match(line)): elif (not in_drawer) and (m := RESULTS_DRAWER_RE.match(line)):
self.add_results_drawer_line(linenum, line, m) self.add_results_drawer_line(linenum, line, m)
in_drawer = True in_drawer = True
list_item_indentation = None
list_item = None
elif m := NODE_PROPERTIES_RE.match(line): elif m := NODE_PROPERTIES_RE.match(line):
self.add_node_properties_line(linenum, m) self.add_node_properties_line(linenum, m)
elif line.strip().startswith('|'): elif line.strip().startswith('|'):
self.add_table_line(linenum, line) self.add_table_line(linenum, line)
list_item_indentation = None
list_item = None
# Not captured # Not captured
else: else:
self.add_raw_line(linenum, line) add_raw_line_with_possible_indentation(linenum, line)
except: except:
logging.error("Error line {}: {}".format(linenum + 1, line)) logging.error("Error line {}: {}".format(linenum + 1, line))
raise raise

View File

@ -36,3 +36,16 @@ exit 0 # Comment
This is another test This is another test
with two lines too with two lines too
:end: :end:
* Escaped code
:PROPERTIES:
:ID: 04-code-escaped-code-id
:CREATED: [2020-01-01 Wed 01:01]
:END:
#+BEGIN_SRC c :results drawer
/* This code has to be escaped to
,* avoid confusion with new headlines.
,*/
main(){}
#+END_SRC

View File

@ -1,5 +1,5 @@
#+TITLE: 06-Links #+TITLE: 06-Lists
#+DESCRIPTION: Simple org file to test links #+DESCRIPTION: Simple org file to test lists
#+TODO: TODO(t) PAUSED(p) | DONE(d) #+TODO: TODO(t) PAUSED(p) | DONE(d)
@ -51,3 +51,25 @@ Also with markup
- _Key_ :: _Value_ - _Key_ :: _Value_
- /Key/ 2 :: /Value/ 2 - /Key/ 2 :: /Value/ 2
* List with multiline elements
:PROPERTIES:
:ID: 07-list-with-multiline-elements
:CREATED: [2020-01-01 Wed 01:01]
:END:
- This is a list item...
that spans multiple lines
- This is another list item...
that has content on multiple lines
Text after a multiline element
- This is another
multiline list
#+begin_quote
With a block element inside
#+end_quote

View File

@ -16,3 +16,18 @@
| Content2-1 | Content2-2 | Content2-3 | | Content2-1 | Content2-2 | Content2-3 |
Content after the table. Content after the table.
** Indented table
:PROPERTIES:
:ID: 10-table-test-id-02-indented
:CREATED: [2020-01-01 Wed 01:01]
:END:
- This table is indented inside a list item.
- Item before in list
| Header1 | Header2 | Header3 |
|------------+------------+------------|
| Content1-1 | Content1-2 | Content1-3 (last cell unclosed)
| Content2-1 | Content2-2 | Content2-3 |
- Item after in list
- This item happens after the indented table.

16
tests/11-nested-lists.org Normal file
View File

@ -0,0 +1,16 @@
#+TITLE: 11-Nested lists
#+DESCRIPTION: Simple org file to test nested lists
#+TODO: TODO(t) PAUSED(p) | DONE(d)
* Nested lists
:PROPERTIES:
:ID: 11-nested-lists
:CREATED: [2020-01-01 Wed 01:01]
:END:
- 1
- 1.1
- 1.2
- 2
- 2.1
- 2.2
- 3

View File

@ -4,7 +4,8 @@ import unittest
from datetime import date from datetime import date
from datetime import datetime as DT from datetime import datetime as DT
from org_rw import MarkerToken, MarkerType, Timestamp, dumps, load, loads from org_rw import MarkerToken, MarkerType, Timestamp, dumps, load, loads, dom
import org_rw
from utils.assertions import (BOLD, CODE, HL, ITALIC, SPAN, STRIKE, UNDERLINED, from utils.assertions import (BOLD, CODE, HL, ITALIC, SPAN, STRIKE, UNDERLINED,
VERBATIM, WEB_LINK, Doc, Tokens) VERBATIM, WEB_LINK, Doc, Tokens)
@ -433,13 +434,14 @@ class TestSerde(unittest.TestCase):
doc = load(f) doc = load(f)
snippets = list(doc.get_code_snippets()) snippets = list(doc.get_code_snippets())
self.assertEqual(len(snippets), 2) self.assertEqual(len(snippets), 3)
self.assertEqual( self.assertEqual(
snippets[0].content, snippets[0].content,
'echo "This is a test"\n' 'echo "This is a test"\n'
+ 'echo "with two lines"\n' + 'echo "with two lines"\n'
+ "exit 0 # Exit successfully", + "exit 0 # Exit successfully",
) )
self.assertEqual(snippets[0].arguments.split(), ['shell', ':results', 'verbatim'])
self.assertEqual( self.assertEqual(
snippets[0].result, snippets[0].result,
"This is a test\n" + "with two lines", "This is a test\n" + "with two lines",
@ -455,6 +457,14 @@ class TestSerde(unittest.TestCase):
snippets[1].result, "This is another test\n" + "with two lines too" snippets[1].result, "This is another test\n" + "with two lines too"
) )
self.assertEqual(
snippets[2].content,
'/* This code has to be escaped to\n'
+ ' * avoid confusion with new headlines.\n'
+ ' */\n'
+ 'main(){}',
)
def test_mimic_write_file_05(self): def test_mimic_write_file_05(self):
with open(os.path.join(DIR, "05-dates.org")) as f: with open(os.path.join(DIR, "05-dates.org")) as f:
orig = f.read() orig = f.read()
@ -551,7 +561,7 @@ class TestSerde(unittest.TestCase):
MarkerToken(closing=False, tok_type=MarkerType.UNDERLINED_MODE), MarkerToken(closing=False, tok_type=MarkerType.UNDERLINED_MODE),
"markup", "markup",
MarkerToken(closing=True, tok_type=MarkerType.UNDERLINED_MODE), MarkerToken(closing=True, tok_type=MarkerType.UNDERLINED_MODE),
".", ".", "\n"
], ],
) )
@ -567,7 +577,7 @@ class TestSerde(unittest.TestCase):
self.assertEqual(lists2[0][0].counter, "1") self.assertEqual(lists2[0][0].counter, "1")
self.assertEqual(lists2[0][0].counter_sep, ".") self.assertEqual(lists2[0][0].counter_sep, ".")
self.assertEqual(lists2[0][1].content, ["Second element"]) self.assertEqual(lists2[0][1].content, ["Second element", "\n"])
self.assertEqual(lists2[0][1].counter, "2") self.assertEqual(lists2[0][1].counter, "2")
self.assertEqual(lists2[0][1].counter_sep, ".") self.assertEqual(lists2[0][1].counter_sep, ".")
@ -575,10 +585,24 @@ class TestSerde(unittest.TestCase):
self.assertEqual(lists2[1][0].counter, "1") self.assertEqual(lists2[1][0].counter, "1")
self.assertEqual(lists2[1][0].counter_sep, ")") self.assertEqual(lists2[1][0].counter_sep, ")")
self.assertEqual(lists2[1][1].content, ["Second element"]) self.assertEqual(lists2[1][1].content, ["Second element", "\n"])
self.assertEqual(lists2[1][1].counter, "2") self.assertEqual(lists2[1][1].counter, "2")
self.assertEqual(lists2[1][1].counter_sep, ")") self.assertEqual(lists2[1][1].counter_sep, ")")
hl4 = doc.getTopHeadlines()[3]
# ...
lists4 = hl4.getLists()
print(lists4)
self.assertEqual(len(lists4), 2)
self.assertEqual(lists4[0][0].content, ["This is a list item...", "\n that spans multiple lines", "\n"])
self.assertEqual(lists4[0][0].bullet, "-")
self.assertEqual(lists4[0][1].content, ["This is another list item...", "\n that has content on multiple lines", "\n"])
self.assertEqual(lists4[0][1].bullet, "-")
self.assertEqual(lists4[1][0].content, ["This is another", "\n multiline list", "\n"])
self.assertEqual(lists4[1][0].bullet, "-")
def test_org_roam_07(self): def test_org_roam_07(self):
with open(os.path.join(DIR, "07-org-roam-v2.org")) as f: with open(os.path.join(DIR, "07-org-roam-v2.org")) as f:
orig = f.read() orig = f.read()
@ -658,3 +682,99 @@ class TestSerde(unittest.TestCase):
self.assertEqual(first_table[0].cells[1].strip(), 'Header2') self.assertEqual(first_table[0].cells[1].strip(), 'Header2')
self.assertEqual(first_table[0].cells[2].strip(), 'Header3') self.assertEqual(first_table[0].cells[2].strip(), 'Header3')
hl = hl.children[0]
tables = hl.get_tables()
first_table = tables[0]
self.assertEqual(len(first_table), 4)
print(first_table[0])
self.assertEqual(len(first_table[0].cells), 3)
self.assertEqual(first_table[0].cells[0].strip(), 'Header1')
self.assertEqual(first_table[0].cells[1].strip(), 'Header2')
self.assertEqual(first_table[0].cells[2].strip(), 'Header3')
def test_tables_html_file_10(self):
with open(os.path.join(DIR, "10-tables.org")) as f:
doc = load(f)
hl = doc.getTopHeadlines()[0]
tree = hl.as_dom()
non_props = [
item
for item in tree
if not isinstance(item, dom.PropertyDrawerNode)
]
self.assertTrue(isinstance(non_props[0], dom.Text)
and isinstance(non_props[1], dom.TableNode)
and isinstance(non_props[2], dom.Text),
'Expected <Text><Table><Text>')
hl = hl.children[0]
tree = hl.as_dom()
non_props = [
item
for item in tree
if not (isinstance(item, dom.PropertyDrawerNode)
or isinstance(item, dom.Text))
]
print_tree(non_props)
self.assertTrue(len(non_props) == 1,
'Expected <List>, with only (1) element')
def test_nested_lists_html_file_11(self):
with open(os.path.join(DIR, "11-nested-lists.org")) as f:
doc = load(f)
hl = doc.getTopHeadlines()[0]
tree = hl.as_dom()
non_props = [
item
for item in tree
if not isinstance(item, dom.PropertyDrawerNode)
]
print_tree(non_props)
self.assertTrue((len(non_props) == 1) and (isinstance(non_props[0], dom.ListGroupNode)),
'Expected only <List> as top level')
dom_list = non_props[0]
children = dom_list.children
self.assertTrue(len(children) == 5, 'Expected 5 items inside <List>, 3 texts and 2 sublists')
# Assert texts
self.assertEqual(children[0].content, ['1'])
self.assertEqual(children[2].content, ['2'])
self.assertEqual(children[4].content[0], '3') # Might be ['3', '\n'] but shouldn't be a breaking change
# Assert lists
self.assertTrue(isinstance(children[1], dom.ListGroupNode), 'Expected sublist inside "1"')
self.assertEqual(children[1].children[0].content, ['1.1'])
self.assertEqual(children[1].children[1].content, ['1.2'])
self.assertTrue(isinstance(children[3], dom.ListGroupNode), 'Expected sublist inside "2"')
self.assertEqual(children[3].children[0].content, ['2.1'])
self.assertEqual(children[3].children[1].content, ['2.2'])
def print_tree(tree, indentation=0, headline=None):
for element in tree:
print(" " * indentation * 2, "EL:", element)
if "children" in dir(element):
if len(element.children) > 0:
print_element(element.children, indentation + 1, headline)
print()
elif "content" in dir(element):
for content in element.content:
print_element(content, indentation + 1, headline)
def print_element(element, indentation, headline):
if isinstance(element, org_rw.Link):
print(" " * indentation * 2, "Link:", element.get_raw())
elif isinstance(element, str):
print(" " * indentation * 2, "Str[" + element.replace('\n', '<NL>') + "]", type(element))
else:
print_tree(element, indentation, headline)