#!/usr/bin/python3 # pylint: disable=superfluous-parens # superfluous-parens: Parens are good for clarity and portability """Find the most recent files.""" # Someday we could use a treap or similar to get the n most recent, but for now, we always list all files, # because timsort is pretty blazing, and it's rare to have so many files that it'll matter. # pylint: disable=wrong-import-position import os import sys import glob import time sys.path.insert(0, '/usr/local/lib') import python2x3 # noqa: ignore=E402 import readline0 # noqa: ignore=E402 def make_used(*var): """Pretend var is used, to keep pyflakes/pylint happy.""" assert True or var def usage(retval): """Output a usage message.""" if retval == 0: file_ = sys.stdout else: file_ = sys.stderr file_.write('Usage: {}\n'.format(sys.argv[0])) file_.write(' [--glob | --here | --stdin]\n') file_.write(' [--heap | --sort]\n') file_.write(' [--count n]\n') file_.write(' [--ctime | --atime]\n') file_.write(' --filenames-only\n') file_.write('\n') file_.write('In --here mode, os.walk() is used to traverse the CWD\n') file_.write('In --glob mode, glob.glob() is used to list the CWD nonrecursively\n') file_.write('In --stdin mode, readline0() is used to obtain null-terminated filenames from stdin\n') file_.write('\n') file_.write('In either case, the n most recent files are listed in order from most recent to least recent\n') file_.write('By default, n is 10\n') file_.write('\n') file_.write('With --heap a heap is used to get the n largest timestamps. This is the default.\n') file_.write('With --sort a big list and sort are used to get the n largest timestamps\n') file_.write('\n') file_.write('Note that --heap and --sort can produce different outputs sometimes, due to files with equal timestamps\n') file_.write('\n') file_.write('--ctime says to use inode-change-times instead of modification times\n') file_.write('--atime says to use access times instead of modification times\n') sys.exit(retval) def find_recent(timestamp_attribute, means, files_iter, count): """Find the n most recent files.""" if means == 'sort': list_ = [] for filename in files_iter(): stat = os.lstat(filename) timestamp = getattr(stat, timestamp_attribute) tuple_ = (-timestamp, filename) list_.append(tuple_) list_.sort() del list_[count:] elif means == 'heap': import heapq heap = [] for filename in files_iter(): try: stat = os.lstat(filename) except OSError: os.write(2, b'Failed to lstat %s\n' % filename) continue timestamp = getattr(stat, timestamp_attribute) tuple_ = (timestamp, filename) heapq.heappush(heap, tuple_) if heap[count:]: dummy = heapq.heappop(heap) make_used(dummy) list_ = [(-timestamp, filename) for timestamp, filename in heap] # list_ = heap list_.sort() else: sys.stderr.write('{}: Internal error: means not sort or heap\n'.format(sys.argv[0])) sys.exit(1) return list_ class Options(object): # pylint: disable=too-few-public-methods # too-few-public-methods: We're just a container """Hold our command line option variables.""" def __init__(self): """Parse command line options, setting instance attributes as needed.""" self.means = '' self.count = 10 self.mode_temp = '' self.timestamp_attribute = 'st_mtime' self.filenames_only = False self.parse() if self.means == '': self.means = 'heap' if self.mode_temp == '': sys.stderr.write('{}: You must specify exactly one of --here and --stdin\n'.format(sys.argv[0])) sys.exit(1) else: self.mode = self.mode_temp def parse(self): # pylint: disable=too-many-branches,too-many-statements """Do the actual conversion.""" while sys.argv[1:]: if sys.argv[1] == '--count': self.count = int(sys.argv[2]) del sys.argv[1] elif sys.argv[1] == '--filenames-only': self.filenames_only = True elif sys.argv[1] == '--ctime': self.timestamp_attribute = 'st_ctime' elif sys.argv[1] == '--atime': self.timestamp_attribute = 'st_atime' elif sys.argv[1] == '--here': if self.mode_temp != '': sys.stderr.write('{}: --here, --glob and --stdin are incompatible\n'.format(sys.argv[0])) sys.exit(1) self.mode_temp = 'here' elif sys.argv[1] == '--glob': if self.mode_temp != '': sys.stderr.write('{}: --here, --glob and --stdin are incompatible\n'.format(sys.argv[0])) sys.exit(1) self.mode_temp = 'glob' elif sys.argv[1] == '--stdin': if self.mode_temp != '': sys.stderr.write('{}: --here, --glob and --stdin are incompatible\n'.format(sys.argv[0])) sys.exit(1) self.mode_temp = 'readline0' elif sys.argv[1] == '--sort': if self.means != '': sys.stderr.write('{}: --sort and --heap are incompatible\n'.format(sys.argv[0])) sys.exit(1) self.means = 'sort' elif sys.argv[1] == '--heap': if self.means != '': sys.stderr.write('{}: --sort and --heap are incompatible\n'.format(sys.argv[0])) sys.exit(1) self.means = 'heap' elif sys.argv[1] in ['-h', '--help']: usage(0) else: sys.stderr.write('{}: Unrecognized option: {}\n'.format(sys.argv[0], sys.argv[1])) usage(1) del sys.argv[1] if self.mode_temp == 'here': pass elif self.mode_temp == 'glob': pass elif self.mode_temp == 'readline0': pass else: sys.stderr.write('{}: Not --here, --glob or --stdin\n'.format(sys.argv[0])) usage(1) if self.timestamp_attribute not in ('st_mtime', 'st_ctime', 'st_atime'): sys.stderr.write('{}: Internal error: timestamp_attribute has strange value: {}\n'.format( sys.argv[0], self.timestamp_attribute, )) def main(): """Parse options, stat files, sort and report.""" options = Options() def files_iter(): """Generate a list of files using os.walk() or readline0.""" if options.mode == 'here': for dirname, subdir_list, filenames in os.walk(b'.'): dummy = subdir_list make_used(dummy) for filename in filenames: yield os.path.join(dirname, filename) elif options.mode == 'readline0': for filename in readline0.readline0(file_=0): yield filename.rstrip(b'\0') elif options.mode == 'glob': for filename in glob.glob('*'): yield filename else: sys.stderr.write('{}: You must specify --here or --stdin\n'.format(sys.argv[0])) sys.exit(1) list_ = find_recent(options.timestamp_attribute, options.means, files_iter, options.count) for timestamp, filename in list_: try: if not options.filenames_only: neg_time = -timestamp sys.stdout.write('{:.3f}'.format(neg_time)) sys.stdout.write(' ') sys.stdout.write(time.ctime(neg_time)) sys.stdout.write(' ') sys.stdout.flush() os.write(1, python2x3.string_to_binary(filename) + b'\n') except BrokenPipeError: pass main()