diff --git a/orgparse/node.py b/orgparse/node.py index b94735b..489f21b 100644 --- a/orgparse/node.py +++ b/orgparse/node.py @@ -1,6 +1,6 @@ import re import itertools -from typing import List, Iterable, Iterator, Optional, Union, Tuple, cast, Dict, Set +from typing import List, Iterable, Iterator, Optional, Union, Tuple, cast, Dict, Set, Sequence try: from collections.abc import Sequence except ImportError: @@ -146,21 +146,24 @@ def parse_property(line: str) -> Tuple[Optional[str], Optional[PropertyValue]]: RE_PROP = re.compile(r'^\s*:(.*?):\s*(.*?)\s*$') -def parse_comment(line): +def parse_comment(line: str): # -> Optional[Tuple[str, Sequence[str]]]: # todo wtf?? it says 'ABCMeta isn't subscriptable??' """ Parse special comment such as ``#+SEQ_TODO`` >>> parse_comment('#+SEQ_TODO: TODO | DONE') - ('SEQ_TODO', 'TODO | DONE') + ('SEQ_TODO', ['TODO | DONE']) >>> parse_comment('# not a special comment') # None + >>> parse_comment('#+FILETAGS: :tag1:tag2:') + ('FILETAGS', ['tag1', 'tag2']) """ match = re.match(r'\s*#\+', line) if match: end = match.end(0) comment = line[end:].split(':') - if len(comment) == 2: - return (comment[0], comment[1].strip()) + if len(comment) >= 2: + return (comment[0], [c.strip() for c in comment[1:] if len(c.strip()) > 0]) + return None def parse_seq_todo(line): @@ -642,8 +645,9 @@ def _parse_comments(self): for line in self._lines: parsed = parse_comment(line) if parsed: - (key, val) = parsed - special_comments.setdefault(key, []).append(val) + (key, vals) = parsed + key = key.upper() # case insensitive, so keep as uppercase + special_comments.setdefault(key, []).extend(vals) self._special_comments = special_comments # parse TODO keys and store in OrgEnv for todokey in ['TODO', 'SEQ_TODO', 'TYP_TODO']: @@ -770,14 +774,14 @@ def get_file_property_list(self, property): """ Return a list of the selected property """ - vals = self._special_comments.get(property, None) + vals = self._special_comments.get(property.upper(), None) return [] if vals is None else vals def get_file_property(self, property): """ Return a single element of the selected property or None if it doesn't exist """ - vals = self._special_comments.get(property, None) + vals = self._special_comments.get(property.upper(), None) if vals is None: return None elif len(vals) == 1: @@ -805,6 +809,10 @@ def _body_lines(self) -> List[str]: # type: ignore[override] def heading(self) -> str: return '' + def _get_tags(self, inher=False) -> Set[str]: + filetags = self.get_file_property_list('FILETAGS') + return set(filetags) + @property def level(self): return 0 diff --git a/orgparse/tests/test_misc.py b/orgparse/tests/test_misc.py index 61868f7..37128a5 100644 --- a/orgparse/tests/test_misc.py +++ b/orgparse/tests/test_misc.py @@ -24,6 +24,7 @@ def test_root() -> None: assert len(root.children) == 1 # todo not sure if should strip special comments?? assert root.body.endswith('Whatever\n# comment') + assert root.heading == '' def test_stars(): @@ -119,13 +120,14 @@ def test_get_file_property(): root = loads(content) assert root.get_file_property('Nosuchproperty') is None assert root.get_file_property_list('TITLE') == ['Test title'] - assert root.get_file_property('TITLE') == 'Test title' + # also it's case insensitive + assert root.get_file_property('title') == 'Test title' assert root.get_file_property_list('Nosuchproperty') == [] def test_get_file_property_multivalued(): content = """ #+TITLE: Test #+OTHER: Test title - #+TITLE: alternate title + #+title: alternate title * Node 1 test 1 @@ -141,3 +143,16 @@ def test_get_file_property_multivalued(): with pytest.raises(RuntimeError): # raises because there are multiple of them root.get_file_property('TITLE') + +def test_filetags_are_tags() -> None: + content = ''' +#+FILETAGS: :f1:f2: + +* heading :h1: +** child :f2: + '''.strip() + root = loads(content) + # breakpoint() + assert root.tags == {'f1', 'f2'} + child = root.children[0].children[0] + assert child.tags == {'f1', 'f2', 'h1'}