Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Repeating timestamps and better handling of TODO states. #13

Merged
merged 4 commits into from

2 participants

@m3wolf

split TODO states into OrgNode.todo_states and OrgNode.done_states,
and added method OrgDataStructure.extract_todo_list.

Repeating timestamps can now be stored with the "+4w" string saved as
an attribute.

The new class OrgTodo holds individual todo items, useful for making todo lists
and agendas.

TODO states are now output properly in OrgNode._output().

There are now two lists of todo states: todo_states and done_states. A new method
was added to OrgDataStructure (add_todo_state) that allows for adding to the latter
list.

New method OrgDataStructure.extract_todo_list returns a list of OrgTodo states.
By default it returns states listed in OrgNode.todo_states but this can be modified
by arguments. It will raise an exception if a todo state is passed that is not on
either list since these will not be extracted properly during OrgDataStructure.load_from_file().

m3wolf added some commits
@m3wolf m3wolf New method allows reading from string in addition to files.
Added a new method: OrgDataStructure.load_from_string(string)
      Similar to load_from_file except accepts a string as input. The string should be the contents of an org-mode file (complete with '\n') that is then passed to load_from_file and treated normally. This allows org-mode information to be saved in a database or other non-file storage.
b303288
@m3wolf m3wolf Merge remote-tracking branch 'upstream/master'
a119702
@m3wolf m3wolf Repeating timestamps, new class OrgTodo, better handling of TODO stat…
…es in

OrgNode, split TODO states into OrgNode.todo_states and OrgNode.done_states,
and added method OrgDataStructure.extract_todo_list.

Repeating timestamps can now be stored with the "+4w" string saved as
an attribute.

The new class OrgTodo holds individual todo items, useful for making todo lists
and agendas.

TODO states are now output properly in OrgNode._output().

There are now two lists of todo states: todo_states and done_states. A new method
was added to OrgDataStructure (add_todo_state) that allows for adding to the latter
list.

New method OrgDataStructure.extract_todo_list returns a list of OrgTodo states.
By default it returns states listed in OrgNode.todo_states but this can be modified
by arguments. It will raise an exception if a todo state is passed that is not on
either list since these will not be extracted properly during OrgDataStructure.load_from_file().
d0ec4d9
@m3wolf m3wolf Remove OrgDataStructure.extract_agenda method placeholder.
I won't be working on it any time soon so it doesn't make sense to keep an
empty method around.
06dfc2b
@bjonnh bjonnh merged commit 3e907fe into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Sep 28, 2012
  1. @m3wolf

    New method allows reading from string in addition to files.

    m3wolf authored
    Added a new method: OrgDataStructure.load_from_string(string)
          Similar to load_from_file except accepts a string as input. The string should be the contents of an org-mode file (complete with '\n') that is then passed to load_from_file and treated normally. This allows org-mode information to be saved in a database or other non-file storage.
  2. @m3wolf
Commits on Oct 2, 2012
  1. @m3wolf

    Repeating timestamps, new class OrgTodo, better handling of TODO stat…

    m3wolf authored
    …es in
    
    OrgNode, split TODO states into OrgNode.todo_states and OrgNode.done_states,
    and added method OrgDataStructure.extract_todo_list.
    
    Repeating timestamps can now be stored with the "+4w" string saved as
    an attribute.
    
    The new class OrgTodo holds individual todo items, useful for making todo lists
    and agendas.
    
    TODO states are now output properly in OrgNode._output().
    
    There are now two lists of todo states: todo_states and done_states. A new method
    was added to OrgDataStructure (add_todo_state) that allows for adding to the latter
    list.
    
    New method OrgDataStructure.extract_todo_list returns a list of OrgTodo states.
    By default it returns states listed in OrgNode.todo_states but this can be modified
    by arguments. It will raise an exception if a todo state is passed that is not on
    either list since these will not be extracted properly during OrgDataStructure.load_from_file().
Commits on Oct 5, 2012
  1. @m3wolf

    Remove OrgDataStructure.extract_agenda method placeholder.

    m3wolf authored
    I won't be working on it any time soon so it doesn't make sense to keep an
    empty method around.
This page is out of date. Refresh to see the latest.
Showing with 178 additions and 44 deletions.
  1. +178 −44 PyOrgMode.py
View
222 PyOrgMode.py
@@ -41,12 +41,14 @@ class OrgDate:
ACTIVE = 8
INACTIVE = 16
RANGED = 32
+ REPEAT = 64
# TODO: Timestamp with repeater interval
DICT_RE = {'start': '[[<]',
'end': '[]>]',
'date': '([0-9]{4})-([0-9]{2})-([0-9]{2})(\s+([\w]+))?',
- 'time': '([0-9]{2}):([0-9]{2})'}
+ 'time': '([0-9]{2}):([0-9]{2})',
+ 'repeat': '[\+\.]{1,2}\d+[dwmy]'}
def __init__(self,value=None):
"""
@@ -58,7 +60,7 @@ def __init__(self,value=None):
def parse_datetime(self, s):
"""
Parses an org-mode date time string.
- Returns (timed, weekdayed, time_struct).
+ Returns (timed, weekdayed, time_struct, repeat).
"""
search_re = '(?P<date>{date})(\s+(?P<time>{time}))?'.format(
**self.DICT_RE)
@@ -120,12 +122,14 @@ def set_value(self,value):
self.format |= self.DATED | self.RANGED
return
# single date with no range
- search_re = '{start}(?P<datetime>{date}(\s+{time})?){end}'.format(
- **self.DICT_RE)
+ search_re = '{start}(?P<datetime>{date}(\s+{time})?)(\s+(?P<repeat>{repeat}))?{end}'.format(**self.DICT_RE)
match = re.search(search_re, value)
if match:
timed, weekdayed, self.value = self.parse_datetime(
match.group('datetime'))
+ if match.group('repeat'):
+ self.repeat = match.group('repeat')
+ self.format |= self.REPEAT
self.format |= self.DATED
if timed:
self.format |= self.TIMED
@@ -169,14 +173,18 @@ def get_value(self):
time.strftime(
'{start}{date}{end}'.format(**fmt_dict),
self.end))
- else:
- # non-ranged time
+ else: # non-ranged time
+ # Repeated
+ if self.format & self.REPEAT:
+ fmt_dict['repeat'] = ' ' + self.repeat
+ else:
+ fmt_dict['repeat'] = ''
if self.format & self.TIMED:
return time.strftime(
- '{start}{date} {time}{end}'.format(**fmt_dict), self.value)
+ '{start}{date} {time}{repeat}{end}'.format(**fmt_dict), self.value)
else:
return time.strftime(
- '{start}{date}{end}'.format(**fmt_dict), self.value)
+ '{start}{date}{repeat}{end}'.format(**fmt_dict), self.value)
class OrgPlugin:
"""
@@ -251,6 +259,23 @@ def __str__(self):
""" Used to return a text when called. """
return self.output()
+class OrgTodo():
+ """Describes an individual TODO item for use in agendas and TODO lists"""
+ def __init__(self, heading, todo_state,
+ scheduled=None, deadline=None,
+ tags=None, priority=None,
+ path=[0]
+ ):
+ self.heading = heading
+ self.todo_state = todo_state
+ self.scheduled = scheduled
+ self.deadline = deadline
+ self.tags = tags
+ self.priority = priority
+ def __str__(self):
+ string = self.todo_state + " " + self.heading
+ return string
+
class OrgClock(OrgPlugin):
"""Plugin for Clock elements"""
def __init__(self):
@@ -416,23 +441,25 @@ def _output(self):
class OrgNode(OrgPlugin):
def __init__(self):
OrgPlugin.__init__(self)
- self.regexp = re.compile("^(\*+)\s*(\[.*\])?\s*(.*)$")
- self.todo_list = ['TODO', 'DONE']
+ self.todo_list = ['TODO']
+ self.done_list = ['DONE']
self.keepindent = False # If the line starts by an indent, it is not a node
def _treat(self,current,line):
+ # Build regexp
+ regexp_string = "^(\*+)\s*"
+ if self.todo_list:
+ separator = ""
+ re_todos = "("
+ for todo_keyword in self.todo_list + self.done_list:
+ re_todos += separator
+ separator = "|"
+ re_todos += todo_keyword
+ re_todos += ")?\s*"
+ regexp_string += re_todos
+ regexp_string += "(\[.*\])?\s*(.*)$"
+ self.regexp = re.compile(regexp_string)
heading = self.regexp.findall(line)
if heading: # We have a heading
- # Build the regexp for finding TODO items
- if self.todo_list:
- separator = ""
- regexp_string = "^(\*+)\s*("
- for todo_keyword in self.todo_list:
- regexp_string += separator
- separator = "|"
- regexp_string += todo_keyword
- regexp_string += ")\s*(\[.*\])?\s*(.*)$"
- self.regexp_todo = re.compile(regexp_string)
- todo = self.regexp_todo.findall(line)
if current.parent :
current.parent.append(current)
@@ -449,14 +476,13 @@ def _treat(self,current,line):
# Creating a new node and assigning parameters
current = OrgNode.Element()
current.level = len(heading[0][0])
- current.heading = re.sub(":([\w]+):","",heading[0][2]) # Remove tags
- current.priority = heading[0][1]
+ current.heading = re.sub(":([\w]+):","",heading[0][3]) # Remove tags
+ current.priority = heading[0][2].strip('[#]')
current.parent = parent
-
- if todo: # This item has a todo associated with it
- current.todo = todo[0][1]
-
- # Looking for tags
+ if heading[0][1]:
+ current.todo = heading[0][1]
+
+ # Looking for tags
heading_without_links = re.sub(" \[(.+)\]","",heading[0][2])
current.tags = re.findall(":([\w]+):",heading_without_links)
else:
@@ -486,11 +512,14 @@ def _output(self):
if hasattr(self,"level"):
output = output + "*"*self.level
-
+
+ if hasattr(self, "todo"):
+ output = output + " " + self.todo
+
if self.parent is not None:
output = output + " "
if self.priority:
- output = output + self.priority + " "
+ output = output + "[#" + self.priority + "] "
output = output + self.heading
for tag in self.tags:
@@ -550,27 +579,61 @@ def load_plugins(self,*arguments,**keywords):
"""
for plugin in arguments:
self.plugins.append(plugin)
- def set_todo_states(self,new_todo_states):
+ def set_todo_states(self,new_states):
"""
Used to override the default list of todo states for any
OrgNode plugins in this object's plugins list. Expects
- a list[] of strings as its argument.
+ a list[] of strings as its argument. The list can be split
+ by '|' entries into TODO items and DONE items. Anything after
+ a second '|' will not be processed and be returned.
Setting to an empty list will disable TODO checking.
"""
+ new_todo_states = []
+ new_done_states = []
+ num_lists = 1
+ # Process the first part of the list (delimited by '|')
+ for new_state in new_states:
+ if new_state == '|':
+ num_lists += 1
+ break
+ new_todo_states.append(new_state)
+ # Clean up the lists so far
+ if num_lists > 1:
+ new_states.remove('|')
+ for todo_state in new_todo_states:
+ new_states.remove(todo_state)
+ # Process the second part of the list (delimited by '|')
+ for new_state in new_states:
+ if new_state == '|':
+ num_lists += 1
+ break
+ new_done_states.append(new_state)
+ # Clean up the second list
+ if num_lists > 2:
+ new_states.remove('|')
+ for todo_state in new_done_states:
+ new_states.remove(todo_state)
+ # Write the relevant attributes
for plugin in self.plugins:
if plugin.__class__ == OrgNode:
plugin.todo_list = new_todo_states
- def get_todo_states(self):
+ plugin.done_list = new_done_states
+ if new_states:
+ return new_states # Return any leftovers
+ def get_todo_states(self, list_type="todo"):
"""
- Returns a list of lists of todo states, one entry for each instance
- of OrgNode in this object's plugins list. An empty list means that
- instance of OrgNode has TODO checking disabled.
+ Returns a list of todo states. An empty list means that
+ instance of OrgNode has TODO checking disabled. The first argument
+ determines the list that is pulled ("todo"*, "done" or "all").
"""
- all_todo_states = []
+ all_states = []
for plugin in self.plugins:
if plugin.__class__ == OrgNode:
- all_todo_states.append(plugin.todo_list)
- return all_todo_states
+ if plugin.todo_list and (list_type == "todo" or list_type == "all"):
+ all_states += plugin.todo_list
+ if plugin.done_list and (list_type == "done" or list_type == "all"):
+ all_states += plugin.done_list
+ return list(set(all_states))
def add_todo_state(self, new_state):
"""
Appends a todo state to the list of todo states of any OrgNode
@@ -580,15 +643,81 @@ def add_todo_state(self, new_state):
for plugin in self.plugins:
if plugin.__class__ == OrgNode:
plugin.todo_list.append(new_state)
- def load_from_file(self,name):
+ def add_done_state(self, new_state):
+ """
+ Appends a todo state to the list of todo states of any OrgNode
+ plugins in this objects plugins list.
+ Expects a string as its argument.
+ """
+ for plugin in self.plugins:
+ if plugin.__class__ == OrgNode:
+ plugin.done_list.append(new_state)
+ def remove_todo_state(self, old_state):
+ """
+ Remove a given todo state from both the todo list and the done list.
+ Returns True if the plugin was actually found.
+ """
+ found = False
+ for plugin in self.plugins:
+ if plugin.__class__ == OrgNode:
+ while old_state in plugin.todo_list:
+ found = True
+ plugin.todo_list.remove(old_state)
+ while old_state in plugin.done_list:
+ found = True
+ plugin.done_list.remove(old_state)
+ return found
+ def extract_todo_list(self, todo_list=None):
+ """
+ Extract a list of headings with TODO states specified by the first argument.
+ """
+ if todo_list == None: # Set default
+ # Kludge to get around lack of self in function declarations
+ todo_list = self.get_todo_states()
+ else:
+ # Check to make sure all todo_list items are registered
+ # with the OrgNode plugin
+ for possible_state in todo_list:
+ if possible_state not in self.get_todo_states("all"):
+ raise ValueError("State " + possible_state + " not registered. See PyOrgMode.OrgDataStructure.add_todo_state.")
+ results_list = []
+ # Recursive function that steps through each node in current level,
+ # looking for TODO items and then calls itself to look for
+ # TODO items one level down.
+ def extract_from_level(content):
+ for node in content:
+ # Check if it's a TODO item and add to results
+ try:
+ current_todo = node.todo
+ except AttributeError:
+ pass
+ else: # Handle it
+ if current_todo in todo_list:
+ new_todo = OrgTodo(node.heading, node.todo)
+ results_list.append(new_todo)
+ # Now check if it has sub-headings
+ try:
+ next_content = node.content
+ except AttributeError:
+ pass
+ else: # Hanble it
+ extract_from_level(next_content)
+ extract_from_level(self.root.content)
+ return results_list
+ def load_from_file(self,name,form="file"):
"""
Used to load an org-file inside this DataStructure
"""
current = self.root
- file = open(name,'r')
+ # Determine content type and put in appropriate form
+ if form == "file":
+ content = open(name,'r')
+ elif form == "string":
+ content = name.split("\n")
+ else:
+ raise ValueError("Form \""+form+"\" not recognized")
- for line in file:
-
+ for line in content:
for plugin in self.plugins:
current = plugin.treat(current,line)
if plugin.treated: # Plugin found something
@@ -601,7 +730,12 @@ def load_from_file(self,name):
for plugin in self.plugins:
current = plugin.close(current)
- file.close()
+
+ def load_from_string(self, string):
+ """
+ A wrapper calling load_from_file but with a string instead of reading from a file.
+ """
+ self.load_from_file(string, "string")
def save_to_file(self,name,node=None):
"""
Something went wrong with that request. Please try again.