# HG changeset patch # User Steve Losh # Date 1348352909 14400 # Node ID 3fdab63a3e837f6c28e7d316754f9b536ef92fa3 # Parent 3e4cf205210b44298a1246f811c9f49ce81c6d5a First hacky attempt at time filtering. diff -r 3e4cf205210b -r 3fdab63a3e83 ffind --- a/ffind Sat Sep 22 17:36:44 2012 -0400 +++ b/ffind Sat Sep 22 18:28:29 2012 -0400 @@ -8,6 +8,7 @@ # # The friendlier file finder. +import time import os import optparse import string @@ -48,9 +49,30 @@ TYPES_ALL = TYPES_FILE | TYPES_DIR +SECOND = 1 +MINUTE = 60 * SECOND +HOUR = 60 * MINUTE +DAY = 24 * HOUR +WEEK = 7 * DAY +MONTH = 30 * DAY +YEAR = int(365.2425 * DAY) + # Regexes --------------------------------------------------------------------- SIZE_RE = re.compile(r'^(\d+(?:\.\d+)?)([bkmgtp])?[a-z]*$', re.IGNORECASE) +AGO_RE = re.compile(r''' + (\d+(?:\.\d+)?) # The number (float/int) + \s* # Optional whitespace + ( # Units + y(?:ears?)? # y/year/years + | mos?(?:nths?)? # mo/mos/month/months + | w(?:eeks?)? # w/week/weeks + | d(?:ays?)? # d/day/days + | h(?:ours?)? # h/hour/hours + | m(?:ins?(?:utes?)?)? # m/min/mins/minute/minutes + | s(?:ecs?(?:onds?)?)? # s/sec/secs/second/seconds + ) + ''', re.VERBOSE | re.IGNORECASE) # Global Options -------------------------------------------------------------- @@ -99,16 +121,23 @@ if not query(basename): return False + stat = os.lstat(path) if options.larger_than: - stat = os.stat(path) if stat.st_size < options.larger_than: return False if options.smaller_than: - stat = os.stat(path) if stat.st_size > options.smaller_than: return False + if options.before: + if stat.st_mtime > options.before: + return False + + if options.after: + if stat.st_mtime < options.after: + return False + if not options.binary: with open(path) as f: if '\0' in f.read(1024): @@ -342,6 +371,55 @@ return not all(c.lower() in string.letters + '_-' for c in s) +def clean_ago_piece(n, unit): + n = float(n) + + if unit in ['s', 'sec', 'secs', 'second', 'seconds']: + unit = SECOND + if unit in ['m', 'min', 'mins', 'minute', 'minutes']: + unit = MINUTE + if unit in ['h', 'hour', 'hours']: + unit = HOUR + if unit in ['d', 'day', 'days']: + unit = DAY + if unit in ['w', 'week', 'weeks']: + unit = WEEK + if unit in ['mo', 'mos', 'month', 'months']: + unit = MONTH + if unit in ['y', 'year', 'years']: + unit = YEAR + + return n, unit + +def parse_ago(start_time, timestr): + pieces = AGO_RE.findall(timestr) + + units = set() + result = start_time + + for piece in pieces: + n, unit = clean_ago_piece(*piece) + + if unit in units: + die('duplicate "%s" in time specification' % unit) + + units.add(unit) + result -= n * unit + + return int(result) + +def parse_time(timestr): + """Parse a time string into milliseconds past the epoch.""" + start_time = int(time.time()) + + timestr = timestr.strip().lower() + + if AGO_RE.match(timestr): + return parse_ago(start_time, timestr) + + return None + + def main(): global options @@ -385,6 +463,13 @@ # Directory sizes are not supported. options.type = options.type - TYPES_DIR + # time filtering + if options.before: + options.before = parse_time(options.before) + + if options.after: + options.after = parse_time(options.after) + # Build the query matcher. if options.literal or not is_re(query): if options.case == CASE_SENSITIVE: