diff --git a/.gitea/workflows/tests.yaml b/.gitea/workflows/tests.yaml deleted file mode 100644 index ee49a29..0000000 --- a/.gitea/workflows/tests.yaml +++ /dev/null @@ -1,33 +0,0 @@ -name: Testing -# run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀 -on: [push] - -jobs: - pytest: - runs-on: ubuntu-latest - steps: - - name: Check out repository code - uses: actions/checkout@v3 - - run: apt-get update && apt-get install -y python3-pip - - run: pip install -e . - - run: pip install pytest - - run: pytest - - mypy: - runs-on: ubuntu-latest - steps: - - name: Check out repository code - uses: actions/checkout@v3 - - run: apt-get update && apt-get install -y python3-pip - - run: pip install -e . - - run: pip install mypy - - run: mypy org_rw --check-untyped-defs - - stability-extra-test: - runs-on: ubuntu-latest - steps: - - name: Check out repository code - uses: actions/checkout@v3 - - run: apt-get update && apt-get install -y git-core python3-pip - - run: pip install -e . - - run: bash extra-tests/check_all.sh diff --git a/org_rw/dom.py b/org_rw/dom.py index cd8d63b..cb3d8fd 100644 --- a/org_rw/dom.py +++ b/org_rw/dom.py @@ -1,6 +1,3 @@ -from typing import List, Optional, Union - - class DrawerNode: def __init__(self): self.children = [] @@ -95,7 +92,7 @@ class CodeBlock(BlockNode): def __init__(self, header, subtype, arguments): super().__init__() self.header = header - self.lines: Optional[List] = None + self.lines = None self.subtype = subtype self.arguments = arguments @@ -103,23 +100,6 @@ class CodeBlock(BlockNode): self.lines = lines def __repr__(self): - return "".format(len(self.lines or [])) - -DomNode = Union[DrawerNode, - PropertyNode, - ListGroupNode, - TableNode, - TableSeparatorRow, - TableRow, - Text, - ListItem, - BlockNode, - ] - -ContainerDomNode = Union[DrawerNode, - ListGroupNode, - TableNode, - BlockNode, - ] + return "".format(len(self.lines)) from .utils import get_raw_contents diff --git a/org_rw/org_rw.py b/org_rw/org_rw.py index 5bb205e..9a60199 100644 --- a/org_rw/org_rw.py +++ b/org_rw/org_rw.py @@ -1,7 +1,6 @@ from __future__ import annotations import collections -from ctypes import ArgumentError import difflib import logging import os @@ -9,9 +8,7 @@ import re import sys from datetime import date, datetime, timedelta from enum import Enum -from typing import cast, Iterator, List, Literal, Optional, Tuple, Union - -from .types import HeadlineDict +from typing import Generator, List, Optional, Tuple, Union from . import dom @@ -157,12 +154,12 @@ class RangeInRaw: contents.insert(start_idx + i + 1, element) -def unescape_block_lines(block: str) -> str: +def unescape_block_lines(lines: str) -> str: """ Remove leading ',' from block_lines if they escape `*` characters. """ i = 0 - lines = block.split('\n') + lines = lines.split('\n') while i < len(lines): line = lines[i] if (line.lstrip(' ').startswith(',') @@ -180,8 +177,8 @@ def unescape_block_lines(block: str) -> str: def get_links_from_content(content): in_link = False in_description = False - link_value: List[str] = [] - link_description: List[str] = [] + link_value = [] + link_description = [] for i, tok in enumerate(get_tokens(content)): if isinstance(tok, LinkToken): @@ -213,8 +210,8 @@ def text_to_dom(tokens, item): in_link = False in_description = False - link_value: List[str] = [] - link_description: List[str] = [] + link_value = [] + link_description = [] contents = [] @@ -364,10 +361,9 @@ class Headline: + self.delimiters ) - tree: List[dom.DomNode] = [] - current_node: Optional[dom.DomNode] = None - indentation_tree: List[dom.ContainerDomNode] = [] - contents: Optional[str] = None + tree = [] + current_node = None + indentation_tree = [] for line in sorted(everything, key=get_line): if isinstance(current_node, dom.CodeBlock): @@ -402,13 +398,13 @@ class Headline: elif isinstance(line, Text): tree_up = list(indentation_tree) while len(tree_up) > 0: - node: dom.DomNode = tree_up[-1] + node = tree_up[-1] if (isinstance(node, dom.BlockNode) or isinstance(node, dom.DrawerNode) ): node.append(dom.Text(line)) current_node = node - contents = None + contents = [] break elif ((not isinstance(node, dom.TableNode)) and (type(node) not in NON_FINISHED_GROUPS) @@ -423,7 +419,7 @@ class Headline: tree_up.pop(-1) else: current_node = None - contents = None + contents = [] tree.append(dom.Text(text_to_dom(line.contents, line))) indentation_tree = tree_up @@ -508,7 +504,6 @@ class Headline: node = dom.TableSeparatorRow(orig=line) else: node = dom.TableRow(line.cells, orig=line) - current_node = cast(dom.ContainerDomNode, current_node) current_node.append(node) elif ( @@ -608,7 +603,7 @@ class Headline: return self.get_lists() def get_tables(self): - tables: List[List] = [] # TableRow[][] + tables = [] last_line = None for row in self.table_rows: @@ -667,7 +662,6 @@ class Headline: time_seg = content[len("CLOCK:") :].strip() - parsed: Union[None, OrgTime, TimeRange] = None if "--" in time_seg: # TODO: Consider duration start, end = time_seg.split("=")[0].split("--") @@ -675,9 +669,7 @@ class Headline: parsed = as_time_range else: parsed = OrgTime.parse(time_seg) - - if parsed is not None: - times.append(parsed) + times.append(parsed) return times @@ -1138,9 +1130,6 @@ def parse_time(value: str) -> Union[None, TimeRange, OrgTime]: # @TODO properly consider "=> DURATION" section start, end = value.split("=")[0].split("--") 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 ( as_time_range.end_time is not None ): @@ -1153,13 +1142,8 @@ def parse_time(value: str) -> Union[None, TimeRange, OrgTime]: return None -def parse_org_time_range(start, end) -> Optional[TimeRange]: - 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) +def parse_org_time_range(start, end) -> TimeRange: + return TimeRange(OrgTime.parse(start), OrgTime.parse(end)) class OrgTime: @@ -1186,13 +1170,12 @@ class OrgTime: return f"OrgTime({self.to_raw()})" @classmethod - def parse(self, value: str) -> Optional[OrgTime]: + def parse(self, value: str) -> OrgTime: if m := ACTIVE_TIME_STAMP_RE.match(value): active = True elif m := INACTIVE_TIME_STAMP_RE.match(value): active = False else: - # raise ArgumentError("Cannot parse `{}` as OrgTime".format(value)) return None repetition = None @@ -1236,7 +1219,7 @@ class OrgTime: ) -def time_from_str(s: str) -> Optional[OrgTime]: +def time_from_str(s: str) -> OrgTime: return OrgTime.parse(s) @@ -1297,7 +1280,7 @@ class Line: class Link: - def __init__(self, value: str, description: Optional[str], origin: RangeInRaw): + def __init__(self, value: str, description: str, origin: RangeInRaw): self._value = value self._description = description self._origin = origin @@ -1309,7 +1292,7 @@ class Link: return "[[{}]]".format(self.value) def _update_content(self): - new_contents: List[Union[str, LinkToken]] = [] + new_contents = [] new_contents.append(self._value) if self._description: new_contents.append(LinkToken(LinkTokenType.OPEN_DESCRIPTION)) @@ -1469,7 +1452,7 @@ class Verbatim: return f"{self.Marker}{raw}{self.Marker}" -def is_pre(char: Optional[str]) -> bool: +def is_pre(char: str) -> bool: if isinstance(char, str): return char in "\n\r\t -({'\"" else: @@ -1511,16 +1494,12 @@ TOKEN_TYPE_OPEN_LINK = 3 TOKEN_TYPE_CLOSE_LINK = 4 TOKEN_TYPE_OPEN_DESCRIPTION = 5 -TokenItems = Union[ - Tuple[int, Union[None, str, MarkerToken]], -] - -def tokenize_contents(contents: str) -> List[TokenItems]: - tokens: List[TokenItems] = [] +def tokenize_contents(contents: str): + tokens = [] last_char = None - text: List[str] = [] + text = [] closes = set() in_link = False in_link_description = False @@ -1640,7 +1619,7 @@ def parse_contents(raw_contents: List[RawLine]): return [] blocks = [] - current_block: List[RawLine] = [] + current_block = [] for line in raw_contents: if len(current_block) == 0: @@ -1648,7 +1627,6 @@ def parse_contents(raw_contents: List[RawLine]): current_line = line.linenum current_block.append(line) else: - current_line = cast(int, current_line) if line.linenum == current_line + 1: # Continue with the current block current_line = line.linenum @@ -1674,24 +1652,21 @@ def parse_content_block(raw_contents: Union[List[RawLine],str]): for line in raw_contents: contents_buff.append(line.line) - contents_buff_text = "\n".join(contents_buff) - tokens = tokenize_contents(contents_buff_text) + contents = "\n".join(contents_buff) + tokens = tokenize_contents(contents) if isinstance(raw_contents, str): current_line = None else: current_line = raw_contents[0].linenum - contents: List[Union[str, MarkerToken, LinkToken]] = [] + contents = [] # Use tokens to tag chunks of text with it's container type for (tok_type, tok_val) in tokens: if tok_type == TOKEN_TYPE_TEXT: - assert isinstance(tok_val, str) contents.append(tok_val) elif tok_type == TOKEN_TYPE_OPEN_MARKER: - assert isinstance(tok_val, str) contents.append(MarkerToken(False, MARKERS[tok_val])) elif tok_type == TOKEN_TYPE_CLOSE_MARKER: - assert isinstance(tok_val, str) contents.append(MarkerToken(True, MARKERS[tok_val])) elif tok_type == TOKEN_TYPE_OPEN_LINK: contents.append(LinkToken(LinkTokenType.OPEN_LINK)) @@ -1918,7 +1893,7 @@ class OrgDoc: def getTopHeadlines(self): return self.headlines - def getAllHeadlines(self) -> Iterator[Headline]: + def getAllHeadlines(self) -> Generator[Headline]: todo = self.headlines[::-1] # We go backwards, to pop/append and go depth-first while len(todo) != 0: hl = todo.pop() @@ -1943,7 +1918,7 @@ class OrgDoc: raw_title = token_list_to_raw(headline.title.contents) tags_padding = "" - if not (raw_title.endswith(" ") or raw_title.endswith("\t")) and tags: + if not raw_title.endswith(" ") and tags: tags_padding = " " yield "*" * headline.depth + headline.spacing + state + raw_title + tags_padding + tags @@ -2041,16 +2016,15 @@ class OrgDoc: class OrgDocReader: def __init__(self): - self.headlines: List[HeadlineDict] = [] - self.keywords: List[Keyword] = [] - self.headline_hierarchy: List[HeadlineDict] = [] + self.headlines: List[Headline] = [] + self.keywords: List[Property] = [] + self.headline_hierarchy: List[OrgDoc] = [] self.contents: List[RawLine] = [] self.delimiters: List[DelimiterLine] = [] self.list_items: List[ListItem] = [] self.table_rows: List[TableRow] = [] self.structural: List = [] self.properties: List = [] - self.current_drawer: Optional[List] = None def finalize(self): return OrgDoc( @@ -2063,12 +2037,12 @@ class OrgDocReader: ) ## Construction - def add_headline(self, linenum: int, match: re.Match): + def add_headline(self, linenum: int, match: re.Match) -> int: # Position reader on the proper headline stars = match.group("stars") depth = len(stars) - headline: HeadlineDict = { + headline = { "linenum": linenum, "orig": match, "title": match.group("line"), @@ -2084,35 +2058,27 @@ class OrgDocReader: "table_rows": [], } - headline_hierarchy: List[Optional[HeadlineDict]] = list(self.headline_hierarchy) - - while (depth - 1) > len(headline_hierarchy): + while (depth - 1) > len(self.headline_hierarchy): # Introduce structural headlines - headline_hierarchy.append(None) - while depth <= len(headline_hierarchy): - headline_hierarchy.pop() + self.headline_hierarchy.append(None) + while depth <= len(self.headline_hierarchy): + self.headline_hierarchy.pop() if depth == 1: self.headlines.append(headline) else: - parent_idx = len(headline_hierarchy) - 1 - while headline_hierarchy[parent_idx] is None: + parent_idx = len(self.headline_hierarchy) - 1 + while self.headline_hierarchy[parent_idx] is None: parent_idx -= 1 - parent_headline = headline_hierarchy[parent_idx] - assert parent_headline is not None - parent_headline["children"].append(headline) - headline_hierarchy.append(headline) + self.headline_hierarchy[parent_idx]["children"].append(headline) + self.headline_hierarchy.append(headline) - if all([hl is not None for hl in headline_hierarchy]): + if all([hl is not None 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))): raise AssertionError('Error on Headline Hierarchy') - else: - raise AssertionError('None found on headline hierarchy') - self.headline_hierarchy = cast(List[HeadlineDict], headline_hierarchy) - - def add_list_item_line(self, linenum: int, match: re.Match) -> ListItem: + def add_list_item_line(self, linenum: int, match: re.Match) -> int: li = ListItem( linenum=linenum, match=match, @@ -2137,7 +2103,7 @@ class OrgDocReader: self.headline_hierarchy[-1]["list_items"].append(li) return li - def add_table_line(self, linenum: int, line: str): + def add_table_line(self, linenum: int, line: str) -> int: chunks = line.split('|') indentation = len(chunks[0]) if chunks[-1].strip() == '': @@ -2162,7 +2128,7 @@ class OrgDocReader: else: self.headline_hierarchy[-1]["table_rows"].append(row) - def add_keyword_line(self, linenum: int, match: re.Match): + def add_keyword_line(self, linenum: int, match: re.Match) -> int: options = match.group("options") kw = Keyword( linenum, @@ -2222,19 +2188,22 @@ class OrgDocReader: else: self.headline_hierarchy[-1]["structural"].append((linenum, line)) - def add_node_properties_line(self, linenum: int, match: re.Match): + def add_node_properties_line(self, linenum: int, match: re.Match) -> int: key = match.group("key") value = match.group("value").strip() if as_time := parse_time(value): value = as_time - if self.current_drawer is None: # 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)) + try: + self.current_drawer.append(Property(linenum, match, key, value, None)) + except Exception: + 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?" + ) + else: + raise # Let the exception pass def read(self, s, environment): lines = s.split("\n") @@ -2347,7 +2316,6 @@ def loads(s, environment=BASE_ENVIRONMENT, extra_cautious=True): context_start = i context_last_line = i elif context_start: - assert context_last_line is not None if i > (context_last_line + DEBUG_DIFF_CONTEXT): start = max(0, context_start - DEBUG_DIFF_CONTEXT) end = min(len(diff), context_last_line + DEBUG_DIFF_CONTEXT) diff --git a/org_rw/types.py b/org_rw/types.py deleted file mode 100644 index eff7f59..0000000 --- a/org_rw/types.py +++ /dev/null @@ -1,17 +0,0 @@ -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