diff --git a/README.org b/README.org index 95ec98a..6f03720 100644 --- a/README.org +++ b/README.org @@ -7,6 +7,12 @@ A python library to parse, modify and save Org-mode files. - Modify these data and write it back to disk. - Keep the original structure intact (indentation, spaces, format, ...). +** Principles +- Avoid any dependency outside of Python's standard library. +- Don't do anything outside of the scope of parsing/re-serializing Org-mode files. +- *Modification of the original text if there's no change is considered a bug (see [[id:7363ba38-1662-4d3c-9e83-0999824975b7][Known issues]]).* +- Data structures should be exposed as it's read on Emacs's org-mode or when in doubt as raw as possible. +- Data in the objects should be modificable as a way to update the document itself. *Consider this a Object-oriented design.* ** Safety mechanism As this library is still in early development. Running it over files might produce unexpected changes on them. For this reason it's heavily recommended to @@ -21,6 +27,9 @@ Also, see [[id:76e77f7f-c9e0-4c83-ad2f-39a5a8894a83][Known issues:Structure modi not properly stored and can trigger this safety mechanism on a false-positive. * Known issues +:PROPERTIES: +:ID: 7363ba38-1662-4d3c-9e83-0999824975b7 +:END: ** Structure modifications :PROPERTIES: :ID: 76e77f7f-c9e0-4c83-ad2f-39a5a8894a83 diff --git a/org_rw/org_rw.py b/org_rw/org_rw.py index 77644b7..6baadd1 100644 --- a/org_rw/org_rw.py +++ b/org_rw/org_rw.py @@ -67,9 +67,7 @@ BASE_ENVIRONMENT = { ), } -HEADLINE_TAGS_RE = re.compile( - r"((?P\s+)(:(\w|[0-9_@#%])+)+:)(?P\s*)$" -) +HEADLINE_TAGS_RE = re.compile(r"((:(\w|[0-9_@#%])+)+:)\s*$") HEADLINE_RE = re.compile(r"^(?P\*+)(?P\s+)(?P.*?)$") KEYWORDS_RE = re.compile( r"^(?P\s*)#\+(?P[^:\[]+)(\[(?P[^\]]*)\])?:(?P\s*)(?P.*)$" @@ -317,8 +315,6 @@ class Headline: state, tags_start, tags, - space_before_tags, - space_after_tags, contents, children, structural, @@ -344,8 +340,6 @@ class Headline: self.title = parse_content_block([RawLine(linenum=start_line, line=title)]) self._state = state self.tags_start = tags_start - self.space_before_tags = space_before_tags - self.space_after_tags = space_after_tags self.shallow_tags = tags self.contents = contents self.children = children @@ -423,6 +417,7 @@ class Headline: and line.delimiter_type == DelimiterLineType.END_BLOCK and line.type_data.subtype == current_node.header.type_data.subtype ): + start = current_node.header.linenum end = line.linenum @@ -829,6 +824,7 @@ class Headline: def set_property(self, name: str, value: str): for prop in self.properties: + # A matching property is found, update it if prop.key == name: prop.value = value @@ -1028,6 +1024,7 @@ class Headline: and result_first[0] == "structural" and result_first[1].strip().upper() == ":RESULTS:" ): + (end_line, _) = self.get_structural_end_after( kword.linenum + 1 ) @@ -1078,8 +1075,6 @@ class Headline: state="", tags_start=None, tags=[], - space_before_tags="", - space_after_tags="", contents=[], children=[], structural=[], @@ -2046,6 +2041,7 @@ def tokenize_contents(contents: str) -> List[TokenItems]: and is_pre(last_char) and ((i + 1 < len(contents)) and is_border(contents[i + 1])) ): + is_valid_mark = False # Check that is closed later text_in_line = True @@ -2190,11 +2186,8 @@ def parse_headline(hl, doc, parent) -> Headline: if hl_tags is None: tags = [] - space_before_tags = space_after_tags = "" else: - tags = hl_tags.group(0).strip()[1:-1].split(":") - space_before_tags = hl_tags.group("space_before_tags") or "" - space_after_tags = hl_tags.group("space_after_tags") or "" + tags = hl_tags.group(0)[1:-1].split(":") line = HEADLINE_TAGS_RE.sub("", line) hl_state = None @@ -2214,13 +2207,6 @@ def parse_headline(hl, doc, parent) -> Headline: is_done = True break - if len(tags) == 0: - # No tags, so title might contain trailing whitespaces, handle it - title_ends_with_whitespace_match = re.search(r"\s+$", title) - if title_ends_with_whitespace_match is not None: - space_before_tags = title_ends_with_whitespace_match.group(0) - title = title[: -len(space_before_tags)] - contents = parse_contents(hl["contents"]) if not (isinstance(parent, OrgDoc) or depth > parent.depth): @@ -2247,8 +2233,6 @@ def parse_headline(hl, doc, parent) -> Headline: priority_start=None, tags_start=None, tags=tags, - space_before_tags=space_before_tags, - space_after_tags=space_after_tags, parent=parent, is_todo=is_todo, is_done=is_done, @@ -2450,19 +2434,21 @@ class OrgDoc: # Writing def dump_headline(self, headline, recursive=True): - tags = headline.space_before_tags + + tags = "" if len(headline.shallow_tags) > 0: - tags += ( - ":" + ":".join(headline.shallow_tags) + ":" + headline.space_after_tags - ) + tags = ":" + ":".join(headline.shallow_tags) + ":" state = "" if headline._state: state = headline._state["name"] + " " raw_title = token_list_to_raw(headline.title.contents) + tags_padding = "" + if not (raw_title.endswith(" ") or raw_title.endswith("\t")) and tags: + tags_padding = " " - yield ("*" * headline.depth + headline.spacing + state + raw_title + tags) + yield "*" * headline.depth + headline.spacing + state + raw_title + tags_padding + tags planning = headline.get_planning_line() if planning is not None: diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 1c51c66..0000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -# No external requirements at this point diff --git a/tests/14-titles.org b/tests/14-titles.org deleted file mode 100644 index 75b88a7..0000000 --- a/tests/14-titles.org +++ /dev/null @@ -1,12 +0,0 @@ -#+TITLE: 14-Simple -#+DESCRIPTION: Org file to evaluate titles -#+TODO: TODO(t) PAUSED(p) | DONE(d) - - -* Simple title - -* Simple title with tags :tag: - -* Simple title with trailing space - -* Simple title with leading space diff --git a/tests/test_org.py b/tests/test_org.py index d6b4351..a1fdff1 100644 --- a/tests/test_org.py +++ b/tests/test_org.py @@ -955,24 +955,6 @@ class TestSerde(unittest.TestCase): h1_2_h2 = h1_2.children[0] self.assertEqual(sorted(h1_2_h2.tags), ["otherh2tag"]) - def test_titles_file(self): - with open(os.path.join(DIR, "14-titles.org")) as f: - doc = load(f) - - h1, h2, h3, h4 = doc.getTopHeadlines() - self.assertEqual(h1.title.get_text(), "Simple title") - self.assertEqual(h2.title.get_text(), "Simple title with tags") - self.assertEqual(h3.title.get_text(), "Simple title with trailing space") - self.assertEqual(h4.title.get_text(), "Simple title with leading space") - - def test_mimic_write_file_14(self): - """A goal of this library is to be able to update a file without changing parts not directly modified.""" - with open(os.path.join(DIR, "14-titles.org")) as f: - orig = f.read() - doc = loads(orig) - - self.assertEqual(dumps(doc), orig) - def test_update_headline_from_none_to_todo(self): orig = "* First entry" doc = loads(orig)