argparser
This commit is contained in:
parent
d78a50c7d7
commit
fb953069f3
5
src/tww/data/custom_dt.csv
Normal file
5
src/tww/data/custom_dt.csv
Normal 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,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)
|
||||
|
140
src/tww/tww.py
140
src/tww/tww.py
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user