import datetime
import calendar
__all__ = ["CronExpression", "parse_atom", "DEFAULT_EPOCH", "SUBSTITUTIONS"]
__license__ = "Public Domain"
DAY_NAMES = zip(('sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'), xrange(7))
MINUTES = (0, 59)
HOURS = (0, 23)
DAYS_OF_MONTH = (1, 31)
MONTHS = (1, 12)
DAYS_OF_WEEK = (0, 6)
L_FIELDS = (DAYS_OF_WEEK, DAYS_OF_MONTH)
FIELD_RANGES = (MINUTES, HOURS, DAYS_OF_MONTH, MONTHS, DAYS_OF_WEEK)
MONTH_NAMES = zip(('jan', 'feb', 'mar', 'apr', 'may', 'jun',
'jul', 'aug', 'sep', 'oct', 'nov', 'dec'), xrange(1, 13))
DEFAULT_EPOCH = (1970, 1, 1, 0, 0, 0)
SUBSTITUTIONS = {
"@yearly": "0 0 1 1 *",
"@anually": "0 0 1 1 *",
"@monthly": "0 0 1 * *",
"@weekly": "0 0 * * 0",
"@daily": "0 0 * * *",
"@midnight": "0 0 * * *",
"@hourly": "0 * * * *"
}
class CronExpression(object):
def __init__(self, line, epoch=DEFAULT_EPOCH, epoch_utc_offset=0):
"""
Instantiates a CronExpression object with an optionally defined epoch.
If the epoch is defined, the UTC offset can be specified one of two
ways: as the sixth element in 'epoch' or supplied in epoch_utc_offset.
The epoch should be defined down to the minute sorted by
descending significance.
"""
for key, value in SUBSTITUTIONS.items():
if line.startswith(key):
line = line.replace(key, value)
break
fields = line.split(None, 5)
if len(fields) == 5:
fields.append('')
minutes, hours, dom, months, dow, self.comment = fields
dow = dow.replace('7', '0').replace('?', '*')
dom = dom.replace('?', '*')
for monthstr, monthnum in MONTH_NAMES:
months = months.lower().replace(monthstr, str(monthnum))
for dowstr, downum in DAY_NAMES:
dow = dow.lower().replace(dowstr, str(downum))
self.string_tab = [minutes, hours, dom.upper(), months, dow.upper()]
self.compute_numtab()
if len(epoch) == 5:
y, mo, d, h, m = epoch
self.epoch = (y, mo, d, h, m, epoch_utc_offset)
else:
self.epoch = epoch
def __str__(self):
base = self.__class__.__name__ + "(%s)"
cron_line = self.string_tab + [str(self.comment])
if not self.comment:
cron_line.pop()
arguments = '"' + ' '.join(cron_line) + '"'
if self.epoch != DEFAULT_EPOCH:
return base % (arguments + ", epoch=" + repr(self.epoch))
else:
return base % arguments
def __repr__(self):
return str(self)
def compute_numtab(self):
"""
Recomputes the sets for the static ranges of the trigger time.
This method should only be called by the user if the string_tab
member is modified.
"""
self.numerical_tab = []
for field_str, span in zip(self.string_tab, FIELD_RANGES):
split_field_str = field_str.split(',')
if len(split_field_str) > 1 and "*" in split_field_str:
raise ValueError("\"*\" must be alone in a field.")
unified = set()
for cron_atom in split_field_str:
# parse_atom only handles static cases
for special_char in ('%', '#', 'L', 'W'):
if special_char in cron_atom:
break
else:
unified.update(parse_atom(cron_atom, span))
self.numerical_tab.append(unified)
if self.string_tab[2] == "*" and self.string_tab[4] != "*":
self.numerical_tab[2] = set()
def check_trigger(self, date_tuple, utc_offset=0):
"""
Returns boolean indicating if the trigger is active at the given time.
The date tuple should be in the local time. Unless periodicities are
used, utc_offset does not need to be specified. If periodicities are
used, specifically in the hour and minutes fields, it is crucial that
the utc_offset is specified.
"""
year, month, day, hour, mins = date_tuple
given_date = datetime.date(year, month, day)
zeroday = datetime.date(*self.epoch[:3])
last_dom = calendar.monthrange(year, month)[-1]
dom_matched = True
# In calendar and datetime.date.weekday, Monday = 0
given_dow = (datetime.date.weekday(given_date) + 1) % 7
first_dow = (given_dow + 1 - day) % 7
# Figure out how much time has passed from the epoch to the given date
utc_diff = utc_offset - self.epoch[5]
mod_delta_yrs = year - self.epoch[0]
mod_delta_mon = month - self.epoch[1] + mod_delta_yrs * 12
mod_delta_day = (given_date - zeroday).days
mod_delta_hrs = hour - self.epoch[3] + mod_delta_day * 24 + utc_diff
mod_delta_min = mins - self.epoch[4] + mod_delta_hrs * 60
# Makes iterating through like components easier.
quintuple = zip(
(mins, hour, day, month, given_dow),
self.numerical_tab,
self.string_tab,
(mod_delta_min, mod_delta_hrs, mod_delta_day, mod_delta_mon,
mod_delta_day),
FIELD_RANGES)
for value, valid_values, field_str, delta_t, field_type in quintuple:
# All valid, static values for the fields are stored in sets
if value in valid_values:
continue
# The following for loop implements the logic for context
# sensitive and epoch sensitive constraints. break statements,
# which are executed when a match is found, lead to a continue
# in the outer loop. If there are no matches found, the given date
# does not match expression constraints, so the function returns
# False as seen at the end of this for...else... construct.
for cron_atom in field_str.split(','):
if cron_atom[0] == '%':
if not(delta_t % int(cron_atom[1:])):
break
elif field_type == DAYS_OF_WEEK and '#' in cron_atom:
D, N = int(cron_atom[0]), int(cron_atom[2])
# Computes Nth occurence of D day of the week
if (((D - first_dow) % 7) + 1 + 7 * (N - 1)) == day:
break
elif field_type == DAYS_OF_MONTH and cron_atom[-1] == 'W':
target = min(int(cron_atom[:-1]), last_dom)
lands_on = (first_dow + target - 1) % 7
if lands_on == 0:
# Shift from Sun. to Mon. unless Mon. is next month
target += 1 if target < last_dom else -2
elif lands_on == 6:
# Shift from Sat. to Fri. unless Fri. in prior month
target += -1 if target > 1 else 2
# Break if the day is correct, and target is a weekday
if target == day and (first_dow + target - 7) % 7 > 1:
break
elif field_type in L_FIELDS and cron_atom.endswith('L'):
# In dom field, L means the last day of the month
target = last_dom
if field_type == DAYS_OF_WEEK:
# Calculates the last occurence of given day of week
desired_dow = int(cron_atom[:-1])
target = (((desired_dow - first_dow) % 7) + 29)
target -= 7 if target > last_dom else 0
if target == day:
break
else:
# See 2010.11.15 of CHANGELOG
if field_type == DAYS_OF_MONTH and self.string_tab[4] != '*':
dom_matched = False
continue
elif field_type == DAYS_OF_WEEK and self.string_tab[2] != '*':
# If we got here, then days of months validated so it does
# not matter that days of the week failed.
return dom_matched
# None of the expressions matched which means this field fails
return False
# Arriving at this point means the date landed within the constraints
# of all fields; the associated trigger should be fired.
return True
def parse_atom(parse, minmax):
"""
Returns a set containing valid values for a given cron-style range of
numbers. The 'minmax' arguments is a two element iterable containing the
inclusive upper and lower limits of the expression.
Examples:
>>> parse_atom("1-5",(0,6))
set([1, 2, 3, 4, 5])
>>> parse_atom("*/6",(0,23))
set([0, 6, 12, 18])
>>> parse_atom("18-6/4",(0,23))
set([18, 22, 0, 4])
>>> parse_atom("*/9",(0,23))
set([0, 9, 18])
"""
parse = parse.strip()
increment = 1
if parse == '*':
return set(xrange(minmax[0], minmax[1] + 1))
elif parse.isdigit():
# A single number still needs to be returned as a set
value = int(parse)
if value >= minmax[0] and value <= minmax[1]:
return set((value,))
else:
raise ValueError("Invalid bounds: \"%s\"" % parse)
elif '-' in parse or '/' in parse:
divide = parse.split('/')
subrange = divide[0]
if len(divide) == 2:
# Example: 1-3/5 or */7 increment should be 5 and 7 respectively
increment = int(divide[1])
if '-' in subrange:
# Example: a-b
prefix, suffix = [int(n) for n in subrange.split('-')]
if prefix < minmax[0] or suffix > minmax[1]:
raise ValueError("Invalid bounds: \"%s\"" % parse)
elif subrange == '*':
# Include all values with the given range
prefix, suffix = minmax
else:
raise ValueError("Unrecognized symbol: \"%s\"" % subrange)
if prefix < suffix:
# Example: 7-10
return set(xrange(prefix, suffix + 1, increment))
else:
# Example: 12-4/2; (12, 12 + n, ..., 12 + m*n) U (n_0, ..., 4)
noskips = list(xrange(prefix, minmax[1] + 1))
noskips+= list(xrange(minmax[0], suffix + 1))
return set(noskips[::increment])
aW1wb3J0IGRhdGV0aW1lCmltcG9ydCBjYWxlbmRhcgoKX19hbGxfXyA9IFsiQ3JvbkV4cHJlc3Npb24iLCAicGFyc2VfYXRvbSIsICJERUZBVUxUX0VQT0NIIiwgIlNVQlNUSVRVVElPTlMiXQpfX2xpY2Vuc2VfXyA9ICJQdWJsaWMgRG9tYWluIgoKREFZX05BTUVTID0gemlwKCgnc3VuJywgJ21vbicsICd0dWUnLCAnd2VkJywgJ3RodScsICdmcmknLCAnc2F0JyksIHhyYW5nZSg3KSkKTUlOVVRFUyA9ICgwLCA1OSkKSE9VUlMgPSAoMCwgMjMpCkRBWVNfT0ZfTU9OVEggPSAoMSwgMzEpCk1PTlRIUyA9ICgxLCAxMikKREFZU19PRl9XRUVLID0gKDAsIDYpCkxfRklFTERTID0gKERBWVNfT0ZfV0VFSywgREFZU19PRl9NT05USCkKRklFTERfUkFOR0VTID0gKE1JTlVURVMsIEhPVVJTLCBEQVlTX09GX01PTlRILCBNT05USFMsIERBWVNfT0ZfV0VFSykKTU9OVEhfTkFNRVMgPSB6aXAoKCdqYW4nLCAnZmViJywgJ21hcicsICdhcHInLCAnbWF5JywgJ2p1bicsCiAgICAgICAgICAgICAgICAgICAnanVsJywgJ2F1ZycsICdzZXAnLCAnb2N0JywgJ25vdicsICdkZWMnKSwgeHJhbmdlKDEsIDEzKSkKREVGQVVMVF9FUE9DSCA9ICgxOTcwLCAxLCAxLCAwLCAwLCAwKQpTVUJTVElUVVRJT05TID0gewogICAgIkB5ZWFybHkiOiAiMCAwIDEgMSAqIiwKICAgICJAYW51YWxseSI6ICIwIDAgMSAxICoiLAogICAgIkBtb250aGx5IjogIjAgMCAxICogKiIsCiAgICAiQHdlZWtseSI6ICIwIDAgKiAqIDAiLAogICAgIkBkYWlseSI6ICIwIDAgKiAqICoiLAogICAgIkBtaWRuaWdodCI6ICIwIDAgKiAqICoiLAogICAgIkBob3VybHkiOiAiMCAqICogKiAqIgp9CgpjbGFzcyBDcm9uRXhwcmVzc2lvbihvYmplY3QpOgogICAgZGVmIF9faW5pdF9fKHNlbGYsIGxpbmUsIGVwb2NoPURFRkFVTFRfRVBPQ0gsIGVwb2NoX3V0Y19vZmZzZXQ9MCk6CiAgICAgICAgIiIiCiAgICAgICAgSW5zdGFudGlhdGVzIGEgQ3JvbkV4cHJlc3Npb24gb2JqZWN0IHdpdGggYW4gb3B0aW9uYWxseSBkZWZpbmVkIGVwb2NoLgogICAgICAgIElmIHRoZSBlcG9jaCBpcyBkZWZpbmVkLCB0aGUgVVRDIG9mZnNldCBjYW4gYmUgc3BlY2lmaWVkIG9uZSBvZiB0d28KICAgICAgICB3YXlzOiBhcyB0aGUgc2l4dGggZWxlbWVudCBpbiAnZXBvY2gnIG9yIHN1cHBsaWVkIGluIGVwb2NoX3V0Y19vZmZzZXQuCiAgICAgICAgVGhlIGVwb2NoIHNob3VsZCBiZSBkZWZpbmVkIGRvd24gdG8gdGhlIG1pbnV0ZSBzb3J0ZWQgYnkKICAgICAgICBkZXNjZW5kaW5nIHNpZ25pZmljYW5jZS4KICAgICAgICAiIiIKICAgICAgICBmb3Iga2V5LCB2YWx1ZSBpbiBTVUJTVElUVVRJT05TLml0ZW1zKCk6CiAgICAgICAgICAgIGlmIGxpbmUuc3RhcnRzd2l0aChrZXkpOgogICAgICAgICAgICAgICAgbGluZSA9IGxpbmUucmVwbGFjZShrZXksIHZhbHVlKQogICAgICAgICAgICAgICAgYnJlYWsKCiAgICAgICAgZmllbGRzID0gbGluZS5zcGxpdChOb25lLCA1KQogICAgICAgIGlmIGxlbihmaWVsZHMpID09IDU6CiAgICAgICAgICAgIGZpZWxkcy5hcHBlbmQoJycpCgogICAgICAgIG1pbnV0ZXMsIGhvdXJzLCBkb20sIG1vbnRocywgZG93LCBzZWxmLmNvbW1lbnQgPSBmaWVsZHMKCiAgICAgICAgZG93ID0gZG93LnJlcGxhY2UoJzcnLCAnMCcpLnJlcGxhY2UoJz8nLCAnKicpCiAgICAgICAgZG9tID0gZG9tLnJlcGxhY2UoJz8nLCAnKicpCgogICAgICAgIGZvciBtb250aHN0ciwgbW9udGhudW0gaW4gTU9OVEhfTkFNRVM6CiAgICAgICAgICAgIG1vbnRocyA9IG1vbnRocy5sb3dlcigpLnJlcGxhY2UobW9udGhzdHIsIHN0cihtb250aG51bSkpCgogICAgICAgIGZvciBkb3dzdHIsIGRvd251bSBpbiBEQVlfTkFNRVM6CiAgICAgICAgICAgIGRvdyA9IGRvdy5sb3dlcigpLnJlcGxhY2UoZG93c3RyLCBzdHIoZG93bnVtKSkKCiAgICAgICAgc2VsZi5zdHJpbmdfdGFiID0gW21pbnV0ZXMsIGhvdXJzLCBkb20udXBwZXIoKSwgbW9udGhzLCBkb3cudXBwZXIoKV0KICAgICAgICBzZWxmLmNvbXB1dGVfbnVtdGFiKCkKICAgICAgICBpZiBsZW4oZXBvY2gpID09IDU6CiAgICAgICAgICAgIHksIG1vLCBkLCBoLCBtID0gZXBvY2gKICAgICAgICAgICAgc2VsZi5lcG9jaCA9ICh5LCBtbywgZCwgaCwgbSwgZXBvY2hfdXRjX29mZnNldCkKICAgICAgICBlbHNlOgogICAgICAgICAgICBzZWxmLmVwb2NoID0gZXBvY2gKCiAgICBkZWYgX19zdHJfXyhzZWxmKToKICAgICAgICBiYXNlID0gc2VsZi5fX2NsYXNzX18uX19uYW1lX18gKyAiKCVzKSIKICAgICAgICBjcm9uX2xpbmUgPSBzZWxmLnN0cmluZ190YWIgKyBbc3RyKHNlbGYuY29tbWVudF0pCiAgICAgICAgaWYgbm90IHNlbGYuY29tbWVudDoKICAgICAgICAgICAgY3Jvbl9saW5lLnBvcCgpCiAgICAgICAgYXJndW1lbnRzID0gJyInICsgJyAnLmpvaW4oY3Jvbl9saW5lKSArICciJwogICAgICAgIGlmIHNlbGYuZXBvY2ggIT0gREVGQVVMVF9FUE9DSDoKICAgICAgICAgICAgcmV0dXJuIGJhc2UgJSAoYXJndW1lbnRzICsgIiwgZXBvY2g9IiArIHJlcHIoc2VsZi5lcG9jaCkpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgcmV0dXJuIGJhc2UgJSBhcmd1bWVudHMKCiAgICBkZWYgX19yZXByX18oc2VsZik6CiAgICAgICAgcmV0dXJuIHN0cihzZWxmKQoKICAgIGRlZiBjb21wdXRlX251bXRhYihzZWxmKToKICAgICAgICAiIiIKICAgICAgICBSZWNvbXB1dGVzIHRoZSBzZXRzIGZvciB0aGUgc3RhdGljIHJhbmdlcyBvZiB0aGUgdHJpZ2dlciB0aW1lLgoKICAgICAgICBUaGlzIG1ldGhvZCBzaG91bGQgb25seSBiZSBjYWxsZWQgYnkgdGhlIHVzZXIgaWYgdGhlIHN0cmluZ190YWIKICAgICAgICBtZW1iZXIgaXMgbW9kaWZpZWQuCiAgICAgICAgIiIiCiAgICAgICAgc2VsZi5udW1lcmljYWxfdGFiID0gW10KCiAgICAgICAgZm9yIGZpZWxkX3N0ciwgc3BhbiBpbiB6aXAoc2VsZi5zdHJpbmdfdGFiLCBGSUVMRF9SQU5HRVMpOgogICAgICAgICAgICBzcGxpdF9maWVsZF9zdHIgPSBmaWVsZF9zdHIuc3BsaXQoJywnKQogICAgICAgICAgICBpZiBsZW4oc3BsaXRfZmllbGRfc3RyKSA+IDEgYW5kICIqIiBpbiBzcGxpdF9maWVsZF9zdHI6CiAgICAgICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKCJcIipcIiBtdXN0IGJlIGFsb25lIGluIGEgZmllbGQuIikKCiAgICAgICAgICAgIHVuaWZpZWQgPSBzZXQoKQogICAgICAgICAgICBmb3IgY3Jvbl9hdG9tIGluIHNwbGl0X2ZpZWxkX3N0cjoKICAgICAgICAgICAgICAgICMgcGFyc2VfYXRvbSBvbmx5IGhhbmRsZXMgc3RhdGljIGNhc2VzCiAgICAgICAgICAgICAgICBmb3Igc3BlY2lhbF9jaGFyIGluICgnJScsICcjJywgJ0wnLCAnVycpOgogICAgICAgICAgICAgICAgICAgIGlmIHNwZWNpYWxfY2hhciBpbiBjcm9uX2F0b206CiAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrCiAgICAgICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgICAgIHVuaWZpZWQudXBkYXRlKHBhcnNlX2F0b20oY3Jvbl9hdG9tLCBzcGFuKSkKCiAgICAgICAgICAgIHNlbGYubnVtZXJpY2FsX3RhYi5hcHBlbmQodW5pZmllZCkKCiAgICAgICAgaWYgc2VsZi5zdHJpbmdfdGFiWzJdID09ICIqIiBhbmQgc2VsZi5zdHJpbmdfdGFiWzRdICE9ICIqIjoKICAgICAgICAgICAgc2VsZi5udW1lcmljYWxfdGFiWzJdID0gc2V0KCkKCiAgICBkZWYgY2hlY2tfdHJpZ2dlcihzZWxmLCBkYXRlX3R1cGxlLCB1dGNfb2Zmc2V0PTApOgogICAgICAgICIiIgogICAgICAgIFJldHVybnMgYm9vbGVhbiBpbmRpY2F0aW5nIGlmIHRoZSB0cmlnZ2VyIGlzIGFjdGl2ZSBhdCB0aGUgZ2l2ZW4gdGltZS4KICAgICAgICBUaGUgZGF0ZSB0dXBsZSBzaG91bGQgYmUgaW4gdGhlIGxvY2FsIHRpbWUuIFVubGVzcyBwZXJpb2RpY2l0aWVzIGFyZQogICAgICAgIHVzZWQsIHV0Y19vZmZzZXQgZG9lcyBub3QgbmVlZCB0byBiZSBzcGVjaWZpZWQuIElmIHBlcmlvZGljaXRpZXMgYXJlCiAgICAgICAgdXNlZCwgc3BlY2lmaWNhbGx5IGluIHRoZSBob3VyIGFuZCBtaW51dGVzIGZpZWxkcywgaXQgaXMgY3J1Y2lhbCB0aGF0CiAgICAgICAgdGhlIHV0Y19vZmZzZXQgaXMgc3BlY2lmaWVkLgogICAgICAgICIiIgogICAgICAgIHllYXIsIG1vbnRoLCBkYXksIGhvdXIsIG1pbnMgPSBkYXRlX3R1cGxlCiAgICAgICAgZ2l2ZW5fZGF0ZSA9IGRhdGV0aW1lLmRhdGUoeWVhciwgbW9udGgsIGRheSkKICAgICAgICB6ZXJvZGF5ID0gZGF0ZXRpbWUuZGF0ZSgqc2VsZi5lcG9jaFs6M10pCiAgICAgICAgbGFzdF9kb20gPSBjYWxlbmRhci5tb250aHJhbmdlKHllYXIsIG1vbnRoKVstMV0KICAgICAgICBkb21fbWF0Y2hlZCA9IFRydWUKCiAgICAgICAgIyBJbiBjYWxlbmRhciBhbmQgZGF0ZXRpbWUuZGF0ZS53ZWVrZGF5LCBNb25kYXkgPSAwCiAgICAgICAgZ2l2ZW5fZG93ID0gKGRhdGV0aW1lLmRhdGUud2Vla2RheShnaXZlbl9kYXRlKSArIDEpICUgNwogICAgICAgIGZpcnN0X2RvdyA9IChnaXZlbl9kb3cgKyAxIC0gZGF5KSAlIDcKCiAgICAgICAgIyBGaWd1cmUgb3V0IGhvdyBtdWNoIHRpbWUgaGFzIHBhc3NlZCBmcm9tIHRoZSBlcG9jaCB0byB0aGUgZ2l2ZW4gZGF0ZQogICAgICAgIHV0Y19kaWZmID0gdXRjX29mZnNldCAtIHNlbGYuZXBvY2hbNV0KICAgICAgICBtb2RfZGVsdGFfeXJzID0geWVhciAtIHNlbGYuZXBvY2hbMF0KICAgICAgICBtb2RfZGVsdGFfbW9uID0gbW9udGggLSBzZWxmLmVwb2NoWzFdICsgbW9kX2RlbHRhX3lycyAqIDEyCiAgICAgICAgbW9kX2RlbHRhX2RheSA9IChnaXZlbl9kYXRlIC0gemVyb2RheSkuZGF5cwogICAgICAgIG1vZF9kZWx0YV9ocnMgPSBob3VyIC0gc2VsZi5lcG9jaFszXSArIG1vZF9kZWx0YV9kYXkgKiAyNCArIHV0Y19kaWZmCiAgICAgICAgbW9kX2RlbHRhX21pbiA9IG1pbnMgLSBzZWxmLmVwb2NoWzRdICsgbW9kX2RlbHRhX2hycyAqIDYwCgogICAgICAgICMgTWFrZXMgaXRlcmF0aW5nIHRocm91Z2ggbGlrZSBjb21wb25lbnRzIGVhc2llci4KICAgICAgICBxdWludHVwbGUgPSB6aXAoCiAgICAgICAgICAgIChtaW5zLCBob3VyLCBkYXksIG1vbnRoLCBnaXZlbl9kb3cpLAogICAgICAgICAgICBzZWxmLm51bWVyaWNhbF90YWIsCiAgICAgICAgICAgIHNlbGYuc3RyaW5nX3RhYiwKICAgICAgICAgICAgKG1vZF9kZWx0YV9taW4sIG1vZF9kZWx0YV9ocnMsIG1vZF9kZWx0YV9kYXksIG1vZF9kZWx0YV9tb24sCiAgICAgICAgICAgICAgICBtb2RfZGVsdGFfZGF5KSwKICAgICAgICAgICAgRklFTERfUkFOR0VTKQoKICAgICAgICBmb3IgdmFsdWUsIHZhbGlkX3ZhbHVlcywgZmllbGRfc3RyLCBkZWx0YV90LCBmaWVsZF90eXBlIGluIHF1aW50dXBsZToKICAgICAgICAgICAgIyBBbGwgdmFsaWQsIHN0YXRpYyB2YWx1ZXMgZm9yIHRoZSBmaWVsZHMgYXJlIHN0b3JlZCBpbiBzZXRzCiAgICAgICAgICAgIGlmIHZhbHVlIGluIHZhbGlkX3ZhbHVlczoKICAgICAgICAgICAgICAgIGNvbnRpbnVlCgogICAgICAgICAgICAjIFRoZSBmb2xsb3dpbmcgZm9yIGxvb3AgaW1wbGVtZW50cyB0aGUgbG9naWMgZm9yIGNvbnRleHQKICAgICAgICAgICAgIyBzZW5zaXRpdmUgYW5kIGVwb2NoIHNlbnNpdGl2ZSBjb25zdHJhaW50cy4gYnJlYWsgc3RhdGVtZW50cywKICAgICAgICAgICAgIyB3aGljaCBhcmUgZXhlY3V0ZWQgd2hlbiBhIG1hdGNoIGlzIGZvdW5kLCBsZWFkIHRvIGEgY29udGludWUKICAgICAgICAgICAgIyBpbiB0aGUgb3V0ZXIgbG9vcC4gSWYgdGhlcmUgYXJlIG5vIG1hdGNoZXMgZm91bmQsIHRoZSBnaXZlbiBkYXRlCiAgICAgICAgICAgICMgZG9lcyBub3QgbWF0Y2ggZXhwcmVzc2lvbiBjb25zdHJhaW50cywgc28gdGhlIGZ1bmN0aW9uIHJldHVybnMKICAgICAgICAgICAgIyBGYWxzZSBhcyBzZWVuIGF0IHRoZSBlbmQgb2YgdGhpcyBmb3IuLi5lbHNlLi4uIGNvbnN0cnVjdC4KICAgICAgICAgICAgZm9yIGNyb25fYXRvbSBpbiBmaWVsZF9zdHIuc3BsaXQoJywnKToKICAgICAgICAgICAgICAgIGlmIGNyb25fYXRvbVswXSA9PSAnJSc6CiAgICAgICAgICAgICAgICAgICAgaWYgbm90KGRlbHRhX3QgJSBpbnQoY3Jvbl9hdG9tWzE6XSkpOgogICAgICAgICAgICAgICAgICAgICAgICBicmVhawoKICAgICAgICAgICAgICAgIGVsaWYgZmllbGRfdHlwZSA9PSBEQVlTX09GX1dFRUsgYW5kICcjJyBpbiBjcm9uX2F0b206CiAgICAgICAgICAgICAgICAgICAgRCwgTiA9IGludChjcm9uX2F0b21bMF0pLCBpbnQoY3Jvbl9hdG9tWzJdKQogICAgICAgICAgICAgICAgICAgICMgQ29tcHV0ZXMgTnRoIG9jY3VyZW5jZSBvZiBEIGRheSBvZiB0aGUgd2VlawogICAgICAgICAgICAgICAgICAgIGlmICgoKEQgLSBmaXJzdF9kb3cpICUgNykgKyAxICsgNyAqIChOIC0gMSkpID09IGRheToKICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWsKCiAgICAgICAgICAgICAgICBlbGlmIGZpZWxkX3R5cGUgPT0gREFZU19PRl9NT05USCBhbmQgY3Jvbl9hdG9tWy0xXSA9PSAnVyc6CiAgICAgICAgICAgICAgICAgICAgdGFyZ2V0ID0gbWluKGludChjcm9uX2F0b21bOi0xXSksIGxhc3RfZG9tKQogICAgICAgICAgICAgICAgICAgIGxhbmRzX29uID0gKGZpcnN0X2RvdyArIHRhcmdldCAtIDEpICUgNwogICAgICAgICAgICAgICAgICAgIGlmIGxhbmRzX29uID09IDA6CiAgICAgICAgICAgICAgICAgICAgICAgICMgU2hpZnQgZnJvbSBTdW4uIHRvIE1vbi4gdW5sZXNzIE1vbi4gaXMgbmV4dCBtb250aAogICAgICAgICAgICAgICAgICAgICAgICB0YXJnZXQgKz0gMSBpZiB0YXJnZXQgPCBsYXN0X2RvbSBlbHNlIC0yCiAgICAgICAgICAgICAgICAgICAgZWxpZiBsYW5kc19vbiA9PSA2OgogICAgICAgICAgICAgICAgICAgICAgICAjIFNoaWZ0IGZyb20gU2F0LiB0byBGcmkuIHVubGVzcyBGcmkuIGluIHByaW9yIG1vbnRoCiAgICAgICAgICAgICAgICAgICAgICAgIHRhcmdldCArPSAtMSBpZiB0YXJnZXQgPiAxIGVsc2UgMgoKICAgICAgICAgICAgICAgICAgICAjIEJyZWFrIGlmIHRoZSBkYXkgaXMgY29ycmVjdCwgYW5kIHRhcmdldCBpcyBhIHdlZWtkYXkKICAgICAgICAgICAgICAgICAgICBpZiB0YXJnZXQgPT0gZGF5IGFuZCAoZmlyc3RfZG93ICsgdGFyZ2V0IC0gNykgJSA3ID4gMToKICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWsKCiAgICAgICAgICAgICAgICBlbGlmIGZpZWxkX3R5cGUgaW4gTF9GSUVMRFMgYW5kIGNyb25fYXRvbS5lbmRzd2l0aCgnTCcpOgogICAgICAgICAgICAgICAgICAgICMgSW4gZG9tIGZpZWxkLCBMIG1lYW5zIHRoZSBsYXN0IGRheSBvZiB0aGUgbW9udGgKICAgICAgICAgICAgICAgICAgICB0YXJnZXQgPSBsYXN0X2RvbQoKICAgICAgICAgICAgICAgICAgICBpZiBmaWVsZF90eXBlID09IERBWVNfT0ZfV0VFSzoKICAgICAgICAgICAgICAgICAgICAgICAgIyBDYWxjdWxhdGVzIHRoZSBsYXN0IG9jY3VyZW5jZSBvZiBnaXZlbiBkYXkgb2Ygd2VlawogICAgICAgICAgICAgICAgICAgICAgICBkZXNpcmVkX2RvdyA9IGludChjcm9uX2F0b21bOi0xXSkKICAgICAgICAgICAgICAgICAgICAgICAgdGFyZ2V0ID0gKCgoZGVzaXJlZF9kb3cgLSBmaXJzdF9kb3cpICUgNykgKyAyOSkKICAgICAgICAgICAgICAgICAgICAgICAgdGFyZ2V0IC09IDcgaWYgdGFyZ2V0ID4gbGFzdF9kb20gZWxzZSAwCgogICAgICAgICAgICAgICAgICAgIGlmIHRhcmdldCA9PSBkYXk6CiAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAjIFNlZSAyMDEwLjExLjE1IG9mIENIQU5HRUxPRwogICAgICAgICAgICAgICAgaWYgZmllbGRfdHlwZSA9PSBEQVlTX09GX01PTlRIIGFuZCBzZWxmLnN0cmluZ190YWJbNF0gIT0gJyonOgogICAgICAgICAgICAgICAgICAgIGRvbV9tYXRjaGVkID0gRmFsc2UKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICAgICAgZWxpZiBmaWVsZF90eXBlID09IERBWVNfT0ZfV0VFSyBhbmQgc2VsZi5zdHJpbmdfdGFiWzJdICE9ICcqJzoKICAgICAgICAgICAgICAgICAgICAjIElmIHdlIGdvdCBoZXJlLCB0aGVuIGRheXMgb2YgbW9udGhzIHZhbGlkYXRlZCBzbyBpdCBkb2VzCiAgICAgICAgICAgICAgICAgICAgIyBub3QgbWF0dGVyIHRoYXQgZGF5cyBvZiB0aGUgd2VlayBmYWlsZWQuCiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGRvbV9tYXRjaGVkCgogICAgICAgICAgICAgICAgIyBOb25lIG9mIHRoZSBleHByZXNzaW9ucyBtYXRjaGVkIHdoaWNoIG1lYW5zIHRoaXMgZmllbGQgZmFpbHMKICAgICAgICAgICAgICAgIHJldHVybiBGYWxzZQoKICAgICAgICAjIEFycml2aW5nIGF0IHRoaXMgcG9pbnQgbWVhbnMgdGhlIGRhdGUgbGFuZGVkIHdpdGhpbiB0aGUgY29uc3RyYWludHMKICAgICAgICAjIG9mIGFsbCBmaWVsZHM7IHRoZSBhc3NvY2lhdGVkIHRyaWdnZXIgc2hvdWxkIGJlIGZpcmVkLgogICAgICAgIHJldHVybiBUcnVlCgoKZGVmIHBhcnNlX2F0b20ocGFyc2UsIG1pbm1heCk6CiAgICAiIiIKICAgIFJldHVybnMgYSBzZXQgY29udGFpbmluZyB2YWxpZCB2YWx1ZXMgZm9yIGEgZ2l2ZW4gY3Jvbi1zdHlsZSByYW5nZSBvZgogICAgbnVtYmVycy4gVGhlICdtaW5tYXgnIGFyZ3VtZW50cyBpcyBhIHR3byBlbGVtZW50IGl0ZXJhYmxlIGNvbnRhaW5pbmcgdGhlCiAgICBpbmNsdXNpdmUgdXBwZXIgYW5kIGxvd2VyIGxpbWl0cyBvZiB0aGUgZXhwcmVzc2lvbi4KCiAgICBFeGFtcGxlczoKICAgID4+PiBwYXJzZV9hdG9tKCIxLTUiLCgwLDYpKQogICAgc2V0KFsxLCAyLCAzLCA0LCA1XSkKCiAgICA+Pj4gcGFyc2VfYXRvbSgiKi82IiwoMCwyMykpCiAgICBzZXQoWzAsIDYsIDEyLCAxOF0pCgogICAgPj4+IHBhcnNlX2F0b20oIjE4LTYvNCIsKDAsMjMpKQogICAgc2V0KFsxOCwgMjIsIDAsIDRdKQoKICAgID4+PiBwYXJzZV9hdG9tKCIqLzkiLCgwLDIzKSkKICAgIHNldChbMCwgOSwgMThdKQogICAgIiIiCiAgICBwYXJzZSA9IHBhcnNlLnN0cmlwKCkKICAgIGluY3JlbWVudCA9IDEKICAgIGlmIHBhcnNlID09ICcqJzoKICAgICAgICByZXR1cm4gc2V0KHhyYW5nZShtaW5tYXhbMF0sIG1pbm1heFsxXSArIDEpKQogICAgZWxpZiBwYXJzZS5pc2RpZ2l0KCk6CiAgICAgICAgIyBBIHNpbmdsZSBudW1iZXIgc3RpbGwgbmVlZHMgdG8gYmUgcmV0dXJuZWQgYXMgYSBzZXQKICAgICAgICB2YWx1ZSA9IGludChwYXJzZSkKICAgICAgICBpZiB2YWx1ZSA+PSBtaW5tYXhbMF0gYW5kIHZhbHVlIDw9IG1pbm1heFsxXToKICAgICAgICAgICAgcmV0dXJuIHNldCgodmFsdWUsKSkKICAgICAgICBlbHNlOgogICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKCJJbnZhbGlkIGJvdW5kczogXCIlc1wiIiAlIHBhcnNlKQogICAgZWxpZiAnLScgaW4gcGFyc2Ugb3IgJy8nIGluIHBhcnNlOgogICAgICAgIGRpdmlkZSA9IHBhcnNlLnNwbGl0KCcvJykKICAgICAgICBzdWJyYW5nZSA9IGRpdmlkZVswXQogICAgICAgIGlmIGxlbihkaXZpZGUpID09IDI6CiAgICAgICAgICAgICMgRXhhbXBsZTogMS0zLzUgb3IgKi83IGluY3JlbWVudCBzaG91bGQgYmUgNSBhbmQgNyByZXNwZWN0aXZlbHkKICAgICAgICAgICAgaW5jcmVtZW50ID0gaW50KGRpdmlkZVsxXSkKCiAgICAgICAgaWYgJy0nIGluIHN1YnJhbmdlOgogICAgICAgICAgICAjIEV4YW1wbGU6IGEtYgogICAgICAgICAgICBwcmVmaXgsIHN1ZmZpeCA9IFtpbnQobikgZm9yIG4gaW4gc3VicmFuZ2Uuc3BsaXQoJy0nKV0KICAgICAgICAgICAgaWYgcHJlZml4IDwgbWlubWF4WzBdIG9yIHN1ZmZpeCA+IG1pbm1heFsxXToKICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoIkludmFsaWQgYm91bmRzOiBcIiVzXCIiICUgcGFyc2UpCiAgICAgICAgZWxpZiBzdWJyYW5nZSA9PSAnKic6CiAgICAgICAgICAgICMgSW5jbHVkZSBhbGwgdmFsdWVzIHdpdGggdGhlIGdpdmVuIHJhbmdlCiAgICAgICAgICAgIHByZWZpeCwgc3VmZml4ID0gbWlubWF4CiAgICAgICAgZWxzZToKICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigiVW5yZWNvZ25pemVkIHN5bWJvbDogXCIlc1wiIiAlIHN1YnJhbmdlKQoKICAgICAgICBpZiBwcmVmaXggPCBzdWZmaXg6CiAgICAgICAgICAgICMgRXhhbXBsZTogNy0xMAogICAgICAgICAgICByZXR1cm4gc2V0KHhyYW5nZShwcmVmaXgsIHN1ZmZpeCArIDEsIGluY3JlbWVudCkpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgIyBFeGFtcGxlOiAxMi00LzI7ICgxMiwgMTIgKyBuLCAuLi4sIDEyICsgbSpuKSBVIChuXzAsIC4uLiwgNCkKICAgICAgICAgICAgbm9za2lwcyA9IGxpc3QoeHJhbmdlKHByZWZpeCwgbWlubWF4WzFdICsgMSkpCiAgICAgICAgICAgIG5vc2tpcHMrPSBsaXN0KHhyYW5nZShtaW5tYXhbMF0sIHN1ZmZpeCArIDEpKQogICAgICAgICAgICByZXR1cm4gc2V0KG5vc2tpcHNbOjppbmNyZW1lbnRdKQo=