Make typed functions pass mypy check.

This commit is contained in:
Sergio Martínez Portela 2023-10-15 16:34:19 +02:00
parent 99e44fd8b2
commit 5ed34df57a
3 changed files with 104 additions and 51 deletions

View File

@ -1,3 +1,6 @@
from typing import Union
class DrawerNode: class DrawerNode:
def __init__(self): def __init__(self):
self.children = [] self.children = []
@ -102,4 +105,15 @@ class CodeBlock(BlockNode):
def __repr__(self): def __repr__(self):
return "<Code: {}>".format(len(self.lines)) return "<Code: {}>".format(len(self.lines))
DomNode = Union[DrawerNode,
PropertyNode,
ListGroupNode,
TableNode,
TableSeparatorRow,
TableRow,
Text,
ListItem,
BlockNode,
]
from .utils import get_raw_contents from .utils import get_raw_contents

View File

@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
import collections import collections
from ctypes import ArgumentError
import difflib import difflib
import logging import logging
import os import os
@ -8,7 +9,9 @@ import re
import sys import sys
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
from enum import Enum from enum import Enum
from typing import Generator, List, Optional, Tuple, Union from typing import cast, Iterator, List, Optional, Tuple, Union
from .types import HeadlineDict
from . import dom from . import dom
@ -154,12 +157,12 @@ class RangeInRaw:
contents.insert(start_idx + i + 1, element) contents.insert(start_idx + i + 1, element)
def unescape_block_lines(lines: str) -> str: def unescape_block_lines(block: str) -> str:
""" """
Remove leading ',' from block_lines if they escape `*` characters. Remove leading ',' from block_lines if they escape `*` characters.
""" """
i = 0 i = 0
lines = lines.split('\n') lines = block.split('\n')
while i < len(lines): while i < len(lines):
line = lines[i] line = lines[i]
if (line.lstrip(' ').startswith(',') if (line.lstrip(' ').startswith(',')
@ -177,8 +180,8 @@ def unescape_block_lines(lines: str) -> str:
def get_links_from_content(content): def get_links_from_content(content):
in_link = False in_link = False
in_description = False in_description = False
link_value = [] link_value: List[str] = []
link_description = [] link_description: List[str] = []
for i, tok in enumerate(get_tokens(content)): for i, tok in enumerate(get_tokens(content)):
if isinstance(tok, LinkToken): if isinstance(tok, LinkToken):
@ -210,8 +213,8 @@ def text_to_dom(tokens, item):
in_link = False in_link = False
in_description = False in_description = False
link_value = [] link_value: List[str] = []
link_description = [] link_description: List[str] = []
contents = [] contents = []
@ -361,9 +364,10 @@ class Headline:
+ self.delimiters + self.delimiters
) )
tree = [] tree: List[dom.DomNode] = []
current_node = None current_node: Optional[dom.DomNode] = None
indentation_tree = [] indentation_tree: List[dom.DomNode] = []
contents: Optional[str] = None
for line in sorted(everything, key=get_line): for line in sorted(everything, key=get_line):
if isinstance(current_node, dom.CodeBlock): if isinstance(current_node, dom.CodeBlock):
@ -404,7 +408,7 @@ class Headline:
): ):
node.append(dom.Text(line)) node.append(dom.Text(line))
current_node = node current_node = node
contents = [] contents = None
break break
elif ((not isinstance(node, dom.TableNode)) and elif ((not isinstance(node, dom.TableNode)) and
(type(node) not in NON_FINISHED_GROUPS) (type(node) not in NON_FINISHED_GROUPS)
@ -419,7 +423,7 @@ class Headline:
tree_up.pop(-1) tree_up.pop(-1)
else: else:
current_node = None current_node = None
contents = [] contents = None
tree.append(dom.Text(text_to_dom(line.contents, line))) tree.append(dom.Text(text_to_dom(line.contents, line)))
indentation_tree = tree_up indentation_tree = tree_up
@ -669,7 +673,9 @@ class Headline:
parsed = as_time_range parsed = as_time_range
else: else:
parsed = OrgTime.parse(time_seg) parsed = OrgTime.parse(time_seg)
times.append(parsed)
if parsed is not None:
times.append(parsed)
return times return times
@ -1130,6 +1136,9 @@ def parse_time(value: str) -> Union[None, TimeRange, OrgTime]:
# @TODO properly consider "=> DURATION" section # @TODO properly consider "=> DURATION" section
start, end = value.split("=")[0].split("--") start, end = value.split("=")[0].split("--")
as_time_range = parse_org_time_range(start, end) as_time_range = parse_org_time_range(start, end)
if as_time_range is None:
return None
if (as_time_range.start_time is not None) and ( if (as_time_range.start_time is not None) and (
as_time_range.end_time is not None as_time_range.end_time is not None
): ):
@ -1142,8 +1151,13 @@ def parse_time(value: str) -> Union[None, TimeRange, OrgTime]:
return None return None
def parse_org_time_range(start, end) -> TimeRange: def parse_org_time_range(start, end) -> Optional[TimeRange]:
return TimeRange(OrgTime.parse(start), OrgTime.parse(end)) start_time = OrgTime.parse(start)
end_time = OrgTime.parse(end)
if start_time is None or end_time is None:
return None
return TimeRange(start_time, end_time)
class OrgTime: class OrgTime:
@ -1170,12 +1184,13 @@ class OrgTime:
return f"OrgTime({self.to_raw()})" return f"OrgTime({self.to_raw()})"
@classmethod @classmethod
def parse(self, value: str) -> OrgTime: def parse(self, value: str) -> Optional[OrgTime]:
if m := ACTIVE_TIME_STAMP_RE.match(value): if m := ACTIVE_TIME_STAMP_RE.match(value):
active = True active = True
elif m := INACTIVE_TIME_STAMP_RE.match(value): elif m := INACTIVE_TIME_STAMP_RE.match(value):
active = False active = False
else: else:
# raise ArgumentError("Cannot parse `{}` as OrgTime".format(value))
return None return None
repetition = None repetition = None
@ -1219,7 +1234,7 @@ class OrgTime:
) )
def time_from_str(s: str) -> OrgTime: def time_from_str(s: str) -> Optional[OrgTime]:
return OrgTime.parse(s) return OrgTime.parse(s)
@ -1280,7 +1295,7 @@ class Line:
class Link: class Link:
def __init__(self, value: str, description: str, origin: RangeInRaw): def __init__(self, value: str, description: Optional[str], origin: RangeInRaw):
self._value = value self._value = value
self._description = description self._description = description
self._origin = origin self._origin = origin
@ -1452,7 +1467,7 @@ class Verbatim:
return f"{self.Marker}{raw}{self.Marker}" return f"{self.Marker}{raw}{self.Marker}"
def is_pre(char: str) -> bool: def is_pre(char: Optional[str]) -> bool:
if isinstance(char, str): if isinstance(char, str):
return char in "\n\r\t -({'\"" return char in "\n\r\t -({'\""
else: else:
@ -1499,7 +1514,7 @@ def tokenize_contents(contents: str):
tokens = [] tokens = []
last_char = None last_char = None
text = [] text: List[str] = []
closes = set() closes = set()
in_link = False in_link = False
in_link_description = False in_link_description = False
@ -1619,7 +1634,7 @@ def parse_contents(raw_contents: List[RawLine]):
return [] return []
blocks = [] blocks = []
current_block = [] current_block: List[RawLine] = []
for line in raw_contents: for line in raw_contents:
if len(current_block) == 0: if len(current_block) == 0:
@ -1627,6 +1642,7 @@ def parse_contents(raw_contents: List[RawLine]):
current_line = line.linenum current_line = line.linenum
current_block.append(line) current_block.append(line)
else: else:
current_line = cast(int, current_line)
if line.linenum == current_line + 1: if line.linenum == current_line + 1:
# Continue with the current block # Continue with the current block
current_line = line.linenum current_line = line.linenum
@ -1652,8 +1668,8 @@ def parse_content_block(raw_contents: Union[List[RawLine],str]):
for line in raw_contents: for line in raw_contents:
contents_buff.append(line.line) contents_buff.append(line.line)
contents = "\n".join(contents_buff) contents_buff_text = "\n".join(contents_buff)
tokens = tokenize_contents(contents) tokens = tokenize_contents(contents_buff_text)
if isinstance(raw_contents, str): if isinstance(raw_contents, str):
current_line = None current_line = None
else: else:
@ -1893,7 +1909,7 @@ class OrgDoc:
def getTopHeadlines(self): def getTopHeadlines(self):
return self.headlines return self.headlines
def getAllHeadlines(self) -> Generator[Headline]: def getAllHeadlines(self) -> Iterator[Headline]:
todo = self.headlines[::-1] # We go backwards, to pop/append and go depth-first todo = self.headlines[::-1] # We go backwards, to pop/append and go depth-first
while len(todo) != 0: while len(todo) != 0:
hl = todo.pop() hl = todo.pop()
@ -2016,15 +2032,16 @@ class OrgDoc:
class OrgDocReader: class OrgDocReader:
def __init__(self): def __init__(self):
self.headlines: List[Headline] = [] self.headlines: List[HeadlineDict] = []
self.keywords: List[Property] = [] self.keywords: List[Keyword] = []
self.headline_hierarchy: List[OrgDoc] = [] self.headline_hierarchy: List[HeadlineDict] = []
self.contents: List[RawLine] = [] self.contents: List[RawLine] = []
self.delimiters: List[DelimiterLine] = [] self.delimiters: List[DelimiterLine] = []
self.list_items: List[ListItem] = [] self.list_items: List[ListItem] = []
self.table_rows: List[TableRow] = [] self.table_rows: List[TableRow] = []
self.structural: List = [] self.structural: List = []
self.properties: List = [] self.properties: List = []
self.current_drawer: Optional[List] = None
def finalize(self): def finalize(self):
return OrgDoc( return OrgDoc(
@ -2037,12 +2054,12 @@ class OrgDocReader:
) )
## Construction ## Construction
def add_headline(self, linenum: int, match: re.Match) -> int: def add_headline(self, linenum: int, match: re.Match):
# Position reader on the proper headline # Position reader on the proper headline
stars = match.group("stars") stars = match.group("stars")
depth = len(stars) depth = len(stars)
headline = { headline: HeadlineDict = {
"linenum": linenum, "linenum": linenum,
"orig": match, "orig": match,
"title": match.group("line"), "title": match.group("line"),
@ -2058,27 +2075,35 @@ class OrgDocReader:
"table_rows": [], "table_rows": [],
} }
while (depth - 1) > len(self.headline_hierarchy): headline_hierarchy: List[Optional[HeadlineDict]] = list(self.headline_hierarchy)
while (depth - 1) > len(headline_hierarchy):
# Introduce structural headlines # Introduce structural headlines
self.headline_hierarchy.append(None) headline_hierarchy.append(None)
while depth <= len(self.headline_hierarchy): while depth <= len(headline_hierarchy):
self.headline_hierarchy.pop() headline_hierarchy.pop()
if depth == 1: if depth == 1:
self.headlines.append(headline) self.headlines.append(headline)
else: else:
parent_idx = len(self.headline_hierarchy) - 1 parent_idx = len(headline_hierarchy) - 1
while self.headline_hierarchy[parent_idx] is None: while headline_hierarchy[parent_idx] is None:
parent_idx -= 1 parent_idx -= 1
self.headline_hierarchy[parent_idx]["children"].append(headline) parent_headline = headline_hierarchy[parent_idx]
self.headline_hierarchy.append(headline) assert parent_headline is not None
parent_headline["children"].append(headline)
headline_hierarchy.append(headline)
if all([hl is not None for hl in self.headline_hierarchy]): if all([hl is not None for hl in headline_hierarchy]):
if not ([ len(hl['orig'].group('stars')) for hl in self.headline_hierarchy ] if not ([ len(hl['orig'].group('stars')) for hl in self.headline_hierarchy ]
== list(range(1, len(self.headline_hierarchy) + 1))): == list(range(1, len(self.headline_hierarchy) + 1))):
raise AssertionError('Error on Headline Hierarchy') raise AssertionError('Error on Headline Hierarchy')
else:
raise AssertionError('None found on headline hierarchy')
def add_list_item_line(self, linenum: int, match: re.Match) -> int: self.headline_hierarchy = cast(List[HeadlineDict], headline_hierarchy)
def add_list_item_line(self, linenum: int, match: re.Match) -> ListItem:
li = ListItem( li = ListItem(
linenum=linenum, linenum=linenum,
match=match, match=match,
@ -2103,7 +2128,7 @@ class OrgDocReader:
self.headline_hierarchy[-1]["list_items"].append(li) self.headline_hierarchy[-1]["list_items"].append(li)
return li return li
def add_table_line(self, linenum: int, line: str) -> int: def add_table_line(self, linenum: int, line: str):
chunks = line.split('|') chunks = line.split('|')
indentation = len(chunks[0]) indentation = len(chunks[0])
if chunks[-1].strip() == '': if chunks[-1].strip() == '':
@ -2128,7 +2153,7 @@ class OrgDocReader:
else: else:
self.headline_hierarchy[-1]["table_rows"].append(row) self.headline_hierarchy[-1]["table_rows"].append(row)
def add_keyword_line(self, linenum: int, match: re.Match) -> int: def add_keyword_line(self, linenum: int, match: re.Match):
options = match.group("options") options = match.group("options")
kw = Keyword( kw = Keyword(
linenum, linenum,
@ -2188,22 +2213,19 @@ class OrgDocReader:
else: else:
self.headline_hierarchy[-1]["structural"].append((linenum, line)) self.headline_hierarchy[-1]["structural"].append((linenum, line))
def add_node_properties_line(self, linenum: int, match: re.Match) -> int: def add_node_properties_line(self, linenum: int, match: re.Match):
key = match.group("key") key = match.group("key")
value = match.group("value").strip() value = match.group("value").strip()
if as_time := parse_time(value): if as_time := parse_time(value):
value = as_time value = as_time
try: if self.current_drawer is None: # Throw a better error on this case
self.current_drawer.append(Property(linenum, match, key, value, None)) raise Exception(
except Exception: "Found properties before :PROPERTIES: line. Error on Org file?"
if "current_drawer" not in dir(self): # Throw a better error on this case )
raise Exception(
"Found properties before :PROPERTIES: line. Error on Org file?" self.current_drawer.append(Property(linenum, match, key, value, None))
)
else:
raise # Let the exception pass
def read(self, s, environment): def read(self, s, environment):
lines = s.split("\n") lines = s.split("\n")

17
org_rw/types.py Normal file
View File

@ -0,0 +1,17 @@
import re
from typing import List, TypedDict
class HeadlineDict(TypedDict):
linenum: int
orig: re.Match
title: str
contents: List
children: List
keywords: List
properties: List
logbook: List
structural: List
delimiters: List
results: List # TODO: Move to each specific code block?
list_items: List
table_rows: List