Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge pull request #3 from ajoslin/master

Added date parsing with hashtags to --add
  • Loading branch information...
commit 666c80386ffc193a350d3049eea47f263f8d4248 2 parents a05e48c + 56b8957
@tyler-dodge authored
Showing with 229 additions and 71 deletions.
  1. +0 −3  README
  2. +17 −0 README.md
  3. +212 −68 hashcal.py
View
3  README
@@ -1,3 +0,0 @@
-A small command line interface for adding events to a calendar. Planning on having it connect to iCalendar eventually.
-
-Basically addresses the obnoxiousness of entering in events in any calendar program with the number of fields that they require by using optional hashtags.
View
17 README.md
@@ -0,0 +1,17 @@
+\#hashcal
+========
+
+A small command line interface for adding events to a calendar. Planning on having it connect to iCalendar eventually.
+
+Basically addresses the obnoxiousness of entering in events in any calendar program with the number of fields that they require by using optional hashtags.
+
+**Usage**:
+
+`py hashcal.py -a <phrase>`
+
+A few examples of phrases:
+
+`'Get dog #tomorrow #3pm'`
+`'Do stats homework #apr23 #800'`
+`'Run a marathon #octob25 #830pm-9am'`
+`'This is event spans more than one day #23-1'`
View
280 hashcal.py
@@ -14,80 +14,224 @@
import tokenize
import pickle
import os
+import datetime
+import re
+
+#TODO make this a class so it's prettier
+def parse_datetime(tags):
+
+ weekdays = [
+ 'monday', 'tuesday', 'wedesnday', 'thursday',
+ 'friday', 'saturday', 'sunday'
+ ]
+ months = [
+ 'january', 'february', 'march', 'april',
+ 'may', 'june', 'july', 'august', 'september',
+ 'october', 'november', 'december'
+ ]
+ today = 'today'
+ tomorrow = 'tomorrow'
+ time_regex = [
+ '\d{1,4}', # eg 1pm or 830pm or 1430
+ '\d{1,2}[\.:]\d{2}', # eg 8:30pm or 14.30
+ ]
+
+ default_duration = [1,0] # events are 1 hour if none other specified
+
+ now = datetime.datetime.today()
+
+ def get_tag_type(tag):
+ for day in weekdays:
+ if day.find(tag)==0:
+ return 'date'
+
+ for month in months:
+ if month.find(tag)==0:
+ return 'date'
+
+ if today.find(tag)==0 or tomorrow.find(tag)==0:
+ return 'date'
+
+ for expr in time_regex:
+ if re.match(expr, tag) != None:
+ return 'time'
+
+ return None
+
+ # Assumes it's a proper day tag already
+ def parse_day(tag):
+ if today.find(tag)==0:
+ return now
+
+ if tomorrow.find(tag)==0:
+ return now + datetime.timedelta(1)
+
+ # See if it's a weekday
+ for day_num,day in enumerate(weekdays):
+ if day.find(tag)==0:
+ delta = (1+day_num - now.isoweekday()) % 7
+ return now + datetime.timedelta(delta)
+
+ # Then see if it's a month and day like apr23
+ mo = filter(lambda c: c.isalpha(), tag);
+ day = int( filter(lambda c: c.isdigit(), tag) )
+ for month_num,month in enumerate(months):
+ if month.find(mo)==0:
+ year = now.year
+ if 1+month_num < now.month: year += 1
+ return datetime.datetime(year, 1+month_num, day)
+
+ # else it's not a day
+ return None
+
+ # Assumes it's a proper time tag already
+ def parse_time(tag):
+ time_range = tag.split('-') #eg 8:30-9:30
+ times = []
+ for expr in time_range:
+ pm = 'pm' in expr.lower()
+ time = int( filter(lambda c: c.isdigit(), expr) )
+ hour, minute = 0, 0
+ if time<=24: #if it's just eg "#1pm" or "#23"
+ hour = time
+ else: #else it's like 830 or 2308
+ hour = time / 100
+ minute = time % 100
+ if pm: hour += 12
+ times.append(datetime.time(hour,minute))
+ # if no range, set it to 1 hour
+ if len(times)==1:
+ start = times[0]
+ end = datetime.time(
+ (default_duration[0]+start.hour)%24,
+ (default_duration[1]+start.minute)%60,
+ )
+ times.append(end)
+ return times
+
+ date = None
+ times = None
+ for tag in tags:
+ tag = tag.lower()
+ tag_type = get_tag_type(tag)
+ # tag is either a date or a time, can't be both
+ if tag_type == 'date':
+ date = parse_day(tag)
+ elif tag_type == 'time':
+ times = parse_time(tag)
+ else:
+ print "tag "+tag+" has no type"
+
+ if date == None:
+ date = now
+ print "Defaulting to today, could not find date"
+ if times == None:
+ times = [ datetime.time(now.hour), datetime.time(now.hour) ]
+ print "Defaulting to instant event, could not find time"
+
+ start = datetime.datetime(date.year, date.month, date.day, times[0].hour, times[0].minute, 0)
+ end = datetime.datetime(date.year, date.month, date.day, times[1].hour, times[1].minute, 0)
+ if times[1] < times[0]:
+ end += datetime.timedelta(1) # add one day if the time goes into next day
+
+ return start, end
def file_check(fn):
- def wrap(self, file_name, *args):
- if not os.path.exists(file_name):
- os.makedirs(options.file[:file_name.rfind("/")]) #create directory
- file(file_name, "w").close() # create file and close it
- fn(self, file_name, *args)
- return wrap
+ def wrap(self, file_name, *args):
+ # create directory if it doesn't exist
+ directory = file_name[:file_name.rfind("/")]
+ if not os.path.exists(directory):
+ os.makedirs(directory)
+ # create file if it doesn't exist
+ if not os.path.exists(file_name):
+ file(file_name, "w").close()
+ fn(self, file_name, *args)
+ return wrap
class HashCal(object):
- def __init__(self, source_file=None):
- """Create HashCal. If file is given, load events from it"""
- if source_file is not None:
- self.load_from_file(source_file)
- else:
- self.events = []
- def split_hashtags(self, args):
- """Given a set of args, will return a tuple of:
- An array of all the hashtagged args with the hashtags stripped,
- and an array of all the args without hashtags."""
- tag_args = []
- notag_args = []
- for element in args:
- for token in element.split(" "):
- if token[0] == '#':
- tag_args.append(token[1:])
- else:
- notag_args.append(token)
- return tag_args, notag_args
-
- def add_item(self, options, args):
- """Adds an item to event list"""
- self.events.append(" ".join(args))
-
- @file_check
- def load_from_file(self, file_name):
- """Loads all the events from given file"""
- f = file(file_name, "rb")
- self.events = pickle.load(f)
- f.close()
-
- @file_check
- def save_to_file(self, file_name):
- """Saves Self.events to given file,
- making the directories for the file if needed"""
- f = file(file_name, "wb")
- pickle.Pickler(f).dump(self.events)
- f.close()
-
- def print_items(self):
- """Prints all the items"""
- print self.events
+ def __init__(self, source_file=None):
+ """Create HashCal. If file is given, load events from it"""
+
+ if source_file is not None:
+ self.load_from_file(source_file)
+ else:
+ self.events = []
+
+ def split_hashtags(self, args):
+ """Given a set of args, will return:
+ An array of all the hashtagged args with the hashtags stripped,
+ and a word with the description"""
+
+ tags = []
+ description = ""
+ for element in args:
+ for token in element.split(" "):
+ if token[0] == '#':
+ tags.append(token[1:])
+ else:
+ if len(description)>0: description += " "
+ description += token
+ return tags, description
+
+ def add_item(self, options, tags, description):
+ """Adds an item to event list"""
+ start,end = parse_datetime(tags)
+ print start,end
+ self.events.append( { 'start': start, 'end': end, 'text': description })
+
+
+ @file_check
+ def load_from_file(self, file_name):
+ """Loads all the events from given file"""
+
+ f = file(file_name, "rb")
+ # Make sure file isn't empty when loading the
+ # pickle, or it will give EOFERROR
+ if os.path.getsize(file_name) > 0:
+ self.events = pickle.load(f)
+ else:
+ self.events = []
+ f.close()
+
+ @file_check
+ def save_to_file(self, file_name):
+ """Saves Self.events to given file,
+ making the directories for the file if needed"""
+
+ f = file(file_name, "wb")
+ pickle.dump(self.events, f)
+ f.close()
+
+ def print_items(self):
+ """Prints all the items"""
+
+ print self.events
def main(options, args):
- """Executes depending on given options"""
- has_done_something = False
- calendar = HashCal(options.file)
- if options.verbose:
- print args
- print options
- has_done_something = True
- print calendar.split_hashtags(args)
- if options.add:
- calendar.add_item(options, args)
- calendar.save_to_file(options.file)
- has_done_something = True
- if options.show:
- calendar.print_items()
- has_done_something = True
- if not has_done_something:
- print __doc__
+ """Executes depending on given options"""
+
+ has_done_something = False
+ calendar = HashCal(options.file)
+ if options.verbose:
+ print args
+ print options
+ has_done_something = True
+
+ if options.add:
+ tags, description = calendar.split_hashtags(args)
+ calendar.add_item(options, tags, description)
+ calendar.save_to_file(options.file)
+ has_done_something = True
+
+ if options.show:
+ calendar.print_items()
+ has_done_something = True
+
+ if not has_done_something:
+ print __doc__
if __name__ == '__main__':
- options, args = docopt(__doc__)
- options.file = os.path.expanduser(options.file)
- main(options, args)
+ options, args = docopt(__doc__)
+ options.file = os.path.expanduser(options.file)
+ main(options, args)
Please sign in to comment.
Something went wrong with that request. Please try again.