1from __future__ import absolute_import, print_function, unicode_literals 2 3import re 4import datetime 5 6 7class DeleteMarker: 8 pass 9 10 11class JSONTemplateError(Exception): 12 def __init__(self, message): 13 super(JSONTemplateError, self).__init__(message) 14 self.location = [] 15 16 def add_location(self, loc): 17 self.location.insert(0, loc) 18 19 def __str__(self): 20 location = ' at template' + ''.join(self.location) 21 return "{}{}: {}".format( 22 self.__class__.__name__, 23 location if self.location else '', 24 self.args[0]) 25 26 27class TemplateError(JSONTemplateError): 28 pass 29 30 31class InterpreterError(JSONTemplateError): 32 pass 33 34 35# Regular expression matching: X days Y hours Z minutes 36# todo: support hr, wk, yr 37FROMNOW_RE = re.compile(''.join([ 38 '^(\s*(?P<years>\d+)\s*y(ears?)?)?', 39 '(\s*(?P<months>\d+)\s*mo(nths?)?)?', 40 '(\s*(?P<weeks>\d+)\s*w(eeks?)?)?', 41 '(\s*(?P<days>\d+)\s*d(ays?)?)?', 42 '(\s*(?P<hours>\d+)\s*h(ours?)?)?', 43 '(\s*(?P<minutes>\d+)\s*m(in(utes?)?)?)?\s*', 44 '(\s*(?P<seconds>\d+)\s*s(ec(onds?)?)?)?\s*$', 45])) 46 47 48def fromNow(offset, reference): 49 # copied from taskcluster-client.py 50 # We want to handle past dates as well as future 51 future = True 52 offset = offset.lstrip() 53 if offset.startswith('-'): 54 future = False 55 offset = offset[1:].lstrip() 56 if offset.startswith('+'): 57 offset = offset[1:].lstrip() 58 59 # Parse offset 60 m = FROMNOW_RE.match(offset) 61 if m is None: 62 raise ValueError("offset string: '%s' does not parse" % offset) 63 64 # In order to calculate years and months we need to calculate how many days 65 # to offset the offset by, since timedelta only goes as high as weeks 66 days = 0 67 hours = 0 68 minutes = 0 69 seconds = 0 70 if m.group('years'): 71 # forget leap years, a year is 365 days 72 years = int(m.group('years')) 73 days += 365 * years 74 if m.group('months'): 75 # assume "month" means 30 days 76 months = int(m.group('months')) 77 days += 30 * months 78 days += int(m.group('days') or 0) 79 hours += int(m.group('hours') or 0) 80 minutes += int(m.group('minutes') or 0) 81 seconds += int(m.group('seconds') or 0) 82 83 # Offset datetime from utc 84 delta = datetime.timedelta( 85 weeks=int(m.group('weeks') or 0), 86 days=days, 87 hours=hours, 88 minutes=minutes, 89 seconds=seconds, 90 ) 91 92 if isinstance(reference, string): 93 reference = datetime.datetime.strptime( 94 reference, '%Y-%m-%dT%H:%M:%S.%fZ') 95 elif reference is None: 96 reference = datetime.datetime.utcnow() 97 return stringDate(reference + delta if future else reference - delta) 98 99 100datefmt_re = re.compile(r'(\.[0-9]{3})[0-9]*(\+00:00)?') 101 102 103def to_str(v): 104 if isinstance(v, bool): 105 return {True: 'true', False: 'false'}[v] 106 elif isinstance(v, list): 107 return ','.join(to_str(e) for e in v) 108 elif v is None: 109 return 'null' 110 else: 111 return str(v) 112 113 114def stringDate(date): 115 # Convert to isoFormat 116 try: 117 string = date.isoformat(timespec='microseconds') 118 # py2.7 to py3.5 does not have timespec 119 except TypeError as e: 120 string = date.isoformat() 121 if string.find('.') == -1: 122 string += '.000' 123 string = datefmt_re.sub(r'\1Z', string) 124 return string 125 126 127# the base class for strings, regardless of python version 128try: 129 string = basestring 130except NameError: 131 string = str 132