Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge remote-tracking branch 'upstream/master'

  • Loading branch information...
commit a1197027e2710b6fb33d735fbdbe909e497aa815 2 parents b303288 + b816e20
@m3wolf authored
Showing with 250 additions and 64 deletions.
  1. +5 −0 AUTHORS
  2. +244 −62 PyOrgMode.org
  3. +1 −2  TODO
View
5 AUTHORS
@@ -0,0 +1,5 @@
+
+Jonathan BISSON <firstname.lastname at bjonnh.net> : initiator of the project
+Antti KAIHOLA <akaihol plus orgmode at ambitone dot com>
+m3wolf
+Will Roberts
View
306 PyOrgMode.org
@@ -97,9 +97,14 @@
- Optimizations
Class DataStructure : Trying to simplify the Reg exps
#+end_src
-*** Authors [2/2]
-- [X] BISSON Jonathan <bissonjonathan on the googlethingy>
-- [X] KAIHOLA Antti <akaihol plus orgmode at ambitone dot com>
+*** Authors
+#+srcname: authors
+#+begin_src ascii :tangle AUTHORS :exports code
+Jonathan BISSON <firstname.lastname at bjonnh.net> : initiator of the project
+Antti KAIHOLA <akaihol plus orgmode at ambitone dot com>
+m3wolf
+Will Roberts
+#+end_src
** Code
*** License
:PROPERTIES:
@@ -188,6 +193,13 @@ setup(
WEEKDAYED = 4
ACTIVE = 8
INACTIVE = 16
+ RANGED = 32
+
+ # 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})'}
def __init__(self,value=None):
"""
@@ -196,74 +208,128 @@ setup(
if value != None:
self.set_value(value)
+ def parse_datetime(self, s):
+ """
+ Parses an org-mode date time string.
+ Returns (timed, weekdayed, time_struct).
+ """
+ search_re = '(?P<date>{date})(\s+(?P<time>{time}))?'.format(
+ **self.DICT_RE)
+ s = re.search(search_re, s)
+ weekdayed = (len(s.group('date').split()) > 1)
+ if s.group('time'):
+ return (True,
+ weekdayed,
+ time.strptime(
+ s.group('date').split()[0] + ' ' + s.group('time'),
+ '%Y-%m-%d %H:%M'))
+ else:
+ return (False,
+ weekdayed,
+ time.strptime(s.group('date').split()[0], '%Y-%m-%d'))
+
def set_value(self,value):
"""
Setting the value of this element (automatic recognition of format)
"""
# Checking whether it is an active date-time or not
- if value[0]=="<":
- self.format = self.format | self.ACTIVE
- value = re.findall("(?:<)(.*)(?:>)",value)[0]
- elif value[0]=="[":
- self.format = self.format | self.INACTIVE
- value = re.findall("(?:\[)(.*)(?:\])",value)[0]
- # Checking if it is a date, a date+time or only a time
- value_splitted = value.split()
-
- timed = re.compile(".*?:.*?")
- dated = re.compile(".*?-.*?-.*?")
-
- if timed.findall(value):
- self.format = self.format | self.TIMED
- if dated.findall(value):
- self.format = self.format | self.DATED
-
- if len(value_splitted) == 3 :
- # We have a three parts date so it's dated, timed and weekdayed
- self.format = self.format | self.WEEKDAYED
- self.value = time.strptime(value_splitted[0]+" "+value_splitted[2],"%Y-%m-%d %H:%M")
- elif len(value_splitted) == 2 and (self.format & self.DATED) and not (self.format & self.TIMED):
- # We have a two elements date that is dated and not timed. So we must have a dated weekdayed item
- self.format = self.format | self.WEEKDAYED
- self.value = time.strptime(value_splitted[0],"%Y-%m-%d")
- elif self.format & self.TIMED:
- # We have only a time
- self.value = time.strptime(value,"%H:%M")
- elif self.format & self.DATED:
- self.value = time.strptime(value,"%Y-%m-%d")
+ if value[0] == '<':
+ self.format |= self.ACTIVE
+ elif value[0] == '[':
+ self.format |= self.INACTIVE
+
+ # time range on a single day
+ search_re = ('{start}(?P<date>{date})\s+(?P<time1>{time})'
+ '-(?P<time2>{time}){end}').format(**self.DICT_RE)
+ match = re.search(search_re, value)
+ if match:
+ #timed, weekdayed, date = self.parse_datetime(match.group('date'))
+ #self.value = time.strptime(match.group('time1').split()[0], '%H:%M')
+ #self.value = time.struct_time(date[:3] + self.value[3:])
+ timed, weekdayed, self.value = self.parse_datetime(
+ match.group('date') + ' ' + match.group('time1'))
+ if weekdayed:
+ self.format |= self.WEEKDAYED
+ timed, weekdayed, self.end = self.parse_datetime(
+ match.group('date') + ' ' + match.group('time2'))
+ #self.end = time.strptime(match.group('time2').split()[0], '%H:%M')
+ #self.end = time.struct_time(date[:3] + self.end[3:])
+ self.format |= self.TIMED | self.DATED | self.RANGED
+ return
+ # date range over several days
+ search_re = ('{start}(?P<date1>{date}(\s+{time})?){end}--'
+ '{start}(?P<date2>{date}(\s+{time})?){end}').format(
+ **self.DICT_RE)
+ match = re.search(search_re, value)
+ if match:
+ timed, weekdayed, self.value = self.parse_datetime(
+ match.group('date1'))
+ if timed:
+ self.format |= self.TIMED
+ if weekdayed:
+ self.format |= self.WEEKDAYED
+ timed, weekdayed, self.end = self.parse_datetime(
+ match.group('date2'))
+ 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)
+ match = re.search(search_re, value)
+ if match:
+ timed, weekdayed, self.value = self.parse_datetime(
+ match.group('datetime'))
+ self.format |= self.DATED
+ if timed:
+ self.format |= self.TIMED
+ if weekdayed:
+ self.format |= self.WEEKDAYED
+ self.end = None
def get_value(self):
"""
Get the timestamp as a text according to the format
"""
+ fmt_dict = {'time': '%H:%M'}
if self.format & self.ACTIVE:
- pre = "<"
- post = ">"
- elif self.format & self.INACTIVE:
- pre = "["
- post = "]"
+ fmt_dict['start'], fmt_dict['end'] = '<', '>'
else:
- pre = ""
- post = ""
-
- if self.format & self.DATED:
- # We have a dated event
- dateformat = "%Y-%m-%d"
- if self.format & self.WEEKDAYED:
- # We have a weekday
- dateformat = dateformat + " %a"
+ fmt_dict['start'], fmt_dict['end'] = '[', ']'
+ if self.format & self.WEEKDAYED:
+ fmt_dict['date'] = '%Y-%m-%d %a'
+ else:
+ fmt_dict['date'] = '%Y-%m-%d'
+ if self.format & self.RANGED:
+ if self.value[:3] == self.end[:3]:
+ # range is between two times on a single day
+ assert self.format & self.TIMED
+ return (time.strftime(
+ '{start}{date} {time}-'.format(**fmt_dict), self.value) +
+ time.strftime('{time}{end}'.format(**fmt_dict),
+ self.end))
+ else:
+ # range is between two days
+ if self.format & self.TIMED:
+ return (time.strftime(
+ '{start}{date} {time}{end}--'.format(**fmt_dict),
+ self.value) +
+ time.strftime(
+ '{start}{date} {time}{end}'.format(**fmt_dict),
+ self.end))
+ else:
+ return (time.strftime(
+ '{start}{date}{end}--'.format(**fmt_dict), self.value) +
+ time.strftime(
+ '{start}{date}{end}'.format(**fmt_dict),
+ self.end))
+ else:
+ # non-ranged time
if self.format & self.TIMED:
- # We have a time also
- dateformat = dateformat + " %H:%M"
-
- return pre+time.strftime(dateformat,self.value)+post
-
- elif self.format & self.TIMED:
- # We have a time only
- timestr = time.strftime("%H:%M",self.value)
- if timestr[0] == '0':
- return timestr[1:]
- return pre+timestr+post
+ return time.strftime(
+ '{start}{date} {time}{end}'.format(**fmt_dict), self.value)
+ else:
+ return time.strftime(
+ '{start}{date}{end}'.format(**fmt_dict), self.value)
#+end_src
**** Test
@@ -458,9 +524,9 @@ setup(
def __init__(self):
OrgPlugin.__init__(self)
- self.regexp_scheduled = re.compile("SCHEDULED: ((<|\[).*?(>|\]))")
- self.regexp_deadline = re.compile("DEADLINE: ((<|\[).*?(>|\]))")
- self.regexp_closed = re.compile("CLOSED: ((<|\[).*?(>|\]))")
+ self.regexp_scheduled = re.compile("SCHEDULED: ((<|\[).*?(>|\])(--(<|\[).*?(>|\]))?)")
+ self.regexp_deadline = re.compile("DEADLINE: ((<|\[).*?(>|\])(--(<|\[).*?(>|\]))?)")
+ self.regexp_closed = re.compile("CLOSED: ((<|\[).*?(>|\])(--(<|\[).*?(>|\]))?)")
def _treat(self,current,line):
scheduled = self.regexp_scheduled.findall(line)
deadline = self.regexp_deadline.findall(line)
@@ -638,11 +704,23 @@ setup(
def __init__(self):
OrgPlugin.__init__(self)
self.regexp = re.compile("^(\*+)\s*(\[.*\])?\s*(.*)$")
+ self.todo_list = ['TODO', 'DONE']
self.keepindent = False # If the line starts by an indent, it is not a node
def _treat(self,current,line):
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)
@@ -661,6 +739,9 @@ setup(
current.heading = re.sub(":([\w]+):","",heading[0][2]) # Remove tags
current.priority = heading[0][1]
current.parent = parent
+
+ if todo: # This item has a todo associated with it
+ current.todo = todo[0][1]
# Looking for tags
heading_without_links = re.sub(" \[(.+)\]","",heading[0][2])
@@ -765,6 +846,36 @@ setup(
"""
for plugin in arguments:
self.plugins.append(plugin)
+ def set_todo_states(self,new_todo_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.
+ Setting to an empty list will disable TODO checking.
+ """
+ for plugin in self.plugins:
+ if plugin.__class__ == OrgNode:
+ plugin.todo_list = new_todo_states
+ def get_todo_states(self):
+ """
+ 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.
+ """
+ all_todo_states = []
+ for plugin in self.plugins:
+ if plugin.__class__ == OrgNode:
+ all_todo_states.append(plugin.todo_list)
+ return all_todo_states
+ def add_todo_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.todo_list.append(new_state)
def load_from_file(self,name):
"""
Used to load an org-file inside this DataStructure
@@ -946,6 +1057,77 @@ if __name__ == '__main__':
unittest.main()
#+end_src
*** Date and time formatting
+#+srcname: test_dates.org
+#+begin_src python :tangle test_dates.py :exports code
+
+ import PyOrgMode
+ import unittest
+
+
+ class TestDates(unittest.TestCase):
+ """Test the org file parser with several date formats"""
+
+ def test_baredate(self):
+ """
+ Tests parsing dates without time.
+ """
+ datestr = '<2013-11-20 Wed>'
+ date = PyOrgMode.OrgDate(datestr)
+ self.assertEqual(tuple(date.value), (2013, 11, 20, 0, 0, 0, 2, 324, -1))
+ self.assertEqual(date.get_value(), datestr)
+
+ def test_datetime(self):
+ """
+ Tests parsing dates with time.
+ """
+ datestr = '<2011-12-12 Mon 09:00>'
+ date = PyOrgMode.OrgDate(datestr)
+ self.assertEqual(tuple(date.value), (2011, 12, 12, 9, 0, 0, 0, 346, -1))
+ self.assertEqual(date.get_value(), datestr)
+
+ def test_datenoweekday(self):
+ """
+ Tests parsing simple dates without weekdays.
+ """
+ datestr = '<2013-11-20>'
+ date = PyOrgMode.OrgDate(datestr)
+ self.assertEqual(tuple(date.value), (2013, 11, 20, 0, 0, 0, 2, 324, -1))
+ self.assertEqual(date.get_value(), datestr)
+
+ def test_timerange(self):
+ """
+ Tests parsing time ranges on the same day.
+ """
+ datestr = '<2012-06-28 Thu 12:00-13:00>'
+ date = PyOrgMode.OrgDate(datestr)
+ self.assertEqual(tuple(date.value), (2012, 6, 28, 12, 0, 0, 3, 180, -1))
+ self.assertEqual(tuple(date.end), (2012, 6, 28, 13, 0, 0, 3, 180, -1))
+ self.assertEqual(date.get_value(), datestr)
+
+ def test_daterange(self):
+ """
+ Tests parsing date ranges.
+ """
+ datestr = '<2012-07-20 Fri>--<2012-07-31 Tue>'
+ date = PyOrgMode.OrgDate(datestr)
+ self.assertEqual(tuple(date.value), (2012, 7, 20, 0, 0, 0, 4, 202, -1))
+ self.assertEqual(tuple(date.end), (2012, 7, 31, 0, 0, 0, 1, 213, -1))
+ self.assertEqual(date.get_value(), datestr)
+
+ def test_daterangewithtimes(self):
+ """
+ Tests parsing date ranges with times.
+ """
+ datestr = '<2012-07-20 Fri 09:00>--<2012-07-31 Tue 14:00>'
+ date = PyOrgMode.OrgDate(datestr)
+ self.assertEqual(tuple(date.value), (2012, 7, 20, 9, 0, 0, 4, 202, -1))
+ self.assertEqual(tuple(date.end), (2012, 7, 31, 14, 0, 0, 1, 213, -1))
+ self.assertEqual(date.get_value(), datestr)
+
+ if __name__ == '__main__':
+ unittest.main()
+
+#+end_src
*** RegExLab
This part is used for internal testing. It allows you to test some piece of code inside your org document.
:PROPERTIES:
View
3  TODO
@@ -5,7 +5,6 @@
- [ ] Add some examples
- [ ] Error/Warning managment
- [ ] Check for other OS compatibility
-- [ ] Do a validator (input file MUST be output file, and check every function)
- [ ] TODO tags (and others)
- [ ] Add more types of data (List…)
@@ -15,7 +14,7 @@
- [ ] Must use data validation
- [ ] Must support recurrent events (+1w …)
---- Class OrgProtocol
+--- Class OrgList
- [ ] Must be written
--- Class OrgProtocol
Please sign in to comment.
Something went wrong with that request. Please try again.