123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385 |
- #!/usr/bin/env python
- # License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
- import os
- import stat
- from contextlib import suppress
- from typing import Dict, Generator, Optional, Tuple, Union
- DEFAULT_DIRCOLORS = r"""# {{{
- # Configuration file for dircolors, a utility to help you set the
- # LS_COLORS environment variable used by GNU ls with the --color option.
- # Copyright (C) 1996-2019 Free Software Foundation, Inc.
- # Copying and distribution of this file, with or without modification,
- # are permitted provided the copyright notice and this notice are preserved.
- # The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the
- # slackware version of dircolors) are recognized but ignored.
- # Below are TERM entries, which can be a glob patterns, to match
- # against the TERM environment variable to determine if it is colorizable.
- TERM Eterm
- TERM ansi
- TERM *color*
- TERM con[0-9]*x[0-9]*
- TERM cons25
- TERM console
- TERM cygwin
- TERM dtterm
- TERM gnome
- TERM hurd
- TERM jfbterm
- TERM konsole
- TERM kterm
- TERM linux
- TERM linux-c
- TERM mlterm
- TERM putty
- TERM rxvt*
- TERM screen*
- TERM st
- TERM terminator
- TERM tmux*
- TERM vt100
- TERM xterm*
- # Below are the color init strings for the basic file types.
- # One can use codes for 256 or more colors supported by modern terminals.
- # The default color codes use the capabilities of an 8 color terminal
- # with some additional attributes as per the following codes:
- # Attribute codes:
- # 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed
- # Text color codes:
- # 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white
- # Background color codes:
- # 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white
- #NORMAL 00 # no color code at all
- #FILE 00 # regular file: use no color at all
- RESET 0 # reset to "normal" color
- DIR 01;34 # directory
- LINK 01;36 # symbolic link. (If you set this to 'target' instead of a
- # numerical value, the color is as for the file pointed to.)
- MULTIHARDLINK 00 # regular file with more than one link
- FIFO 40;33 # pipe
- SOCK 01;35 # socket
- DOOR 01;35 # door
- BLK 40;33;01 # block device driver
- CHR 40;33;01 # character device driver
- ORPHAN 40;31;01 # symlink to nonexistent file, or non-stat'able file ...
- MISSING 00 # ... and the files they point to
- SETUID 37;41 # file that is setuid (u+s)
- SETGID 30;43 # file that is setgid (g+s)
- CAPABILITY 30;41 # file with capability
- STICKY_OTHER_WRITABLE 30;42 # dir that is sticky and other-writable (+t,o+w)
- OTHER_WRITABLE 34;42 # dir that is other-writable (o+w) and not sticky
- STICKY 37;44 # dir with the sticky bit set (+t) and not other-writable
- # This is for files with execute permission:
- EXEC 01;32
- # List any file extensions like '.gz' or '.tar' that you would like ls
- # to colorize below. Put the extension, a space, and the color init string.
- # (and any comments you want to add after a '#')
- # If you use DOS-style suffixes, you may want to uncomment the following:
- #.cmd 01;32 # executables (bright green)
- #.exe 01;32
- #.com 01;32
- #.btm 01;32
- #.bat 01;32
- # Or if you want to colorize scripts even if they do not have the
- # executable bit actually set.
- #.sh 01;32
- #.csh 01;32
- # archives or compressed (bright red)
- .tar 01;31
- .tgz 01;31
- .arc 01;31
- .arj 01;31
- .taz 01;31
- .lha 01;31
- .lz4 01;31
- .lzh 01;31
- .lzma 01;31
- .tlz 01;31
- .txz 01;31
- .tzo 01;31
- .t7z 01;31
- .zip 01;31
- .z 01;31
- .dz 01;31
- .gz 01;31
- .lrz 01;31
- .lz 01;31
- .lzo 01;31
- .xz 01;31
- .zst 01;31
- .tzst 01;31
- .bz2 01;31
- .bz 01;31
- .tbz 01;31
- .tbz2 01;31
- .tz 01;31
- .deb 01;31
- .rpm 01;31
- .jar 01;31
- .war 01;31
- .ear 01;31
- .sar 01;31
- .rar 01;31
- .alz 01;31
- .ace 01;31
- .zoo 01;31
- .cpio 01;31
- .7z 01;31
- .rz 01;31
- .cab 01;31
- .wim 01;31
- .swm 01;31
- .dwm 01;31
- .esd 01;31
- # image formats
- .jpg 01;35
- .jpeg 01;35
- .mjpg 01;35
- .mjpeg 01;35
- .gif 01;35
- .bmp 01;35
- .pbm 01;35
- .pgm 01;35
- .ppm 01;35
- .tga 01;35
- .xbm 01;35
- .xpm 01;35
- .tif 01;35
- .tiff 01;35
- .png 01;35
- .svg 01;35
- .svgz 01;35
- .mng 01;35
- .pcx 01;35
- .mov 01;35
- .mpg 01;35
- .mpeg 01;35
- .m2v 01;35
- .mkv 01;35
- .webm 01;35
- .ogm 01;35
- .mp4 01;35
- .m4v 01;35
- .mp4v 01;35
- .vob 01;35
- .qt 01;35
- .nuv 01;35
- .wmv 01;35
- .asf 01;35
- .rm 01;35
- .rmvb 01;35
- .flc 01;35
- .avi 01;35
- .fli 01;35
- .flv 01;35
- .gl 01;35
- .dl 01;35
- .xcf 01;35
- .xwd 01;35
- .yuv 01;35
- .cgm 01;35
- .emf 01;35
- # https://wiki.xiph.org/MIME_Types_and_File_Extensions
- .ogv 01;35
- .ogx 01;35
- # audio formats
- .aac 00;36
- .au 00;36
- .flac 00;36
- .m4a 00;36
- .mid 00;36
- .midi 00;36
- .mka 00;36
- .mp3 00;36
- .mpc 00;36
- .ogg 00;36
- .ra 00;36
- .wav 00;36
- # https://wiki.xiph.org/MIME_Types_and_File_Extensions
- .oga 00;36
- .opus 00;36
- .spx 00;36
- .xspf 00;36
- """ # }}}
- # special file?
- special_types = (
- (stat.S_IFLNK, 'ln'), # symlink
- (stat.S_IFIFO, 'pi'), # pipe (FIFO)
- (stat.S_IFSOCK, 'so'), # socket
- (stat.S_IFBLK, 'bd'), # block device
- (stat.S_IFCHR, 'cd'), # character device
- (stat.S_ISUID, 'su'), # setuid
- (stat.S_ISGID, 'sg'), # setgid
- )
- CODE_MAP = {
- 'RESET': 'rs',
- 'DIR': 'di',
- 'LINK': 'ln',
- 'MULTIHARDLINK': 'mh',
- 'FIFO': 'pi',
- 'SOCK': 'so',
- 'DOOR': 'do',
- 'BLK': 'bd',
- 'CHR': 'cd',
- 'ORPHAN': 'or',
- 'MISSING': 'mi',
- 'SETUID': 'su',
- 'SETGID': 'sg',
- 'CAPABILITY': 'ca',
- 'STICKY_OTHER_WRITABLE': 'tw',
- 'OTHER_WRITABLE': 'ow',
- 'STICKY': 'st',
- 'EXEC': 'ex',
- }
- def stat_at(file: str, cwd: Optional[Union[int, str]] = None, follow_symlinks: bool = False) -> os.stat_result:
- dirfd: Optional[int] = None
- need_to_close = False
- if isinstance(cwd, str):
- dirfd = os.open(cwd, os.O_RDONLY | getattr(os, 'O_CLOEXEC', 0))
- need_to_close = True
- elif isinstance(cwd, int):
- dirfd = cwd
- try:
- return os.stat(file, dir_fd=dirfd, follow_symlinks=follow_symlinks)
- finally:
- if need_to_close and dirfd is not None:
- os.close(dirfd)
- class Dircolors:
- def __init__(self) -> None:
- self.codes: Dict[str, str] = {}
- self.extensions: Dict[str, str] = {}
- if not self.load_from_environ() and not self.load_from_file():
- self.load_defaults()
- def clear(self) -> None:
- self.codes.clear()
- self.extensions.clear()
- def load_from_file(self) -> bool:
- for candidate in (os.path.expanduser('~/.dir_colors'), '/etc/DIR_COLORS'):
- with suppress(Exception):
- with open(candidate) as f:
- return self.load_from_dircolors(f.read())
- return False
- def load_from_lscolors(self, lscolors: str) -> bool:
- self.clear()
- if not lscolors:
- return False
- for item in lscolors.split(':'):
- try:
- code, color = item.split('=', 1)
- except ValueError:
- continue
- if code.startswith('*.'):
- self.extensions[code[1:]] = color
- else:
- self.codes[code] = color
- return bool(self.codes or self.extensions)
- def load_from_environ(self, envvar: str = 'LS_COLORS') -> bool:
- return self.load_from_lscolors(os.environ.get(envvar) or '')
- def load_from_dircolors(self, database: str, strict: bool = False) -> bool:
- self.clear()
- for line in database.splitlines():
- line = line.split('#')[0].strip()
- if not line:
- continue
- split = line.split()
- if len(split) != 2:
- if strict:
- raise ValueError(f'Warning: unable to parse dircolors line "{line}"')
- continue
- key, val = split
- if key == 'TERM':
- continue
- if key in CODE_MAP:
- self.codes[CODE_MAP[key]] = val
- elif key.startswith('.'):
- self.extensions[key] = val
- elif strict:
- raise ValueError(f'Warning: unable to parse dircolors line "{line}"')
- return bool(self.codes or self.extensions)
- def load_defaults(self) -> bool:
- self.clear()
- return self.load_from_dircolors(DEFAULT_DIRCOLORS, True)
- def generate_lscolors(self) -> str:
- """ Output the database in the format used by the LS_COLORS environment variable. """
- def gen_pairs() -> Generator[Tuple[str, str], None, None]:
- for pair in self.codes.items():
- yield pair
- for pair in self.extensions.items():
- # change .xyz to *.xyz
- yield '*' + pair[0], pair[1]
- return ':'.join('{}={}'.format(*pair) for pair in gen_pairs())
- def _format_code(self, text: str, code: str) -> str:
- val = self.codes.get(code)
- return '\033[{}m{}\033[{}m'.format(val, text, self.codes.get('rs', '0')) if val else text
- def _format_ext(self, text: str, ext: str) -> str:
- val = self.extensions.get(ext, '0')
- return '\033[{}m{}\033[{}m'.format(val, text, self.codes.get('rs', '0')) if val else text
- def format_mode(self, text: str, sr: os.stat_result) -> str:
- mode = sr.st_mode
- if stat.S_ISDIR(mode):
- if (mode & (stat.S_ISVTX | stat.S_IWOTH)) == (stat.S_ISVTX | stat.S_IWOTH):
- # sticky and world-writable
- return self._format_code(text, 'tw')
- if mode & stat.S_ISVTX:
- # sticky but not world-writable
- return self._format_code(text, 'st')
- if mode & stat.S_IWOTH:
- # world-writable but not sticky
- return self._format_code(text, 'ow')
- # normal directory
- return self._format_code(text, 'di')
- for mask, code in special_types:
- if (mode & mask) == mask:
- return self._format_code(text, code)
- # executable file?
- if mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH):
- return self._format_code(text, 'ex')
- # regular file, format according to its extension
- ext = os.path.splitext(text)[1]
- if ext:
- return self._format_ext(text, ext)
- return text
- def __call__(self, path: str, text: str, cwd: Optional[Union[int, str]] = None) -> str:
- follow_symlinks = self.codes.get('ln') == 'target'
- try:
- sr = stat_at(path, cwd, follow_symlinks)
- except OSError:
- return text
- return self.format_mode(text, sr)
- def develop() -> None:
- import sys
- print(Dircolors()(sys.argv[-1], sys.argv[-1]))
|