diff --git a/.gitignore b/.gitignore
index c2a9c3f..9839280 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,162 +1,4 @@
-## Python gitignore
-# Byte-compiled / optimized / DLL files
-__pycache__/
-*.py[cod]
-*$py.class
-
-# C extensions
-*.so
-
-# Distribution / packaging
-.Python
+*.pyc
build/
-develop-eggs/
dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-wheels/
-*.egg-info/
-.installed.cfg
-*.egg
-MANIFEST
-
-# PyInstaller
-# Usually these files are written by a python script from a template
-# before PyInstaller builds the exe, so as to inject date/other infos into it.
-*.manifest
-*.spec
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.coverage
-.coverage.*
-.cache
-nosetests.xml
-coverage.xml
-*.cover
-.hypothesis/
-.pytest_cache/
-
-# Translations
-*.mo
-*.pot
-
-# Django stuff:
-*.log
-local_settings.py
-db.sqlite3
-
-# Flask stuff:
-instance/
-.webassets-cache
-
-# Scrapy stuff:
-.scrapy
-
-# Sphinx documentation
-docs/_build/
-
-# PyBuilder
-target/
-
-# Jupyter Notebook
-.ipynb_checkpoints
-
-# pyenv
-.python-version
-
-# celery beat schedule file
-celerybeat-schedule
-
-# SageMath parsed files
-*.sage.py
-
-# Environments
-.env
-.venv
-env/
-venv/
-ENV/
-env.bak/
-venv.bak/
-
-# Spyder project settings
-.spyderproject
-.spyproject
-
-# Rope project settings
-.ropeproject
-
-# mkdocs documentation
-/site
-
-# mypy
-.mypy_cache/
-
-## Jetbrains
-# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
-# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
-
-# User-specific stuff
-.idea/**/workspace.xml
-.idea/**/tasks.xml
-.idea/**/usage.statistics.xml
-.idea/**/dictionaries
-.idea/**/shelf
-
-# Sensitive or high-churn files
-.idea/**/dataSources/
-.idea/**/dataSources.ids
-.idea/**/dataSources.local.xml
-.idea/**/sqlDataSources.xml
-.idea/**/dynamic.xml
-.idea/**/uiDesigner.xml
-.idea/**/dbnavigator.xml
-
-# Gradle
-.idea/**/gradle.xml
-.idea/**/libraries
-
-# Mongo Explorer plugin
-.idea/**/mongoSettings.xml
-
-# Editor-based Rest Client
-.idea/httpRequests
-
-# SonarLint
-.idea/sonarlint
-
-# CMake
-cmake-build-*/
-
-# File-based project format
-*.iws
-
-# IntelliJ
-out/
-
-# mpeltonen/sbt-idea plugin
-.idea_modules/
-
-# JIRA plugin
-atlassian-ide-plugin.xml
-
-# Cursive Clojure plugin
-.idea/replstate.xml
-
-# Crashlytics plugin (for Android Studio and IntelliJ)
-com_crashlytics_export_strings.xml
-crashlytics.properties
-crashlytics-build.properties
-fabric.properties
+colout.egg-info/
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index b7997b5..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-language: python
-python:
- - "3.5"
- - "3.6"
- - "3.7"
- - "3.8"
- - "pypy3"
-install:
- - pip install .
-script:
- - colout --help
- - echo heyoo | colout hey yellow
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index 1aba38f..0000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1 +0,0 @@
-include LICENSE
diff --git a/README.md b/README.md
index 10bde37..779777b 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,16 @@
-colout — Color Up Arbitrary Command Output
-==========================================
+colout(1) -- Color Up Arbitrary Command Output
+==============================================
-
-
-
+## SYNOPSIS
-## Synopsis
+`colout` [-h] [-r]
-`colout [-h] [-r [RESOURCE]]`
+`colout` [-g] [-c] [-l] [-a] [-t] [-T] [-P] [-s] PATTERN [COLOR(S) [STYLE(S)]]
-`colout [-g] [-c] [-l min,max] [-a] [-t] [-T DIR] [-P DIR] [-d COLORMAP] [-s] [-e CHAR] [-E CHAR] [--debug] PATTERN [COLOR(S) [STYLE(S)]]`
-
-## Description
+## DESCRIPTION
`colout` read lines of text stream on the standard input and output characters
-matching a given regular expression *PATTERN* in given *COLOR* and *STYLE*.
+matching a given regular expression *PATTERN* in given and *STYLE*.
If groups are specified in the regular expression pattern, only them are taken
into account, else the whole matching pattern is colored.
@@ -29,43 +21,26 @@ If you ask for fewer colors, the last one will be duplicated across remaining
groups.
Available colors are: blue, black, yellow, cyan, green, magenta, white, red,
-rainbow, random, Random, Spectrum, spectrum, scale, Scale, hash, Hash, none, an
-RGB hexadecimal triplet (`#11aaff`, for example) or any number between 0 and 255.
+rainbow, random, Random, scale, none, an RGB hexadecimal triplet or any number
+between 0 and 255.
Available styles are: normal, bold, faint, italic, underline, blink,
rapid_blink, reverse, conceal or random (some styles may have no effect, depending
on your terminal).
-In some case, you can indicate a foreground and a background color, by indicating both colors
-separated by a period (for example: `red.blue`). You can also use this system to combine two styles
-(for example, for a bold style that also blinks: `bold.blink`).
+`rainbow` will cycle over a 8 colors rainbow at each matching pattern.
+`Rainbow` will do the same over 24 colors (this requires a terminal that supports
+the 256 color escape sequences).
-`rainbow` will cycle over a the default colormap at each matching pattern.
-`Rainbow` will do the same over the default colormap for the 256-colors mode
-(this requires a terminal that supports the 256 color escape sequences).
+`Random` will color each matching pattern with a random color among the 255
+available in the ANSI table. `random` will do the same in 8 colors mode.
-`Random` will color each matching pattern with a random color among the default colormap
-(the 255 available in the ANSI table, by default).
-`random` will do the same in 8 colors mode.
+`scale` (8 colors) and `Scale` (36 colors) will parse the matching text as
+a decimal number and apply the rainbow colormap according to its position
+on the scale defined by the `-l` option (see below, "0,100" by default).
-`spectrum` and `Spectrum` are like rainbows, but with more colors (8 and 36
-colors).
-
-`scale` (8 colors) and `Scale` (256 colors) will parse the numbers characters in
-the matching text as a decimal number and apply the default colormap according
-to its position on the scale defined by the `-l` option (see below, "0,100" by
-default).
-
-`hash` (8 colors) and `Hash` (256 colors) will take a fingerprint of the matching
-text and apply the default colormap according to it. This ensure that matching
-texts appearing several times will always get the same color.
-
-Before interpreting the matched string as a number, colout will remove any
-character not supposed to be used to write down numbers. This permits to apply
-this special color on a large group, while interpreting only its numerical part.
-
-You can use the name of a syntax-coloring ["lexer"](http://pygments.org/docs/lexers/)
-as a color (for example: "Cpp", "ruby", "xml+django", etc.).
+If the python-pygments library is installed, you can use the name of a
+syntax-coloring "lexer" as a color (for example: "Cpp", "ruby", "xml+django", etc.).
If GIMP palettes files (*.gpl) are available, you can also use their names as a
colormap (see the `-P` switch below).
@@ -78,7 +53,8 @@ When not specified, a *COLOR* defaults to _red_ and a *STYLE* defaults to _bold_
`colout` comes with some predefined themes to rapidly color well-known outputs
(see the `-t` switch below).
-`colout` can be used as an interface to pygments (see also the `--source` switch below).
+If the python-pygments library is available, `colout` can be used as an interface
+to it (see also the `-s` switch below).
To have a list of all colors, styles, special colormaps, themes, palettes and lexers,
use the `-r` switch (see below).
@@ -86,29 +62,37 @@ use the `-r` switch (see below).
`colout` is released under the GNU Public License v3.
-## Installation
+## INSTALLATION
-The recomended method is using pip to install the package for the local user:
+ sudo python3 setup.py install
-```console
-$ pip install --user colout
-```
+and then soft link `/usr/local/bin/colout` to your colout.py under your installation
+directory, which is usually something like
-Another method is using [pipsi](https://github.com/mitsuhiko/pipsi)
-(_pipsi is no longer maintained, _)
-```console
-$ pipsi install colout
-```
+ /usr/local/lib/python3/dist-packages/colout-0.1-py3.egg/colout/colout.py
-There is also a PPA for Ubuntu 16.04 (Xenial)/18.04 (Bionic) (@`0.6.1-3~dist7`, not actively maintained)
-```console
-$ sudo add-apt-repository ppa:csaba-kertesz/random
-$ sudo apt-get update
-$ sudo apt-get/aptitude install colout
-```
+## OTHER INSTALLATION METHOD
-## Options
+Pypi (the Python Package Index)
+
+ sudo pip install colout
+
+or
+
+ sudo easy_install colout
+
+Ubuntu 13.04's ppa
+
+ sudo add-apt-repository ppa:ciici123/colout
+ sudo apt-get update
+ sudo apt-get/aptitude install colout
+
+Gentoo
+
+ sudo emerge colout
+
+## OPTIONS
* `-h`, `--help`:
Show a help message and exit
@@ -120,9 +104,8 @@ $ sudo apt-get/aptitude install colout
Use the given list of comma-separated colors as a colormap (cycle the colors at each match).
* `-l min,max`, `--scale min,max`:
- When using the 'scale' colormap, parse matches as decimal numbers (taking your locale into
- account) or as arithmetic expression (like "1+2/0.9*3") and apply the rainbow colormap linearly
- between the given min,max (0,100, by default).
+ When using the 'scale' colormap, parse matches as decimal numbers (taking your locale into account)
+ and apply the rainbow colormap linearly between the given min,max (0,100, by default).
* `-a`, `--all`:
Color the whole input at once instead of line per line
@@ -137,35 +120,21 @@ $ sudo apt-get/aptitude install colout
* `-P DIR`, `--palettes-dir DIR`:
Search for additional palettes (*.gpl files) in this directory.
-* `-d COLORMAP`, `--default COLORMAP`:
- When using special colormaps (`random`, `scale` or `hash`), use this COLORMAP instead of the default one.
- This can be either one of the available colormaps or a comma-separated list of colors.
- WARNING: be sure to specify a default colormap that is compatible with the special colormap's mode,
- or else the colors may not appear the same.
- Also, external palettes are converted from RGB to 256-ANSI and will thus not work if you use
- them as default colormaps for a 8-colors mode special color.
-
-* `-r [TYPE(S)]`, `--resources [TYPE(S)]`:
- Print the names of available resources. Use a comma-separated list of resources names
- (styles, colors, special, themes, palettes, colormaps or lexers),
- use 'all' (or no argument) to print all resources.
+* `-r`, `--resources`:
+ Print the names of all available colors, styles, themes and palettes.
+ A bug currently made it mandatory to use an additional dummy argument to this option
+ to make it work correctly, use `-r x`.
* `-s`, `--source`:
Interpret PATTERN as source code readable by the Pygments library. If the first letter of PATTERN
is upper case, use the 256 color mode, if it is lower case, use the 8 colors mode.
In 256 color mode, interpret COLOR as a Pygments style (e.g. "default").
-* `-e CHAR`, `--sep-list CHAR`:
- Use this character as a separator for list of colors/resources/numbers (instead of comma).
-
-* `-E CHAR`, `--sep-pair CHAR`:
- Use this character as a separator for foreground/background pairs (instead of period).
-
* `--debug`:
Debug mode: print what's going on internally, if you want to check what features are available.
-## Regular expressions
+## REGULAR EXPRESSIONS
A regular expression (or _regex_) is a pattern that describes a set of strings
that matches it.
@@ -175,25 +144,21 @@ that matches it.
special characters that would be recognize by your shell.
-## Dependencies
+## DEPENDENCIES
-Necessary Python modules:
+Recommended packages:
+* `argparse` for a usable arguments parsing
* `pygments` for the source code syntax coloring
* `babel` for a locale-aware number parsing
-## Limitations
+## LIMITATIONS
-Don't use nested groups or colout will duplicate the corresponding input text
-with each matching colors.
+Don't use nested groups or colout will duplicate the corresponding input text with each matching colors.
-Using a default colormap that is incompatible with the special colormap's mode
-(i.e. number of colors) will end badly.
-Color pairs (`foreground.background`) work in 8-colors mode for simple coloring, but may fail with `--colormap`.
-
-## Examples
+## EXAMPLES
### Simple
@@ -203,7 +168,7 @@ Color pairs (`foreground.background`) work in 8-colors mode for simple coloring,
* Color in bold violet home directories in _/etc/passwd_:
`colout '/home/[a-z]+' 135 < /etc/passwd`
-* Color in yellow user/groups id, in bold green name and in bold red home directories in `/etc/passwd`:
+* Color in yellow user/groups id, in bold green name and in bold red home directories in _/etc/passwd_:
`colout ':x:([0-9]+:[0-9]+):([^:]+).*(/home/[a-z]+)' yellow,green,red normal,bold < /etc/passwd`
* Color in yellow file permissions with read rights for everyone:
@@ -236,7 +201,7 @@ Color pairs (`foreground.background`) work in 8-colors mode for simple coloring,
* Color a make output, line numbers in yellow, errors in bold red, warning in magenta, pragma in green and C++ file base names in cyan:
`make 2>&1 | colout ':([0-9]+):[0-9]*' yellow normal | colout error | colout warning magenta | colout pragma green normal | colout '/(\w+)*\.(h|cpp)' cyan normal`
Or using themes:
- `make 2>&1 | colout -t cmake | colout -t g++`
+ `make 2>&³ | colout -t cmake | colout -t g++`
* Color each word in the head of auth.log with a rainbow color map, starting a new colormap at each new line (the
beginning of the command is just bash magic to repeat the string "(\\w+)\\W+":
@@ -254,144 +219,18 @@ Color pairs (`foreground.background`) work in 8-colors mode for simple coloring,
* Color a source code substring:
`echo "There is an error in 'static void Functor::operator()( EOT& indiv ) { return indiv; }' you should fix it" | colout "'(.*)'" Cpp monokai`
-* Color the percent of progress part of a CMake's makefile output, with a color
- related to the value of the progress (from 0%=blue to 100%=red):
- `cmake .. && make | colout "^(\[\s*[0-9]+%\])" Scale`
-
-* Color hosts and users in `auth.log`, with consistent colors:
- `cat /var/log/auth.log | colout "^(\S+\s+){3}(\S+)\s(\S+\s+){3}(\S+)\s+(\S+\s+){2}(\S+)\s*" none,hash,none,hash,none,hash`
-
### Bash alias
The following bash function color the output of any command with the
-cmake and g++ themes:
+cmake and g77 themes:
-```bash
-function cm()
-{
- set -o pipefail
- $@ 2>&1 | colout -t cmake | colout -t g++
-}
-```
+ function cm()
+ {
+ set -o pipefail
+ $@ 2>&1 | colout -t cmake | colout -t g++
+ }
You then can use the `cm` alias as a prefix to your build command,
for example: `cm make test`
-
-### GDB integration
-
-You can use `colout` within the GNU debuger (`gbd`) to color its output.
-For example, the following script `.gdbinit` configuration will color
-the output of the backtrace command:
-
-```gdb
-set confirm off
-
-# Don't wrap line or the coloring regexp won't work.
-set width 0
-
-# Create a named pipe to get outputs from gdb
-shell test -e /tmp/coloutPipe && rm /tmp/coloutPipe
-shell mkfifo /tmp/coloutPipe
-
-define logging_on
- # Instead of printing on stdout only, log everything...
- set logging redirect on
- # ... in our named pipe.
- set logging on /tmp/coloutPipe
-end
-
-define logging_off
- set logging off
- set logging redirect off
- # Because both gdb and our commands are writing on the same pipe at the same
- # time, it is more than probable that gdb will end before our (higher level)
- # commands. The gdb prompt will thus render before the result of the command,
- # which is highly akward. To prevent this, we need to wait before displaying
- # the prompt again. The more your commands are complex, the higher you will
- # need to set this.
- shell sleep 0.4s
-end
-
-define hook-backtrace
- # Note: match path = [path]file[.ext] = (.*/)?(?:$|(.+?)(?:(\.[^.]*)|))
- # This line color highlights:
- # – lines that link to source code,
- # – function call in green,
- # – arguments names in yellow, values in magenta,
- # — the parent directory in bold red (assuming that the debug session would be in a "project/build/" directory).
- shell cat /tmp/coloutPipe | colout "^(#)([0-9]+)\s+(0x\S+ )*(in )*(.*) (\(.*\)) (at) (.*/)?(?:$|(.+?)(?:(\.[^.]*)|)):([0-9]+)" red,red,blue,red,green,magenta,red,none,white,white,yellow normal,bold,normal,normal,normal,normal,normal,bold,bold,bold | colout "([\w\s]*?)(=)([^,]*?)([,\)])" yellow,blue,magenta,blue normal | colout "/($(basename $(dirname $(pwd))))/" red bold &
- logging_on
-end
-define hookpost-backtrace
- logging_off
-end
-
-# Don't forget to clean the adhoc pipe.
-define hook-quit
- set confirm off
- shell rm -f /tmp/coloutPipe
-end
-```
-
-Take a look at the `example.gdbinit` file distributed with colout for more gdb commands.
-
-
-
-### Themes
-
-You can easily add your own theme to colout.
-A theme is basically a module with a function named `theme` that take the configuration context as
-an argument and return back the (modified) context and a list of triplets.
-Each triplet figures the same arguments than those of the command line interface.
-
-```python
-def theme(context):
- return context,[ [regexp, colors, styles] ]
-```
-
-With the context dictionary at hand, you have access to the internal configuration of colout, you
-can thus change colormaps for special keywords, the scale, even the available colors, styles or
-themes.
-
-See the cmake them for how to modify an existing colormap if (and only if) the user didn't ask for an alternative one.
-See the ninja theme for how to extend an existing theme with more regexps and a different configuration.
-See the gcc theme for an example of how to use the localization of existing softwares to build translated regexp.
-
-
-### Buffering
-
-Note that when you use colout within real time streams (like `tail -f X | grep Y | colout Z`) of
-commands, you may observe that the lines are printed by large chunks and not one by one, in real
-time.
-This is not due to colout but to the buffering behavior of your shell.
-
-To fix that, use `stdbuf`, for example: `tail -f X | stdbuf -o0 grep Y | colout Z`.
-
-## Authors
-
-* nojhan : original idea, main developer, maintainer.
-* Adrian Sadłocha
-* Alex Burka
-* Brian Foley
-* Charles Lewis
-* DainDwarf
-* Dimitri Merejkowsky
-* Dong Wei Ming
-* Fabien MARTY
-* Jason Green
-* John Anderson
-* Jonathan Poelen
-* Louis-Kenzo Furuya Cahier
-* Mantas
-* Martin Ueding
-* Nicolas Pouillard
-* Nurono
-* Oliver Bristow
-* orzrd <61966225@qq.com>
-* Philippe Daouadi
-* Piotr Staroszczyk
-* Scott Lawrence
-* Xu Di
-* https://github.com/stdedos: maintainer.
diff --git a/bin/colout b/bin/colout
new file mode 100644
index 0000000..7702d17
--- /dev/null
+++ b/bin/colout
@@ -0,0 +1,11 @@
+#!/bin/bash
+# Copyright (c) 2013 Martin Ueding
+
+# Small launcher script for the main module.
+
+# Licence: GPL 3
+
+set -e
+set -u
+
+python3 -m colout.colout "$@"
diff --git a/colout/colout.py b/colout/colout.py
index 2d51fac..70e4b16 100755
--- a/colout/colout.py
+++ b/colout/colout.py
@@ -1,147 +1,29 @@
#!/usr/bin/env python3
#encoding: utf-8
-# Color Up Arbitrary Command Output
+# Color Up Arbitrary Command Ouput
# Licensed under the GPL version 3
# 2012 (c) nojhan
-import os
-import re
import sys
-import copy
+import re
+import random
+import os
import glob
import math
-import pprint
-import random
-import signal
-import string
-import hashlib
-import logging
-import argparse
import importlib
-import functools
-import babel.numbers as bn
+import logging
+import signal
# set the SIGPIPE handler to kill the program instead of
# ending in a write error when a broken pipe occurs
signal.signal( signal.SIGPIPE, signal.SIG_DFL )
-###############################################################################
-# Global variable(s)
-###############################################################################
-
-context = {}
-debug = False
-
-# Available styles
-context["styles"] = {
- "normal": 0, "bold": 1, "faint": 2, "italic": 3, "underline": 4,
- "blink": 5, "rapid_blink": 6,
- "reverse": 7, "conceal": 8
-}
-
-error_codes = {"UnknownColor": 1, "DuplicatedPalette": 2, "MixedModes": 3, "UnknownLexer": 4, "UnknownResource": 5}
-
-# Available color names in 8-colors mode.
-eight_colors = ["black","red","green","yellow","blue","magenta","cyan","white"]
-# Given in that order, the ASCII code is the index.
-eight_color_codes = {n:i for i,n in enumerate(eight_colors)}
-# One can add synonyms.
-eight_color_codes["orange"] = eight_color_codes["yellow"]
-eight_color_codes["purple"] = eight_color_codes["magenta"]
-
-# Foreground colors has a special "none" item.
-# Note: use copy to avoid having the same reference over fore/background.
-context["colors"] = copy.copy(eight_color_codes)
-context["colors"]["none"] = -1
-
-# Background has the same colors than foreground, but without the none code.
-context["backgrounds"] = copy.copy(eight_color_codes)
-
-context["themes"] = {}
-
-# pre-defined colormaps
-# 8-colors mode should start with a lower-case letter (and can contains either named or indexed colors)
-# 256-colors mode should start with an upper-case letter (and should contains indexed colors)
-context["colormaps"] = {
- # Rainbows
- "rainbow" : ["magenta", "blue", "cyan", "green", "yellow", "red"],
- "Rainbow" : [92, 93, 57, 21, 27, 33, 39, 45, 51, 50, 49, 48, 47, 46, 82, 118, 154, 190, 226, 220, 214, 208, 202, 196],
-
- # From magenta to red, with white in the middle
- "spectrum" : ["magenta", "blue", "cyan", "white", "green", "yellow", "red"],
- "Spectrum" : [91, 92, 56, 57, 21, 27, 26, 32, 31, 37, 36, 35, 41, 40, 41, 77, 83, 84, 120, 121, 157, 194, 231, 254, 255, 231, 230, 229, 228, 227, 226, 220, 214, 208, 202, 196],
-
- # All the colors are available for the default `random` special
- "random" : context["colors"],
- "Random" : list(range(256))
-} # colormaps
-
-context["colormaps"]["scale"] = context["colormaps"]["spectrum"]
-context["colormaps"]["Scale"] = context["colormaps"]["Spectrum"]
-context["colormaps"]["hash"] = context["colormaps"]["rainbow"]
-context["colormaps"]["Hash"] = context["colormaps"]["Rainbow"]
-context["colormaps"]["default"] = context["colormaps"]["spectrum"]
-context["colormaps"]["Default"] = context["colormaps"]["Spectrum"]
-
-context["user_defined_colormaps"] = False
-
-context["colormap_idx"] = 0
-
-context["scale"] = (0,100)
-
-context["lexers"] = []
-
-# Character use as a delimiter
-# between foreground and background.
-context["sep_pair"]="."
-context["sep_list"]=","
-
-class UnknownColor(Exception):
- pass
-
-class DuplicatedPalette(Exception):
- pass
-
-class DuplicatedTheme(Exception):
- pass
-
-class MixedModes(Exception):
- pass
-
-
###############################################################################
# Ressource parsing helpers
###############################################################################
-def make_colormap( colors, sep_list = context["sep_list"] ):
- cmap = colors.split(sep_list)
-
- # Check unicity of mode.
- modes = [mode(c) for c in cmap]
- if len(uniq(modes)) > 1:
- # Format a list of color:mode, for error display.
- raise MixedModes(", ".join(["%s:%s" % cm for cm in zip(cmap,modes)]))
-
- return cmap
-
-
-def set_special_colormaps( cmap, sep_list = context["sep_list"] ):
- """Change all the special colors to a single colormap (which must be a list of colors)."""
- global context
- context["colormaps"]["scale"] = cmap
- context["colormaps"]["Scale"] = cmap
- context["colormaps"]["hash"] = cmap
- context["colormaps"]["Hash"] = cmap
- context["colormaps"]["default"] = cmap
- context["colormaps"]["Default"] = cmap
- context["colormaps"]["random"] = cmap
- context["colormaps"]["Random"] = cmap
- context["user_defined_colormaps"] = True
- logging.debug("user-defined special colormap: %s" % sep_list.join([str(i) for i in cmap]) )
-
-
def parse_gimp_palette( filename ):
"""
Parse the given filename as a GIMP palette (.gpl)
@@ -177,7 +59,7 @@ def parse_gimp_palette( filename ):
palette = []
for line in lines:
# skip lines with only a comment
- if re.match(r"^\s*#.*$", line ):
+ if re.match("^\s*#.*$", line ):
continue
# decode the columns-ths codes. Generally [R G B] followed by a comment
colors = [ int(c) for c in line.split()[:columns] ]
@@ -205,13 +87,7 @@ def uniq( lst ):
def rgb_to_ansi( r, g, b ):
"""Convert a RGB color to its closest 256-colors ANSI index"""
-
- # Range limits for the *colored* section of ANSI,
- # this does not include the *gray* section.
- ansi_min = 16
- ansi_max = 234
-
- # ansi_max is the higher possible RGB value for ANSI *colors*
+ # ansi_max is the higher possible RGB value for ANSI colors
# limit RGB values to ansi_max
red,green,blue = tuple([ansi_max if c>ansi_max else c for c in (r,g,b)])
@@ -242,91 +118,131 @@ def hex_to_rgb(h):
return tuple( int(h[i:i+lh//3], 16) for i in range(0, lh, lh//3) )
+###############################################################################
+# Global variables
+###############################################################################
+
+# Escaped end markers for given color modes
+endmarks = {8: ";", 256: ";38;5;"}
+
+ansi_min = 16
+ansi_max = 234
+
+# Available styles
+styles = {
+ "normal": 0, "bold": 1, "faint": 2, "italic": 3, "underline": 4,
+ "blink": 5, "rapid_blink": 6,
+ "reverse": 7, "conceal": 8
+}
+
+# Available color names in 8-colors mode
+colors = {
+ "black": 0, "red": 1, "green": 2, "yellow": 3, "blue": 4,
+ "magenta": 5, "cyan": 6, "white": 7, "none": -1
+}
+
+themes = {}
+
+# pre-defined colormaps
+# 8-colors mode should start with a lower-case letter (and can contains either named or indexed colors)
+# 256-colors mode should start with an upper-case letter (and should contains indexed colors)
+colormaps = {
+ # Rainbows
+ "rainbow" : ["magenta", "blue", "cyan", "green", "yellow", "red"],
+ "Rainbow" : [92, 93, 57, 21, 27, 33, 39, 45, 51, 50, 49, 48, 47, 46, 82, 118, 154, 190, 226, 220, 214, 208, 202, 196],
+
+ # from magenta to red, with white in the middle
+ "spectrum" : ["magenta", "blue", "cyan", "white", "green", "yellow", "red"],
+ "Spectrum" : [91, 92, 56, 57, 21, 27, 26, 32, 31, 37, 36, 35, 41, 40, 41, 77, 83, 84, 120, 121, 157, 194, 231, 254, 255, 231, 230, 229, 228, 227, 226, 220, 214, 208, 202, 196]
+} # colormaps
+
+colormap = colormaps["rainbow"] # default colormap to rainbow
+colormap_idx = 0
+
+scale = (0,100)
+
+class UnknownColor(Exception):
+ pass
+
+class DuplicatedPalette(Exception):
+ pass
+
+class DuplicatedTheme(Exception):
+ pass
+
+
###############################################################################
# Load available extern resources
###############################################################################
def load_themes( themes_dir):
- global context
+ global themes
logging.debug("search for themes in: %s" % themes_dir)
- sys.path.append( themes_dir )
+ os.chdir( themes_dir )
# load available themes
- for f in glob.iglob(os.path.join(themes_dir, "colout_*.py")):
- basename = os.path.basename(f) # Remove path.
- module = os.path.splitext(basename)[0] # Remove extension.
- name = "_".join(module.split("_")[1:]) # Remove the 'colout_' prefix.
- if name in context["themes"]:
+ for f in glob.iglob("colout_*.py"):
+ module = ".".join(f.split(".")[:-1]) # remove extension
+ name = "_".join(module.split("_")[1:]) # remove the prefix
+ if name in themes:
raise DuplicatedTheme(name)
logging.debug("load theme %s" % name)
- context["themes"][name] = importlib.import_module(module)
+ themes[name] = importlib.import_module(module)
-def load_palettes( palettes_dir, ignore_duplicates = True ):
- global context
+def load_palettes( palettes_dir ):
+ global colormaps
logging.debug("search for palettes in: %s" % palettes_dir)
+ os.chdir( palettes_dir )
# load available colormaps (GIMP palettes format)
- for p in glob.iglob(os.path.join(palettes_dir, "*.gpl")):
+ for p in glob.iglob("*.gpl"):
try:
name,palette = parse_gimp_palette(p)
except Exception as e:
logging.warning("error while parsing palette %s: %s" % ( p,e ) )
continue
- if name in context["colormaps"]:
- if ignore_duplicates:
- logging.warning("ignore this duplicated palette name: %s" % name)
- else:
- raise DuplicatedPalette(name)
+ if name in colormaps:
+ raise DuplicatedPalette(name)
# Convert the palette to ANSI
ansi_palette = [ rgb_to_ansi(r,g,b) for r,g,b in palette ]
# Compress it so that there isn't two consecutive identical colors
compressed = uniq(ansi_palette)
logging.debug("load %i ANSI colors in palette %s: %s" % (len(compressed), name, compressed))
- context["colormaps"][name] = compressed
+ colormaps[name] = compressed
def load_lexers():
- global context
+ global lexers
# load available pygments lexers
lexers = []
- global get_lexer_by_name
- from pygments.lexers import get_lexer_by_name
-
- global highlight
- from pygments import highlight
-
- global Terminal256Formatter
- from pygments.formatters import Terminal256Formatter
-
- global TerminalFormatter
- from pygments.formatters import TerminalFormatter
-
- from pygments.lexers import get_all_lexers
try:
+ global get_lexer_by_name
+ from pygments.lexers import get_lexer_by_name
+
+ global highlight
+ from pygments import highlight
+
+ global Terminal256Formatter
+ from pygments.formatters import Terminal256Formatter
+
+ global TerminalFormatter
+ from pygments.formatters import TerminalFormatter
+
+ from pygments.lexers import get_all_lexers
+ except ImportError:
+ logging.warning("the pygments module has not been found, syntax coloring is not available")
+ pass
+ else:
for lexer in get_all_lexers():
- l = None
- # If the tuple has one-word aliases
- # (which are usually a better option than the long names
- # for a command line argument).
- if lexer[1]:
- l = lexer[1][0] # Take the first one.
- else:
- assert(lexer[0])
- l = lexer[0] # Take the long name, which should alway exists.
- if not l:
+ try:
+ lexers.append(lexer[1][0])
+ except IndexError:
logging.warning("cannot load lexer: %s" % lexer[1][0])
- pass # Forget about this lexer.
+ pass
else:
- assert(" " not in l) # Should be very rare, but probably a source of bugs.
- lexers.append(l)
- except:
- logging.warning("error while executing the pygment module, syntax coloring is not available")
-
- lexers.sort()
- logging.debug("loaded %i lexers: %s" % (len(lexers), ", ".join(lexers)))
-
- context["lexers"] = lexers
+ logging.debug("loaded lexer %s" % lexer[1][0])
+ lexers.sort()
def load_resources( themes_dir, palettes_dir ):
@@ -339,184 +255,10 @@ def load_resources( themes_dir, palettes_dir ):
# Library
###############################################################################
-def mode( color ):
- global context
- if type(color) is int:
- if 0 <= color and color <= 255 :
- return 256
- else:
- raise UnknownColor(color)
- elif color in context["colors"]:
- return 8
- elif color in context["colormaps"].keys():
- if color[0].islower():
- return 8
- elif color[0].isupper():
- return 256
- elif color.lower() in ("scale","hash","random") or color.lower() in context["lexers"]:
- if color[0].islower():
- return 8
- elif color[0].isupper():
- return 256
- elif color[0] == "#":
- return 256
- elif color.isdigit() and (0 <= int(color) and int(color) <= 255) :
- return 256
- else:
- raise UnknownColor(color)
-
-
-def next_in_map( name ):
- global context
- # loop over indices in colormap
- return (context["colormap_idx"]+1) % len(context["colormaps"][name])
-
-
-def color_random( color ):
- global context
- m = mode(color)
- if m == 8:
- color_name = random.choice(list(context["colormaps"]["random"]))
- color_code = context["colors"][color_name]
- color_code = str(30 + color_code)
-
- elif m == 256:
- color_nb = random.choice(context["colormaps"]["Random"])
- color_code = str(color_nb)
-
- return color_code
-
-
-def color_in_colormaps( color ):
- global context
- m = mode(color)
- if m == 8:
- c = context["colormaps"][color][context["colormap_idx"]]
- if c.isdigit():
- color_code = str(30 + c)
- else:
- color_code = str(30 + context["colors"][c])
-
- else:
- color_nb = context["colormaps"][color][context["colormap_idx"]]
- color_code = str( color_nb )
-
- context["colormap_idx"] = next_in_map(color)
-
- return color_code
-
-
-def color_scale( name, text ):
- # filter out everything that does not seem to be necessary to interpret the string as a number
- # this permits to transform "[ 95%]" to "95" before number conversion,
- # and thus allows to color a group larger than the matched number
- chars_in_numbers = "-+.,e/*"
- allowed = string.digits + chars_in_numbers
- nb = "".join([i for i in filter(allowed.__contains__, text)])
-
- # interpret as decimal
- f = None
- try:
- f = float(bn.parse_decimal(nb))
- except bn.NumberFormatError:
- pass
- if f is not None:
- # normalize with scale if it's a number
- f = (f - context["scale"][0]) / (context["scale"][1]-context["scale"][0])
- else:
- # interpret as float between 0 and 1 otherwise
- f = eval(nb)
-
- # if out of scale, do not color
- if f < 0 or f > 1:
- return None
-
- # normalize and scale over the nb of colors in cmap
- colormap = context["colormaps"][name]
- i = int( math.ceil( f * (len(colormap)-1) ) )
- color = colormap[i]
-
- # infer mode from the color in the colormap
- m = mode(color)
-
- if m == 8:
- color_code = str(30 + context["colors"][color])
- else:
- color_code = str(color)
-
- return color_code
-
-
-def color_hash( name, text ):
- hasher = hashlib.md5()
- hasher.update(text.encode('utf-8'))
- hash = hasher.hexdigest()
-
- f = float(functools.reduce(lambda x, y: x+ord(y), hash, 0) % 101)
-
- # normalize and scale over the nb of colors in cmap
- colormap = context["colormaps"][name]
- i = int( math.ceil( (f - context["scale"][0]) / (context["scale"][1]-context["scale"][0]) * (len(colormap)-1) ) )
- color = colormap[i]
-
- # infer mode from the color in the colormap
- m = mode(color)
-
- if m == 8:
- color_code = str(30 + context["colors"][color])
- else:
- color_code = str(color)
-
- return color_code
-
-
-def color_map(name):
- global context
- # current color
- color = context["colormaps"][name][ context["colormap_idx"] ]
-
- m = mode(color)
- if m == 8:
- color_code = str(30 + context["colors"][color])
- else:
- color_nb = int(color)
- assert( 0 <= color_nb <= 255 )
- color_code = str(color_nb)
-
- context["colormap_idx"] = next_in_map(name)
-
- return color,color_code
-
-
-def color_lexer( name, style, text ):
- lexer = get_lexer_by_name(name.lower())
- # Python => 256 colors, python => 8 colors
- m = mode(name)
- if m == 256:
- try:
- formatter = Terminal256Formatter(style=style)
- except: # style not found
- formatter = Terminal256Formatter()
- else:
- if style not in ("light","dark"):
- style = "dark" # dark color scheme by default
- formatter = TerminalFormatter(bg=style)
- # We should return all but the last character,
- # because Pygments adds a newline char.
- if not debug:
- return highlight(text, lexer, formatter)[:-1]
- else:
- return "<"+name+">"+ highlight(text, lexer, formatter)[:-1] + ""+name+">"
-
-
-def colorin(text, color="red", style="normal", sep_pair=context["sep_pair"]):
+def colorin(text, color="red", style="normal"):
"""
Return the given text, surrounded by the given color ASCII markers.
- The given color may be either a single name, encoding the foreground color,
- or a pair of names, delimited by the given sep_pair,
- encoding foreground and background, e.g. "red.blue".
-
If the given color is a name that exists in available colors,
a 8-colors mode is assumed, else, a 256-colors mode.
@@ -530,115 +272,153 @@ def colorin(text, color="red", style="normal", sep_pair=context["sep_pair"]):
assert( type(color) is str )
+ global colormap_idx
global debug
# Special characters.
start = "\033["
stop = "\033[0m"
- # Escaped end markers for given color modes
- endmarks = {8: ";", 256: ";38;5;"}
-
color_code = ""
style_code = ""
- background_code = ""
- style_codes = []
# Convert the style code
if style == "random" or style == "Random":
- style = random.choice(list(context["styles"].keys()))
+ style = random.choice(list(styles.keys()))
else:
- styles = style.split(sep_pair)
- for astyle in styles:
- if astyle in context["styles"]:
- style_codes.append(str(context["styles"][astyle]))
- style_code = ";".join(style_codes)
+ if style in styles:
+ style_code = str(styles[style])
- color_pair = color.strip().split(sep_pair)
- color = color_pair[0]
- background = color_pair[1] if len(color_pair) == 2 else "none"
-
- if color == "none" and background == "none":
+ if color == "none":
# if no color, style cannot be applied
if not debug:
return text
else:
return ""+text+""
- elif color.lower() == "random":
- color_code = color_random( color )
+ elif color == "random":
+ mode = 8
+ color_code = random.choice(list(colors.values()))
+ color_code = str(30 + color_code)
+
+ elif color == "Random":
+ mode = 256
+ color_nb = random.randint(0, 255)
+ color_code = str(color_nb)
+
+ elif color in colormaps.keys():
+ if color[0].islower(): # lower case first letter
+ mode = 8
+ c = colormaps[color][colormap_idx]
+ if c.isdigit():
+ color_code = str(30 + c)
+ else:
+ color_code = str(30 + colors[c])
+
+ else: # upper case
+ mode = 256
+ color_nb = colormaps[color][colormap_idx]
+ color_code = str( color_nb )
+
+ if colormap_idx < len(colormaps[color])-1:
+ colormap_idx += 1
+ else:
+ colormap_idx = 0
elif color.lower() == "scale": # "scale" or "Scale"
- color_code = color_scale( color, text )
+ try:
+ import babel.numbers as bn
+ f = float(bn.parse_decimal(text))
+ except ImportError:
+ f = float(text)
- # "hash" or "Hash"; useful to randomly but consistently color strings
- elif color.lower() == "hash":
- color_code = color_hash( color, text )
+ # if out of scale, do not color
+ if f < scale[0] or f > scale[1]:
+ return text
- # The user can change the "colormap" variable to its favorite one before calling colorin.
+ if color[0].islower():
+ mode = 8
+ cmap = colormaps["spectrum"]
+
+ # normalize and scale over the nb of colors in cmap
+ i = int( math.ceil( (f - scale[0]) / (scale[1]-scale[0]) * (len(cmap)-1) ) )
+
+ color = cmap[i]
+ color_code = str(30 + colors[color])
+
+ else:
+ mode = 256
+ cmap = colormaps["Spectrum"]
+ i = int( math.ceil( (f - scale[0]) / (scale[1]-scale[0]) * (len(cmap)-1) ) )
+ color = cmap[i]
+ color_code = str(color)
+
+ # Really useful only when using colout as a library
+ # thus you can change the "colormap" variable to your favorite one before calling colorin
elif color == "colormap":
- # "default" should have been set to the user-defined colormap.
- color,color_code = color_map("default")
+ color = colormap[colormap_idx]
+ if color in colors:
+ mode = 8
+ color_code = str(30 + colors[color])
+ else:
+ mode = 256
+ color_nb = int(color)
+ assert(0 <= color_nb <= 255)
+ color_code = str(color_nb)
- # Registered colormaps should be tested after special colors,
- # because special tags are also registered as colormaps,
- # but do not have the same simple behavior.
- elif color in context["colormaps"].keys():
- color_code = color_in_colormaps( color )
+ if colormap_idx < len(colormap)-1:
+ colormap_idx += 1
+ else:
+ colormap_idx = 0
# 8 colors modes
- elif color in context["colors"]:
- color_code = str(30 + context["colors"][color])
+ elif color in colors:
+ mode = 8
+ color_code = str(30 + colors[color])
# hexadecimal color
elif color[0] == "#":
+ mode = 256
color_nb = rgb_to_ansi(*hex_to_rgb(color))
assert(0 <= color_nb <= 255)
color_code = str(color_nb)
# 256 colors mode
elif color.isdigit():
+ mode = 256
color_nb = int(color)
assert(0 <= color_nb <= 255)
color_code = str(color_nb)
# programming language
- elif color.lower() in context["lexers"]:
- # bypass color encoding and return text colored by the lexer
- return color_lexer(color,style,text)
+ elif color.lower() in lexers:
+ lexer = get_lexer_by_name(color.lower())
+ # Python => 256 colors, python => 8 colors
+ ask_256 = color[0].isupper()
+ if ask_256:
+ try:
+ formatter = Terminal256Formatter(style=style)
+ except: # style not found
+ formatter = Terminal256Formatter()
+ else:
+ if style not in ("light","dark"):
+ style = "dark" # dark color scheme by default
+ formatter = TerminalFormatter(bg=style)
+ # We should return all but the last character,
+ # because Pygments adds a newline char.
+ if not debug:
+ return highlight(text, lexer, formatter)[:-1]
+ else:
+ return "<"+color+">"+ highlight(text, lexer, formatter)[:-1] + ""+color+">"
# unrecognized
else:
raise UnknownColor(color)
- m = mode(color)
-
- if background in context["backgrounds"] and m == 8:
- background_code = endmarks[m] + str(40 + context["backgrounds"][background])
- elif background == "none":
- background_code = ""
+ if not debug:
+ return start + style_code + endmarks[mode] + color_code + "m" + text + stop
else:
- raise UnknownColor(background)
-
- if color_code is not None:
- if not debug:
- return start + style_code + endmarks[m] + color_code + background_code + "m" + text + stop
- else:
- return start + style_code + endmarks[m] + color_code + background_code + "m" \
- + "" \
- + text + "" + stop
- else:
- if not debug:
- return text
- else:
- return "" + text + ""
+ return start + style_code + endmarks[mode] + color_code + "m<" + color + ">" + text + "" + color + ">" + stop
def colorout(text, match, prev_end, color="red", style="normal", group=0):
@@ -654,7 +434,7 @@ def colorout(text, match, prev_end, color="red", style="normal", group=0):
return colored_text, end
-def colorup(text, pattern, color="red", style="normal", on_groups=False, sep_list=context["sep_list"]):
+def colorup(text, pattern, color="red", style="normal", on_groups=False):
"""
Color up every characters that match the given regexp patterns.
If groups are specified, only color up them and not the whole pattern.
@@ -663,10 +443,21 @@ def colorup(text, pattern, color="red", style="normal", on_groups=False, sep_lis
in which case the different matching groups may be formatted differently.
If there is less colors/styles than groups, the last format is used
for the additional groups.
- """
- global context
- global debug
+ >>> colorup("Fetchez la vache", "vache", "red", "bold")
+ 'Fetchez la \x1b[1;31mvache\x1b[0m'
+ >>> colorup("Faites chier la vache", "[Fv]a", "red", "bold")
+ '\x1b[1;31mFa\x1b[0mites chier la \x1b[1;31mva\x1b[0mche'
+ >>> colorup("Faites Chier la Vache", "[A-Z](\S+)\s", "red", "bold")
+ 'F\x1b[1;31maites\x1b[0m C\x1b[1;31mhier\x1b[0m la Vache'
+ >>> colorup("Faites Chier la Vache", "([A-Z])(\S+)\s", "red,green", "bold")
+ '\x1b[1;31mF\x1b[0m\x1b[1;32maites\x1b[0m \x1b[1;31mC\x1b[0m\x1b[1;32mhier\x1b[0m la Vache'
+ >>> colorup("Faites Chier la Vache", "([A-Z])(\S+)\s", "green")
+ '\x1b[0;32mF\x1b[0m\x1b[0;32maites\x1b[0m \x1b[0;32mC\x1b[0m\x1b[0;32mhier\x1b[0m la Vache'
+ >>> colorup("Faites Chier la Vache", "([A-Z])(\S+)\s", "blue", "bold,italic")
+ '\x1b[1;34mF\x1b[0m\x1b[3;34maites\x1b[0m \x1b[1;34mC\x1b[0m\x1b[3;34mhier\x1b[0m la Vache'
+ """
+ global colormap_idx
if not debug:
regex = re.compile(pattern)
@@ -690,17 +481,17 @@ def colorup(text, pattern, color="red", style="normal", on_groups=False, sep_lis
# Build a list of colors that match the number of grouped,
# if there is not enough colors, duplicate the last one.
- colors_l = color.split(sep_list)
+ colors_l = color.split(",")
group_colors = colors_l + [colors_l[-1]] * (nb_groups - len(colors_l))
# Same for styles
- styles_l = style.split(sep_list)
+ styles_l = style.split(",")
group_styles = styles_l + [styles_l[-1]] * (nb_groups - len(styles_l))
# If we want to iterate colormaps on groups instead of patterns
if on_groups:
# Reset the counter at the beginning of each match
- context["colormap_idx"] = 0
+ colormap_idx = 0
# For each group index.
# Note that match.groups returns a tuple (thus being indexed in [0,n[),
@@ -765,8 +556,6 @@ def map_write( stream_in, stream_out, function, *args ):
while True:
try:
item = stream_in.readline()
- except UnicodeDecodeError:
- continue
except KeyboardInterrupt:
break
if not item:
@@ -774,7 +563,7 @@ def map_write( stream_in, stream_out, function, *args ):
write( function(item, *args), stream_out )
-def colorgen(stream, pattern, color="red", style="normal", on_groups=False, sep_list=context["sep_list"]):
+def colorgen(stream, pattern, color="red", style="normal", on_groups=False):
"""
A generator that colors the items given in an iterable input.
@@ -790,14 +579,71 @@ def colorgen(stream, pattern, color="red", style="normal", on_groups=False, sep_
break
if not item:
break
- yield colorup(item, pattern, color, style, on_groups, sep_list)
+ yield colorup(item, pattern, color, style, on_groups)
######################
# Command line tools #
######################
-def _args_parse(argv, usage=""):
+def __args_dirty__(argv, usage=""):
+ """
+ Roughly extract options from the command line arguments.
+ To be used only when argparse is not available.
+
+ Returns a tuple of (pattern,color,style,on_stderr).
+
+ >>> colout.__args_dirty__(["colout","pattern"],"usage")
+ ('pattern', 'red', 'normal', False)
+ >>> colout.__args_dirty__(["colout","pattern","colors","styles"],"usage")
+ ('pattern', 'colors', 'styles', False)
+ >>> colout.__args_dirty__(["colout","pattern","colors","styles","True"],"usage")
+ ('pattern', 'colors', 'styles', True)
+ """
+
+ # Use a dirty argument picker
+ # Check for bad usage or an help flag
+ if len(argv) < 2 \
+ or len(argv) > 10 \
+ or argv[1] == "--help" \
+ or argv[1] == "-h":
+ print(usage+"\n")
+ print("Usage:", argv[0], " [