Apply autoformatting with black.

This commit is contained in:
Sergio Martínez Portela 2020-11-26 23:44:56 +01:00
parent d5f8d76aeb
commit 1d71d1a3c3
5 changed files with 302 additions and 168 deletions

View File

@ -63,6 +63,7 @@ INACTIVE_TIME_STAMP_RE = re.compile(r"\[{}\]".format(BASE_TIME_STAMP_RE))
# BASE_TIME_RANGE_RE = (r'(?P<start_year>\d{4})-(?P<start_month>\d{2})-(?P<start_day>\d{2}) (?P<start_dow>[^ ]+)((?P<start_hour>\d{1,2}):(?P<start_minute>\d{1,2}))?', # BASE_TIME_RANGE_RE = (r'(?P<start_year>\d{4})-(?P<start_month>\d{2})-(?P<start_day>\d{2}) (?P<start_dow>[^ ]+)((?P<start_hour>\d{1,2}):(?P<start_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}))?') # 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}))?')
def get_tokens(value): def get_tokens(value):
if isinstance(value, Text): if isinstance(value, Text):
return value.contents return value.contents
@ -70,6 +71,7 @@ def get_tokens(value):
return [value.line] return [value.line]
raise Exception("Unknown how to get tokens from: {}".format(value)) raise Exception("Unknown how to get tokens from: {}".format(value))
def get_links_from_content(content): def get_links_from_content(content):
in_link = False in_link = False
in_description = False in_description = False
@ -85,7 +87,7 @@ def get_links_from_content(content):
elif tok.tok_type == LinkTokenType.CLOSE: elif tok.tok_type == LinkTokenType.CLOSE:
in_link = False in_link = False
in_description = False in_description = False
yield Link(''.join(link_value), ''.join(link_description)) yield Link("".join(link_value), "".join(link_description))
link_value = [] link_value = []
link_description = [] link_description = []
elif isinstance(tok, str) and in_link: elif isinstance(tok, str) and in_link:
@ -94,8 +96,25 @@ def get_links_from_content(content):
else: else:
link_value.append(tok) link_value.append(tok)
class Headline: class Headline:
def __init__(self, start_line, depth, orig, properties, keywords, priority_start, priority, title_start, title, tags_start, tags, contents, children, structural): def __init__(
self,
start_line,
depth,
orig,
properties,
keywords,
priority_start,
priority,
title_start,
title,
tags_start,
tags,
contents,
children,
structural,
):
self.start_line = start_line self.start_line = start_line
self.depth = depth self.depth = depth
self.orig = orig self.orig = orig
@ -115,6 +134,7 @@ class Headline:
for content in self.contents: for content in self.contents:
yield from get_links_from_content(content) yield from get_links_from_content(content)
RawLine = collections.namedtuple("RawLine", ("linenum", "line")) RawLine = collections.namedtuple("RawLine", ("linenum", "line"))
Keyword = collections.namedtuple( Keyword = collections.namedtuple(
"Keyword", ("linenum", "match", "key", "value", "options") "Keyword", ("linenum", "match", "key", "value", "options")
@ -140,6 +160,7 @@ class MarkerType(Enum):
UNDERLINED_MODE = 0b10000 UNDERLINED_MODE = 0b10000
VERBATIM_MODE = 0b100000 VERBATIM_MODE = 0b100000
MARKERS = { MARKERS = {
"*": MarkerType.BOLD_MODE, "*": MarkerType.BOLD_MODE,
"~": MarkerType.CODE_MODE, "~": MarkerType.CODE_MODE,
@ -157,14 +178,17 @@ for tok, mode in MARKERS.items():
MarkerToken = collections.namedtuple("MarkerToken", ("closing", "tok_type")) MarkerToken = collections.namedtuple("MarkerToken", ("closing", "tok_type"))
LinkToken = collections.namedtuple("LinkToken", ("tok_type")) LinkToken = collections.namedtuple("LinkToken", ("tok_type"))
class LinkTokenType(Enum): class LinkTokenType(Enum):
OPEN_LINK = 3 OPEN_LINK = 3
OPEN_DESCRIPTION = 5 OPEN_DESCRIPTION = 5
CLOSE = 4 CLOSE = 4
BEGIN_PROPERTIES = "OPEN_PROPERTIES" BEGIN_PROPERTIES = "OPEN_PROPERTIES"
END_PROPERTIES = "CLOSE_PROPERTIES" END_PROPERTIES = "CLOSE_PROPERTIES"
def token_from_type(tok_type): def token_from_type(tok_type):
return ModeToMarker[tok_type] return ModeToMarker[tok_type]
@ -258,9 +282,9 @@ class Link:
def get_raw(self): def get_raw(self):
if self.description: if self.description:
return '[[{}][{}]]'.format(self.value, self.description) return "[[{}][{}]]".format(self.value, self.description)
else: else:
return '[[{}]]'.format(self.value) return "[[{}]]".format(self.value)
class Text: class Text:
@ -278,16 +302,16 @@ class Text:
contents.append(chunk) contents.append(chunk)
elif isinstance(chunk, LinkToken): elif isinstance(chunk, LinkToken):
if chunk.tok_type == LinkTokenType.OPEN_LINK: if chunk.tok_type == LinkTokenType.OPEN_LINK:
contents.append('[[') contents.append("[[")
elif chunk.tok_type == LinkTokenType.OPEN_DESCRIPTION: elif chunk.tok_type == LinkTokenType.OPEN_DESCRIPTION:
contents.append('][') contents.append("][")
else: else:
assert chunk.tok_type == LinkTokenType.CLOSE assert chunk.tok_type == LinkTokenType.CLOSE
contents.append(']]') contents.append("]]")
else: else:
assert isinstance(chunk, MarkerToken) assert isinstance(chunk, MarkerToken)
contents.append(token_from_type(chunk.tok_type)) contents.append(token_from_type(chunk.tok_type))
return ''.join(contents) return "".join(contents)
class Bold: class Bold:
@ -417,62 +441,67 @@ def tokenize_contents(contents: str):
tokens.append((TOKEN_TYPE_TEXT, "".join(text))) tokens.append((TOKEN_TYPE_TEXT, "".join(text)))
text = [] text = []
cursor = enumerate(contents) cursor = enumerate(contents)
for i, char in cursor: for i, char in cursor:
has_changed = False has_changed = False
# Possible link opening # Possible link opening
if char == '[': if char == "[":
if (len(contents) > i + 3 if (
len(contents) > i + 3
# At least 3 characters more to open and close a link # At least 3 characters more to open and close a link
and contents[i + 1] == '['): and contents[i + 1] == "["
close = contents.find(']', i) ):
close = contents.find("]", i)
if close != -1 and contents[close + 1] == ']': if close != -1 and contents[close + 1] == "]":
# Link with no description # Link with no description
cut_string() cut_string()
in_link = True in_link = True
tokens.append((TOKEN_TYPE_OPEN_LINK, None)) tokens.append((TOKEN_TYPE_OPEN_LINK, None))
assert '[' == (next(cursor)[1]) assert "[" == (next(cursor)[1])
last_link_start = i last_link_start = i
continue continue
if close != -1 and contents[close + 1] == '[': if close != -1 and contents[close + 1] == "[":
# Link with description? # Link with description?
close = contents.find(']', close + 1) close = contents.find("]", close + 1)
if close != -1 and contents[close + 1] == ']': if close != -1 and contents[close + 1] == "]":
# No match here means this is not an Org link # No match here means this is not an Org link
cut_string() cut_string()
in_link = True in_link = True
tokens.append((TOKEN_TYPE_OPEN_LINK, None)) tokens.append((TOKEN_TYPE_OPEN_LINK, None))
assert '[' == (next(cursor)[1]) assert "[" == (next(cursor)[1])
last_link_start = i last_link_start = i
continue continue
# Possible link close or open of description # Possible link close or open of description
if char == ']' and in_link: if char == "]" and in_link:
if contents[i + 1] == ']': if contents[i + 1] == "]":
cut_string() cut_string()
tokens.append((TOKEN_TYPE_CLOSE_LINK, None)) tokens.append((TOKEN_TYPE_CLOSE_LINK, None))
assert ']' == (next(cursor)[1]) assert "]" == (next(cursor)[1])
in_link = False in_link = False
in_link_description = False in_link_description = False
continue continue
if contents[i + 1] == '[' and not in_link_description: if contents[i + 1] == "[" and not in_link_description:
cut_string() cut_string()
tokens.append((TOKEN_TYPE_OPEN_DESCRIPTION, None)) tokens.append((TOKEN_TYPE_OPEN_DESCRIPTION, None))
assert '[' == (next(cursor)[1]) assert "[" == (next(cursor)[1])
continue continue
raise Exception("Link cannot contain ']' not followed by '[' or ']'. Starting with {}".format(contents[last_link_start:i + 10])) raise Exception(
"Link cannot contain ']' not followed by '[' or ']'. Starting with {}".format(
contents[last_link_start : i + 10]
)
)
if (in_link and not in_link_description): if in_link and not in_link_description:
# Link's pointer have no formatting # Link's pointer have no formatting
pass pass

View File

@ -1,5 +1,15 @@
from .org_dom import (Bold, Code, Headline, Italic, Line, RawLine, Strike, from .org_dom import (
Text, Underlined, Verbatim) Bold,
Code,
Headline,
Italic,
Line,
RawLine,
Strike,
Text,
Underlined,
Verbatim,
)
def get_hl_raw_contents(doc: Headline) -> str: def get_hl_raw_contents(doc: Headline) -> str:
@ -8,7 +18,7 @@ def get_hl_raw_contents(doc: Headline) -> str:
for content in doc.contents: for content in doc.contents:
lines.append(get_raw_contents(content)) lines.append(get_raw_contents(content))
raw = ''.join(lines) raw = "".join(lines)
return raw return raw
@ -19,9 +29,11 @@ def get_rawline_contents(doc: RawLine) -> str:
def get_span_contents(doc: Line) -> str: def get_span_contents(doc: Line) -> str:
return doc.get_raw() return doc.get_raw()
def get_text_contents(doc: Text) -> str: def get_text_contents(doc: Text) -> str:
return doc.get_raw() return doc.get_raw()
def get_raw_contents(doc) -> str: def get_raw_contents(doc) -> str:
if isinstance(doc, Headline): if isinstance(doc, Headline):
return get_hl_raw_contents(doc) return get_hl_raw_contents(doc)
@ -30,8 +42,8 @@ def get_raw_contents(doc) -> str:
if isinstance(doc, Line): if isinstance(doc, Line):
return get_span_contents(doc) return get_span_contents(doc)
if isinstance(doc, list): if isinstance(doc, list):
return ''.join([get_raw_contents(chunk) for chunk in doc]) return "".join([get_raw_contents(chunk) for chunk in doc])
if isinstance(doc, (Text, Bold, Code, Italic, Strike, Underlined, Verbatim)): if isinstance(doc, (Text, Bold, Code, Italic, Strike, Underlined, Verbatim)):
return doc.get_raw() return doc.get_raw()
print('Unhandled type: ' + str(doc)) print("Unhandled type: " + str(doc))
raise NotImplementedError('Unhandled type: ' + str(doc)) raise NotImplementedError("Unhandled type: " + str(doc))

View File

@ -1,15 +1,15 @@
from setuptools import setup from setuptools import setup
setup( setup(
name='org-dom', name="org-dom",
version='0.0.1', version="0.0.1",
description= description="Library to de/serialize org-files and manipulate them in a DOM-like manner.",
'Library to de/serialize org-files and manipulate them in a DOM-like manner.', author="kenkeiras",
author='kenkeiras', author_email="kenkeiras@codigoparallevar.com",
author_email='kenkeiras@codigoparallevar.com', license="Apache License 2.0",
license='Apache License 2.0', packages=["org_dom"],
packages=['org_dom'],
scripts=[], scripts=[],
include_package_data=False, include_package_data=False,
install_requires=[], install_requires=[],
zip_safe=True) zip_safe=True,
)

View File

@ -4,37 +4,60 @@ 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 (BOLD, CODE, HL, ITALIC, SPAN, STRIKE,
UNDERLINED, VERBATIM, WEB_LINK, Dom, Tokens) from utils.dom_assertions import (
BOLD,
CODE,
HL,
ITALIC,
SPAN,
STRIKE,
UNDERLINED,
VERBATIM,
WEB_LINK,
Dom,
Tokens,
)
DIR = os.path.dirname(os.path.abspath(__file__)) DIR = os.path.dirname(os.path.abspath(__file__))
class TestSerde(unittest.TestCase): class TestSerde(unittest.TestCase):
def test_simple_file_01(self): def test_simple_file_01(self):
with open(os.path.join(DIR, '01-simple.org')) as f: with open(os.path.join(DIR, "01-simple.org")) as f:
doc = load(f) doc = load(f)
ex = Dom(props=[('TITLE', '01-Simple'), ex = Dom(
('DESCRIPTION', 'Simple org file'), props=[
('TODO', 'TODO(t) PAUSED(p) | DONE(d)')], ("TITLE", "01-Simple"),
children=(HL( ("DESCRIPTION", "Simple org file"),
'First level', ("TODO", "TODO(t) PAUSED(p) | DONE(d)"),
props=[ ],
('ID', '01-simple-first-level-id'), children=(
('CREATED', DT(2020, 1, 1, 1, 1)), HL(
], "First level",
content=' First level content\n', props=[
children=[ ("ID", "01-simple-first-level-id"),
HL('Second level', ("CREATED", DT(2020, 1, 1, 1, 1)),
props=[('ID', '01-simple-second-level-id')], ],
content='\n Second level content\n', content=" First level content\n",
children=[
HL(
"Second level",
props=[("ID", "01-simple-second-level-id")],
content="\n Second level content\n",
children=[ children=[
HL('Third level', HL(
props=[('ID', '01-simple-third-level-id')], "Third level",
content='\n Third level content\n') props=[("ID", "01-simple-third-level-id")],
]) content="\n Third level content\n",
]))) )
],
)
],
)
),
)
ex.assert_matches(self, doc) ex.assert_matches(self, doc)
@ -55,119 +78,180 @@ class TestSerde(unittest.TestCase):
self.assertEqual(dumps(doc), orig) self.assertEqual(dumps(doc), orig)
def test_markup_file_02(self): def test_markup_file_02(self):
self.maxDiff = 10000 with open(os.path.join(DIR, "02-markup.org")) as f:
with open(os.path.join(DIR, '02-markup.org')) as f:
doc = load(f) doc = load(f)
ex = Dom(props=[('TITLE', '02-Markup'), ex = Dom(
('DESCRIPTION', 'Simple org file to test markup'), props=[
('TODO', 'TODO(t) PAUSED(p) | DONE(d)')], ("TITLE", "02-Markup"),
children=(HL('First level', ("DESCRIPTION", "Simple org file to test markup"),
props=[ ("TODO", "TODO(t) PAUSED(p) | DONE(d)"),
('ID', '02-markup-first-level-id'), ],
('CREATED', DT(2020, 1, 1, 1, 1)), children=(
], HL(
content=[ "First level",
SPAN(" This is a ", BOLD("bold phrase"), props=[
".\n"), ("ID", "02-markup-first-level-id"),
SPAN("\n"), ("CREATED", DT(2020, 1, 1, 1, 1)),
SPAN(" This is a ", ],
VERBATIM("verbatim phrase"), ".\n"), content=[
SPAN("\n"), SPAN(" This is a ", BOLD("bold phrase"), ".\n"),
SPAN(" This is a ", ITALIC("italic phrase"), SPAN("\n"),
".\n"), SPAN(" This is a ", VERBATIM("verbatim phrase"), ".\n"),
SPAN("\n"), SPAN("\n"),
SPAN(" This is a ", SPAN(" This is a ", ITALIC("italic phrase"), ".\n"),
STRIKE("strike-through phrase"), ".\n"), SPAN("\n"),
SPAN("\n"), SPAN(" This is a ", STRIKE("strike-through phrase"), ".\n"),
SPAN(" This is a ", SPAN("\n"),
UNDERLINED("underlined phrase"), ".\n"), SPAN(" This is a ", UNDERLINED("underlined phrase"), ".\n"),
SPAN("\n"), SPAN("\n"),
SPAN(" This is a ", CODE("code phrase"), SPAN(" This is a ", CODE("code phrase"), ".\n"),
".\n"), SPAN("\n"),
SPAN(
SPAN("\n"), " This is a nested ",
SPAN(" This is a nested ", BOLD(["bold ", VERBATIM(["verbatim ", ITALIC(["italic ", STRIKE(["strike ", UNDERLINED(["underlined ", CODE("code ."), " ."]), " ."]), " ."]), " ."]), " ."])), BOLD(
SPAN("\n"), [
"bold ",
SPAN("\n"), VERBATIM(
# THIS IS INTERLEAVED, not nested [
SPAN([" This is a interleaved ", "verbatim ",
Tokens.BOLD_START, ITALIC(
"bold ", [
Tokens.VERBATIM_START, "italic ",
"verbatim ", STRIKE(
Tokens.ITALIC_START, [
"italic ", "strike ",
Tokens.STRIKE_START, UNDERLINED(
"strike ", [
Tokens.UNDERLINED_START, "underlined ",
"underlined ", CODE("code ."),
Tokens.CODE_START, " .",
"code .", ]
Tokens.BOLD_END, ),
" .", " .",
Tokens.VERBATIM_END, ]
" .", ),
Tokens.ITALIC_END, " .",
" .", ]
Tokens.STRIKE_END, ),
" .", " .",
Tokens.UNDERLINED_END, ]
" .", ),
Tokens.CODE_END, " .",
"\n"]), ]
),
SPAN("\n"), ),
SPAN(" This is a _ non-underlined phrase because an incorrectly placed content _.\n"), SPAN("\n"),
SPAN("\n"), SPAN("\n"),
# THIS IS INTERLEAVED, not nested
SPAN(" This is a _ non-underlined phrase because an incorrectly placed content beginning_.\n"), SPAN(
SPAN("\n"), [
" This is a interleaved ",
SPAN(""), Tokens.BOLD_START,
SPAN(" This is a _non-underlined phrase because an incorrectly placed content end _.\n"), "bold ",
SPAN("\n"), Tokens.VERBATIM_START,
"verbatim ",
SPAN(""), Tokens.ITALIC_START,
SPAN(" This is a _non-underlined phrase because the lack of an end.\n"), "italic ",
SPAN("\n"), Tokens.STRIKE_START,
"strike ",
SPAN("\n"), Tokens.UNDERLINED_START,
SPAN(" This is a _non-underlined phrase because an empty line between beginning and\n"), "underlined ",
SPAN("\n"), Tokens.CODE_START,
"code .",
SPAN(""), Tokens.BOLD_END,
SPAN(" end._\n"), " .",
]))) Tokens.VERBATIM_END,
" .",
Tokens.ITALIC_END,
" .",
Tokens.STRIKE_END,
" .",
Tokens.UNDERLINED_END,
" .",
Tokens.CODE_END,
"\n",
]
),
SPAN("\n"),
SPAN(
" This is a _ non-underlined phrase because an incorrectly placed content _.\n"
),
SPAN("\n"),
SPAN(
" This is a _ non-underlined phrase because an incorrectly placed content beginning_.\n"
),
SPAN("\n"),
SPAN(""),
SPAN(
" This is a _non-underlined phrase because an incorrectly placed content end _.\n"
),
SPAN("\n"),
SPAN(""),
SPAN(
" This is a _non-underlined phrase because the lack of an end.\n"
),
SPAN("\n"),
SPAN("\n"),
SPAN(
" This is a _non-underlined phrase because an empty line between beginning and\n"
),
SPAN("\n"),
SPAN(""),
SPAN(" end._\n"),
],
)
),
)
ex.assert_matches(self, doc) ex.assert_matches(self, doc)
def test_links_file_03(self): def test_links_file_03(self):
with open(os.path.join(DIR, '03-links.org')) as f: with open(os.path.join(DIR, "03-links.org")) as f:
doc = load(f) doc = load(f)
links = list(doc.get_links()) links = list(doc.get_links())
self.assertEqual(len(links), 2) self.assertEqual(len(links), 2)
self.assertEqual(links[0].value, 'https://codigoparallevar.com/1') self.assertEqual(links[0].value, "https://codigoparallevar.com/1")
self.assertEqual(links[0].description, 'web link') self.assertEqual(links[0].description, "web link")
self.assertEqual(links[1].value, 'https://codigoparallevar.com/2') self.assertEqual(links[1].value, "https://codigoparallevar.com/2")
self.assertEqual(links[1].description, 'web link') self.assertEqual(links[1].description, "web link")
ex = Dom(props=[('TITLE', '03-Links'), ex = Dom(
('DESCRIPTION', 'Simple org file to test links'), props=[
('TODO', 'TODO(t) PAUSED(p) | DONE(d)')], ("TITLE", "03-Links"),
children=(HL('First level', ("DESCRIPTION", "Simple org file to test links"),
props=[ ("TODO", "TODO(t) PAUSED(p) | DONE(d)"),
('ID', '03-markup-first-level-id'), ],
('CREATED', DT(2020, 1, 1, 1, 1)), children=(
], HL(
content=[ "First level",
SPAN(" This is a ", WEB_LINK("web link", "https://codigoparallevar.com/1"), props=[
".\n"), ("ID", "03-markup-first-level-id"),
SPAN("\n"), ("CREATED", DT(2020, 1, 1, 1, 1)),
SPAN(" This is a ", ITALIC(["italized ", WEB_LINK("web link", "https://codigoparallevar.com/2")]), ],
".\n"), content=[
]))) SPAN(
" This is a ",
WEB_LINK("web link", "https://codigoparallevar.com/1"),
".\n",
),
SPAN("\n"),
SPAN(
" This is a ",
ITALIC(
[
"italized ",
WEB_LINK(
"web link", "https://codigoparallevar.com/2"
),
]
),
".\n",
),
],
)
),
)
ex.assert_matches(self, doc) ex.assert_matches(self, doc)

View File

@ -2,8 +2,17 @@ import collections
import unittest import unittest
from datetime import datetime from datetime import datetime
from org_dom import (Bold, Code, Italic, Line, Strike, Text, Underlined, from org_dom import (
Verbatim, get_raw_contents) Bold,
Code,
Italic,
Line,
Strike,
Text,
Underlined,
Verbatim,
get_raw_contents,
)
def timestamp_to_datetime(ts): def timestamp_to_datetime(ts):