From d71f98f4b9813d331ec45cae80fd9cd43a319d8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Wed, 6 Jan 2021 00:25:06 +0100 Subject: [PATCH] Add support for reading Headline planning info. This includes: `SCHEDULED`, `DEADLINE` and `CLOSED`. --- org_rw/org_rw.py | 46 +++++++++++++++++++++++++++++++++++++++++++++- tests/05-dates.org | 1 + tests/test_org.py | 11 +++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/org_rw/org_rw.py b/org_rw/org_rw.py index c317d6e..9cb19a7 100644 --- a/org_rw/org_rw.py +++ b/org_rw/org_rw.py @@ -4,7 +4,7 @@ import logging import os import re import sys -from datetime import datetime, timedelta +from datetime import date, datetime, timedelta from enum import Enum from typing import List, Tuple, Union @@ -55,9 +55,25 @@ NODE_PROPERTIES_RE = re.compile( ) RAW_LINE_RE = re.compile(r"^\s*([^\s#:*]|$)") BASE_TIME_STAMP_RE = r"(?P\d{4})-(?P\d{2})-(?P\d{2})( ?(?P[^ ]+))?( (?P\d{1,2}):(?P\d{1,2})(--(?P\d{1,2}):(?P\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}))?)?" +) ACTIVE_TIME_STAMP_RE = re.compile(r"<{}>".format(BASE_TIME_STAMP_RE)) INACTIVE_TIME_STAMP_RE = re.compile(r"\[{}\]".format(BASE_TIME_STAMP_RE)) +PLANNING_RE = re.compile( + r"(?P\s*)" + + r"(SCHEDULED:\s*(?P[<\[]" + + CLEAN_TIME_STAMP_RE + + r"[>\]])\s*" + + r"|CLOSED:\s*(?P[<\[]" + + CLEAN_TIME_STAMP_RE + + r"[>\]])\s*" + + r"|DEADLINE:\s*(?P[<\[]" + + CLEAN_TIME_STAMP_RE + + r"[>\]])\s*" + r")+\s*" +) # Org-Babel BEGIN_SRC_RE = re.compile(r"^\s*#\+BEGIN_SRC(?P.*)$", re.I) @@ -180,6 +196,24 @@ class Headline: self.parent = parent self.is_todo = is_todo self.is_done = is_done + self.scheduled = None + self.deadline = None + self.closed = None + + # Read planning line + planning_line = self.get_element_in_line(start_line + 1) + + # Ignore if not found or is a structural line + if planning_line is None or isinstance(planning_line, tuple): + return + + if m := PLANNING_RE.match(planning_line.get_raw()): + if scheduled := m.group("scheduled"): + self.scheduled = time_from_str(scheduled) + if closed := m.group("closed"): + self.closed = time_from_str(closed) + if deadline := m.group("deadline"): + self.deadline = time_from_str(deadline) @property def clock(self): @@ -464,6 +498,16 @@ def parse_org_time(value): ) +class OrgTime: + def __init__(self, ts: Timestamp): + self.time = ts + self.date = date(ts.year, ts.month, ts.day) + + +def time_from_str(s: str): + return OrgTime(parse_org_time(s)) + + def timerange_to_string(tr: TimeRange): return timestamp_to_string(tr.start_time) + "--" + timestamp_to_string(tr.end_time) diff --git a/tests/05-dates.org b/tests/05-dates.org index ec128e0..d837035 100644 --- a/tests/05-dates.org +++ b/tests/05-dates.org @@ -3,6 +3,7 @@ #+TODO: TODO(t) PAUSED(p) | DONE(d) * Headline properties +SCHEDULED: <2020-12-12 Sáb> CLOSED: <2020-12-13 Dom> DEADLINE: <2020-12-14 Lun> :PROPERTIES: :JUST_DAY: [2020-12-10] :DAY_AND_WEEKDAY: [2020-12-10 Xov] diff --git a/tests/test_org.py b/tests/test_org.py index 563a9d6..858b50a 100644 --- a/tests/test_org.py +++ b/tests/test_org.py @@ -1,6 +1,7 @@ import logging import os import unittest +from datetime import date from datetime import datetime as DT from org_rw import dumps, load, loads @@ -391,3 +392,13 @@ class TestSerde(unittest.TestCase): doc = loads(orig) self.assertEqual(dumps(doc), orig) + + def test_planning_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] + 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))