forked from kenkeiras/org-rw
Add support for planning info update.
This commit is contained in:
parent
d71f98f4b9
commit
1d3b4c187b
143
org_rw/org_rw.py
143
org_rw/org_rw.py
@ -54,10 +54,8 @@ NODE_PROPERTIES_RE = re.compile(
|
||||
r"^(?P<indentation>\s*):(?P<key>[^+:]+)(?P<plus>\+)?:(?P<spacing>\s*)(?P<value>.+)$"
|
||||
)
|
||||
RAW_LINE_RE = re.compile(r"^\s*([^\s#:*]|$)")
|
||||
BASE_TIME_STAMP_RE = r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})( ?(?P<dow>[^ ]+))?( (?P<start_hour>\d{1,2}):(?P<start_minute>\d{1,2})(--(?P<end_hour>\d{1,2}):(?P<end_minute>\d{1,2}))?)?"
|
||||
CLEAN_TIME_STAMP_RE = (
|
||||
r"\d{4}-\d{2}-\d{2}( ?([^ ]+))?( (\d{1,2}):(\d{1,2})(--(\d{1,2}):(\d{1,2}))?)?"
|
||||
)
|
||||
BASE_TIME_STAMP_RE = r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})( ?(?P<dow>[^ ]+))?( (?P<start_hour>\d{1,2}):(?P<start_minute>\d{1,2})(--(?P<end_hour>\d{1,2}):(?P<end_minute>\d{1,2}))?)?(?P<repetition> (?P<rep_mark>(\+|\+\+|\.\+|-|--))(?P<rep_value>\d+)(?P<rep_unit>[hdwmy]))?"
|
||||
CLEAN_TIME_STAMP_RE = r"\d{4}-\d{2}-\d{2}( ?([^ ]+))?( (\d{1,2}):(\d{1,2})(--(\d{1,2}):(\d{1,2}))?)?( (\+|\+\+|\.\+|-|--)\d+[hdwmy])?"
|
||||
|
||||
ACTIVE_TIME_STAMP_RE = re.compile(r"<{}>".format(BASE_TIME_STAMP_RE))
|
||||
INACTIVE_TIME_STAMP_RE = re.compile(r"\[{}\]".format(BASE_TIME_STAMP_RE))
|
||||
@ -208,6 +206,20 @@ class Headline:
|
||||
return
|
||||
|
||||
if m := PLANNING_RE.match(planning_line.get_raw()):
|
||||
self._planning_indendation = m.group("indentation")
|
||||
self._planning_order = []
|
||||
|
||||
keywords = ["SCHEDULED", "CLOSED", "DEADLINE"]
|
||||
plan = planning_line.get_raw().split("\n")[0]
|
||||
indexes = [(kw, plan.find(kw)) for kw in keywords]
|
||||
|
||||
self._planning_order = [
|
||||
kw
|
||||
for (kw, idx) in sorted(
|
||||
filter(lambda v: v[1] >= 0, indexes), key=lambda v: v[1]
|
||||
)
|
||||
]
|
||||
|
||||
if scheduled := m.group("scheduled"):
|
||||
self.scheduled = time_from_str(scheduled)
|
||||
if closed := m.group("closed"):
|
||||
@ -215,6 +227,37 @@ class Headline:
|
||||
if deadline := m.group("deadline"):
|
||||
self.deadline = time_from_str(deadline)
|
||||
|
||||
# Remove from contents
|
||||
self._remove_element_in_line(start_line + 1)
|
||||
|
||||
def get_planning_line(self):
|
||||
if self.scheduled is None and self.closed is None and self.deadline is None:
|
||||
return None
|
||||
|
||||
contents = [self._planning_indendation]
|
||||
|
||||
for el in self._planning_order:
|
||||
if el == "SCHEDULED" and self.scheduled is not None:
|
||||
contents.append("SCHEDULED: {} ".format(self.scheduled.to_raw()))
|
||||
|
||||
elif el == "CLOSED" and self.closed is not None:
|
||||
contents.append("CLOSED: {} ".format(self.closed.to_raw()))
|
||||
|
||||
elif el == "DEADLINE" and self.deadline is not None:
|
||||
contents.append("DEADLINE: {} ".format(self.deadline.to_raw()))
|
||||
|
||||
# Consider elements added (not present on planning order)
|
||||
if ("SCHEDULED" not in self._planning_order) and (self.scheduled is not None):
|
||||
contents.append("SCHEDULED: {} ".format(self.scheduled.to_raw()))
|
||||
|
||||
if ("CLOSED" not in self._planning_order) and (self.closed is not None):
|
||||
contents.append("CLOSED: {} ".format(self.closed.to_raw()))
|
||||
|
||||
if ("DEADLINE" not in self._planning_order) and (self.deadline is not None):
|
||||
contents.append("DEADLINE: {} ".format(self.deadline.to_raw()))
|
||||
|
||||
return "".join(contents).rstrip()
|
||||
|
||||
@property
|
||||
def clock(self):
|
||||
times = []
|
||||
@ -279,6 +322,27 @@ class Headline:
|
||||
if linenum == s_lnum:
|
||||
return ("structural", struc)
|
||||
|
||||
def _remove_element_in_line(self, linenum):
|
||||
found = None
|
||||
for i, line in enumerate(self.contents):
|
||||
if linenum == line.linenum:
|
||||
found = i
|
||||
break
|
||||
|
||||
assert found is not None
|
||||
el = self.contents[found]
|
||||
assert isinstance(el, Text)
|
||||
|
||||
raw = el.get_raw()
|
||||
if "\n" not in raw:
|
||||
# Remove the element found
|
||||
self.contents.pop(found)
|
||||
else:
|
||||
# Remove the first line
|
||||
self.contents[found] = parse_content_block(
|
||||
[RawLine(self.contents[found].linenum + 1, raw.split("\n", 1)[1])]
|
||||
)
|
||||
|
||||
def get_structural_end_after(self, linenum):
|
||||
for (s_lnum, struc) in self.structural:
|
||||
if s_lnum > linenum and struc.strip().upper() == ":END:":
|
||||
@ -376,9 +440,61 @@ Property = collections.namedtuple(
|
||||
|
||||
# @TODO How are [YYYY-MM-DD HH:mm--HH:mm] and ([... HH:mm]--[... HH:mm]) differentiated ?
|
||||
# @TODO Consider recurrence annotations
|
||||
Timestamp = collections.namedtuple(
|
||||
"Timestamp", ("active", "year", "month", "day", "dow", "hour", "minute")
|
||||
)
|
||||
class Timestamp:
|
||||
def __init__(self, active, year, month, day, dow, hour, minute, repetition=None):
|
||||
self.active = active
|
||||
self._year = year
|
||||
self._month = month
|
||||
self._day = day
|
||||
self.dow = dow
|
||||
self.hour = hour
|
||||
self.minute = minute
|
||||
self.repetition = repetition
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Timestamp):
|
||||
return False
|
||||
return (
|
||||
(self.active == other.active)
|
||||
and (self.year == other.year)
|
||||
and (self.month == other.month)
|
||||
and (self.day == other.day)
|
||||
and (self.dow == other.dow)
|
||||
and (self.hour == other.hour)
|
||||
and (self.minute == other.minute)
|
||||
and (self.repetition == other.repetition)
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return timestamp_to_string(self)
|
||||
|
||||
# Properties whose modification changes the Day-Of-Week
|
||||
@property
|
||||
def year(self):
|
||||
return self._year
|
||||
|
||||
@year.setter
|
||||
def year(self, value):
|
||||
self._year = value
|
||||
self.dow = None
|
||||
|
||||
@property
|
||||
def month(self):
|
||||
return self._month
|
||||
|
||||
@month.setter
|
||||
def month(self, value):
|
||||
self._month = value
|
||||
self.dow = None
|
||||
|
||||
@property
|
||||
def day(self):
|
||||
return self._day
|
||||
|
||||
@day.setter
|
||||
def day(self, value):
|
||||
self._day = value
|
||||
self.dow = None
|
||||
|
||||
|
||||
class DelimiterLineType(Enum):
|
||||
@ -495,13 +611,17 @@ def parse_org_time(value):
|
||||
m.group("dow"),
|
||||
int(m.group("start_hour")) if m.group("start_hour") else None,
|
||||
int(m.group("start_minute")) if m.group("start_minute") else None,
|
||||
m.group("repetition").strip() if m.group("repetition") else None,
|
||||
)
|
||||
|
||||
|
||||
class OrgTime:
|
||||
def __init__(self, ts: Timestamp):
|
||||
assert ts is not None
|
||||
self.time = ts
|
||||
self.date = date(ts.year, ts.month, ts.day)
|
||||
|
||||
def to_raw(self):
|
||||
return timestamp_to_string(self.time)
|
||||
|
||||
|
||||
def time_from_str(s: str):
|
||||
@ -526,6 +646,9 @@ def timestamp_to_string(ts: Timestamp):
|
||||
else:
|
||||
base = date
|
||||
|
||||
if ts.repetition:
|
||||
base = base + " " + ts.repetition
|
||||
|
||||
if ts.active:
|
||||
return "<{}>".format(base)
|
||||
else:
|
||||
@ -1079,6 +1202,10 @@ class OrgDoc:
|
||||
"spacing"
|
||||
) + headline.title + tags
|
||||
|
||||
planning = headline.get_planning_line()
|
||||
if planning is not None:
|
||||
yield planning
|
||||
|
||||
lines = []
|
||||
KW_T = 0
|
||||
CONTENT_T = 1
|
||||
|
@ -4,7 +4,7 @@ import unittest
|
||||
from datetime import date
|
||||
from datetime import datetime as DT
|
||||
|
||||
from org_rw import dumps, load, loads
|
||||
from org_rw import Timestamp, dumps, load, loads
|
||||
|
||||
from utils.assertions import (BOLD, CODE, HL, ITALIC, SPAN, STRIKE, UNDERLINED,
|
||||
VERBATIM, WEB_LINK, Doc, Tokens)
|
||||
@ -399,6 +399,37 @@ class TestSerde(unittest.TestCase):
|
||||
doc = loads(orig)
|
||||
|
||||
hl = doc.getTopHeadlines()[0]
|
||||
self.assertEqual(hl.scheduled.date, date(2020, 12, 12))
|
||||
self.assertEqual(hl.closed.date, date(2020, 12, 13))
|
||||
self.assertEqual(hl.deadline.date, date(2020, 12, 14))
|
||||
self.assertEqual(
|
||||
hl.scheduled.time, Timestamp(True, 2020, 12, 12, "Sáb", None, None)
|
||||
)
|
||||
self.assertEqual(
|
||||
hl.closed.time, Timestamp(True, 2020, 12, 13, "Dom", None, None)
|
||||
)
|
||||
self.assertEqual(
|
||||
hl.deadline.time, Timestamp(True, 2020, 12, 14, "Lun", None, None)
|
||||
)
|
||||
|
||||
def test_update_info_file_05(self):
|
||||
with open(os.path.join(DIR, "05-dates.org")) as f:
|
||||
orig = f.read()
|
||||
doc = loads(orig)
|
||||
|
||||
hl = doc.getTopHeadlines()[0]
|
||||
hl.scheduled.time.day = 15
|
||||
hl.closed.time.day = 16
|
||||
hl.deadline.time.day = 17
|
||||
|
||||
# Account for removeing 3 days-of-week + 1 space each
|
||||
self.assertEqual(len(dumps(doc)), len(orig) - (4) * 3)
|
||||
doc_updated = loads(dumps(doc))
|
||||
|
||||
hl_up = doc_updated.getTopHeadlines()[0]
|
||||
self.assertEqual(
|
||||
hl.scheduled.time, Timestamp(True, 2020, 12, 15, None, None, None)
|
||||
)
|
||||
self.assertEqual(
|
||||
hl.closed.time, Timestamp(True, 2020, 12, 16, None, None, None)
|
||||
)
|
||||
self.assertEqual(
|
||||
hl.deadline.time, Timestamp(True, 2020, 12, 17, None, None, None)
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user