qol-scripts/ls.py

209 lines
7.1 KiB
Python
Executable File

#!/usr/bin/python3
import os
import sys
import pwd
import grp
import time
import argparse
from enum import Enum
import subprocess
# ANSI escape codes for colors
COLORS = {
'RESET': '\033[0m',
'BOLD': '\033[1m',
'RED': '\033[91m',
'GREEN': '\033[92m',
'YELLOW': '\033[93m',
'BLUE': '\033[94m',
'MAGENTA': '\033[95m',
'CYAN': '\033[96m',
'WHITE': '\033[97m'
}
class Filetype(Enum):
S_IFMT = 0o0170000
S_IFSOCK = 0o140000
S_IFLNK = 0o120000
S_IFREG = 0o100000
S_IFBLK = 0o060000
S_IFDIR = 0o040000
S_IFCHR = 0o020000
S_IFIFO = 0o010000
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")
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'
case Filetype.S_IFLNK:
permissions = 'l'
case Filetype.S_IFREG:
permissions = '-'
case Filetype.S_IFBLK:
permissions = 'b'
case Filetype.S_IFDIR:
permissions = 'd'
case Filetype.S_IFCHR:
permissions = 'c'
case Filetype.S_IFIFO:
permissions = 'f'
case _:
permissions = '?'
permissions += ''.join([
"r" if mode & 0o400 else "-",
"w" if mode & 0o200 else "-",
"x" if mode & 0o100 else "-",
"r" if mode & 0o040 else "-",
"w" if mode & 0o020 else "-",
"x" if mode & 0o010 else "-",
"r" if mode & 0o004 else "-",
"w" if mode & 0o002 else "-",
"x" if mode & 0o001 else "-"
])
return permissions
def sizeof_fmt(num):
for unit in ("", "K", "M", "G", "T", "P", "E", "Z"):
if abs(num) < 1024.0:
if unit == "": return f"{num}" #exception to not do .0 for bytes
return f"{num:3.1f}{unit}"
num /= 1024.0
return f"{num:.1f}Y"
def git_gud(path):
try:
output = subprocess.check_output(['git', 'status', '--porcelain'], cwd=path).strip().decode("utf-8")
if output.strip():
lines=output.split('\n')
mods = len([x for x in lines if x[0] == 'M'])
unt = len([x for x in lines if x[0] == '?'])
ding = ''
ding += colorize('!'+str(mods), "YELLOW") if mods > 0 else ''
ding += ' ' if mods > 0 and unt > 0 else ''
ding += colorize('?'+str(unt), "CYAN") if unt > 0 else ''
return ding
# output = subprocess.check_output(['git', 'rev-list', '--count', '--left-only', '@{u}...HEAD'], cwd=file_path, stderr=subprocess.DEVNULL)
# if int(output.strip()) > 0:
# print("bbbb")
except (subprocess.CalledProcessError, FileNotFoundError):
pass
return ''
def list_files(directory='.', args={}):
files = os.listdir(directory)
print(f"files: {len(files)}")
if args.time_sorted:
files.sort(key=lambda name: os.lstat(name).st_mtime, reverse=(not args.reverse))
else:
files.sort()
# filter out hid files if needed
if not args.all:
files = [file for file in files if not file.startswith('.')]
if 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:
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)
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)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="List files in a directory", add_help=False)
parser.add_argument('directory', nargs='?', default='.', help="Directory path")
parser.add_argument('-a', '--all', action='store_true', help="Show hidden files")
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")
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")
args = parser.parse_args()
print(f"ls.py {' '.join(sys.argv[1:])}")
list_files(args.directory, args)