#!/usr/bin/env python import json import os.path import time import sys from optparse import OptionParser from operator import itemgetter # Start Parser I grabbed from http://pyparsing.wikispaces.com/UnderDevelopment#toc0 from datetime import datetime, timedelta from pyparsing import * import calendar # string conversion parse actions def convertToTimedelta(toks): unit = toks.timeunit.lower().rstrip("s") td = { 'week' : timedelta(7), 'day' : timedelta(1), 'hour' : timedelta(0,0,0,0,0,1), 'hr' : timedelta(0,0,0,0,0,1), 'minute' : timedelta(0,0,0,0,1), 'min' : timedelta(0,0,0,0,1), 'second' : timedelta(0,1), 'sec' : timedelta(0,1), }[unit] if toks.qty: td *= int(toks.qty) x = 0 if toks.dir == "": x = 1 else: x = toks.dir if x > 0: td *= -x else: td *= x toks["timeOffset"] = td def convertToDay(toks): now = datetime.now() if "wkdayRef" in toks: todaynum = now.weekday() daynames = [n.lower() for n in calendar.day_name] nameddaynum = daynames.index(toks.wkdayRef.day.lower()) if toks.wkdayRef.dir > 0: daydiff = (nameddaynum + 7 - todaynum) % 7 else: daydiff = -((todaynum + 7 - nameddaynum) % 7) toks["absTime"] = datetime(now.year, now.month, now.day)+timedelta(daydiff) else: name = toks.name.lower() toks["absTime"] = { "now" : now, "today" : datetime(now.year, now.month, now.day), "yesterday" : datetime(now.year, now.month, now.day)+timedelta(-1), "tomorrow" : datetime(now.year, now.month, now.day)+timedelta(+1), }[name] def convertToAbsTime(toks): now = datetime.now() if "dayRef" in toks: day = toks.dayRef.absTime day = datetime(day.year, day.month, day.day) else: day = datetime(now.year, now.month, now.day) if "timeOfDay" in toks: if isinstance(toks.timeOfDay,basestring): timeOfDay = { "now" : timedelta(0, (now.hour*60+now.minute)*60+now.second, now.microsecond), "noon" : timedelta(0,0,0,0,0,12), "midnight" : timedelta(), }[toks.timeOfDay] else: hhmmss = toks.timeparts if hhmmss.miltime: hh,mm = hhmmss.miltime ss = 0 else: hh,mm,ss = (hhmmss.HH % 12), hhmmss.MM, hhmmss.SS if not mm: mm = 0 if not ss: ss = 0 if toks.timeOfDay.ampm == 'pm': hh += 12 timeOfDay = timedelta(0, (hh*60+mm)*60+ss, 0) else: timeOfDay = timedelta(0, (now.hour*60+now.minute)*60+now.second, now.microsecond) toks["absTime"] = day + timeOfDay def calculateTime(toks): if toks.absTime: absTime = toks.absTime else: absTime = datetime.now() if toks.timeOffset: absTime += toks.timeOffset toks["calculatedTime"] = absTime # grammar definitions CL = CaselessLiteral today, tomorrow, yesterday, noon, midnight, now = map( CL, "today tomorrow yesterday noon midnight now".split()) plural = lambda s : Combine(CL(s) + Optional(CL("s"))) week, day, hour, hr, minute, min, second, sec = map( plural, "week day hour hr minute min second sec".split()) am = CL("am") pm = CL("pm") COLON = Suppress(':') # are these actually operators? in_ = CL("in").setParseAction(replaceWith(1)) from_ = CL("from").setParseAction(replaceWith(1)) before = CL("before").setParseAction(replaceWith(-1)) after = CL("after").setParseAction(replaceWith(1)) ago = CL("ago").setParseAction(replaceWith(-1)) next_ = CL("next").setParseAction(replaceWith(1)) last_ = CL("last").setParseAction(replaceWith(-1)) couple = (Optional(CL("a")) + CL("couple") + Optional(CL("of"))).setParseAction(replaceWith(2)) a_qty = CL("a").setParseAction(replaceWith(1)) integer = Word(nums).setParseAction(lambda t:int(t[0])) int4 = Group(Word(nums,exact=4).setParseAction(lambda t: [int(t[0][:2]),int(t[0][2:])] )) qty = integer | couple | a_qty dayName = oneOf( list(calendar.day_name) + [x.lower() for x in calendar.day_name] ) dayOffset = (qty("qty") + (week | day)("timeunit")) dayFwdBack = (from_ + now.suppress() | ago)("dir") weekdayRef = (Optional(next_ | last_, -1 )("dir") + dayName("day")) dayRef = Optional( (dayOffset + (before | after | from_)("dir") ).setParseAction(convertToTimedelta) ) + \ ((yesterday | today | tomorrow)("name")| weekdayRef("wkdayRef")).setParseAction(convertToDay) todayRef = (dayOffset + dayFwdBack).setParseAction(convertToTimedelta) | \ (in_("dir") + qty("qty") + day("timeunit")).setParseAction(convertToTimedelta) dayTimeSpec = dayRef | todayRef dayTimeSpec.setParseAction(calculateTime) hourMinuteOrSecond = (hour | hr | minute | min | second | sec) timespec = Group(int4("miltime") | integer("HH") + Optional(COLON + integer("MM")) + Optional(COLON + integer("SS")) + (am | pm)("ampm") ) absTimeSpec = ((noon | midnight | now | timespec("timeparts"))("timeOfDay") + Optional(dayRef)("dayRef")) absTimeSpec.setParseAction(convertToAbsTime,calculateTime) relTimeSpec = qty("qty") + hourMinuteOrSecond("timeunit") + \ (from_ | before | after)("dir") + \ absTimeSpec("absTime") | \ qty("qty") + hourMinuteOrSecond("timeunit") + ago("dir") | \ in_ + qty("qty") + hourMinuteOrSecond("timeunit") | \ qty("qty") + hourMinuteOrSecond("timeunit") relTimeSpec.setParseAction(convertToTimedelta,calculateTime) nlTimeExpression = (absTimeSpec | dayTimeSpec | relTimeSpec) # End parser def db_append(data): db_file = open(os.path.expanduser("~/.ptwdb"), "r") db = json.load(db_file) db_file.close() if not timestamp_and_text_exists(data["timestamp"], data["text"], db): db.append(data) db_file = open(os.path.expanduser("~/.ptwdb"), "w") json.dump(db, db_file, indent=2) db_file.close() def db_new(): db_file = open(os.path.expanduser("~/.ptwdb"), "w") json.dump([], db_file) db_file.close() def db_get(count=None, since=None): if count is None and since is None: count = 10 if count is None and since is not None: count = 500000 out = [] db_file = open(os.path.expanduser("~/.ptwdb"), "r") db = json.load(db_file) db_file.close() db.sort(key=itemgetter(u"timestamp")) while count != 0: if len(db) == 0: break x = db.pop() if since is not None and x["timestamp"] < since: break out.append(x) count -= 1 return out def timestamp_and_text_exists(timestamp, text, db): for x in db: if x["timestamp"] == timestamp: if text.startswith(x["text"]): return True return False def format(l): out = [] for x in l: timestr = time.strftime("%a %b %d %Y @ %I:%M%p", time.localtime(x["timestamp"])) out.append("%s: %s" % (timestr, x["text"])) return out parser = OptionParser() parser.add_option("--host", dest="hostname", help="Hostname from post", metavar="HOSTNAME", default="localhost") parser.add_option("--client", dest="client", help="Client for post", default="Command Line", metavar="CLIENT") parser.add_option("-n", dest="num", help="Number of posts to show", type="int", default=None, metavar="N") parser.add_option("--source", dest="import_source", help="Source of git imports", type="str", default="", metavar="STR") parser.add_option("-s", "--since", dest="since", help="Number of posts to show", default=None, metavar="SINCE") parser.add_option("-v", "--verbose", dest="verbose", help="Debug output", default=False, action="store_true") (options, args) = parser.parse_args() if len(args) < 1: parser.print_help() exit() command = args[0] text = " ".join(args[1:]) if command == "post": if len(text) < 1: parser.print_help() exit() data = {"timestamp" : time.time(), "text" : text, "client" : options.client, "hostname" : options.hostname } if not os.path.exists(os.path.expanduser("~/.ptwdb")): db_new() db_append(data) elif command == "get": if options.since is not None: p = nlTimeExpression.parseString(options.since) if "calculatedTime" in p: options.since = time.mktime(p.calculatedTime.timetuple()) if options.verbose: print p.calculatedTime.isoformat() else: options.since = None for x in format(db_get(options.num, options.since)): print x elif command == "since": if len(args) < 2: parser.print_help() exit() p = nlTimeExpression.parseString(text) if "calculatedTime" in p: options.since = time.mktime(p.calculatedTime.timetuple()) if options.verbose: print p.calculatedTime.isoformat() else: options.since = None for x in format(db_get(options.num, options.since)): print x elif command == "import": for line in sys.stdin: fields = line.strip().split("\t") ts = int(fields[0]) text = fields[1] if options.import_source != "": text = "%s: %s" % (options.import_source, text) data = {"timestamp" : ts, "text" : text, "client" : options.client, "hostname" : options.hostname } if not os.path.exists(os.path.expanduser("~/.ptwdb")): db_new() db_append(data) else: print "Invalid Command" parser.print_help() exit()