282 lines
9.5 KiB
Python
Executable file
282 lines
9.5 KiB
Python
Executable file
#!/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()
|