# Copyright 2009 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import logging import datetime from ext.pyparsing.pyparsing import Or, oneOf, replaceWith, Word, nums, Combine, Optional, CaselessLiteral, ParseException import models def parse(func, data, *args): ret = [] for listargs in data: listargs = list(listargs) listargs.extend(args) ret.append(func(*listargs)) return Or(ret) def replace(grammar, replacement, group): return grammar.setResultsName(group).setParseAction(replaceWith(replacement)) def convert(grammar, conversion_func, group): return grammar.setResultsName(group).setParseAction(conversion_func) def scale(amount): def lambda_scale(x): return float(x[0]) * amount return lambda_scale def get_datetime(month): def lambda_datetime(x): now = datetime.datetime.utcnow().replace(tzinfo = models.UTC()) setdate = now.replace(month=month, day=int(x[1])) if setdate > now: setdate = setdate.replace(year=setdate.year-1) return setdate return lambda_datetime def get_timedelta(years=None, months=None, days=None): setdate = datetime.datetime.utcnow().replace(tzinfo = models.UTC()) if years: newyear = setdate.year + years setdate = setdate.replace(year=newyear) if months: newyear = setdate.year newmonth = setdate.month + months if newmonth > 12: extramonths = newmonth - 1 newyear = newyear + int(extramonths / 12) newmonth = newmonth - (newyear * 12) elif newmonth < 1: extramonths = 1 - newmonth newyear = newyear - int(extramonths / 12) newmonth = newmonth + (newyear * 12) setdate = setdate.replace(month=newmonth, year=newyear) if days: setdate = setdate + datetime.timedelta(days=days) return setdate def ignore(word): return Optional(CaselessLiteral(word)) NUMBER_INTS = Word(nums) NUMBER_REALS = Combine(NUMBER_INTS + '.' + NUMBER_INTS) NUMBER = Or([ NUMBER_INTS, NUMBER_REALS ]) DISTANCE_ACTIONS = [ (oneOf('ran run', caseless=True), { 'activity' : models.DistanceActivity.RUN, 'speed' : 6.0 }), (oneOf('walk walked', caseless=True), { 'activity' : models.DistanceActivity.WALK, 'speed' : 3.0 }), (oneOf('swim swam swum swimming swims', caseless=True), { 'activity' : models.DistanceActivity.SWIM, 'speed' : 2.0 }), (oneOf('bike biked biking cycle spin spinning cycling', caseless=True), { 'activity' : models.DistanceActivity.BIKE, 'speed' : 15.0 }), (oneOf('row rowing rows rowed', caseless=True), { 'activity' : models.DistanceActivity.ROW, 'speed' : 10.0 }), ] DISTANCE_UNITS = [ (NUMBER + oneOf('miles mi mile', caseless=True).ignore('min'), scale(1.0)), (NUMBER + oneOf("feet ft foot '", caseless=True), scale(1.0/5280.0)), (NUMBER + oneOf('inches in "', caseless=True), scale(1.0/(12.0 * 5280.0))), ] DISTANCE_SPEEDS = [ (NUMBER + oneOf(["mph", "miles per hour"], caseless=True), scale(1.0)), (NUMBER + oneOf(["fps", "feet per second"], caseless=True), scale(0.681818182)), ] DISTANCE_TIME = [ (NUMBER + oneOf('hour hours h', caseless=True), scale(1.0)), (NUMBER + oneOf('minute minutes m', caseless=True), scale(1.0/60.0)), ] COUNT_ACTIONS = [ (oneOf(["pushups", "pushup", "push up", "push ups"], caseless=True), { 'activity' : models.CountActivity.PUSHUP }), (oneOf(["situps", "situp", "sit up", "sit ups"], caseless=True), { 'activity' : models.CountActivity.SITUP }), (oneOf(["chinups", "chinup", "chin up", "chin ups", "pullups", "pullup", "pull up", "pull ups"], caseless=True), { 'activity' : models.CountActivity.PULLUP }), ] COUNT_UNITS = [ ((NUMBER + oneOf(['times', 'x'], caseless=True)) | NUMBER, scale(1.0)), ] TIME_LITERALS = [ (CaselessLiteral('today'), get_timedelta(days=0)), (CaselessLiteral('yesterday'), get_timedelta(days=-1)), ] TIME_DELTAS = [ (NUMBER_INTS + oneOf('month m months', caseless=True), lambda x: get_timedelta(months=-int(x[0]))), (NUMBER_INTS + oneOf('day d days', caseless=True), lambda x: get_timedelta(days=-int(x[0]))), (NUMBER_INTS + oneOf('week weeks', caseless=True), lambda x: get_timedelta(days=-int(x[0]) * 7)), (NUMBER_INTS + oneOf('year y years', caseless=True), lambda x: get_timedelta(years=-int(x[0]))), ] TIME_SPECIFIC = [ (oneOf('jan january', caseless=True) + NUMBER_INTS, get_datetime(1)), (oneOf('feb february', caseless=True) + NUMBER_INTS, get_datetime(2)), (oneOf('mar march', caseless=True) + NUMBER_INTS, get_datetime(3)), (oneOf('apr april', caseless=True) + NUMBER_INTS, get_datetime(4)), (oneOf('may', caseless=True) + NUMBER_INTS, get_datetime(5)), (oneOf('jun june', caseless=True) + NUMBER_INTS, get_datetime(6)), (oneOf('jul july', caseless=True) + NUMBER_INTS, get_datetime(7)), (oneOf('aug august', caseless=True) + NUMBER_INTS, get_datetime(8)), (oneOf('sep september', caseless=True) + NUMBER_INTS, get_datetime(9)), (oneOf('oct october', caseless=True) + NUMBER_INTS, get_datetime(10)), (oneOf('nov november', caseless=True) + NUMBER_INTS, get_datetime(11)), (oneOf('dec december', caseless=True) + NUMBER_INTS, get_datetime(12)), ] distance_action = parse(replace, DISTANCE_ACTIONS, 'distance') distance_units = parse(convert, DISTANCE_UNITS, 'miles') distance_speed = parse(convert, DISTANCE_SPEEDS, 'speed').ignore('at') distance_time = parse(convert, DISTANCE_TIME, 'duration').ignore('in') distance_activity = ((distance_action + distance_units) & Optional(distance_speed) & Optional(distance_time)) count_action = parse(replace, COUNT_ACTIONS, 'count') count_units = parse(convert, COUNT_UNITS, 'times') count_activity = ignore('did') + (count_action & count_units) time_literal = parse(replace, TIME_LITERALS, 'datetime') time_relative = parse(convert, TIME_DELTAS, 'datetime') + ignore('ago') time_specific = ignore('on') + parse(convert, TIME_SPECIFIC, 'datetime') when = time_literal | time_relative | time_specific activity_grammar = (distance_activity | count_activity) & Optional(when) def parse_activity(text): try: parsed = activity_grammar.parseString(text) logging.info("Parsed '%s' as %s" % (text, dict(parsed))) return dict(parsed) except ParseException, ex: logging.exception(ex) return None