From 771267eb5097f9528c32871e4f67295d4a9bcd61 Mon Sep 17 00:00:00 2001 From: icaema Date: Thu, 22 Feb 2024 22:23:06 -0600 Subject: [PATCH] Minor rewrite to make it more extendable --- ls.py | 207 ++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 120 insertions(+), 87 deletions(-) diff --git a/ls.py b/ls.py index 7e741a9..6f1929b 100755 --- a/ls.py +++ b/ls.py @@ -7,6 +7,7 @@ import time import argparse from enum import Enum import subprocess +import functools # ANSI escape codes for colors COLORS = { @@ -30,54 +31,60 @@ class Filetype(Enum): S_IFDIR = 0o040000 S_IFCHR = 0o020000 S_IFIFO = 0o010000 + ERR = 0 def colorize(text, color): return f"{COLORS[color]}{text}{COLORS['RESET']}" -def color_file(text, mode): - match Filetype(mode & Filetype.S_IFMT.value): - case Filetype.S_IFREG: - if mode & 0o100 or mode & 0o010 or mode & 0o001: - return colorize(text, "GREEN") # file is executable, return green - return colorize(text, "WHITE") - case Filetype.S_IFDIR: - return colorize(text, "BLUE") - case Filetype.S_IFLNK: - return colorize(text, "CYAN") +def gen_color_file_string(filepath, mode): + perms = parse_permissions(mode) + color = perms['color'] + filestring = colorize(filepath, color) + # dircolors? + if perms['char'] == 'l': + pointfile = os.readlink(filepath) + pointfilereal = os.path.join(os.path.dirname(filepath), os.readlink(filepath)) + filestring += ' -> ' + colorize(pointfile, "GREEN" if os.path.exists(pointfilereal) else "RED" ) + return filestring + + +def parse_permissions(mode): + char = "-" + color = "WHITE" + prettybits = "" + filetype = Filetype(mode & Filetype.S_IFMT.value) or Filetype.ERR + + match filetype: case Filetype.S_IFSOCK: - return colorize(text, "MAGENTA") - case Filetype.S_IFBLK: - return colorize(text, "YELLOW") - case Filetype.S_IFCHR: - return colorize(text, "YELLOW") - case Filetype.S_IFIFO: - return colorize(text, "MAGENTA") - case _: - return colorize(text, "RED") - - -def parse_permission(mode): - permissions = "-" - match Filetype(mode & Filetype.S_IFMT.value): - case Filetype.S_IFSOCK: - permissions = 's' + char = 's' + color = "MAGENTA" case Filetype.S_IFLNK: - permissions = 'l' + char = 'l' + color = "CYAN" case Filetype.S_IFREG: - permissions = '-' + char = '-' + if mode & 0o100 or mode & 0o010 or mode & 0o001: # is file executable? + color = "GREEN" + else: + color = "WHITE" case Filetype.S_IFBLK: - permissions = 'b' + char = 'b' + color = "YELLOW" case Filetype.S_IFDIR: - permissions = 'd' + char = 'd' + color = "BLUE" case Filetype.S_IFCHR: - permissions = 'c' + char = 'c' + color = "YELLOW" case Filetype.S_IFIFO: - permissions = 'f' + char = 'f' + color = "MAGENTA" case _: - permissions = '?' + char = '?' + color = "RED" - permissions += ''.join([ + prettybits = char + ''.join([ "r" if mode & 0o400 else "-", "w" if mode & 0o200 else "-", "x" if mode & 0o100 else "-", @@ -88,7 +95,8 @@ def parse_permission(mode): "w" if mode & 0o002 else "-", "x" if mode & 0o001 else "-" ]) - return permissions + + return {'mode':mode, 'type': filetype, 'char': char, 'color': color, 'prettybits': prettybits } def sizeof_fmt(num): for unit in ("", "K", "M", "G", "T", "P", "E", "Z"): @@ -98,6 +106,10 @@ def sizeof_fmt(num): num /= 1024.0 return f"{num:.1f}Y" +def is_path_git_repo_dir(path, mode): + #I hate having to take in the mode here, but it makes it easier for usage + return Filetype(mode & Filetype.S_IFMT.value) == Filetype.S_IFDIR and os.path.exists(os.path.join(path, '.git')) + def git_gud(path): try: output = subprocess.check_output(['git', 'status', '--porcelain'], cwd=path).strip().decode("utf-8") @@ -121,68 +133,79 @@ def git_gud(path): def list_files(directory='.', args={}): - files = os.listdir(directory) + filenames = os.listdir(directory) + + + #get file metadata + def get_file_metadata(path, name): + rel_file_path = os.path.join(path, name) # for rel paths + abs_file_path = os.path.abspath(rel_file_path) # for full path + stats = os.lstat(rel_file_path) + return { + "name": name, + "rel_file_path": rel_file_path, + "abs_file_path": abs_file_path, + "stats": stats + } + files = [ get_file_metadata(directory, name) for name in os.listdir(directory) ] + + + longest_size_length = len(str(functools.reduce(lambda a, b: a if len(str(a['stats'].st_size)) > len(str(b['stats'].st_size)) else b, files)['stats'].st_size)) print(f"files: {len(files)}") + # Sort stuff if args.time_sorted: - files.sort(key=lambda name: os.lstat(name).st_mtime, reverse=(not args.reverse)) + files.sort(key=lambda f: f["stats"].st_mtime, reverse=(not args.reverse)) + elif args.size_sorted: + files.sort(key=lambda f: f["stats"].st_size, reverse=(not args.reverse)) else: - files.sort() + files.sort(key=lambda f:f["name"], reverse=(args.reverse)) - # filter out hid files if needed + + # filter out hidden if not specify -a if not args.all: - files = [file for file in files if not file.startswith('.')] + files = [file for file in files if not file['name'].startswith('.')] - if args.long: + if not args.long: for file in files: - #file_path = os.path.join(directory, file) # for rel paths - file_path = os.path.abspath(file) # for full path - stats = os.lstat(file_path) - mode = stats.st_mode - permissions = parse_permission(mode) - if args.sparse and permissions[0] == 'l': - permissions = 'l ' - nlink = stats.st_nlink - user = pwd.getpwuid(stats.st_uid).pw_name - group = grp.getgrgid(stats.st_gid).gr_name - - size = stats.st_size - if args.human: - size=sizeof_fmt(size) - if args.sparse and Filetype(mode & Filetype.S_IFMT.value) == Filetype.S_IFDIR: + colored_file = gen_color_file_string(file["abs_file_path"], file["stats"].st_mode) + # rel is broken + print(colored_file) + return + + + for file in files: + size = file['stats'].st_size + user = pwd.getpwuid(file['stats'].st_uid).pw_name + group = grp.getgrgid(file['stats'].st_gid).gr_name + permissions = parse_permissions(file['stats'].st_mode) + nlinks = file['stats'].st_nlink + + perm_prettybits = permissions['prettybits'] + timestring = time.strftime("%b %d %H:%M", time.localtime(file["stats"].st_mtime)) + + if args.sparse: + if perm_prettybits == 'l': + perm_prettybits = 'l ' + if permissions['type'] == Filetype.S_IFDIR: size="" - - if args.full_time: - mtime = time.strftime("%b %d %Y %X %z", time.localtime(stats.st_mtime)) # -0400 - #mtime = time.strftime("%b %d %X %Z", time.localtime(stats.st_mtime)) # CST - else: - mtime = time.strftime("%b %d %H:%M", time.localtime(stats.st_mtime)) - colored_file = color_file(file_path, mode) - + if args.human: + size=sizeof_fmt(size) + + if args.full_time: + timestring = time.strftime("%b %d %Y %X %z", time.localtime(file["stats"].st_mtime)) # -0400 + #mtime = time.strftime("%b %d %X %Z", time.localtime(stats.st_mtime)) # CST + + colored_file = gen_color_file_string(file["abs_file_path"], file["stats"].st_mode) + gitstat = (args.extend and is_path_git_repo_dir(file["abs_file_path"], file["stats"].st_mode) and git_gud(file["abs_file_path"])) or '' + + ## do printing + print(f"{perm_prettybits} {nlinks:2d} {user:<8s} {group:<8s} {size:>{longest_size_length}} {timestring} {colored_file} {gitstat}") - git='' - if args.extend and Filetype(mode & Filetype.S_IFMT.value) == Filetype.S_IFDIR and os.path.exists(os.path.join(file_path, '.git')): - git=git_gud(file_path) - if os.path.islink(file_path): - color = "RED" - if os.path.exists(os.readlink(file_path)): - color = "GREEN" - print(f"{permissions} {nlink:2d} {user:<8s} {group:<8s} {size:>6} {mtime} {colored_file} -> {colorize(os.readlink(file_path), color)} {git}") - else: - print(f"{permissions} {nlink:2d} {user:<8s} {group:<8s} {size:>6} {mtime} {colored_file} {git}") - else: - for file in files: - file_path = os.path.join(directory, file) - if os.path.islink(file_path): - print(colorize(file + " -> " + os.readlink(file_path), 'CYAN')) - elif os.path.isdir(file_path): - print(colorize(file, 'BLUE')) - else: - print(file) @@ -190,17 +213,27 @@ def list_files(directory='.', args={}): if __name__ == "__main__": parser = argparse.ArgumentParser(description="List files in a directory", add_help=False) parser.add_argument('directory', nargs='?', default='.', help="Directory path") + + # filter options parser.add_argument('-a', '--all', action='store_true', help="Show hidden files") + + # output information options parser.add_argument('-l', '--long', action='store_true', help="Use a long listing format") parser.add_argument('-F', '--full-time', action='store_true', help="Show full full time") parser.add_argument('-s', '--sparse', action='store_true', help="Blank irrelevant information") - - parser.add_argument('-t', '--time-sorted', action='store_true', help="Sort by time") + + + # ouput format options + sortargs = parser.add_mutually_exclusive_group() + sortargs.add_argument('-t', '--time-sorted', action='store_true', help="Sort by time") + sortargs.add_argument('-S', '--size-sorted', action='store_true', help="Sort by file size") + parser.add_argument('-r', '--reverse', action='store_true', help="Reverse sort order") - parser.add_argument('-h', '--human', action='store_true', help="Human Readable size") - parser.add_argument('-e', '--extend', action='store_true', help="Human Readable size") + + # extra + parser.add_argument('-e', '--extend', action='store_true', help="Do some extra processing on the directory") args = parser.parse_args()