argparser

This commit is contained in:
Daniel Tsvetkov 2020-02-12 14:20:54 +01:00
parent d78a50c7d7
commit fb953069f3
3 changed files with 229 additions and 76 deletions

View File

@ -0,0 +1,5 @@
christmas,25 december
xmas,25 december
new years eve,31 december 23:59:59
new years,31 december 23:59:59
end of workday,17:00
1 christmas 25 december
2 xmas 25 december
3 new years eve 31 december 23:59:59
4 new years 31 december 23:59:59
5 end of workday 17:00

View File

@ -1,3 +1,4 @@
import argparse
import json
import locale
import re
@ -5,12 +6,15 @@ import sys
from datetime import datetime
from pygments import highlight, lexers, formatters
from scalpl import Cut
from localization import setlocale, resolve_locale
from tww import ISO_FORMAT, time_to_emoji, time_ago, workday_diff, workhours_diff, td_remainders, td_totals, td_iso8601
from tww import resolve_timezone, dateparser_parse_dt, get_utcnow, get_s_since_epoch, get_ms_since_epoch, \
dt_tz_translation, get_local_now, query_to_format_result
custom_locale = None
r_generic = re.compile('(.*)', flags=re.IGNORECASE)
r_time_in_epoch_s_now = re.compile('(?:time since epoch|seconds since epoch)', flags=re.IGNORECASE)
r_time_in_epoch_s2 = re.compile('(.*)?\s*(?:in|to)\s*(?:epoch|seconds since epoch|seconds)', flags=re.IGNORECASE)
@ -33,42 +37,40 @@ r_timezone = re.compile('(.*)?\s(?:timezone|timezones|tz)', flags=re.IGNORECASE)
r_timezone_2 = re.compile('(?:timezone in|timezones in|tz in|timezone|timezones|tz)\s(.*)?', flags=re.IGNORECASE)
def handler_time_s(dt_s: str) -> int:
return get_s_since_epoch(dateparser_parse_dt(dt_s))
def handler_time(dt_s):
return dateparser_parse_dt(dt_s)
def handler_time_ms(dt_s: str) -> int:
return get_ms_since_epoch(dateparser_parse_dt(dt_s))
def handler_time_now_local():
return get_local_now()
def handler_time_s_now_local() -> int:
return get_s_since_epoch(get_local_now())
def handler_time_now_utc():
return get_utcnow()
def handler_time_ms_now_local() -> int:
return get_ms_since_epoch(get_local_now())
def dt_normalize(start_dt, end_dt) -> (datetime, datetime):
if type(start_dt) is str:
start_dt = dateparser_parse_dt(start_dt)
if type(end_dt) is str:
end_dt = dateparser_parse_dt(end_dt)
return start_dt, end_dt
def handler_time_s_now_utc() -> int:
return get_s_since_epoch(get_utcnow())
def handler_time_ms_now_utc() -> int:
return get_ms_since_epoch(get_utcnow())
def handler_time_diff(start_dt: datetime, end_dt: datetime) -> dict:
diff = end_dt - start_dt
def handler_time_diff(start_dt, end_dt) -> dict:
start_dt, end_dt = dt_normalize(start_dt, end_dt)
diff = start_dt - end_dt
return dict(start=dt_pretty(start_dt),
end=dt_pretty(end_dt),
diff=td_pretty(diff))
def handler_time_since_until(start_dt_s: str) -> dict:
return handler_time_diff(dateparser_parse_dt(start_dt_s), get_local_now())
return handler_time_diff(start_dt_s, get_local_now())
def handler_workdays_diff(start_dt: datetime, end_dt: datetime) -> dict:
def handler_workdays_diff(start_dt, end_dt) -> dict:
start_dt, end_dt = dt_normalize(start_dt, end_dt)
diff = workday_diff(start_dt, end_dt)
return dict(start=dt_pretty(start_dt),
end=dt_pretty(end_dt),
@ -79,7 +81,8 @@ def handler_workdays_since_until(start_dt_s: str) -> dict:
return handler_workdays_diff(dateparser_parse_dt(start_dt_s), get_local_now())
def handler_workhours_diff(start_dt: datetime, end_dt: datetime) -> dict:
def handler_workhours_diff(start_dt, end_dt) -> dict:
start_dt, end_dt = dt_normalize(start_dt, end_dt)
diff = workhours_diff(start_dt, end_dt)
return dict(start=dt_pretty(start_dt),
end=dt_pretty(end_dt),
@ -91,6 +94,8 @@ def handler_workhours_since_until(start_dt_s: str) -> dict:
def handler_timezone_translation(dt_s: str, timezone_like_s: str) -> dict:
if dt_s.lower().strip() == "time":
dt_s = "now"
src_dt = dateparser_parse_dt(dt_s)
tz = resolve_timezone(timezone_like_s)
if not tz:
@ -102,7 +107,7 @@ def handler_timezone_translation(dt_s: str, timezone_like_s: str) -> dict:
def handler_generic_parser(dt_s: str) -> datetime:
return query_to_format_result(dt_s, None)
return dateparser_parse_dt(dt_s)
def handler_time_in_parser(dt_s: str) -> datetime:
@ -118,29 +123,38 @@ QUERY_TYPE_DT = "datetime_details"
QUERY_TYPE_TZ = "timezone"
QUERY_TYPE_TD = "timedelta"
h_default = ''
h_unix_s = 'dt->unix_s'
h_unix_ms = 'dt->unix_ms'
h_tz_offset = 'tz->tz_offset'
h_time_in = 'dt->hh:mm'
h_translation = 'dst_dt->iso8601_full'
h_default_dt = 'dt->iso8601_full'
h_default_td = 'diff->duration_iso8601'
regex_handlers = [
(r_time_in_epoch_s_now, handler_time_s_now_local, QUERY_TYPE_DT),
(r_time_in_epoch_s_now, handler_time_s_now_utc, QUERY_TYPE_DT),
(r_time_in_epoch_s2, handler_time_s, QUERY_TYPE_DT),
(r_time_in_epoch_s3, handler_time_s, QUERY_TYPE_DT),
(r_time_in_epoch_ms_now, handler_time_ms_now_local, QUERY_TYPE_DT),
(r_time_in_epoch_ms_now, handler_time_ms_now_utc, QUERY_TYPE_DT),
(r_time_in_epoch_ms2, handler_time_ms, QUERY_TYPE_DT),
(r_time_in_epoch_ms3, handler_time_ms, QUERY_TYPE_DT),
(r_timezone_translation, handler_timezone_translation, QUERY_TYPE_DT_TR),
(r_time_since, handler_time_since_until, QUERY_TYPE_TD),
(r_time_until, handler_time_since_until, QUERY_TYPE_TD),
(r_time_between, handler_time_diff, QUERY_TYPE_TD),
(r_workdays_since, handler_workdays_since_until, QUERY_TYPE_TD),
(r_workdays_until, handler_workdays_since_until, QUERY_TYPE_TD),
(r_workdays_between, handler_workdays_diff, QUERY_TYPE_TD),
(r_workhours_since, handler_workhours_since_until, QUERY_TYPE_TD),
(r_workhours_until, handler_workhours_since_until, QUERY_TYPE_TD),
(r_workhours_between, handler_workhours_diff, QUERY_TYPE_TD),
(r_time_in, handler_time_in_parser, QUERY_TYPE_DT),
(r_timezone, handler_timezone, QUERY_TYPE_TZ),
(r_timezone_2, handler_timezone, QUERY_TYPE_TZ),
(r_generic, handler_generic_parser, QUERY_TYPE_DT),
(r_time_in_epoch_s_now, handler_time_now_local, QUERY_TYPE_DT, h_unix_s),
(r_time_in_epoch_s_now, handler_time_now_utc, QUERY_TYPE_DT, h_unix_s),
(r_time_in_epoch_s2, handler_time, QUERY_TYPE_DT, h_unix_s),
(r_time_in_epoch_s3, handler_time, QUERY_TYPE_DT, h_unix_s),
(r_time_in_epoch_ms_now, handler_time_now_local, QUERY_TYPE_DT, h_unix_ms),
(r_time_in_epoch_ms_now, handler_time_now_utc, QUERY_TYPE_DT, h_unix_ms),
(r_time_in_epoch_ms2, handler_time, QUERY_TYPE_DT, h_unix_ms),
(r_time_in_epoch_ms3, handler_time, QUERY_TYPE_DT, h_unix_ms),
(r_timezone_translation, handler_timezone_translation, QUERY_TYPE_DT_TR, h_translation),
(r_time_since, handler_time_since_until, QUERY_TYPE_TD, h_default_td),
(r_time_until, handler_time_since_until, QUERY_TYPE_TD, h_default_td),
(r_time_between, handler_time_diff, QUERY_TYPE_TD, h_default_td),
(r_workdays_since, handler_workdays_since_until, QUERY_TYPE_TD, h_default_td),
(r_workdays_until, handler_workdays_since_until, QUERY_TYPE_TD, h_default_td),
(r_workdays_between, handler_workdays_diff, QUERY_TYPE_TD, h_default_td),
(r_workhours_since, handler_workhours_since_until, QUERY_TYPE_TD, h_default_td),
(r_workhours_until, handler_workhours_since_until, QUERY_TYPE_TD, h_default_td),
(r_workhours_between, handler_workhours_diff, QUERY_TYPE_TD, h_default_td),
(r_time_in, handler_time_in_parser, QUERY_TYPE_DT, h_time_in),
(r_timezone, handler_timezone, QUERY_TYPE_TZ, h_tz_offset),
(r_timezone_2, handler_timezone, QUERY_TYPE_TZ, h_tz_offset),
(r_generic, handler_generic_parser, QUERY_TYPE_DT, h_default_dt),
]
@ -156,7 +170,7 @@ def try_regex(r, s):
def tokenize(s):
solutions = []
for r, h, t in regex_handlers:
for r, h, t, hi in regex_handlers:
g = try_regex(r, s)
if g is not None:
try:
@ -164,7 +178,7 @@ def tokenize(s):
except Exception as e:
continue
if result is not None:
solutions.append((h.__name__, result, t))
solutions.append((h.__name__, result, t, hi))
return solutions
@ -174,6 +188,20 @@ def pretty_print_dict(obj):
print(colorful_json)
def show_magic_results(obj, args):
for solution in obj['solutions']:
entry_proxy = Cut(solution, sep='->')
highlight_entry = solution["highlight"]
try:
highlight_result = entry_proxy[highlight_entry]
except Exception as e:
continue
if args.handlers:
print("{} -> {}".format(solution['handler'], highlight_result))
else:
print(highlight_result)
def dt_pretty(dt):
rv = {}
global custom_locale
@ -200,7 +228,7 @@ def dt_pretty(dt):
def td_pretty(td):
rv = {
"sign": '-' if td.days < 0 else '+',
"sign": '-' if td.days > 0 else '+',
"in_the": 'future' if td.days < 0 else 'past',
"time_ago": time_ago(td),
"duration_iso8601": td_iso8601(td),
@ -214,7 +242,7 @@ def resolve_query_type(query):
solutions = tokenize(query)
if not solutions:
dt = get_local_now()
return [["now", dt, QUERY_TYPE_DT]]
return [["now", dt, QUERY_TYPE_DT, h_default]]
return solutions
@ -226,9 +254,10 @@ def resolve_query(query):
solutions = resolve_query_type(query)
for solution in solutions:
element = {}
handler, results, query_type = solution
handler, results, query_type, hi = solution
element["handler"] = handler
element["query_type"] = query_type
element["highlight"] = hi
try:
if query_type == QUERY_TYPE_DT:
element["dt"] = dt_pretty(results)
@ -241,8 +270,8 @@ def resolve_query(query):
elif query_type == QUERY_TYPE_TD:
element["timedelta"] = results
rv["solutions"].append(element)
except Exception:
...
except Exception as e:
continue
return rv
@ -273,10 +302,27 @@ def test():
print("{} -> {}".format(s, resolve_query(s)))
if __name__ == "__main__":
custom_locale = ""
custom_locale = resolve_locale(custom_locale)
query = ' '.join(sys.argv[1:])
# query = "workhours until 2/12/2020 12:00"
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument('query', nargs='*', default="now", help="freeform")
parser.add_argument('--locale', dest='locale')
parser.add_argument('--handlers', dest='handlers', action='store_true')
parser.add_argument('--full', dest='full', action='store_true')
args = parser.parse_args()
return args
def main(args):
global custom_locale
custom_locale = resolve_locale(args.locale)
query = ' '.join(args.query)
query = "time in sofia"
result = resolve_query(query)
pretty_print_dict(result)
if args.full:
pretty_print_dict(result)
show_magic_results(result, args)
if __name__ == "__main__":
args = parse_args()
main(args)

View File

@ -4,9 +4,11 @@ Find time now, in the past or future in any timezone or location.
"""
import argparse
import csv
import datetime
import logging
import os
import re
from collections import defaultdict
import dateparser
@ -18,6 +20,7 @@ from datetimerange import DateTimeRange
from dateutil.parser import parse as dutil_parse
from dateparser.timezone_parser import StaticTzInfo
from dateutil.tz import gettz, tzlocal
from fuzzywuzzy import fuzz
from pytz import timezone
from pytz.exceptions import UnknownTimeZoneError
@ -134,8 +137,6 @@ def create_if_not_exists(fname):
def write_to_cache(query, location):
import csv
logger.debug("Writing location to cache")
with open(os.path.join(basepath, "data", ".cache.csv"), 'a+') as wf:
cachewriter = csv.writer(wf)
@ -156,9 +157,7 @@ def resolve_location_local(query):
"""
Find a location by searching in local db of countries and cities
"""
import csv
from heapq import heappush, heappop
from fuzzywuzzy import fuzz
query = query.lower()
create_if_not_exists(os.path.join(basepath, "data", ".cache.csv"))
@ -473,10 +472,113 @@ def tzinfo_from_offset(offset: str) -> pytz.timezone:
return None, []
def dateparser_parse_dt(s: str):
# print("Dateparser query: {}".format(s))
def custom_dt_parse(query):
with open(os.path.join(basepath, "data", "custom_dt.csv")) as f:
cfile = csv.reader(f)
for row in cfile:
entry = row[0]
fuzz_query = query.lower().strip()
fuzz_ratio = fuzz.ratio(fuzz_query, entry)
if fuzz_ratio >= 95:
return row[1]
return query
r_next = re.compile('(?:next)?\s*(.*)', flags=re.IGNORECASE)
r_prev = re.compile('(?:last|prev|previous)?\s*(.*)', flags=re.IGNORECASE)
r_this = re.compile('(?:this|that)?\s*(.*)', flags=re.IGNORECASE)
def get_local_now_parsed(s):
now = get_local_now()
now = now.replace(hour=0, minute=0, second=0, microsecond=0)
parsed = parse_dt(s)
parsed = parsed.replace(tzinfo=now.tzinfo)
return now, parsed
def get_week_start_end(dt):
start = dt - timedelta(days=dt.weekday())
end = start + timedelta(days=6)
return start, end
def handler_next_weekday(s):
now, parsed = get_local_now_parsed(s)
week_start, week_end = get_week_start_end(now)
if parsed > week_end:
# parsed is in next week
return str(parsed)
if week_start <= parsed <= week_end:
# parsed is in this week
return str(parsed + timedelta(days=7))
else:
# parsed is in previous week
return str(parsed + timedelta(days=14))
def handler_prev_weekday(s):
now, parsed = get_local_now_parsed(s)
week_start, week_end = get_week_start_end(now)
if parsed > week_end:
# parsed is in next week
return str(parsed - timedelta(days=14))
if week_start <= parsed <= week_end:
# parsed is in this week
return str(parsed - timedelta(days=7))
else:
# parsed is in previous week
return str(parsed)
def handler_this_weekday(s):
now, parsed = get_local_now_parsed(s)
week_start, week_end = get_week_start_end(now)
if parsed > week_end:
# parsed is in next week
return str(parsed - timedelta(days=7))
if week_start <= parsed <= week_end:
# parsed is in this week
return str(parsed)
else:
# parsed is in previous week
return str(parsed + timedelta(days=7))
def try_regex(r, s):
try:
m = re.match(r, s)
except:
return None
if m:
groups = m.groups()
return groups
regex_handlers = [
(r_next, handler_next_weekday),
(r_prev, handler_prev_weekday),
(r_this, handler_this_weekday),
]
def regex_parsers(s):
for r, h in regex_handlers:
g = try_regex(r, s)
if g is not None:
try:
result = h(*g)
except Exception as e:
continue
if result is not None:
return result
return s
def dateparser_parse_dt(s: str):
s = custom_dt_parse(s)
s = regex_parsers(s)
parsed = parse_dt(s)
# print("Dateparser parsed query: {}".format(parsed))
if not parsed:
parsed = dutil_parse(s)
if not parsed:
@ -643,11 +745,11 @@ def workhours_diff(start, end, workhour_begin="09:00", workhour_end="17:00", wor
def td_remainders(td):
# split seconds to larger units
seconds = td.total_seconds()
minutes, seconds = divmod(seconds, 60)
hours, minutes = divmod(minutes, 60)
days, hours = divmod(hours, 24)
months, days = divmod(days, 30.42)
years, months = divmod(months, 12)
minutes, seconds = divmod(abs(int(seconds)), 60)
hours, minutes = divmod(abs(int(minutes)), 60)
days, hours = divmod(abs(int(hours)), 24)
months, days = divmod(abs(int(days)), 30.42)
years, months = divmod(abs(int(months)), 12)
years, months, days, hours, minutes, seconds = map(int, (years, months, days, hours, minutes, seconds))
years, months, days, hours, minutes, seconds = map(abs, (years, months, days, hours, minutes, seconds))
return dict(
@ -662,12 +764,12 @@ def td_remainders(td):
def td_totals(td):
seconds = td.total_seconds()
minutes = seconds // 60
hours = seconds // (60 * 60)
days = seconds // (24 * 60 * 60)
weeks = seconds // (7 * 24 * 60 * 60)
months = seconds // (30 * 24 * 60 * 60)
years = seconds // (365 * 24 * 60 * 60)
minutes = seconds / 60
hours = seconds / (60 * 60)
days = seconds / (24 * 60 * 60)
weeks = seconds / (7 * 24 * 60 * 60)
months = seconds / (30 * 24 * 60 * 60)
years = seconds / (365 * 24 * 60 * 60)
years, months, weeks, days, hours, minutes, seconds = map(abs,
(years, months, weeks, days, hours, minutes, seconds))
return dict(
@ -694,4 +796,4 @@ def td_iso8601(td):
for short, timeframe in hms:
if rem[timeframe]:
fmt += "{}{}".format(rem[timeframe], short)
return fmt
return fmt