Refactor headline state parsing.

- Add separate function to parse states.
- Handle edge case when no `|` is used to split TODO and DONE states.
- Add typing to the states to future-proof for handling keyboard shortcuts and actions on state changes.
This commit is contained in:
Sergio Martínez Portela 2024-07-20 17:29:25 +02:00
parent da2d8c8c6d
commit b174405c90

View File

@ -9,7 +9,7 @@ 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 cast, Iterator, List, Literal, Optional, Tuple, Union from typing import cast, Iterator, List, Literal, Optional, Tuple, TypedDict, Union
from .types import HeadlineDict from .types import HeadlineDict
@ -107,6 +107,15 @@ CodeSnippet = collections.namedtuple("CodeSnippet", ("name", "content", "result"
NON_FINISHED_GROUPS = (type(None), dom.ListGroupNode, dom.ResultsDrawerNode, dom.PropertyDrawerNode) NON_FINISHED_GROUPS = (type(None), dom.ListGroupNode, dom.ResultsDrawerNode, dom.PropertyDrawerNode)
FREE_GROUPS = (dom.CodeBlock,) FREE_GROUPS = (dom.CodeBlock,)
# States
class HeadlineState(TypedDict):
# To be extended to handle keyboard shortcuts
name: str
class OrgDocDeclaredStates(TypedDict):
not_completed: List[HeadlineState]
completed: List[HeadlineState]
class NonReproducibleDocument(Exception): class NonReproducibleDocument(Exception):
""" """
@ -1759,16 +1768,16 @@ def parse_headline(hl, doc, parent) -> Headline:
title = line title = line
is_done = is_todo = False is_done = is_todo = False
for state in doc.todo_keywords or []: for state in doc.todo_keywords or []:
if title.startswith(state + " "): if title.startswith(state['name'] + " "):
hl_state = state hl_state = state
title = title[len(state + " ") :] title = title[len(state['name'] + " ") :]
is_todo = True is_todo = True
break break
else: else:
for state in doc.done_keywords or []: for state in doc.done_keywords or []:
if title.startswith(state + " "): if title.startswith(state['name'] + " "):
hl_state = state hl_state = state
title = title[len(state + " ") :] title = title[len(state['name'] + " ") :]
is_done = True is_done = True
break break
@ -1863,29 +1872,53 @@ def dump_delimiters(line: DelimiterLine):
return (line.linenum, line.line) return (line.linenum, line.line)
def parse_todo_done_keywords(line: str) -> OrgDocDeclaredStates:
clean_line = re.sub(r"\([^)]+\)", "", line)
if '|' in clean_line:
todo_kws, done_kws = clean_line.split("|", 1)
has_split = True
else:
# Standard behavior in this case is: the last state is the one considered as DONE
todo_kws = clean_line
todo_keywords = re.sub(r"\s{2,}", " ", todo_kws.strip()).split()
if has_split:
done_keywords = re.sub(r"\s{2,}", " ", done_kws.strip()).split()
else:
done_keywods = [todo_keywords[-1]]
todo_keywords = todo_keywords[:-1]
return {
"not_completed": [
HeadlineState(name=keyword)
for keyword in todo_keywords
],
"completed": [
HeadlineState(name=keyword)
for keyword in done_keywords
],
}
class OrgDoc: class OrgDoc:
def __init__( def __init__(
self, headlines, keywords, contents, list_items, structural, properties, self, headlines, keywords, contents, list_items, structural, properties,
environment=BASE_ENVIRONMENT, environment=BASE_ENVIRONMENT,
): ):
self.todo_keywords = DEFAULT_TODO_KEYWORDS self.todo_keywords = [HeadlineState(name=kw) for kw in DEFAULT_TODO_KEYWORDS]
self.done_keywords = DEFAULT_DONE_KEYWORDS self.done_keywords = [HeadlineState(name=kw) for kw in DEFAULT_DONE_KEYWORDS]
keywords_set_in_file = False keywords_set_in_file = False
for keyword in keywords: for keyword in keywords:
if keyword.key in ("TODO", "SEQ_TODO"): if keyword.key in ("TODO", "SEQ_TODO"):
todo_kws, done_kws = re.sub(r"\([^)]+\)", "", keyword.value).split("|", 1) states = parse_todo_done_keywords(keyword.value)
self.todo_keywords, self.done_keywords = states['not_completed'], states['completed']
self.todo_keywords = re.sub(r"\s{2,}", " ", todo_kws.strip()).split()
self.done_keywords = re.sub(r"\s{2,}", " ", done_kws.strip()).split()
keywords_set_in_file = True keywords_set_in_file = True
if not keywords_set_in_file and 'org-todo-keywords' in environment: if not keywords_set_in_file and 'org-todo-keywords' in environment:
# Read keywords from environment # Read keywords from environment
todo_kws, done_kws = re.sub(r"\([^)]+\)", "", environment['org-todo-keywords']).split("|", 1) states = parse_todo_done_keywords(environment['org-todo-keywords'])
self.todo_keywords, self.done_keywords = states['not_completed'], states['completed']
self.todo_keywords = re.sub(r"\s{2,}", " ", todo_kws.strip()).split()
self.done_keywords = re.sub(r"\s{2,}", " ", done_kws.strip()).split()
self.keywords: List[Property] = keywords self.keywords: List[Property] = keywords
self.contents: List[RawLine] = contents self.contents: List[RawLine] = contents
@ -1960,7 +1993,7 @@ class OrgDoc:
state = "" state = ""
if headline.state: if headline.state:
state = headline.state + " " state = headline.state['name'] + " "
raw_title = token_list_to_raw(headline.title.contents) raw_title = token_list_to_raw(headline.title.contents)
tags_padding = "" tags_padding = ""