Extract lscolors to standalone repo (2026.7.27)

This commit is contained in:
Andrew Hamilton 2026-07-27 09:43:46 +10:00
parent c0bb85aa8c
commit 6cb5578326
7 changed files with 2 additions and 1224 deletions

View file

@ -1 +0,0 @@
../LICENSE

View file

@ -1,776 +0,0 @@
# LS_COLORS
# Maintainers: Magnus Woldrich <m@japh.se>,
# Ryan Delaney <ryan.delaney@gmail.com> OpenGPG: 0D98863B4E1D07B6
# URL: https://github.com/trapd00r/LS_COLORS
# Version: 0.254
# Updated: Tue Mar 29 21:25:30 AEST 2016
#
# This is a collection of extension:color mappings, suitable to use as your
# LS_COLORS environment variable. Most of them use the extended color map,
# described in the ECMA-48 document; in other words, you'll need a terminal
# with capabilities of displaying 256 colors.
#
# As of this writing, around 300 different filetypes/extensions is supported.
# That's indeed a lot of extensions, but there's a lot more! Therefore I need
# your help.
#
# Fork this project on github, add the extensions you are missing, and send me
# a pull request.
#
# For files that usually ends up next to each other, like html, css and js,
# try to pick colors that fit nicely together. Filetypes with multiple
# possible extensions, like htm and html, should have the same color.
# This program is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the Perl Artistic License for more details.
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the Perl Artistic License as published by the Perl Foundation,
# either version 1.0 of the License, or (at your option) any later version.
#
# You should have received a copy of the Perl Artistic License along
# with this program. If not, see <http://www.perlfoundation.org/artistic_license_1_0>.
# core {{{1
BLK 38;5;68
CAPABILITY 38;5;17
CHR 38;5;113;1
DIR 38;5;30
DOOR 38;5;127
EXEC 38;5;208;1
FIFO 38;5;126
FILE 0
LINK target
MULTIHARDLINK 38;5;222;1
# "NORMAL don't reset the bold attribute -
# https://github.com/trapd00r/LS_COLORS/issues/11
#NORMAL 38;5;254
NORMAL 0
ORPHAN 48;5;196;38;5;232;1
OTHER_WRITABLE 38;5;220;1
SETGID 48;5;3;38;5;0
SETUID 38;5;220;1;3;100;1
SOCK 38;5;197
STICKY 38;5;86;48;5;234
STICKY_OTHER_WRITABLE 48;5;235;38;5;139;3
*LS_COLORS 48;5;89;38;5;197;1;3;4;7 # :-)
# }}}
# documents {{{1
*README 38;5;220;1
*README.rst 38;5;220;1
*README.md 38;5;220;1
*LICENSE 38;5;220;1
*COPYING 38;5;220;1
*INSTALL 38;5;220;1
*COPYRIGHT 38;5;220;1
*AUTHORS 38;5;220;1
*HISTORY 38;5;220;1
*CONTRIBUTORS 38;5;220;1
*PATENTS 38;5;220;1
*VERSION 38;5;220;1
*NOTICE 38;5;220;1
*CHANGES 38;5;220;1
.log 38;5;190
# plain-text {{{2
.txt 38;5;253
# markup {{{2
.etx 38;5;184
.info 38;5;184
.markdown 38;5;184
.md 38;5;184
.mkd 38;5;184
.nfo 38;5;184
.pod 38;5;184
.rst 38;5;184
.tex 38;5;184
.textile 38;5;184
# key-value, non-relational data {{{2
.bib 38;5;178
.json 38;5;178
.jsonl 38;5;178
.msg 38;5;178
.pgn 38;5;178
.rss 38;5;178
.xml 38;5;178
.toml 38;5;178
.yaml 38;5;178
.yml 38;5;178
.RData 38;5;178
.rdata 38;5;178
# }}}
# binary {{{2
.cbr 38;5;141
.cbz 38;5;141
.chm 38;5;141
.djvu 38;5;141
.pdf 38;5;141
.PDF 38;5;141
.mobi 38;5;141
.epub 38;5;141
# words {{{3
.docm 38;5;111;4
.doc 38;5;111
.docx 38;5;111
.eps 38;5;111
.ps 38;5;111
.odb 38;5;111
.odt 38;5;111
.rtf 38;5;111
# presentation {{{3
.odp 38;5;166
.pps 38;5;166
.ppt 38;5;166
.pptx 38;5;166
# Powerpoint show
.ppts 38;5;166
# Powerpoint with enabled macros
.pptxm 38;5;166;4
# Powerpoint show with enabled macros
.pptsm 38;5;166;4
# spreadsheet {{{3
.csv 38;5;78
.tsv 38;5;78
# Open document spreadsheet
.ods 38;5;112
.xla 38;5;76
# Excel spreadsheet
.xls 38;5;112
.xlsx 38;5;112
# Excel spreadsheet with macros
.xlsxm 38;5;112;4
# Excel module
.xltm 38;5;73;4
.xltx 38;5;73
# }}}
# }}}
# configs {{{2
*config 1
*cfg 1
*conf 1
*rc 1
*authorized_keys 1
*known_hosts 1
.ini 1
.plist 1
# vim
.viminfo 1
# cisco VPN client configuration
.pcf 1
# adobe photoshop proof settings file
.psf 1
# }}}
# }}}
# code {{{1
# version control {{{2
.git 38;5;197
.gitignore 38;5;240
.gitattributes 38;5;240
.gitmodules 38;5;240
# shell {{{2
.awk 38;5;172
.bash 38;5;172
.bat 38;5;172
.BAT 38;5;172
.sed 38;5;172
.sh 38;5;172
.zsh 38;5;172
.vim 38;5;172
# interpreted {{{2
.ahk 38;5;41
# python
.py 38;5;41
.ipynb 38;5;41
# ruby
.rb 38;5;41
.gemspec 38;5;41
# perl
.pl 38;5;208
.PL 38;5;160
.t 38;5;114
# sql
.msql 38;5;222
.mysql 38;5;222
.pgsql 38;5;222
.sql 38;5;222
# Tool Command Language
.tcl 38;5;64;1
# R language
.r 38;5;49
.R 38;5;49
# GrADS script
.gs 38;5;81
# Clojure
.clj 38;5;41
.cljs 38;5;41
.cljc 38;5;41
# Clojure gorilla REPL worksheet
.cljw 38;5;41
# Scala
.scala 38;5;41
# Dart
.dart 38;5;51
# compiled {{{2
#
# assembly language
.asm 38;5;81
# LISP
.cl 38;5;81
.lisp 38;5;81
# lua
.lua 38;5;81
# Moonscript
.moon 38;5;81
# C
.c 38;5;81
.C 38;5;81
.h 38;5;110
.H 38;5;110
.tcc 38;5;110
# C++
.c++ 38;5;81
.h++ 38;5;110
.hpp 38;5;110
.hxx 38;5;110
.ii 38;5;110
# method file for Objective C
.M 38;5;110
.m 38;5;110
# Csharp
.cc 38;5;81
.cs 38;5;81
.cp 38;5;81
.cpp 38;5;81
.cxx 38;5;81
# Crystal
.cr 38;5;81
# Google golang
.go 38;5;81
# fortran
.f 38;5;81
.F 38;5;81
.for 38;5;81
.ftn 38;5;81
.f90 38;5;81
.F90 38;5;81
.f95 38;5;81
.F95 38;5;81
.f03 38;5;81
.F03 38;5;81
.f08 38;5;81
.F08 38;5;81
# Nim
.nim 38;5;81
.nimble 38;5;81
# pascal
.s 38;5;110
.S 38;5;110
# Rust
.rs 38;5;81
# Swift
.swift 38;5;219
# ?
.sx 38;5;81
# Vala
.vala 38;5;81
.vapi 38;5;81
# interface file in GHC - https://github.com/trapd00r/LS_COLORS/pull/9
.hi 38;5;110
# haskell
.hs 38;5;81
.lhs 38;5;81
# agda
.agda 38;5;81
.lagda 38;5;81
.lagda.tex 38;5;81
.lagda.rst 38;5;81
.lagda.md 38;5;81
.agdai 38;5;110
# binaries {{{2
# compiled apps for interpreted languages
.pyc 38;5;240
# }}}
# orchestration {{{2
.tf 38;5;168
.tfstate 38;5;168
.tfvars 38;5;168
# orchestration 2}}}
# html {{{2
.css 38;5;125;1
.less 38;5;125;1
.sass 38;5;125;1
.scss 38;5;125;1
.htm 38;5;125;1
.html 38;5;125;1
.jhtm 38;5;125;1
.mht 38;5;125;1
.eml 38;5;125;1
.mustache 38;5;125;1
# }}}
# java {{{2
.coffee 38;5;074;1
.java 38;5;074;1
.js 38;5;074;1
.mjs 38;5;074;1
.jsm 38;5;074;1
.jsm 38;5;074;1
.jsp 38;5;074;1
# }}}
# php {{{2
.php 38;5;81
# CakePHP view scripts and helpers
.ctp 38;5;81
# Twig template engine
.twig 38;5;81
# }}}
# vb/a {{{2
.vb 38;5;81
.vba 38;5;81
.vbs 38;5;81
# 2}}}
# Build stuff {{{2
*Dockerfile 38;5;155
.dockerignore 38;5;240
*Makefile 38;5;155
*MANIFEST 38;5;243
*pm_to_blib 38;5;240
# Functional Configuration
.nix 38;5;155
.dhall 38;5;178
# ruby rake
.rake 38;5;155
# automake
.am 38;5;242
.in 38;5;242
.hin 38;5;242
.scan 38;5;242
.m4 38;5;242
.old 38;5;242
.out 38;5;242
.SKIP 38;5;244
# }}}
# patch files {{{2
.diff 48;5;197;38;5;232
.patch 48;5;197;38;5;232;1
#}}}
# graphics {{{1
.bmp 38;5;97
.dicom 38;5;97
.tiff 38;5;97
.tif 38;5;97
.TIFF 38;5;97
.cdr 38;5;97
.flif 38;5;97
.gif 38;5;97
.ico 38;5;97
.jpeg 38;5;97
.JPG 38;5;97
.jpg 38;5;97
.nth 38;5;97
.png 38;5;97
.psd 38;5;97
.xpm 38;5;97
.webp 38;5;97
# }}}
# vector {{{1
.ai 38;5;99
.eps 38;5;99
.epsf 38;5;99
.drw 38;5;99
.ps 38;5;99
.svg 38;5;99
# }}}
# video {{{1
.avi 38;5;114
.divx 38;5;114
.IFO 38;5;114
.m2v 38;5;114
.m4v 38;5;114
.mkv 38;5;114
.MOV 38;5;114
.mov 38;5;114
.mp4 38;5;114
.mpeg 38;5;114
.mpg 38;5;114
.ogm 38;5;114
.rmvb 38;5;114
.sample 38;5;114
.wmv 38;5;114
# mobile/streaming {{{2
.3g2 38;5;115
.3gp 38;5;115
.gp3 38;5;115
.webm 38;5;115
.gp4 38;5;115
.asf 38;5;115
.flv 38;5;115
.ts 38;5;115
.ogv 38;5;115
.f4v 38;5;115
# }}}
# lossless {{{2
.VOB 38;5;115;1
.vob 38;5;115;1
# }}}
# audio {{{1
.3ga 38;5;137;1
.S3M 38;5;137;1
.aac 38;5;137;1
.au 38;5;137;1
.dat 38;5;137;1
.dts 38;5;137;1
.fcm 38;5;137;1
.m4a 38;5;137;1
.mid 38;5;137;1
.midi 38;5;137;1
.mod 38;5;137;1
.mp3 38;5;137;1
.mp4a 38;5;137;1
.oga 38;5;137;1
.ogg 38;5;137;1
.opus 38;5;137;1
.s3m 38;5;137;1
.sid 38;5;137;1
.wma 38;5;137;1
# lossless
.ape 38;5;136;1
.aiff 38;5;136;1
.cda 38;5;136;1
.flac 38;5;136;1
.alac 38;5;136;1
.midi 38;5;136;1
.pcm 38;5;136;1
.wav 38;5;136;1
.wv 38;5;136;1
.wvc 38;5;136;1
# }}}
# fonts {{{1
.afm 38;5;66
.fon 38;5;66
.fnt 38;5;66
.pfb 38;5;66
.pfm 38;5;66
.ttf 38;5;66
.otf 38;5;66
# postscript fonts
.PFA 38;5;66
.pfa 38;5;66
# }}}
# archives {{{1
.7z 38;5;40
.a 38;5;40
.arj 38;5;40
.bz2 38;5;40
.cpio 38;5;40
.gz 38;5;40
.lrz 38;5;40
.lz 38;5;40
.lzma 38;5;40
.lzo 38;5;40
.rar 38;5;40
.s7z 38;5;40
.sz 38;5;40
.tar 38;5;40
.tgz 38;5;40
.xz 38;5;40
.z 38;5;40
.zip 38;5;40
.zipx 38;5;40
.zoo 38;5;40
.zpaq 38;5;40
.zst 38;5;40
.zz 38;5;40
# packaged apps {{{2
.apk 38;5;215
.deb 38;5;215
.rpm 38;5;215
.jad 38;5;215
.jar 38;5;215
.cab 38;5;215
.pak 38;5;215
.pk3 38;5;215
.vdf 38;5;215
.vpk 38;5;215
.bsp 38;5;215
.dmg 38;5;215
# }}}
# segments from 0 to three digits after first extension letter {{{2
.r[0-9]{0,2} 38;5;239
.zx[0-9]{0,2} 38;5;239
.z[0-9]{0,2} 38;5;239
# partial files
.part 38;5;239
# }}}
# partition images {{{2
.dmg 38;5;124
.iso 38;5;124
.bin 38;5;124
.nrg 38;5;124
.qcow 38;5;124
.sparseimage 38;5;124
.toast 38;5;124
.vcd 38;5;124
.vmdk 38;5;124
# }}}
# databases {{{2
.accdb 38;5;60
.accde 38;5;60
.accdr 38;5;60
.accdt 38;5;60
.db 38;5;60
.fmp12 38;5;60
.fp7 38;5;60
.localstorage 38;5;60
.mdb 38;5;60
.mde 38;5;60
.sqlite 38;5;60
.typelib 38;5;60
# NetCDF database
.nc 38;5;60
# }}}
# tempfiles {{{1
# undo files
.pacnew 38;5;33
.un~ 38;5;241
.orig 38;5;241
# backups
.BUP 38;5;241
.bak 38;5;241
.o 38;5;241 # *nix Object file (shared libraries, core dumps etc)
*core 38;5;241 # Linux user core dump file (from /proc/sys/kernel/core_pattern)
.rlib 38;5;241 # Static rust library
# temporary files
.swp 38;5;244
.swo 38;5;244
.tmp 38;5;244
.sassc 38;5;244
# state files
.pid 38;5;248
.state 38;5;248
*lockfile 38;5;248
*lock 38;5;248
# error logs
.err 38;5;160;1
.error 38;5;160;1
.stderr 38;5;160;1
# state dumps
.aria2 38;5;241
.dump 38;5;241
.stackdump 38;5;241
.zcompdump 38;5;241
.zwc 38;5;241
# tcpdump, network traffic capture
.pcap 38;5;29
.cap 38;5;29
.dmp 38;5;29
# macOS
.DS_Store 38;5;239
.localized 38;5;239
.CFUserTextEncoding 38;5;239
# }}}
# hosts {{{1
# /etc/hosts.{deny,allow}
.allow 38;5;112
.deny 38;5;196
# }}}
# systemd {{{1
# http://www.freedesktop.org/software/systemd/man/systemd.unit.html
.service 38;5;45
*@.service 38;5;45
.socket 38;5;45
.swap 38;5;45
.device 38;5;45
.mount 38;5;45
.automount 38;5;45
.target 38;5;45
.path 38;5;45
.timer 38;5;45
.snapshot 38;5;45
# }}}
# metadata {{{1
.application 38;5;116
.cue 38;5;116
.description 38;5;116
.directory 38;5;116
.m3u 38;5;116
.m3u8 38;5;116
.md5 38;5;116
.properties 38;5;116
.sfv 38;5;116
.srt 38;5;116
.sub 38;5;116
.theme 38;5;116
.torrent 38;5;116
.urlview 38;5;116
# }}}
# encrypted data {{{1
.asc 38;5;192;3
.bfe 38;5;192;3
.enc 38;5;192;3
.gpg 38;5;192;3
.signature 38;5;192;3
.sig 38;5;192;3
.p12 38;5;192;3
.pem 38;5;192;3
.pgp 38;5;192;3
.asc 38;5;192;3
.enc 38;5;192;3
.sig 38;5;192;3
.p7s 38;5;192;3
*id_dsa 38;5;192;3
*id_rsa 38;5;192;3
*id_ecdsa 38;5;192;3
*id_ed25519 38;5;192;3
# 1}}}
# emulators {{{1
.32x 38;5;213
.cdi 38;5;213
.fm2 38;5;213
.rom 38;5;213
.sav 38;5;213
.st 38;5;213
# atari
.a00 38;5;213
.a52 38;5;213
.A64 38;5;213
.a64 38;5;213
.a78 38;5;213
.adf 38;5;213
.atr 38;5;213
# nintendo
.gb 38;5;213
.gba 38;5;213
.gbc 38;5;213
.gel 38;5;213
.gg 38;5;213
.ggl 38;5;213
.ipk 38;5;213 # Nintendo (DS Packed Images)
.j64 38;5;213
.nds 38;5;213
.nes 38;5;213
# Sega
.sms 38;5;213
# }}}
# unsorted {{{1
#
# Portable Object Translation for GNU Gettext
.pot 38;5;7
# CAD files for printed circuit boards
.pcb 38;5;7
# groff (rendering app for texinfo)
.mm 38;5;7
# perldoc
.pod 38;5;7
# GIMP files
.gbr 38;5;7
.scm 38;5;7
.xcf 38;5;7
# printer spool file
.spl 38;5;7
# RStudio project file
.Rproj 38;5;11
# Nokia Symbian OS files
.sis 38;5;7
.1p 38;5;7
.3p 38;5;7
.cnc 38;5;7
.def 38;5;7
.ex 38;5;7
.example 38;5;7
.feature 38;5;7
.ger 38;5;7
.map 38;5;7
.mf 38;5;7
.mfasl 38;5;7
.mi 38;5;7
.mtx 38;5;7
.pc 38;5;7
.pi 38;5;7
.plt 38;5;7
.pm 38;5;7
.rdf 38;5;7
.ru 38;5;7
.sch 38;5;7
.sty 38;5;7
.sug 38;5;7
.t 38;5;7
.tdy 38;5;7
.tfm 38;5;7
.tfnt 38;5;7
.tg 38;5;7
.vcard 38;5;7
.vcf 38;5;7
.xln 38;5;7
# AppCode files
.iml 38;5;166
# Xcode files
.xcconfig 1
.entitlements 1
.strings 1
.storyboard 38;5;196
.xcsettings 1
.xib 38;5;208
# }}}
# termcap {{{1
TERM ansi
TERM color-xterm
TERM con132x25
TERM con132x30
TERM con132x43
TERM con132x60
TERM con80x25
TERM con80x28
TERM con80x30
TERM con80x43
TERM con80x50
TERM con80x60
TERM cons25
TERM console
TERM cygwin
TERM dtterm
TERM Eterm
TERM eterm-color
TERM gnome
TERM gnome-256color
TERM jfbterm
TERM konsole
TERM kterm
TERM linux
TERM linux-c
TERM mach-color
TERM mlterm
TERM putty
TERM rxvt
TERM rxvt-256color
TERM rxvt-cygwin
TERM rxvt-cygwin-native
TERM rxvt-unicode
TERM rxvt-unicode-256color
TERM rxvt-unicode256
TERM screen
TERM screen-256color
TERM screen-256color-bce
TERM screen-bce
TERM screen-w
TERM screen.linux
TERM screen.rxvt
TERM terminator
TERM vt100
TERM xterm
TERM xterm-16color
TERM xterm-256color
TERM xterm-88color
TERM xterm-color
TERM xterm-debian
TERM xterm-kitty
# }}}
# vim: ft=dircolors:fdm=marker:et:sw=2:

File diff suppressed because one or more lines are too long

View file

@ -1,169 +0,0 @@
"""Give coloring for file types as in the ls command."""
import functools
import importlib
import importlib.resources
import os
import os.path
import stat
import syslog
import lscolors
import termstr
__version__ = "v2025.07.11"
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)
def get_ls_color_codes():
with importlib.resources.open_text(lscolors, "LS_COLORS.sh") as lscolors_file:
codes = lscolors_file.readline().strip()[len("LS_COLORS='"):-len("';")]
return _parse_ls_colors(codes)
_LS_COLOR_CODES = get_ls_color_codes()
def _charstyle_of_path(path):
color_code = color_code_for_path(path, _LS_COLOR_CODES)
if color_code is None:
return termstr.CharStyle()
term_text = termstr.ESC + "[" + color_code + "m-"
return termstr.TermStr.from_term(term_text).style[0]
@functools.lru_cache(maxsize=100)
def path_colored(path):
char_style = _charstyle_of_path(path)
if path.startswith("./"):
path = path[2:]
dirname, basename = os.path.split(path)
if dirname == "":
return termstr.TermStr(basename, char_style)
else:
dirname = dirname + os.path.sep
dir_style = _charstyle_of_path(os.path.sep)
parts = [termstr.TermStr(part, dir_style) for part in dirname.split(os.path.sep)]
path_sep = termstr.TermStr(os.path.sep, fg_color=termstr.Color.grey_100)
dir_name = termstr.join(path_sep, parts)
return dir_name + termstr.TermStr(basename, char_style)

View file

@ -1,32 +0,0 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.metadata]
allow-direct-references = true
[project]
name = "lscolors"
version = "v2025.07.11"
description = "Give coloring for file types as in the ls command."
authors = [
{ name = "Andrew Hamilton", email = "and_hamilton@yahoo.com" },
]
requires-python = ">=3.11"
dependencies = [
"termstr==2026.7.27",
]
[tool.uv.sources]
termstr = { git = "https://forgejo.tail556789.ts.net/ahamilton/termstr.git" }
[project.urls]
Homepage = "https://github.com/ahamilton/eris"
[tool.hatch.version]
path = "lscolors/__init__.py"
[tool.hatch.build.targets.sdist]
include = [
"/lscolors",
]

View file

@ -1,239 +0,0 @@
#!/usr/bin/env python3.11
import os
import os.path
import shutil
import socket
import stat
import subprocess
import tempfile
import unittest
import lscolors
class TempDirTestCase(unittest.TestCase):
def setUp(self):
self.temp_dir = tempfile.mkdtemp()
def tearDown(self):
shutil.rmtree(self.temp_dir)
class ParseLsColorsTestCase(unittest.TestCase):
def test_parse_ls_colors(self):
self.assertRaises(AssertionError, lscolors._parse_ls_colors, "")
self.assertRaises(AssertionError, lscolors._parse_ls_colors, "::")
self.assertEqual(lscolors._parse_ls_colors("*.awk=38;5;148;1"), {".awk": "38;5;148;1"})
self.assertEqual(lscolors._parse_ls_colors("*.tar.gz=38;5;148;1"),
{".tar.gz": "38;5;148;1"})
self.assertEqual(lscolors._parse_ls_colors("*.awk=38;5;148;1:di=38;5;30"),
{".awk": "38;5;148;1", "di": "38;5;30"})
class ColorKeyForFileTestCase(TempDirTestCase):
COLOR_CODES = {lscolors.OTHER_WRITABLE_KEY: "other writable",
lscolors.EXECUTABLE_KEY: "executable", lscolors.ORPHAN_KEY: "orphan",
lscolors.SETGUID_KEY: "setguid", lscolors.SETUID_KEY: "setuid",
lscolors.STICKY_KEY: "sticky",
lscolors.STICKY_OTHER_WRITABLE_KEY: "sticky other writable",
lscolors.MULTI_HARDLINK_KEY: "multi hardlink",
lscolors.CHARACTER_DEVICE_KEY: "character device",
lscolors.BLOCK_DEVICE_KEY: "block device"}
def test_color_key_for_path_without_extension(self):
executable_path = os.path.join(self.temp_dir, "foo")
open(executable_path, "w").close()
self.assertEqual(lscolors.color_key_for_path(executable_path, self.COLOR_CODES),
lscolors.FILE_KEY)
def test_color_key_for_path_with_extension(self):
awk_path = os.path.join(self.temp_dir, "test.awk")
open(awk_path, "w").close()
self.assertEqual(lscolors.color_key_for_path(awk_path, self.COLOR_CODES),
lscolors.FILE_KEY)
def test_color_key_for_path_with_double_extension(self):
tar_gz_path = os.path.join(self.temp_dir, "test.tar.gz")
open(tar_gz_path, "w").close()
self.assertEqual(lscolors.color_key_for_path(tar_gz_path, self.COLOR_CODES),
lscolors.FILE_KEY)
def test_color_code_for_directory(self):
self.assertEqual(lscolors.color_key_for_path(self.temp_dir, self.COLOR_CODES),
lscolors.DIRECTORY_KEY)
def test_color_code_for_directory_thats_other_writable(self):
mode = os.stat(self.temp_dir).st_mode
os.chmod(self.temp_dir, mode | stat.S_IWOTH)
self.assertEqual(lscolors.color_key_for_path(self.temp_dir, self.COLOR_CODES),
lscolors.OTHER_WRITABLE_KEY)
def test_color_code_for_executable(self):
executable_path = os.path.join(self.temp_dir, "a")
open(executable_path, "w").close()
os.chmod(executable_path, stat.S_IEXEC)
self.assertEqual(lscolors.color_key_for_path(executable_path, self.COLOR_CODES),
lscolors.EXECUTABLE_KEY)
def test_color_code_for_executable_with_extension(self):
executable_path = os.path.join(self.temp_dir, "a.awk")
open(executable_path, "w").close()
os.chmod(executable_path, stat.S_IEXEC)
self.assertEqual(lscolors.color_key_for_path(executable_path, self.COLOR_CODES),
lscolors.EXECUTABLE_KEY)
def test_color_code_for_setguid(self):
setguid_path = os.path.join(self.temp_dir, "a")
open(setguid_path, "w").close()
os.chmod(setguid_path, stat.S_ISGID)
self.assertEqual(lscolors.color_key_for_path(setguid_path, self.COLOR_CODES),
lscolors.SETGUID_KEY)
def test_color_code_for_setuid(self):
setuid_path = os.path.join(self.temp_dir, "a")
open(setuid_path, "w").close()
os.chmod(setuid_path, stat.S_ISUID)
self.assertEqual(lscolors.color_key_for_path(setuid_path, self.COLOR_CODES),
lscolors.SETUID_KEY)
def test_color_code_for_broken_symlink(self):
symlink_path = os.path.join(self.temp_dir, "b")
os.symlink(os.path.join(self.temp_dir, "a"), symlink_path)
self.assertEqual(lscolors.color_key_for_path(symlink_path, self.COLOR_CODES),
lscolors.ORPHAN_KEY)
def test_color_code_for_good_symlink(self):
symlink_path = os.path.join(self.temp_dir, "b")
awk_path = os.path.join(self.temp_dir, "test.awk")
open(awk_path, "w").close()
os.symlink(awk_path, symlink_path)
self.assertEqual(lscolors.color_key_for_path(symlink_path, self.COLOR_CODES),
lscolors.FILE_KEY)
def test_color_code_for_pipe(self):
pipe_path = os.path.join(self.temp_dir, "a")
os.mkfifo(pipe_path)
self.assertEqual(lscolors.color_key_for_path(pipe_path, self.COLOR_CODES),
lscolors.PIPE_KEY)
def test_color_code_for_character_device(self):
character_device_path = "/dev/tty"
self.assertEqual(lscolors.color_key_for_path(character_device_path, self.COLOR_CODES),
lscolors.CHARACTER_DEVICE_KEY)
def test_color_code_for_sticky_directory(self):
mode = os.stat(self.temp_dir).st_mode
os.chmod(self.temp_dir, mode | stat.S_ISVTX)
self.assertEqual(lscolors.color_key_for_path(self.temp_dir, self.COLOR_CODES),
lscolors.STICKY_KEY)
def test_color_code_for_sticky_and_other_writable(self):
mode = os.stat(self.temp_dir).st_mode
os.chmod(self.temp_dir, mode | stat.S_ISVTX | stat.S_IWOTH)
self.assertEqual(lscolors.color_key_for_path(self.temp_dir, self.COLOR_CODES),
lscolors.STICKY_OTHER_WRITABLE_KEY)
def test_color_code_for_socket(self):
socket_path = os.path.join(self.temp_dir, "socket")
socket_ = socket.socket(socket.AF_UNIX)
socket_.bind(socket_path)
try:
self.assertEqual(lscolors.color_key_for_path(socket_path, self.COLOR_CODES),
lscolors.SOCKET_KEY)
finally:
socket_.close()
def test_color_code_for_missing_file(self):
missing_path = os.path.join(self.temp_dir, "a")
self.assertEqual(lscolors.color_key_for_path(missing_path, self.COLOR_CODES),
lscolors.MISSING_KEY)
def test_color_code_for_multi_hardlink(self):
a_path = os.path.join(self.temp_dir, "a")
open(a_path, "w").close()
b_path = os.path.join(self.temp_dir, "b")
os.link(a_path, b_path)
self.assertEqual(lscolors.color_key_for_path(a_path, self.COLOR_CODES),
lscolors.MULTI_HARDLINK_KEY)
class ColorCodeForFileTestCase(TempDirTestCase):
AWK_COLOR = "awk color"
TAR_GZ_COLOR = "tar gz color"
COLOR_CODES = {".awk": AWK_COLOR, ".tar.gz": TAR_GZ_COLOR}
def test_color_code_for_path_without_extension(self):
file_path = os.path.join(self.temp_dir, "foo")
open(file_path, "w").close()
self.assertEqual(lscolors.color_code_for_path(file_path, {"fi": "file color"}),
"file color")
def test_color_code_for_path_with_extension(self):
awk_path = os.path.join(self.temp_dir, "test.awk")
open(awk_path, "w").close()
self.assertEqual(lscolors.color_code_for_path(awk_path, self.COLOR_CODES),
self.AWK_COLOR)
def test_color_code_for_path_with_double_extension(self):
tar_gz_path = os.path.join(self.temp_dir, "test.tar.gz")
open(tar_gz_path, "w").close()
self.assertEqual(lscolors.color_code_for_path(tar_gz_path, self.COLOR_CODES),
self.TAR_GZ_COLOR)
def _parse_ls_line(line):
parts = line.split("\x1b[")
if len(parts) == 1:
return (None, line)
for part in parts:
end_color_code = part.find("m")
if end_color_code < (len(part) - 1):
return tuple(part.split("m", 1))
class ParseLsLineTestCase(unittest.TestCase):
def test_parse_ls_line(self):
self.assertEqual(_parse_ls_line("\x1b[0m\x1b[38;5;254m\x1b[m\x1b[38;5;30mhello\x1b[0m\n"),
("38;5;30", "hello"))
def test_against_ls(root_path, environment):
process = subprocess.run(["ls", "--color=always", "-R", root_path],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=environment)
color_codes = lscolors.get_color_codes(environment)
for line in process.stdout.splitlines():
line = line.strip()
if line == "":
continue
if line.endswith(":"):
current_directory = line[:-1]
continue
ls_color_code, filename = _parse_ls_line(line)
path = os.path.join(current_directory, filename)
if os.path.exists(path): # Some paths are already gone. e.g. in /proc
color_code = lscolors.color_code_for_path(path, color_codes)
if color_code != ls_color_code:
print(path, repr(color_code), repr(ls_color_code))
RICH_COLOR_CODES = (
"bd=38;5;68:ca=38;5;17:cd=38;5;113;1:di=38;5;30:do=38;5;127:ex=38;5;166;1:pi=38;5;126:"
"fi=38;5;253:ln=target:mh=38;5;220;1:no=38;5;254:or=48;5;196;38;5;232;1:ow=38;5;33;1:"
"sg=38;5;137;1:su=38;5;137:so=38;5;197:st=48;5;235;38;5;118;1:tw=48;5;235;38;5;139;1:"
"*.BAT=38;5;108:*.PL=38;5;160:*.asm=38;5;240;1:*.awk=38;5;148;1:*.bash=38;5;173:"
"*.bat=38;5;108:*.c=38;5;110:*.cfg=1:*.coffee=38;5;94;1:*.conf=1:*.cpp=38;5;24;1:"
"*.cs=38;5;74;1:*.css=38;5;91:*.csv=38;5;78:*.diff=48;5;197;38;5;232:*.enc=38;5;192;3")
if __name__ == "__main__":
unittest.main()
# root_path = "/"
# test_against_ls(root_path, {"LS_COLORS": RICH_COLOR_CODES})
# test_against_ls(root_path, {}) # Test using default colors

View file

@ -12,7 +12,7 @@ requires-python = ">=3.11"
dependencies = [
"docopt-ng==0.9.0",
"fill3==2026.7.22",
"lscolors @ git+https://forgejo.tail556789.ts.net/ahamilton/eris@v2025.07.11#subdirectory=lscolors",
"lscolors==2026.7.27",
"pexpect==4.9.0",
"pillow==11.2.1",
"pygments==2.19.1",
@ -21,10 +21,7 @@ dependencies = [
[tool.uv.sources]
fill3 = { git = "https://forgejo.tail556789.ts.net/ahamilton/fill3.git" }
lscolors = { workspace = true }
[tool.uv.workspace]
members = ["lscolors"]
lscolors = { git = "https://forgejo.tail556789.ts.net/ahamilton/lscolors.git" }
[project.scripts]
eris = "eris.__main__:entry_point"