Minor rewrite to make it more extendable
This commit is contained in:
parent
108f24255d
commit
771267eb50
207
ls.py
207
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()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue