-
Notifications
You must be signed in to change notification settings - Fork 0
/
colorstring.py
executable file
·1081 lines (896 loc) · 38 KB
/
colorstring.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python3
#
# colorstring.py: Easy access to ANSI terminal color.
# 2018-08-29: Converted by perl2python, by Steven J. DeRose.
# Original Perl version written <2006-10-04, by Steven J. DeRose.
#
import sys
import os
import re
import argparse
from subprocess import check_output, CalledProcessError
import logging
from ColorManager import ColorManager
lg = logging.getLogger()
cm = ColorManager()
args = None
__metadata__ = {
"title" : "colorstring",
"description" : "Easy access to ANSI terminal color.",
"rightsHolder" : "Steven J. DeRose",
"creator" : "http://viaf.org/viaf/50334488",
"type" : "http://purl.org/dc/dcmitype/Software",
"language" : "Python 3.7",
"created" : "2018-08-29",
"modified" : "2022-12-07",
"publisher" : "http://github.com/sderose",
"license" : "https://creativecommons.org/licenses/by-sa/3.0/"
}
__version__ = __metadata__['modified']
descr = """
=Usage=
colorstring.py [options] [text]
* Issue a message to stdout in a given color:
colorstring.py -c red/white/bold "Hello, world"
* Same but issue to stderr (using '--warn' or '-w'):
colorstring.py -w -c red/white/ital "Hello, world"
* Read stdin and colorize lines in a cycle of red, white, and blinking blue
(though many terminals do not support "blink"):
colorstring.py --all -c red -c white -c blue/blink
* Return the escape sequence to switch an ANSI terminal to the given color:
colorstring.py --ansi -c white/black/bold
* Return that same escape string, but with the ESCAPE in the form
that goes in a bash prompt string (in the second case, also have bash assign it).
This also include the special
sequences (\\[...\\]) to make bash not count the escapes into line-length:
colorstring -ps -c Cyan
PS1=`colorstring -bps -c Cyan` Hello, `colorstring -bps -c green`"world ==>"
* Same, but the form needed to embed in a zsh prompt string (incomplete):
colorstring -zps -c Cyan
PS1=`colorstring -zps -c Cyan` Hello, `colorstring -zps -c green`"world ==>"
* Show a neat chart of foreground/background samples (see also `--list`):
colorstring.py --table
* Examine or change environment variable LSCOLORS, which is used to
control how `ls` colorizes various file types. This is similar to
Gnu 'dircolors' (which is not available
by default on MacOS, BSD, etc.), and shell function from my `ShellSetup`:
...
==Color naming==
Colors are specified as a named
foreground color, background color, and/or text effect, for example:
red/yellow/bold
Color names (add 30 for foreground, 40 for background) include:
NAME ANSI CODE
"black" 0
"red" 1
"green" 2
"yellow" 3
"blue" 4
"magenta" 5
"cyan" 6
"white" 7
"default" 9
Effect names (only a few terminals know them all) include:
NAME ANSI CODE
"bold" 1 aka 'bright'
"faint" 2
"italic" 3 (rare)
"underline" 4 aka 'ul'
"blink" 5
"fblink" 6? aka 'fastblink' (rare)
"reverse" 7 aka 'inverse'
"concealed" 8 aka 'invisible' or 'hidden'
"strike" 9 aka 'strikethru' or 'strikethrough'
"plain" 0 (can be used to express "no special effect")
For more details on the naming conventions (shared by many of my utilities),
see `colorNames.md` or `ColorManager.py`. There is also a Perl version.
==Color with emacs==
To get `emacs` (such as with `M-x shell') to handle color escapes, add
to your `.emacs` or other init file:
(add-hook 'shell-mode-hook 'ansi-color-for-comint-mode-on)
=Options=
* ''--all''
Show all of stdin in the `colorname`.
With `--all`, you can specify multiple colornames to alternate, or
specify the predefined patterns 'usa', 'christmas', 'italy', or 'rainbow'.
* ''--help-ls''
Show the reserved file-type-names that can be used to set file
colors for the *nix `ls` command, based on
file ''types'' (rather than filename-expressions).
These names can be used in the `LSCOLORS` or `LS_COLORS` environment variable.
For example, 'ex' can be used to set the color for executable files.
* ''--list''
Show all known combination of colors and effects.
Most terminal programs do not support all effects.
See also `--breakLines`, `--table`, and `--xterm256`.
With `--xterm256` and `--breakLines`, a line will be shown for each color number
0...255, including a word shown in
the foreground color on the default background,
the foreground color on a white background,
the background color with the default foreground,
the background color with white foreground.
* ''--lscolorset'' `oldcolor` `newcolor`
(that's an el at the beginning, not one or eye)
Replace all the environment-variable-specified mappings
for a given color, with a new color (see `--lslist` option for a description
of a typical default mapping).
* ''--lsget'' `fileExpr`
Figure out what color `ls` will use to
display a given file's name. If you give an expression such as '*.html' that's
ok, too.
* ''--lslist''
List how `ls` colors are set up, organized by color
(see also the `dircolors` command).
The two-letter codes before some descriptions below, are
the mnemonics `dircolors` uses to refer to various file classes (otherwise,
it assigns color by file extensions).
See `--lsset` for a slightly easier way to modify `dircolors`.
In general, the default settings are (on my system -- BSD and Linux are
significantly different):
** Red: Archives (tar, jar, zip, etc.)
** Green: ex: Executables
** Blue: di: Directories
** Magenta: so, do: Pictures, video, sockets,...
** Cyan: (Mac but not Ubuntu?):
Audio files (mp3, midi, flac, wav, etc.)
** Bold Cyan: ln: Symbolic links
** Black/Red: ca: Capability files
** Black/Yellow: sg: SetGID files
** White/Red: su: SetUID files
** White/Blue: st: Dir with sticky bit set (+t), hard links
** Black/Green: tw: Other sticky writable files
** Blue/Green: ow: Other writable files
** Yellow/Black: pi: Pipes
** Bold Yellow/black: bd, cd: Block and character device files
** Bold Red/Black: or: Orphan files (e.g., broken symbolic links)
* ''--lsset'' `fileExpr`
Return a modified `ls` color setup that assigns a
named color to files that match the given `fileExpr` (the color name will
be translated to a numeric code).
The `fileExpr` can be a glob (usually of
the form '*.ext' to distinguish files by their extensions), or a 2-letter
reserved code to distinguish files by some property (see above);
see also `--help-ls`.
If there is already an assignment for exactly
the given `fileExpr`, it will be replaced. For example:
The caller should store this in environment variable `LS_COLORS`), e.g.:
export LS_COLORS=`colorstring --lsset di red`
=Known bugs and limitations=
Different *nix versions differ between LSCOLORS and LS_COLORS.
Not yet as thoroughly tested as the Perl predecessor, esp. --msg and --warn
and just getting a raw code.
You can't set more than one effect (such as blink, bold, inverse, hidden,
and underline) at once.
The color-name lookup used with the `--lslist` option can't handle
simultaneous property, foreground, and background settings unless the
environment variable (`LS_COLORS` or `LSCOLORS`)
specifies them in increasing numeric order (which seems typical).
For now, such unmatched entries print the color name as '?'.
Of course, for any of this to work, the terminal or terminal program
(and the user!) have to support color,
and environment variable `TERM` must say so (e.g., be set to 'xterm-color').
`TERM=xterm-256color' is not supported except for `--list` via `--xterm256`.
Does not have a feature to avoid low-contrast pairs, particularly
in relation to the default terminal background color (which is hard to
determine in the first place, though see `xtermcontrol`, `tput`,
and my `getBGColor` command.
Not entirely in sync with all related commands.
=LSCOLORS / LS_COLORS information=
These environment variables determine what color `ls` uses
for each of its distinct file-types. How this works differs between Eunices.
You can display (and decode) the settings (hopefully regardless of
platform) with:
colorstring.py --lslist
==LSCOLORS On MacOSX / BSD==
On MacOSX, to get `ls` to colorize specify `ls -G`, or define
environment variable `CLICOLOR`.
`ls` disables colorizing when output is not going to a terminal,
unless you set `CLICOLOR_FORCE`.
To change color choices on MacOSX / BSD, set environment variable `LSCOLORS`.
It should have a length of 22:
a list of 11 pairs of foreground+background. The pairs assign colors to
these 11 catgories of file:
1. directory
2. symbolic link
3. socket
4. pipe
5. executable
6. block special
7. character special
8. executable with setuid bit set
9. executable with setgid bit set
10. directory writable to others, with sticky bit
11. directory writable to others, w/o sticky bit
The function `getFileCategory()` will return the applicable number (1 to 11)
for a given path.
The default LSCOLORS setting is likely "", which is treated
as "exfxcxdxbxegedabagacad".
The color codes are:
a black
b red
c green
d brown
e blue
f magenta
g cyan
h light grey
x default foreground or background
If capitalized, the corresponding bold color is used.
The Gnu `dircolors` command makes setting up colors easier, but is
not available by default on MacOSX. You may be able to add it as
'gdircolors', with:
brew install coreutils
==LS_COLORS On Linux (note the underscore)==
On Linux, specify `ls --color=auto` (or 'always' or 'never').
Environment variable `CLICOLOR` does not seem to be supported.
With `auto`, color is only used when output is going to a terminal.
Gnu coreutils has `dircolor` to help set up LS_COLORS.
=Related commands=
`sjdUtils.pm` -- provides colorized messages, and functions to get the same
kinds of escapes as here
(`sjdUtils.pm` does not use the `colorstring` command).
`sjdUtils.py` -- Python version of `sjdUtils.pm`.
`alogging.py`, `colorstring` (Perl), `hilite` (Perl).
`colorNames.md` -- documentation about color names and usage.
`ShellSetup/setupColors` -- defines some shell functions for dealing
with $LSCOLORS.
`pygmentize` -- a tool to syntax-color Python code.
See also [https://unix.stackexchange.com/questions/267361/].
Gnu `source-highlight`, `lesspipe.sh`, `dircolors`, and others are similar.
==Related *nix utilities==
Several *nix commands have a `--color=auto' option: `ls`, `grep`, etc.
`grc` and `logtool` can colorize log files.
`info terminfo` has more information about terminal colors.
terminfo fields can be obtained via the `tput` command.
`terminfo2xml` collects it all and displays it, in XML or tabular form.
`tput colors` tells you various terminal control sequences, and the
current values of the corresponding settings. For example, these:
tput bold | od
tput setaf 4 | od
give these:
0000000 1b 5b 31 6d
033 [ 1 m
0000000 1b 5b 33 34 6d
033 [ 3 4 m
which are:
ESC [ 1 m
ESC [ 3 4 m
`locale charmap` tells what character encoding you're set for.
`colortest` displays a terminal color chart (`apt-get`).
Pip `colorama` provides similar output-coloring features.
To determine an `xterm`'s background color, see
[http://stackoverflow.com/questions/2507337/]. One way to get it is:
\\e]11;?\\a
Xterm-compatible terminals should reply with the same sequence, with "?"
replaced by an X11 colorspec, e.g., rgb:0000/0000/0000 for black.
=References=
[http://http://push.cx/2008/256-color-xterms-in-ubuntu]
=History=
* Written <2006-10-04, Steven J. DeRose.
* 2008-02-11 sjd: Add `--perl`, `perl -w`.
* 2008-09-03 sjd: BSD. Improve doc, error-checking, fix bug in `-all`.
* 2010-03-28 sjd: perldoc. Add `\\[\\]` to `-ps`.
* 2010-09-20ff sjd: Cleanup. Add `--color`; `ls` and `dircolors` support.
Simplify numeric handling of codes. Support color combinations. Add `-setenv`.
Change 'fg2_' prefix to 'bold_' and factor out of code.
* 2013-06-11: Add `--xterm256`, but just for `--list`.
* 2013-06-27: Add `--table`. Ditch `fg2_` and `b_` prefixes.
* 2014-07-09: Clean up doc. Add `--python`. Clean up `--perl`. fix `--list`.
* 2015-02-04: Support rest of effects beyond bold.
* 2015-08-25: Start syncing color-refs with `sjdUtils.pm`.
* 2016-01-01: Get rid of extraneous final newline with `-m`.
* 2016-07-21: Merge doc on color names w/ sjdUtils.p[my], etc.
* 2016-10-25: Clean up to integrate w/ ColorManager. Change names.
Debug new (hashless) way of doing colors.
* ''2018-08-29: Port to Python.'' Math alphabet stuff to `mathAlphanumerics.py`.
Switch to depend on ColorManager.py. Reconcile names, no more `fg_` and `bg_`.
* 2018-10-22: Fix minor bugs. Refactor. Add `args.txt`, use `ColorManager` more.
* 2020-10-02: Move effects to end to sync with `colorNames.md`.
* 2021-05-21: Add lots of info on BSD vs. Linux ls colors. Add `--showLSCOLORS`.
* 2021-07-01: Split LSCOLORS-related stuff into separate class. Add bsd/linux
distinction.
* 2022-12-07: Reorganize options to make more sense.
Allow 'light grey' as synonym for 'white'. cf zsh prompt specs.
Fix wrong args to ColorManager.colorize(). Support --table + --effects.
=To do=
* Support numeric colors, too.
* Move in the predefined patterns from Perl version.
* Add alternate setup to tag stuff with HTML instead.
* Consider integrating with mappings from `mathAlphanumerics.py`.
* Offer alternate color sets for `setenv` for light vs. dark backgrounds.
* `lsset` should support replacing all mappings for a given color.
Perhaps move LSColors class to a separate package?
* Use `rgb.txt` with xterm-256color to pick by name?
* Pull in the colorName to HTML mapping from Perl hilite::getStartTag.
* Expand the test/sample feature (and maybe general support?) to cover
other ANSI esacpes, such as:
** Double-Height Letters
** Single/Double width line
** Privacy message
=Rights=
Copyright 2006-10-04 by Steven J. DeRose. This work is licensed under a
Creative Commons Attribution-Share-alike 3.0 unported license.
For further information on this license, see
[https://creativecommons.org/licenses/by-sa/3.0].
For the most recent version, see [http://www.derose.net/steve/utilities]
or [https://github.com/sderose].
=Options=
"""
if ("lscolorVarName" in os.environ):
theEnvVarName = os.environ("lscolorVarName")
elif ("OSTYPE" in os.environ and os.environ["OSTYPE"] == "darwin"):
theEnvVarName = "LSCOLORS"
else:
theEnvVarName = "LS_COLORS"
boldToken = "bold"
blinkToken = "blink"
inverseToken = "inverse"
ulToken = "ul"
esc = chr(27)
# Table of basic color names. +30 for foreground, +40 for background
atomicColors = cm.colorNumbers
if (len(atomicColors) != 9):
raise ValueError("Bad color table from ColorManager!")
effectsOn = cm.effectNumbers
colorTable = {} # Map from named colors to codes (switch to just use ColorManager)
lsColors = []
def cseq(name):
return cm.getColorString(name)
###############################################################################
#
def colorizeString(msg, fg:str="", bg:str="", effect:str=""):
"""Basically the same as ColorManager.colorize().
"""
#print("calling cm.colorize with '%s', '%s', '%s'." % (fg, bg, effect))
return cm.colorize(msg, fg=fg, bg=bg, effect=effect)
def colorSeq(name):
if (isinstance(name, list)):
lg.error("Multi-color not yet supported")
name = name[0]
try:
cs = cm.getColorString(name)
except TypeError as e:
lg.error("Error in ColorManager: %s", e)
cs = ""
return cs
##############################################################################
# TODO: Move lscolors support to other package; add set ability; add direct
# mapping to ColorManager colors, from booleans on stat values?
#
class LSColors:
"""Do some support for `ls` colorizing, for various *nix flavors.
"""
import stat
###############################################################################
# Define explanations for the non-file-glob cases used by LS_COLORS (Linux)
#
linuxLSSpecials = {
"bd" : "BLK Block device driver",
"ca" : "CAPABILITY File with capability",
"cd" : "CHR Character device driver",
"di" : "DIR Directories",
"do" : "DOOR Door (eh?)",
"ex" : "EXEC Executable files",
"??" : "FILE Other file (normally not set)",
"hl" : "HARDLINK Hard link",
"ln" : "LINK Symbolic link (can use 'target' color)",
"or" : "ORPHAN Broken symbolic link, etc.",
"ow" : "OTHER_WRITABLE Other-writable, non-sticky file",
"pi" : "FIFO Pipe",
"rs" : "RESET Reset to default color",
"sg" : "SETGID SetGID",
"so" : "SOCK Socket?",
"st" : "STICKY Directory with sticky bit set (+t)",
"su" : "SETUID SetUID",
"tw" : "STICKY_OTHER_WRITABLE Sticky other writable file",
}
bsdLSSpecials = {
"0" : "regular file", # FILE?
"1" : "directory", # DIR
"2" : "symbolic link", # LINK
"3" : "socket", # SOCK
"4" : "pipe", # FIFO
"5" : "executable", # EXEC
"6" : "block special", # BLK
"7" : "character special", # CHR
"8" : "executable with setuid bit set", #
"9" : "executable with setgid bit set", #
"10" : "directory writable to others, with sticky bit", #
"11" : "directory writable to others, without sticky bit", #
}
bsdColorMap = {
"a": "black",
"b": "red",
"c": "green",
"d": "brown",
"e": "blue",
"f": "magenta",
"g": "cyan",
"h": "light grey",
"A": "bold black", # usually shows up as dark grey",
"B": "bold red",
"C": "bold green",
"D": "bold brown", # usually shows up as yellow",
"E": "bold blue",
"F": "bold magenta",
"G": "bold cyan",
"H": "bold light grey", # looks like bright white",
"x": "default", # foreground or background",
}
@staticmethod
def bsdShowLSColors() -> None:
pairs = LSColors.bsdParseLSColors()
if (pairs):
for i, pair in enumerate(pairs):
cname = "%s/%s" % (LSColors.bsdColorMap[pair[0]], LSColors.bsdColorMap[pair[1]])
print("%2d: %-24s %s" % (i, cname, LSColors.bsdLSSpecials[i]))
return
@staticmethod
def bsdParseLSColors() -> list:
"""PArse a 22-char LSCOLORS BSD value into 11 tuples, each with a
single-char color-codes for foreground and background.
TODO: Should this return one-char codes or names?
"""
if ('LSCOLORS' not in os.environ):
if ('LS_COLORS' in os.environ):
lg.error("No $LSCOLORS in env, but $LS_COLORS is. ")
else:
lg.warning("No $LSCOLORS in env.")
return None
lsc = os.environ['LSCOLORS']
if (len(lsc) != 22):
lg.error("Expected 22 chars in LSCOLORS, but got %d: '%s'.", len(lsc), lsc)
return
colorPairList = []
for codeStart in range(len(lsc)):
fgbg = ( lsc[codeStart], lsc[codeStart+1] )
colorPairList.append( fgbg )
return colorPairList
# Take an n;m... string as used in environment variable LS_COLORS (Linux),
# and try to look up what it means.
# Normalizes to increasing (numeric) order.
#
@staticmethod
def getColorName(code): ### OBSOLETE, FIX
code = re.sub(r'0+(\d)', "\\1", code) # Strip any leading zeros
code2 = ';;'.join(sorted(re.split(';', code)))
lg.log(logging.INFO-1, "normalized order: '%s' -> '%s'", str(code), str(code2))
code = code2
code = "[" + code + "m"
for k in (colorTable):
if (colorSeq(k) == code): return(k)
lg.log(logging.INFO-1, "Couldn't find '%s' in color table.\n", code)
return("?")
@staticmethod
def getFileCategory(path:str) -> None:
"""The category to be used for LSCOLORS on BSD, MacOSX, and similar.
There doesn't seem to be a category for regular files? I guess they
just go in the default color?
@return Category number 1-11, or 0 for regular, or -1 on fail.
TODO: Test and verify vs. reality....
"""
if (not os.path.exists(path)): return -1
st = os.stat(path)
if (st.S_ISLNK): return 2 # symbolic link
if (st.S_ISSOCK): return 3 # socket
if (st.S_ISFIFO): return 4 # pipe
if (st.S_ISBLK): return 6 # block special
if (st.S_ISCHR): return 7 # character special
if (st.S_ISDIR):
if (st.S_ISWOTH and st.S_ISVTX): return 10 # dir w to others, +sticky bit
if (st.S_ISWOTH and not st.S_ISVTX): return 11 # dir w to others, -sticky bit
return 1 # directory
if (LSColors.isExecutable(st)):
if (st.S_ISUID): return 8 # executable with setuid bit set
if (st.S_ISGID): return 9 # executable with setgid bit set
return 5 # executable
assert st.s_ISREG
return 0 # (0 for regular file)
@staticmethod
def isExecutable(st:os.stat_result) -> bool:
"""Return whether (for purposes of getFileCategory()) the item is
executable.
TODO: Check exactly what ls does -- does executable mean by current user,
or current user qua user or qua group or qua other? Or...?
"""
return st.S_IXUSR or st.S_IXGRP or st.S_IXRXO
@staticmethod
def setupDircolors():
"""'dircolors' is a Linux /GNU corutils command that helps set bash colors
for the ls command, via environment variable 'LS_COLORS'.
It is not typically available on BSD/MacOSX.
"""
global lsColors
try:
lsColors = re.split(r':', check_output('dircolors'))
except CalledProcessError as e:
sys.stderr.write("'dircolors' failed. OS dependency?\n %s" % (e))
lsColors[0] = re.sub(r'LS_COLORS=', '', lsColors[0])
lsColors.pop() # "export LS_COLORS"
lsColors[-1] = re.sub(r';;', '', lsColors[-1])
@staticmethod
def helpLSColors():
print("The LS_COLORS keys are (see also dircolors --print-database):")
lssp = sorted(LSColors.linuxLSSpecials.keys())
for sp in (lssp):
print(" sp\t" + LSColors.linuxLSSpecials[sp])
sys.exit()
@staticmethod
def doLsList():
"""Display a list of all the LS_COLORS or LSCOLORS settings.
Lots of OS differences here....
See also 'theEnvVarName', set up top.
"""
bsdName = "LSCOLORS"
bsdValue = os.environ[bsdName] if bsdName in os.environ else ""
print("%s (for BSD): '%s'" % (bsdName, bsdValue))
linuxName = "LS_COLORS"
linuxValue = os.environ[linuxName] if linuxName in os.environ else ""
print("%s (for Linux): '%s'" % (linuxName, linuxValue))
LSColors.setupDircolors()
byColor = {}
for lsc in (lsColors):
mat = re.search(r'^(.*)=(.*)', lsc)
#expr = mat.group(1)
colorCode = mat.group(2)
if (byColor[colorCode] is not None):
byColor[colorCode] = ""
byColor[colorCode] += "expr "
print("Colors for 'ls':")
coff = cm.getColorString('default')
for code in (sorted(byColor.keys())):
name = LSColors.getColorName(code)
if (colorSeq(name) is None): con2 = ""
else: con2 = esc + colorSeq(name)
print("%s%s (%s):%s %s" % (con2, code, name, coff, byColor[code]))
@staticmethod
def doLsGet(what:str):
LSColors.setupDircolors()
found = 0
coff = cm.getColorString('default')
for lsc in (lsColors):
if (re.match(what, lsc)):
found += 1
code = lsc = re.sub(r'^.*=', '', lsc)
name = LSColors.getColorName(code)
if (colorSeq(name) is None): con2 = ""
else: con2 = esc + colorSeq(name)
print("%s\t%s (%s%s%s)" % (lsc, code, con2, name, coff))
if (not (found>0)):
print("No LS_COLORS mapping found for '%s'." % (what))
###############################################################################
# For '--xterm56' (unfinished):
# FG codes: '\e38;5;nm' for `n` from 0 to at least 255.
# BG codes: '\e48;5;nm'.
#
def showTable(sampleText:str="Text") -> None:
"""Show a short table of foreground/background combinations, plain and bold.
A column for each bg color, a row for each fg color.
"""
slen = len(sampleText)
rowHeadWidth = 10
rowHeadFormat = "%2d: %-" + str(rowHeadWidth) + "s"
# Make table header row
thead1 = thead2 = " " * (rowHeadWidth + 4)
for cname, cnum in (atomicColors.items()):
thead1 += cname.ljust(slen+1)
thead2 += str(cnum+40).ljust(slen+1)
print(thead1 + "\n" + thead2)
effects = [ "Bold", "Plain" ]
if (args.effects):
effects = [
"plain" , # 0 (can be used to express "no special effect")
"bold" , # 1 aka 'bright'
"faint" , # 2
"italic" , # 3 (rare)
"underline" , # 4 aka 'ul'
"blink" , # 5
"fblink" , # 6? aka 'fastblink' (rare)
"reverse" , # 7 aka 'inverse'
"concealed" , # 8 aka 'invisible' or 'hidden'
"strike" , # 9 aka 'strikethru' or 'strikethrough'
]
for effectName in effects:
print("\nTable of %s foreground colors on all backgrounds:" % (effectName))
print(thead1 + "\n" + thead2)
for i, fgName in enumerate(atomicColors.keys()):
fgNum = 30 + i
if (fgNum == 38): fgNum = 39 # 'default' or 'off'...
buf = rowHeadFormat % (fgNum, fgName)
for bgName in (atomicColors.keys()):
#fullName = fgname + "/" + bgname
if (effectName=='Plain'): effectName = ""
buf += colorizeString(sampleText, fg=fgName, bg=bgName, effect=effectName) + " "
print(buf)
return
def showList() -> None:
if (args.breakLines): nl = "\n"
else: nl = " "
ctable = cm.getColorStrings()
n = 0
for ct in (sorted(ctable.keys())):
print(ctable[ct] + ct + cseq('default'), end=nl)
n += 1
print("\nDone, %d combinations." % (n))
# TODO: Use ColorManager.
#
def setupEffects() -> None:
shortMap = {
"black" : "blk",
"red" : "red",
"green" : "grn",
"yellow" : "yel",
"blue" : "blu",
"magenta" : "mag",
"cyan" : "cyn",
"white" : "wht",
}
effects = sorted(effectsOn.keys())
for effect in range(len(effects)):
print("")
print("******* Colors with " + (effects[effect] or "no") + " effect:")
for fg in (atomicColors):
print("fg: '%s'." % (fg))
buf0 = ""
for bg in (atomicColors):
sample = ' ' + shortMap[fg] + "/" + shortMap[bg] + ' '
if (args.breakLines): sep = "\n"
else: sep = " "
buf0 += colorizeString(sample, fg=fg, bg=bg, effect=effect) + sep
print(buf0)
print("")
def try256() -> None:
end = esc + "[0m"
print("\n******* Foreground and Background xterm256 colors:")
for i in range(256):
e1 = esc + "[38;5;%dm (fg sample) %s\t" % (i, end)
e2 = esc + "[38;5;%dm %c[47m (fg sample) %s\t" % (i, esc, end)
e3 = esc + "[48;5;%dm (bg sample) %s\t" % (i, end)
e4 = esc + "[48;5;%dm %c[37m (bg sample) %s\t" % (i, esc, end)
nl = ""
if (args.breakLines): nl = "\n"
print("i: " + nl + e1 + e2 + e3 + e4)
print("")
def showEffectSamples() -> None:
for e in (sorted(effectsOn.keys())):
print("%-12s '%s'" % (e, colorizeString(args.sampleText, fg="blue", bg="white", effect=e)))
def colorizeStdin() -> None:
reset = colorSeq("default")
bg_reset = colorSeq("/default")
clist = []
for colorName in (args.colors):
if (colorName == "usa"):
clist.append(cseq("red/bold"))
clist.append(cseq("white/bold"))
clist.append(cseq("blue/bold"))
elif (colorName == "christmas"):
clist.append(cseq("red/bold"))
clist.append(cseq("green/bold"))
elif (colorName == "italy"):
clist.append(cseq("red/bold"))
clist.append(cseq("green/bold"))
clist.append(cseq("white/bold"))
elif (colorName == "rainbow"):
clist.append(cseq("red/bold"))
clist.append(cseq("red"))
clist.append(cseq("yellow/bold"))
clist.append(cseq("green/bold"))
clist.append(cseq("blue/bold"))
clist.append(cseq("magenta/bold"))
clist.append(cseq("magenta"))
else:
seq0 = colorSeq(colorName)
if (not seq0):
print("colorstring: Unknown color '%s'." % (colorName))
print("Known: %s" % (" ".join(cm.colorStrings.keys())))
sys.exit(0)
clist.append(seq0)
#warn "Color sequence: " + (" ".join(clist)) + "\n"
n = 0
for rec in sys.stdin.readlines():
rec = rec.strip()
print(esc + clist[n] + rec
+ esc + reset
+ esc + bg_reset)
n += 1
if (n >= len(clist)):
n = 0
return
def outConvert(s:str) -> str:
"""Convert to the desired output syntax.
"""
if (args.lsset != ""):
lg.info("Attempting --lsset for expr '%s'.", args.lsset)
orig = os.environ["LS_COLORS"]
new = orig = re.sub(r'lsset=.*?(:|$)', '', orig)
new = re.sub(r':+$', '', new)
s = re.sub(r'^\[', '', s)
s = re.sub(r'm$', '', s)
s = "new:lsset=" + s
elif (args.lscolorset):
lg.info("Attempting --lscolorset for color '%s'.\n", args.lscolorset)
s = re.sub(r'^\[', '', s)
s = re.sub(r'm$', '', s)
orig = os.environ["LS_COLORS"]
new = orig
new = re.sub(r'=lscolorset(:|$)', '='+s, new)
if (not (args.quiet)):
sys.stderr.write("Changing n LS_COLOR mappings to new color.\n")
new = re.sub(r':+$', '', new)
s = new
elif (args.ansi):
s = "\x1B" + s
elif (args.bps):
s = "\\[\\e" + s + "\\]\n"
elif (args.zps):
tokens = re.split(r"/", s)
sys.stderr.write("--zps not yet supported, sorry.")
if (len(tokens) < 2):
s = "%%F{%s}%s%%f" % (tokens[0], s)
else:
s = "%%F{%s}%%K{%s}%s%%f%%k" % (tokens[0], tokens[1], s)
elif (args.perl):
s = " \\colors[\"colorName\"] = \"\\e" + s + "\n"
elif (args.python):
s = " colors[colorName] = u\"\\x1B\"" + s + "\n"
elif (args.text or args.warn):
s1 = esc + s
s3 = esc + cseq("default") + "\n"
s = s1 + args.msg + s3
if (args.warn):
sys.stderr.write(s1 + args.warn + s3 + "\n")
if (not args.msg):
sys.exit()
else:
s = esc + s
return s
###############################################################################
# Main
#
def processOptions():
try:
from BlockFormatter import BlockFormatter
parser = argparse.ArgumentParser(
description=descr, formatter_class=BlockFormatter)
except ImportError:
parser = argparse.ArgumentParser(description=descr)
# Output color(s) and format choices
#
parser.add_argument("--colors", "-c", type=str, action='append',
help="Color to use (e.g., 'red/white/bold'). Repeat to cycle by line.")
parser.add_argument("--perl", action='store_true',
help="Return Perl code to generate and assign the color string.")
parser.add_argument("--python", action='store_true',
help="Return Python code to generate and assign the color string.")
parser.add_argument("--printStuff", action='store_true',
help="Print out the color string requested.")
parser.add_argument("--ansi", action='store_true',
help="Return a literal ANSI color escape string.")
parser.add_argument("--bps", "--bash", action='store_true',
help="Return a color command escaped as for a Bash prompt string.")
parser.add_argument("--zps", "--zsh", action='store_true',
help="Return a color command in the form for a zsh prompt string.")
# What to actually colorize and where to send it.
#
parser.add_argument("--all", action='store_true',
help="Show all of stdin in the 'colorname'.")
parser.add_argument("--warn", "-w", action='store_true',
help="Send the text to stderr.")
# Lists and charts and such
#
parser.add_argument("--breakLines", action='store_true',
help="With `--list`, put each example on a separate line.")
parser.add_argument("--effects", action='store_true',
help="Show sample of each effect, to see if your terminal supports it.")
parser.add_argument("--helpls", "--help-ls", action='store_true',
help="Show the file-type-names to set file colors for the 'ls' command")
parser.add_argument("--list", action='store_true',
help="Show all combination of colors and effects (use --table for just colors).")
parser.add_argument("--sampleText", type=str, default="Sample", metavar="TXT",
help="Set the text to be displayed with --table. Default: 'Sampler'.")
parser.add_argument("--table", "--chart", action='store_true',
help="""Show the main color combinations as a table. This only includes
the "plain" and "bold" effects, but shows all foreground/background
combinations, along with the color names and numbers.
See also `--breakLines`, `--list`, `--sampleText`, `-v`, and `--xterm256`.""")
# Options related to LS_COLORS / LSCOLORS
#
parser.add_argument("--envPrefix", type=str, default="COLORSTRING", metavar="P",
help="Prefix to name env variables for color names with --setenv.")
parser.add_argument("--lscolorset", type=str,
help="Replace all `LS_COLOR` mappings for a given color, with a new color")
parser.add_argument("--lsget", type=str, default="",
help="""Find what color `ls` will use to display file names.
Provide a sample file to specify a category (see 'man ls', or the -h here).
This requires the 'dirColors' command (mainly available on Linux.""")
parser.add_argument("--lslist", "--showlscolors", action='store_true',
help="List how `ls` colors are set up, organized by color.")
parser.add_argument("--lsset", type=str, default="",
help="Return a modified `ls` color setup (see above).")
parser.add_argument("--setenv", "--envset", action='store_true',
help="""Returns a (long) string you can
use to set a lot of environment variables, to hold the required escapes to
set given colors. The variable names are 'COLORSTRING_' plus the color names
you can give to this script (but you can change the prefix using `--envPrefix`.""")
# Miscellaneous options
#
parser.add_argument("--quiet", "-q", action='store_true',
help='Suppress most messages.')
parser.add_argument(
"--verbose", "-v", action='count', default=0,
help='Add more messages (repeatable).')
parser.add_argument(
"--version", action='version', version=__version__,
help='Display version information, then exit.')
parser.add_argument("--xterm256", action='store_true',
help="Enable the 256-color set supported by TERM=xterm-256color.")