129 lines
4.3 KiB
Python
129 lines
4.3 KiB
Python
|
|
|
|
"""Give coloring for file types as in the ls command."""
|
|
|
|
|
|
import os
|
|
import os.path
|
|
import stat
|
|
import syslog
|
|
|
|
|
|
__version__ = "v2022.01.27"
|
|
|
|
|
|
FILE_KEY = "fi"
|
|
DIRECTORY_KEY = "di"
|
|
OTHER_WRITABLE_KEY = "ow"
|
|
EXECUTABLE_KEY = "ex"
|
|
SETUID_KEY = "su"
|
|
SETGUID_KEY = "sg"
|
|
SYMLINK_KEY = "ln"
|
|
ORPHAN_KEY = "or"
|
|
PIPE_KEY = "pi"
|
|
CHARACTER_DEVICE_KEY = "cd"
|
|
BLOCK_DEVICE_KEY = "bd"
|
|
STICKY_KEY = "st"
|
|
STICKY_OTHER_WRITABLE_KEY = "tw"
|
|
SOCKET_KEY = "so"
|
|
MISSING_KEY = "mi"
|
|
MULTI_HARDLINK_KEY = "mh"
|
|
|
|
|
|
def _parse_ls_colors(ls_codes):
|
|
color_codes = {}
|
|
for entry in ls_codes.split(":"):
|
|
if "=" not in entry:
|
|
continue
|
|
entry_key, entry_value = entry.split("=")
|
|
if entry_key.startswith("*."):
|
|
entry_key = entry_key[1:]
|
|
color_codes[entry_key] = entry_value
|
|
assert color_codes != {}, color_codes
|
|
return color_codes
|
|
|
|
|
|
_DEFAULT_COLOR_CODES = \
|
|
{BLOCK_DEVICE_KEY: '01;33', SYMLINK_KEY: '01;36', STICKY_OTHER_WRITABLE_KEY: '30;42',
|
|
DIRECTORY_KEY: '01;34', SETUID_KEY: '37;41', CHARACTER_DEVICE_KEY: '01;33',
|
|
SOCKET_KEY: '01;35', EXECUTABLE_KEY: '01;32', STICKY_KEY: '37;44',
|
|
OTHER_WRITABLE_KEY: '34;42', PIPE_KEY: '33', SETGUID_KEY: '30;43', ORPHAN_KEY: '40;31;01'}
|
|
|
|
|
|
def get_color_codes(environment):
|
|
"""Get a dictionary of the color of every file type."""
|
|
if "LS_COLORS" in environment:
|
|
try:
|
|
return _parse_ls_colors(environment["LS_COLORS"])
|
|
except Exception:
|
|
syslog.syslog("Syntax error in LS_COLORS environment variable. Using default colors.")
|
|
return _DEFAULT_COLOR_CODES
|
|
|
|
|
|
def color_key_for_path(path, color_codes, is_link_target=True):
|
|
"""Get the high level type (key) of a file."""
|
|
# see print_color_indicator in the file 'ls.c' in the coreutils codebase
|
|
if not os.path.lexists(path):
|
|
return MISSING_KEY
|
|
elif os.path.islink(path):
|
|
if is_link_target:
|
|
try:
|
|
link_path = os.path.join(os.path.dirname(path), os.readlink(path))
|
|
file_stat = os.stat(link_path)
|
|
except OSError:
|
|
return ORPHAN_KEY
|
|
else:
|
|
return SYMLINK_KEY
|
|
else:
|
|
file_stat = os.stat(path)
|
|
mode = file_stat.st_mode
|
|
if stat.S_ISREG(mode):
|
|
if mode & stat.S_ISUID and SETUID_KEY in color_codes:
|
|
return SETUID_KEY
|
|
elif mode & stat.S_ISGID and SETGUID_KEY in color_codes:
|
|
return SETGUID_KEY
|
|
elif ((mode & stat.S_IXUSR or mode & stat.S_IXGRP or mode & stat.S_IXOTH)
|
|
and EXECUTABLE_KEY in color_codes):
|
|
return EXECUTABLE_KEY
|
|
elif file_stat.st_nlink > 1 and MULTI_HARDLINK_KEY in color_codes:
|
|
return MULTI_HARDLINK_KEY
|
|
else:
|
|
return FILE_KEY
|
|
elif stat.S_ISDIR(mode):
|
|
if mode & stat.S_ISVTX and mode & stat.S_IWOTH and STICKY_OTHER_WRITABLE_KEY in color_codes:
|
|
return STICKY_OTHER_WRITABLE_KEY
|
|
elif (mode & stat.S_IWOTH) != 0 and OTHER_WRITABLE_KEY in color_codes:
|
|
return OTHER_WRITABLE_KEY
|
|
elif (mode & stat.S_ISVTX) != 0 and STICKY_KEY in color_codes:
|
|
return STICKY_KEY
|
|
else:
|
|
return DIRECTORY_KEY
|
|
for test_function, color_key in [(stat.S_ISFIFO, PIPE_KEY), (stat.S_ISSOCK, SOCKET_KEY),
|
|
(stat.S_ISBLK, BLOCK_DEVICE_KEY),
|
|
(stat.S_ISCHR, CHARACTER_DEVICE_KEY)]:
|
|
if test_function(mode):
|
|
return color_key
|
|
return ORPHAN_KEY
|
|
|
|
|
|
def color_code_for_path(path, color_codes):
|
|
"""Get the color of a file."""
|
|
def get_extension(basename, color_codes):
|
|
parts = basename.split(".")
|
|
if len(parts) == 2:
|
|
extension = "." + parts[1]
|
|
if extension in color_codes:
|
|
return extension
|
|
elif len(parts) > 2:
|
|
for extension in color_codes:
|
|
if extension.startswith(".") and basename.endswith(extension):
|
|
return extension
|
|
target_link = color_codes.get(SYMLINK_KEY, None)
|
|
color_key = color_key_for_path(path, color_codes, target_link == "target")
|
|
if color_key == FILE_KEY:
|
|
filename = os.path.basename(path)
|
|
if "." in filename:
|
|
extension = get_extension(filename, color_codes)
|
|
if extension is not None:
|
|
color_key = extension
|
|
return color_codes.get(color_key, None)
|