diff --git a/.gitignore b/.gitignore index 9839280..c2a9c3f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,162 @@ -*.pyc +## Python gitignore +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python build/ +develop-eggs/ dist/ -colout.egg-info/ +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 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..b7997b5 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +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/AUTHORS b/AUTHORS deleted file mode 100644 index 63be891..0000000 --- a/AUTHORS +++ /dev/null @@ -1,6 +0,0 @@ -Colout is written by nojhan and maintained by Dongweiming: - -Writer -`````` - -- Nojhan \ No newline at end of file diff --git a/CHANGES b/CHANGES deleted file mode 100644 index 8fbff91..0000000 --- a/CHANGES +++ /dev/null @@ -1,4 +0,0 @@ -Version 0.1 ------------ - -Released on May 5th 2013, First public preview release. \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..1aba38f --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include LICENSE diff --git a/PKG-INFO b/PKG-INFO deleted file mode 100644 index 26455a4..0000000 --- a/PKG-INFO +++ /dev/null @@ -1,195 +0,0 @@ -Metadata-Version: 1.1 -Name: Colout -Version: 0.1 -Summary: Color text streams with this simple command -Home-page: http://nojhan.github.com/colout/ -Author: Nojhan -Author-email: nojhan@nojhan.net -License: GPL3 -Description: - Colout - ----- - - Colout is Color text streams with this simple commandinja 2 and good - intentions. And before you ask: It's BSD licensed! - - SYNOPSIS - ```````````` - - ``colout`` [-h][-e] [-g][-t] [-s][-l] PATTERN [COLOR(S)][STYLE(S)] - - DESCRIPTION - ````````````````` - - ``colout`` read lines of text stream on the standard input and output - characters 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. - - You can specify severall colors or styles when using groups by - separating them with commas. If you indicate more colors than groups, - the last ones will be ignored. If you ask for less colors, the last one - will be duplicated across remaining groups. - - Available colors are: blue, black, yellow, cyan, green, magenta, white, - red, rainbow, random, Random, scale, none or any number between 0 and - 255. - - Available styles are: normal, bold, faint, italic, underline, blink, - rapid\_blink, reverse, conceal or random. - - ``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. - - rainbow\` will cycle over a 8 colors rainbow at each matching pattern. - - ``scale`` will parse the matching text as a decimal number and apply the - rainbow colormap according to its position on a scale defined by the - ``-l`` option (see below, [0-100] by default). - - When not specified, a *COLOR* defaults to *red* and a *STYLE* defaults - to *bold*. - - ``colout`` comes with some predefined themes to rapidely color - well-known outputs (see the ``-t`` switch below). - - If the python-pygments library is available, ``colout`` can be used as - an interface to it (see also the ``-s`` switch below). - - ``colout`` is released under the GNU Public License v3. - - OPTIONS - ``````` - - - ``-h``, ``--help``: Show an help message and exit - - - ``-g``, ``--groups``: For color maps (like "rainbow"), iterate over - matching groups in the pattern instead of over patterns. - - - ``-c``, ``--colormap``: Use the given list of comma-separated colors - as a colormap (cycle the colors at each match). - - - ``-l``, ``--scale``: When using the 'scale' colormap, parse matches - as decimal numbers (taking your locale into account) and apply the - rainbow colormap linearly between the given SCALE=min,max - (SCALE=0,100, by default). - - - ``-a``, ``--all``: Color the whole input at once instead of line per - line (really useful for coloring a source code file with strings on - multiple lines). - - - ``-t``, ``--theme``: Interpret PATTERN as a predefined theme (perm, - cmake, g++, etc.) - - - ``-s``, ``--source``: Interpret PATTERN as a source code readable by - the Pygments library. If the first letter of PATTERN is upper case, - use the 256 colors mode, if it is lower case, use the 8 colors mode. - In 256 colors, interpret COLOR as a Pygments style (e.g. "default"). - - - REGULAR EXPRESSIONS - ``````````````````` - - A regular expression (or *regex*) is a pattern that describes a set of - strings that matches it. - - ``colout`` understands regex as specifed in the *re* python module. - Given that ``colout`` is generally called by the command line, you may - have to escape special characters that would be recognize by your shell. - - DEPENDENCIES - ```````````` - - Recommended packages : - - - ``argparse`` for a usable arguments parsing - - ``pygments`` for the source code syntax coloring - - ``babel`` for a locale-aware number parsing - - EXAMPLES - ```````` - - - Color in bold red every occurence of the word *color* in colout - sources: ``cat colout.py | colout color red bold`` - - - Color in bold violet home directories in */etc/passwd*: - ``colout '/home/[a-z]+' 135 < /etc/passwd`` - - - Use a different color for each line of the auth log - ``grep user /var/log/auth.log | colout "^.*$" rainbow`` - - - 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: - ``ls -l | colout '.(r.-){3}' yellow normal`` - - - Color in green read permission, in bold red write and execution ones: - ``ls -l | colout '(r)(w*)(x*)' green,red normal,bold`` - - - Color permissions with a predefined template: - ``ls -l | colout -t perm`` - - - Color in green comments in colout sources: - ``colout '.*(#.*)$' green normal < colout.py`` - - - Color permissions with a predefined template: - ``ls -l | colout -t perm`` - - - Color in green comments in colout sources: - ``colout '.*(#.*)$' green normal < colout.py`` - - - Color in light green comments in non-empty colout sources, with the - sharp in bold green: - ``grep -v '^\s*$' colout.py | colout '.*(#)(.*)$' green,119 bold,normal`` - - - Color in bold green every numbers and in bold red the words *error* - in make output: - ``make 2>&1 | colout '[0-9]+' green normal | colout error`` - - - 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>&³ | 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 begining of the command - is just bash magic to repeat the string "(\\w+)\\W+": - ``L=$(seq 10) ; P=${L//??/(\\w+)\\W+} ; head /var/log/auth.log | colout -g "^${P}(.*)$" rainbow`` - - - Color each line of a file with a different color among a 256 color - gradient from cyan to green: - ``head /var/log/auth.log | colout -c "^.*$" 39,38,37,36,35,34`` - - - Color a source code in 8 colors mode, without seeing comments: - ``cat colout.py | grep -v "#" | colout -s python`` - - - Color a source code in 256 colors mode: - ``cat colout.py | colout -s Python monokai`` - - - Color a JSON stream: - ``echo '{"foo": "lorem", "bar":"ipsum"}' | python -mjson.tool | colout -t json`` - - - 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`` - - - - -Platform: any -Classifier: Development Status :: 4 - Beta -Classifier: Environment :: Console -Classifier: Intended Audience :: End Users/Desktop -Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3) -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 2.5 -Classifier: Programming Language :: Python :: 2.6 -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3 -Classifier: Topic :: Text Processing -Classifier: Topic :: Utilities -Classifier: Topic :: Software Development :: Libraries :: Python Modules \ No newline at end of file diff --git a/README b/README deleted file mode 100644 index 7a5a505..0000000 --- a/README +++ /dev/null @@ -1,4 +0,0 @@ -Colout ------ - -This is the [colout](http://nojhan.github.com/colout/)'s pypi source diff --git a/README.md b/README.md new file mode 100644 index 0000000..10bde37 --- /dev/null +++ b/README.md @@ -0,0 +1,397 @@ +colout — Color Up Arbitrary Command Output +========================================== + +

+Colout logo +

+ +## Synopsis + +`colout [-h] [-r [RESOURCE]]` + +`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 + +`colout` read lines of text stream on the standard input and output characters +matching a given regular expression *PATTERN* in given *COLOR* and *STYLE*. + +If groups are specified in the regular expression pattern, only them are taken +into account, else the whole matching pattern is colored. + +You can specify several colors or styles when using groups by separating them +with commas. If you indicate more colors than groups, the last ones will be ignored. +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. + +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 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 default colormap +(the 255 available in the ANSI table, by default). +`random` will do the same in 8 colors mode. + +`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 GIMP palettes files (*.gpl) are available, you can also use their names as a +colormap (see the `-P` switch below). + +Note that the RGB colors (either the hex triplets or the palettes's colors) will +be converted to their nearest ANSI 256 color mode equivalents. + +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). + +To have a list of all colors, styles, special colormaps, themes, palettes and lexers, +use the `-r` switch (see below). + +`colout` is released under the GNU Public License v3. + + +## Installation + +The recomended method is using pip to install the package for the local user: + +```console +$ pip install --user colout +``` + +Another method is using [pipsi](https://github.com/mitsuhiko/pipsi) +(_pipsi is no longer maintained, _) +```console +$ pipsi install colout +``` + +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 +``` + +## Options + +* `-h`, `--help`: + Show a help message and exit + +* `-g`, `--groups`: + For color maps (like "rainbow"), iterate over matching groups in the pattern instead of over patterns. + +* `-c`, `--colormap`: + 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). + +* `-a`, `--all`: + Color the whole input at once instead of line per line + (really useful for coloring a source code file with strings on multiple lines). + +* `-t`, `--theme`: + Interpret PATTERN as a predefined theme (perm, cmake, g++, etc.). + +* `-T DIR`, `--themes-dir DIR`: + Search for additional themes (colout_*.py files) in this directory. + +* `-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. + +* `-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 + +A regular expression (or _regex_) is a pattern that describes a set of strings +that matches it. + +`colout` understands regex as specified in the _re_ python module. Given that +`colout` is generally called by the command line, you may have to escape +special characters that would be recognize by your shell. + + +## Dependencies + +Necessary Python modules: + +* `pygments` for the source code syntax coloring +* `babel` for a locale-aware number parsing + + +## Limitations + +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 + +### Simple + +* Color in bold red every occurrence of the word _color_ in colout sources: + `cat colout.py | colout color red bold` + +* 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`: + `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: + `ls -l | colout '.(r.-){3}' yellow normal` + +* Color in green read permission, in bold red write and execution ones: + `ls -l | colout '(r)(w*)(x*)' green,red normal,bold` + +* Color in green comments in colout sources: + `colout '.*(#.*)$' green normal < colout.py` + +* Color in bold green every numbers and in bold red the words _error_ in make output: + `make 2>&1 | colout '[0-9]+' green normal | colout error` + + +### Somewhat useful + +* Use a different color for each line of the auth log + `grep user /var/log/auth.log | colout "^.*$" rainbow` + +* Color each line of a file with a different color among a 256 color gradient from cyan to green: + `head /var/log/auth.log | colout -c "^.*$" 39,38,37,36,35,34` + +* Color permissions with a predefined template: + `ls -l | colout -t perm` + +* Color in light green comments in non-empty colout sources, with the sharp in bold green: + `grep -v '^\s*$' colout.py | colout '.*(#)(.*)$' green,119 bold,normal` + +* 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++` + +* 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+": + `L=$(seq 10) ; P=${L//??/(\\w+)\\W+} ; head /var/log/auth.log | colout -g "^${P}(.*)$" rainbow` + +* Color source code in 8 colors mode, without seeing comments: + `cat colout.py | grep -v "#" | colout -s python` + +* Color source code in 256 color mode: + `cat colout.py | colout -s Python monokai` + +* Color a JSON stream: + `echo '{"foo": "lorem", "bar":"ipsum"}' | python -mjson.tool | colout -t json` + +* 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: + +```bash +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 deleted file mode 100644 index aedc3eb..0000000 --- a/bin/colout +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -# Copyright @ 2013 Martin Ueding - -# Small launcher script for the main module. - -# Licence: GPL 3 - -set -e -set -u - -python -m colout.colout "$@" diff --git a/colout/colout.py b/colout/colout.py index 0ac5a39..2d51fac 100755 --- a/colout/colout.py +++ b/colout/colout.py @@ -1,45 +1,219 @@ #!/usr/bin/env python3 #encoding: utf-8 -# Color Up Arbitrary Command Ouput +# Color Up Arbitrary Command Output # Licensed under the GPL version 3 # 2012 (c) nojhan -import re -import random import os +import re +import sys +import copy 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 -########### -# Library # -########### +# 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 -styles = { +context["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 -} +error_codes = {"UnknownColor": 1, "DuplicatedPalette": 2, "MixedModes": 3, "UnknownLexer": 4, "UnknownResource": 5} -ansi_min = 16 -ansi_max = 232 +# 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"] -def rgb_rainbow( x, freq = 1.0/(256.0/math.pi) ): - scope = (ansi_max - ansi_min)/2.0 - red = ansi_min + scope * (1+math.sin( 2*freq*x + math.pi/2 )) - green = ansi_min + scope * (1+math.sin( 2*freq*x - math.pi/2 )) - blue = ansi_min + scope * (1+math.sin( freq*x - math.pi/2 )) - return ( red, green, blue ) +# 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 -def rgb_to_ansi( red, green, blue ): +############################################################################### +# 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) + + Return the filename (without path and extension) and a list of ordered + colors. + Generally, the colors are RGB triplets, thus this function returns: + (name, [ [R0,G0,B0], [R1,G1,B1], ... , [RN,GN,BN] ]) + """ + + logging.debug("parse GIMP palette file: %s" % filename) + fd = open(filename) + # remove path and extension, only keep the file name itself + name = os.path.splitext( os.path.basename(filename ))[0] + + # The first .gpl line is a header + assert( fd.readline().strip() == "GIMP Palette" ) + + # Then the full name of the palette + long_name = fd.readline().strip() + + # Then the columns number. + # split on colon, take the second argument as an int + line = fd.readline() + if "Columns:" in line: + columns = int( line.strip().split(":")[1].strip() ) + lines = fd.readlines() + else: + columns=3 + lines = [line] + fd.readlines() + + # Then the colors themselves. + palette = [] + for line in lines: + # skip lines with only a comment + if re.match(r"^\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] ] + palette.append( colors ) + + logging.debug("parsed %i RGB colors from palette %s" % (len(palette), name) ) + return name,palette + + +def uniq( lst ): + """Build a list with uniques consecutive elements in the argument. + + >>> uniq([1,1,2,2,2,3]) + [1,2,3] + >>> uniq([0,1,1,2,3,3,3]) + [0,1,2,3] + """ + assert( len(lst) > 0 ) + uniq = [ lst[0] ] + for i in range(1,len(lst)): + if lst[i] != lst[i-1]: + uniq.append(lst[i]) + return uniq + + +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* + # limit RGB values to ansi_max + red,green,blue = tuple([ansi_max if c>ansi_max else c for c in (r,g,b)]) offset = 42.5 is_gray = True @@ -51,55 +225,298 @@ def rgb_to_ansi( red, green, blue ): if all_gray: val = ansi_max + round( (red + green + blue)/33.0 ) - return int(val) + res = int(val) else: val = ansi_min for color,modulo in zip( [red, green, blue], [6*6, 6, 1] ): val += round(6.0 * (color / 256.0)) * modulo - return int(val) + res = int(val) + + return res -rainbow = ["magenta", "blue", "cyan", "green", "yellow", "red"] -colormap = rainbow # default colormap to rainbow -colormap_idx = 0 +def hex_to_rgb(h): + assert( h[0] == "#" ) + h = h.lstrip('#') + lh = len(h) + return tuple( int(h[i:i+lh//3], 16) for i in range(0, lh, lh//3) ) -scale = (0,100) -# Escaped end markers for given color modes -endmarks = {8: ";", 256: ";38;5;"} +############################################################################### +# Load available extern resources +############################################################################### -# load available themes -themes = {} -themes_dir=os.path.dirname(os.path.realpath(__file__)) -os.chdir( themes_dir ) -for f in glob.iglob("colout_*.py"): - module = ".".join(f.split(".")[:-1]) - name = "_".join(module.split("_")[1:]) - themes[name] = __import__(module) +def load_themes( themes_dir): + global context + logging.debug("search for themes in: %s" % themes_dir) + sys.path.append( themes_dir ) -# load available pygments lexers -lexers = [] -try: - from pygments.lexers import get_all_lexers - from pygments.lexers import get_lexer_by_name - from pygments import highlight - from pygments.formatters import Terminal256Formatter - from pygments.formatters import TerminalFormatter -except ImportError: - pass -else: - for lexer in get_all_lexers(): + # 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"]: + raise DuplicatedTheme(name) + logging.debug("load theme %s" % name) + context["themes"][name] = importlib.import_module(module) + + +def load_palettes( palettes_dir, ignore_duplicates = True ): + global context + logging.debug("search for palettes in: %s" % palettes_dir) + + # load available colormaps (GIMP palettes format) + for p in glob.iglob(os.path.join(palettes_dir, "*.gpl")): try: - lexers.append(lexer[1][0]) - except IndexError: - pass + 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) + # 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 + + +def load_lexers(): + global context + # 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: + 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: + logging.warning("cannot load lexer: %s" % lexer[1][0]) + pass # Forget about this lexer. + 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 -def colorin(text, color="red", style="normal"): +def load_resources( themes_dir, palettes_dir ): + load_themes( themes_dir ) + load_palettes( palettes_dir ) + load_lexers() + + +############################################################################### +# 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] + "" + + +def colorin(text, color="red", style="normal", sep_pair=context["sep_pair"]): """ 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. @@ -110,125 +527,118 @@ def colorin(text, color="red", style="normal"): >>> colout.colorin("Faites chier la vache", 41, "normal") '\x1b[0;38;5;41mFaites chier la vache\x1b[0m' """ - global colormap_idx + + assert( type(color) is str ) + + 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(styles.keys())) + style = random.choice(list(context["styles"].keys())) else: - if style in styles: - style_code = str(styles[style]) + 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 color == "none": + 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 no color, style cannot be applied - return text - - 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 == "rainbow": - mode = 8 - color = colormap[colormap_idx] - color_code = str(30 + colors[color]) - - if colormap_idx < len(colormap)-1: - colormap_idx += 1 - else: - colormap_idx = 0 - - elif color == "Rainbow": - mode = 256 - color_nb = rgb_to_ansi( *rgb_rainbow( colormap_idx ) ) - color_code = str( color_nb ) - - if colormap_idx < 255: - colormap_idx += 1 - else: - colormap_idx = 0 - - elif color == "scale": - try: - import babel.numbers as bn - f = float(bn.parse_decimal(text)) - except ImportError: - f = float(text) - - # if out of scale, do not color - if f < scale[0] or f > scale[1]: + if not debug: return text + else: + return ""+text+"" - # normalize and scale over the nb of colors in colormap - i = int( math.ceil( (f - scale[0]) / (scale[1]-scale[0]) * (len(colormap)-1) ) ) + elif color.lower() == "random": + color_code = color_random( color ) - mode = 8 - color = colormap[i] - color_code = str(30 + colors[color]) + elif color.lower() == "scale": # "scale" or "Scale" + color_code = color_scale( color, text ) + # "hash" or "Hash"; useful to randomly but consistently color strings + elif color.lower() == "hash": + color_code = color_hash( color, text ) + + # The user can change the "colormap" variable to its favorite one before calling colorin. elif color == "colormap": - 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) + # "default" should have been set to the user-defined colormap. + color,color_code = color_map("default") - if colormap_idx < len(colormap)-1: - colormap_idx += 1 - else: - colormap_idx = 0 + # 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 ) # 8 colors modes - elif color in colors: - mode = 8 - color_code = str(30 + colors[color]) + elif color in context["colors"]: + color_code = str(30 + context["colors"][color]) + + # hexadecimal color + elif color[0] == "#": + 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 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. - return highlight(text, lexer, formatter)[:-1] + elif color.lower() in context["lexers"]: + # bypass color encoding and return text colored by the lexer + return color_lexer(color,style,text) # unrecognized else: - raise Exception('Unrecognized color %s' % color) + raise UnknownColor(color) - return start + style_code + endmarks[mode] + color_code + "m" + text + stop + 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 = "" + 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 + "" def colorout(text, match, prev_end, color="red", style="normal", group=0): @@ -244,7 +654,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): +def colorup(text, pattern, color="red", style="normal", on_groups=False, sep_list=context["sep_list"]): """ Color up every characters that match the given regexp patterns. If groups are specified, only color up them and not the whole pattern. @@ -253,22 +663,15 @@ def colorup(text, pattern, color="red", style="normal", on_groups=False): 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. - - >>> 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 - regex = re.compile(pattern) # , re.IGNORECASE) + + global context + global debug + + if not debug: + regex = re.compile(pattern) + else: + regex = re.compile(pattern, re.DEBUG) # Prepare the colored text. colored_text = "" @@ -287,17 +690,17 @@ def colorup(text, pattern, color="red", style="normal", on_groups=False): # 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(",") + colors_l = color.split(sep_list) group_colors = colors_l + [colors_l[-1]] * (nb_groups - len(colors_l)) # Same for styles - styles_l = style.split(",") + styles_l = style.split(sep_list) 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 - colormap_idx = 0 + context["colormap_idx"] = 0 # For each group index. # Note that match.groups returns a tuple (thus being indexed in [0,n[), @@ -326,20 +729,29 @@ def colortheme(item, theme): Used to read themes, which can be something like: [ [ pattern, colors, styles ], [ pattern ], [ pattern, colors ] ] """ + # logging.debug("use a theme with %i arguments" % len(theme)) for args in theme: item = colorup(item, *args) return item -def write(colored): +def write(colored, stream = sys.stdout): """ Write "colored" on sys.stdout, then flush. """ - sys.stdout.write(colored) - sys.stdout.flush() + try: + stream.write(colored) + stream.flush() + + # Silently handle broken pipes + except IOError: + try: + stream.close() + except IOError: + pass -def map_write( stream, function, *args ): +def map_write( stream_in, stream_out, function, *args ): """ Read the given file-like object as a non-blocking stream and call the function on each item (line), @@ -352,15 +764,17 @@ def map_write( stream, function, *args ): """ while True: try: - item = stream.readline() + item = stream_in.readline() + except UnicodeDecodeError: + continue except KeyboardInterrupt: break if not item: break - write( function(item, *args) ) + write( function(item, *args), stream_out ) -def colorgen(stream, pattern, color="red", style="normal", on_groups=False): +def colorgen(stream, pattern, color="red", style="normal", on_groups=False, sep_list=context["sep_list"]): """ A generator that colors the items given in an iterable input. @@ -376,72 +790,14 @@ def colorgen(stream, pattern, color="red", style="normal", on_groups=False): break if not item: break - yield colorup(item, pattern, color, style, on_groups) + yield colorup(item, pattern, color, style, on_groups, sep_list) ###################### # Command line tools # ###################### -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) - """ - import sys - - # 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], " [] [] []") - print("\tAvailable colors:", " ".join(colors)) - print("\tAvailable styles:", " ".join(styles)) - print("Example:", argv[0], "'^(def)\s+(\w*).*$' blue,magenta italic,bold < colout.py") - sys.exit(1) - - assert(len(argv) >= 2) - # Get mandatory arguments - pattern = argv[1] - - # default values for optional args - color = "red" - style = "normal" - on_stderr = False - - if len(argv) >= 3: - color = argv[2] - if len(argv) >= 4: - style = argv[3] - if len(argv) == 5: - on_groups = bool(argv[4]) - if len(argv) == 6: - as_colormap = bool(argv[5]) - if len(argv) == 7: - as_theme = bool(argv[6]) - if len(argv) == 8: - as_source = bool(argv[7]) - if len(argv) == 9: - as_all = bool(argv[8]) - if len(argv) == 10: - scale = bool(argv[9]) - - return pattern, color, style, on_groups, as_colormap, as_theme, as_source, as_all, scale - - -def __args_parse__(argv, usage=""): +def _args_parse(argv, usage=""): """ Parse command line arguments with the argparse library. Returns a tuple of (pattern,color,style,on_stderr). @@ -452,27 +808,30 @@ def __args_parse__(argv, usage=""): parser.add_argument("pattern", metavar="REGEX", type=str, nargs=1, help="A regular expression") + pygments_warn=" You can use a language name to activate syntax coloring (see `-r all` for a list)." + parser.add_argument("color", metavar="COLOR", type=str, nargs='?', default="red", - help="A number in [0…255], one of the available colors or a comma-separated list of values. \ - Available colors: "+", ".join(colors)+ \ - ". Available special colors: none, random, Random, rainbow, Rainbow, scale") + help="A number in [0…255], a color name, a colormap name, \ + a palette or a comma-separated list of those values." + pygments_warn) parser.add_argument("style", metavar="STYLE", type=str, nargs='?', default="bold", - help="One of the available styles or a comma-separated list of styles.\ - Available styles: "+", ".join(styles)) + help="One of the available styles or a comma-separated list of styles.") parser.add_argument("-g", "--groups", action="store_true", - help="For color maps (random, rainbow), iterate over matching groups \ + help="For color maps (random, rainbow, etc.), iterate over matching groups \ in the pattern instead of over patterns") parser.add_argument("-c", "--colormap", action="store_true", - help="Use the given colors as a colormap (cycle the colors at each match)") + help="Interpret the given COLOR comma-separated list of colors as a colormap \ + (cycle the colors at each match)") - parser.add_argument("-l", "--scale", - help="When using the 'scale' colormap, parse matches as decimal numbers (taking your locale into account) \ - and apply the rainbow colormap linearly between the given SCALE=min,max") + babel_warn=" (numbers will be parsed according to your locale)" + + parser.add_argument("-l", "--scale", metavar="SCALE", + help="When using the 'scale' colormap, parse matches as decimal numbers \ + and apply the rainbow colormap linearly between the given SCALE=min,max" + babel_warn) parser.add_argument("-a", "--all", action="store_true", help="Color the whole input at once instead of line per line \ @@ -480,81 +839,257 @@ def __args_parse__(argv, usage=""): on multiple lines).") parser.add_argument("-t", "--theme", action="store_true", - help="Interpret REGEX as a theme. \ - Available themes: "+", ".join(themes.keys())) + help="Interpret REGEX as a theme.") + + parser.add_argument("-T", "--themes-dir", metavar="DIR", action="append", + help="Search for additional themes (colout_*.py files) in the given directory") + + parser.add_argument("-P", "--palettes-dir", metavar="DIR", action="append", + help="Search for additional palettes (*.gpl files) in the given directory") + + parser.add_argument("-d", "--default", metavar="COLORMAP", default=None, + help="When using special colormaps (`random`, `scale` or `hash`), use this COLORMAP. \ + 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 \ + (8 or 256 colors).") + + # This normally should be an option with an argument, but this would end in an error, + # as no regexp is supposed to be passed after calling this option, + # we use it as the argument to this option. + # The only drawback is that the help message lacks a metavar... + parser.add_argument("-r", "--resources", action="store_true", + help="Print the names of available resources. Use a comma-separated list of resources names \ + (styles, colors, special, themes, palettes, colormaps or lexers), \ + use 'all' to print everything.") parser.add_argument("-s", "--source", action="store_true", help="Interpret REGEX as a source code readable by the Pygments library. \ - If the first letter of PATTERN is upper case, use the 256 colors mode, \ - if it is lower case, use the 8 colors mode. \ - Interpret COLOR as a Pygments style. \ - Available languages: "+", ".join(lexers)) + If the first letter of PATTERN is upper case, use the 256 colors mode, \ + if it is lower case, use the 8 colors mode. \ + Interpret COLOR as a Pygments style." + pygments_warn) + + parser.add_argument("-e", "--sep-list", metavar="CHAR", default=",", type=str, + help="Use this character as a separator for list of colors/resources/numbers (instead of comma).") + + parser.add_argument("-E", "--sep-pair", metavar="CHAR", default=".", type=str, + help="Use this character as a separator for foreground/background pairs (instead of period).") + + parser.add_argument("--debug", action="store_true", + help="Debug mode: print what's going on internally, useful if you want to check what features are available.") + + # HACK: Mock up "--resources ALL" if just "--resources" on command line + if (len(sys.argv) == 2 and (sys.argv[1] in ["-r", "--resources"])): + sys.argv.append("ALL") args = parser.parse_args() return args.pattern[0], args.color, args.style, args.groups, \ - args.colormap, args.theme, args.source, args.all, args.scale + args.colormap, args.theme, args.source, args.all, args.scale, args.debug, args.resources, args.palettes_dir, \ + args.themes_dir, args.default, args.sep_list, args.sep_pair -def stdin_write( as_all, function, *args ): +def write_all( as_all, stream_in, stream_out, function, *args ): """ If as_all, print function(*args) on the whole stream, else, print it for each line. """ if as_all: - write( function( sys.stdin.read(), *args ) ) + write( function( stream_in.read(), *args ), stream_out ) else: - map_write( sys.stdin, function, *args ) + map_write( stream_in, stream_out, function, *args ) + + +def main(): + global context + usage = "A regular expression based formatter that color up an arbitrary text stream." + + ##################### + # Arguments parsing # + ##################### + pattern, color, style, on_groups, as_colormap, as_theme, as_source, as_all, myscale, \ + debug, resources, palettes_dirs, themes_dirs, default_colormap, sep_list, sep_pair \ + = _args_parse(sys.argv, usage) + + if debug: + lvl = logging.DEBUG + else: + lvl = logging.ERROR + + logging.basicConfig(format='[colout] %(levelname)s: %(message)s', level=lvl) + + + ################## + # Load resources # + ################## + + context["sep_list"] = sep_list + logging.debug("Color list separator: '%s'" % context["sep_list"]) + context["sep_pair"] = sep_pair + logging.debug("Color pair separator: '%s'" % context["sep_pair"]) + + try: + # Search for available resources files (themes, palettes) + # in the same dir as the colout.py script + res_dir = os.path.dirname(os.path.realpath(__file__)) + + # this must be called before args parsing, because the help can list available resources + load_resources( res_dir, res_dir ) + + # try additional directories if asked + if palettes_dirs: + for adir in palettes_dirs: + if os.path.isdir(adir): + load_palettes( adir ) + else: + logging.warning("cannot read palettes directory %s, ignore it" % adir) + + if themes_dirs: + for adir in themes_dirs: + if os.path.isdir(adir): + load_themes( adir ) + else: + logging.warning("cannot read themes directory %s, ignore it" % adir) + + except DuplicatedPalette as e: + logging.error( "duplicated palette file name: %s" % e ) + sys.exit( error_codes["DuplicatedPalette"] ) + + # if debug: + # setting = pprint.pformat(context, depth=2) + # logging.debug(setting) + + if resources: + asked=[r.lower() for r in pattern.split(context["sep_list"])] + + def join_sort( l ): + """ + Sort the given list in lexicographical order, + with upper-cases first, then lower cases + join the list with a comma. + + >>> join_sort(["a","B","A","b"]) + 'A, a, B, b' + """ + return ", ".join(sorted(l, key=lambda s: s.lower()+s)) + + # print("Available resources:") + resources_not_found = [] + for res in asked: + resource_found = False + + if "style" in res or "all" in res: + print("STYLES: %s" % join_sort(context["styles"]) ) + resource_found = True + + if "color" in res or "all" in res: + print("COLORS: %s" % join_sort(context["colors"]) ) + resource_found = True + + if "special" in res or "all" in res: + print("SPECIAL: %s" % join_sort(["random", "Random", "scale", "Scale", "hash", "Hash", "colormap"]) ) + resource_found = True + + if "theme" in res or "all" in res: + if len(context["themes"]) > 0: + print("THEMES: %s" % join_sort(context["themes"].keys()) ) + else: + print("NO THEME") + resource_found = True + + if "colormap" in res or "all" in res: + if len(context["colormaps"]) > 0: + print("COLORMAPS: %s" % join_sort(context["colormaps"]) ) + else: + print("NO COLORMAPS") + resource_found = True + + if "lexer" in res or "all" in res: + if len(context["lexers"]) > 0: + print("SYNTAX COLORING: %s" % join_sort(context["lexers"]) ) + else: + print("NO SYNTAX COLORING (check that python3-pygments is installed)") + resource_found = True + + if not resource_found: + resources_not_found.append(res) + + if resources_not_found: + logging.error( "Unknown resources: %s" % ", ".join(resources_not_found) ) + sys.exit( error_codes["UnknownResource"] ) + + sys.exit(0) # not an error, we asked for help + + ############ + # Coloring # + ############ + + try: + if myscale: + context["scale"] = tuple([float(i) for i in myscale.split(context["sep_list"])]) + logging.debug("user-defined scale: %f,%f" % context["scale"]) + + # Default color maps + if default_colormap: + if default_colormap not in context["colormaps"]: + cmap = make_colormap(default_colormap,context["sep_list"]) + + elif default_colormap in context["colormaps"]: + cmap = context["colormaps"][default_colormap] + + set_special_colormaps( cmap, context["sep_list"] ) + + # explicit color map + if as_colormap is True and color not in context["colormaps"]: + context["colormaps"]["Default"] = make_colormap(color,context["sep_list"]) # replace the colormap by the given colors + context["colormaps"]["default"] = make_colormap(color,context["sep_list"]) # replace the colormap by the given colors + color = "colormap" # use the keyword to switch to colormap instead of list of colors + logging.debug("used-defined default colormap: %s" % context["sep_list"].join(context["colormaps"]["Default"]) ) + + # if theme + if as_theme: + logging.debug( "asked for theme: %s" % pattern ) + assert(pattern in context["themes"].keys()) + context,theme = context["themes"][pattern].theme(context) + write_all( as_all, sys.stdin, sys.stdout, colortheme, theme ) + + # if pygments + elif as_source: + logging.debug("asked for lexer: %s" % pattern.lower()) + if pattern.lower() not in context["lexers"]: + logging.error("Lexer %r is not available. Run with \"--resources all\" to see the options.") + sys.exit(error_codes["UnknownLexer"]) + lexer = get_lexer_by_name(pattern.lower()) + # Python => 256 colors, python => 8 colors + ask_256 = pattern[0].isupper() + if ask_256: + logging.debug("256 colors mode") + try: + formatter = Terminal256Formatter(style=color) + except: # style not found + logging.warning("style %s not found, fallback to default style" % color) + formatter = Terminal256Formatter() + else: + logging.debug("8 colors mode") + formatter = TerminalFormatter() + + write_all( as_all, sys.stdin, sys.stdout, highlight, lexer, formatter ) + + # if color + else: + write_all( as_all, sys.stdin, sys.stdout, colorup, pattern, color, style, on_groups, context["sep_list"] ) + + except UnknownColor as e: + if debug: + import traceback + print(traceback.format_exc()) + logging.error("Unknown color: %s (maybe you forgot to install python3-pygments?)" % e ) + sys.exit( error_codes["UnknownColor"] ) + + except MixedModes as e: + logging.error("You cannot mix up color modes when defining your own colormap." \ + + " Check the following 'color:mode' pairs: %s." % e ) + sys.exit( error_codes["MixedModes"] ) if __name__ == "__main__": - import sys - - usage = "A regular expression based formatter that color up an arbitrary text stream." - - try: - import argparse - - # if argparse is not installed - except ImportError: - pattern, color, style, on_groups, as_colormap, as_theme, as_source, as_all, myscale \ - = __args_dirty__(sys.argv, usage) - - # if argparse is available - else: - pattern, color, style, on_groups, as_colormap, as_theme, as_source, as_all, myscale \ - = __args_parse__(sys.argv, usage) - - if myscale: - scale = map(int,myscale.split(",")) - - # use the generator: output lines as they come - if as_colormap is True and color != "rainbow": - colormap = color.split(",") # replace the colormap by the given colors - color = "colormap" # use the keyword to switch to colormap instead of list of colors - - # if theme - if as_theme: - assert(pattern in themes.keys()) - stdin_write( as_all, colortheme, themes[pattern].theme() ) - - # if pygments - elif as_source: - assert(pattern.lower() in lexers) - lexer = get_lexer_by_name(pattern.lower()) - # Python => 256 colors, python => 8 colors - ask_256 = pattern[0].isupper() - if ask_256: - try: - formatter = Terminal256Formatter(style=color) - except: # style not found - formatter = Terminal256Formatter() - else: - formatter = TerminalFormatter() - - stdin_write( as_all, highlight, lexer, formatter ) - - # if color - else: - stdin_write( as_all, colorup, pattern, color, style, on_groups ) - + main() diff --git a/colout/colout_catch2.py b/colout/colout_catch2.py new file mode 100644 index 0000000..e67d86a --- /dev/null +++ b/colout/colout_catch2.py @@ -0,0 +1,23 @@ +def theme(context): + + return context,[ + ["^ (Start)(.*): (.*):(.*)$", "yellow", "normal,normal,normal,bold"], # Test start. + # path file ext:line : + ["^(tests): (/.*?)/([^/:]+):([0-9]+): (.*)", "yellow,none,white,yellow,red", "bold,normal,bold,normal,bold"], + ["(`)(.*)('.*)", "red,Cpp,red", "bold,normal,bold"], + [r"^\.+$", "yellow", "bold"], + ["^=+$", "yellow", "bold"], + ["(/.*?)/([^/:]+):([0-9]+): (FAILED):", "white,white,yellow,red", "normal,bold,normal,bold"], + [r"(REQUIRE\(|CHECK\(|REQUIRE_THAT\()(.*)(\))$","yellow,Cpp,yellow","bold,normal,bold"], + # Hide uninteresting stuff: + ["[0-9]+/[0-9]+ Test.*","blue"], + ["^Filters:.*","blue"], + ["^Randomness seeded to:.*","blue"], + ["^tests is a Catch2.*","blue"], + ["^Run with.*", "blue"], + ["^~+$","blue"], + ["^-+$","blue"], + [r"^\s*(Scenario:|Given:|When:|Then:).*","blue"], + ["^(/.*?)/([^/:]+):([0-9]+)", "blue"], + ["^(test cases|assertions)(.*)", "blue"], + ] diff --git a/colout/colout_clang.py b/colout/colout_clang.py new file mode 100644 index 0000000..a5525ef --- /dev/null +++ b/colout/colout_clang.py @@ -0,0 +1,85 @@ +#encoding: utf-8 + +def default_gettext( msg ): + return msg + +def theme(context): + import os + import gettext + import locale + + section="blue" + + # get g++ version + gv = os.popen("g++ -dumpversion").read().strip() + + # get the current translations of gcc + try: + t = gettext.translation("gcc-"+gv) + except IOError: + _ = default_gettext + else: + _ = t.gettext + # _("msg") will return the given message, translated + + # if the locale is unicode + enc = locale.getpreferredencoding() + if "UTF" in enc: + # gcc will use unicode quotes + qo = "[‘`]" + qc = "[’']" + else: + # rather than ascii ones + qo = "['`]" + qc = "'" + + return context,[ + # Command line + [ r"[/\s]([cg]\+\+-*[0-9]*\.*[0-9]*)", "white", "bold" ], + [ r"\s(\-D)(\s*[^\s]+)", "none,green", "normal,bold" ], + [ r"\s(-g)", "green", "normal" ], + [ r"\s-O[0-4]", "green", "normal" ], + [ r"\s-[Wf][^\s]*", "magenta", "normal" ], + [ r"\s-pedantic", "magenta", "normal" ], + [ r"\s(-I)(/*[^\s]+/)([^/\s]+)", "none,blue", "normal,normal,bold" ], + [ r"\s(-L)(/*[^\s]+/)([^/\s]+)", "none,cyan", "normal,normal,bold" ], + [ r"\s(-l)([^/\s]+)", "none,cyan", "normal,bold" ], + [ r"\s-[oc]", "red", "bold" ], + [ r"\s(-+std(?:lib)?)=?([^\s]+)", "red", "normal,bold" ], + + # Important messages + [ _("error: "), "red", "bold" ], + [ _("fatal error: "), "red", "bold" ], + [ _("warning: "), "magenta", "bold" ], + [ _("undefined reference to "), "red", "bold" ], + # [-Wflag] + [ r"\[-W.*\]", "magenta"], + + # Highlight message start: + # path file ext : line : col … + [ "(/.*?)/([^/:]+): (In .*)"+qo, + section, + "normal,normal,bold" ], + + [ "(/.*?)/([^/:]+): (At .*)", + section, + "normal,normal,bold" ], + + [ _("In file included from"), section ], + + # Highlight locations: + # path file ext : line : col … + [ "(/.*?)/([^/:]+):([0-9]+):*([0-9]*)(.*)", + "none,white,yellow,none,none", + "normal,normal,normal,normal" ], + + # source code in single quotes + [ qo+"(.*?)"+qc, "Cpp", "monokai" ], + + # source code after a "note: candidate are/is:" + [ _("note: ")+"((?!.*("+qo+"|"+qc+")).*)$", "Cpp", "monokai" ], + # [ _("note: ")+"(candidate:)(.*)$", "green,Cpp", "normal,monokai" ], + # after the code part, to avoid matching ANSI escape chars + [ _("note: "), "green", "normal" ] + ] + diff --git a/colout/colout_cmake.py b/colout/colout_cmake.py index 2752a22..66ab5a8 100644 --- a/colout/colout_cmake.py +++ b/colout/colout_cmake.py @@ -1,12 +1,20 @@ -def theme(): +def theme(context): # CMake theme: # actions performing in cyan performing="cyan" # actions performed in green performed="green" + # actions taking an unknown time + untimed="blue" - return [ + # If the user do not ask for his own colormap + if not context["user_defined_colormaps"]: + # A palette that goes: purple, orange, white + percs = [45, 39, 33, 27, 21, 57, 63, 62, 98, 97, 133, 132, 138, 173, 172, 208, 214, 220, 226, 228, 229, 230, 231, 255] + context["colormaps"]["Scale"] = percs + + return context,[ # Configure... [ "^--.*works", performed ], [ "^--.*done", performed ], @@ -17,23 +25,31 @@ def theme(): [ "^-- Configuring incomplete, errors occurred!", "red" ], [ "^--.*", performing ], # Errors - [ "CMake Error:", "red" ], - [ "CMake Warning", "yellow" ], + [ "CMake Error", "red" ], + [ "CMake Warning", "magenta" ], + [ "CMake Deprecation Warning", "magenta" ], # Scan [ "^(Scanning dependencies of target)(.*)$", performing, "normal,bold" ], - # Link - [ "^(Linking .* (library|executable) )(.*/)+(.+(\.[aso]+)*)$", - performing, "normal,normal,bold" ], + # Link (make) + # [ "^(Linking .* )(library|executable) (.*/)*(.+(\.[aso]+)*)$", + [ "^(Linking .* )(library|executable) (.*)$", + untimed, "normal,normal,bold" ], + # [percent] Creating something + [ r"^\[\s*[0-9/]+%?\]\s(.*Creating.*)$", + performing, "normal" ], # [percent] Built - [ "^\[\s*[0-9]+%\]\s(Built target)(\s.*)$", + [ r"^\[\s*[0-9/]+%?\]\s(Built target)(\s.*)$", performed, "normal,bold" ], # [percent] Building - [ "^\[\s*[0-9]+%\]\s(Building \w* object)(\s.*/)(\w+.c.*)(.o)$", - performing, "normal,normal,bold,normal"], + [ r"^\[\s*[0-9/]+%?\]\s(Building \w* object)\s+(.*)(\.dir)(.*/)([-\w]+).c.*.o$", + performing+","+performing+","+performing+",Hash,"+performing, "normal,normal,normal,normal,bold"], + # [percent] Generating + [ r"^\[\s*[0-9/]+%?\]\s(Generating)(\s+.*)$", + performing, "normal,bold"], # make errors - [ "make\[[0-9]+\].*", "yellow"], - [ "(make: \*\*\* \[.+\] )(.* [0-9]+)", "red", "normal,bold"], - # progress percentage - [ "^\[\s*([0-9]+)%\]","scale" ] + [ r"make\[[0-9]+\].*", "yellow"], + [ r"(make: \*\*\* \[.+\] )(.* [0-9]+)", "red", "normal,bold"], + # progress percentage (make) + [ r"^(\[\s*[0-9]+%\])","Scale" ] ] diff --git a/colout/colout_configure.py b/colout/colout_configure.py new file mode 100644 index 0000000..63e63a4 --- /dev/null +++ b/colout/colout_configure.py @@ -0,0 +1,16 @@ +#encoding: utf-8 + +def theme(context): + + return context, [ + ["^(checking .*)(yes|found|ok)$","green", "normal,bold"], + ["^(checking .*)(no|none)$", "yellow", "normal,bold"], + ["^(configure:) (error:)(.*)", "red","normal,bold"], + ["^(configure:)(.*)", "magenta","normal,bold"], + ["^(checking .*)", "blue",""], + ["^(config.status:) (creating|linking)(.*)", "cyan,blue","normal,normal,bold"], + ["^(config.status:) (executing )(.*)", "cyan,green","normal,normal,bold"], + ["^(config.status:) (.*)(is unchanged)", "cyan,green","normal,normal,bold"], + [r"^\s*(Build.*)(yes)$","green", "normal,bold"], + [r"^\s*(Build.*)(no)$","yellow", "normal,bold"], + ] diff --git a/colout/colout_ctest.py b/colout/colout_ctest.py new file mode 100644 index 0000000..f2dd372 --- /dev/null +++ b/colout/colout_ctest.py @@ -0,0 +1,19 @@ + +def theme(context): + # CTest theme: + passed="green" + notrun="yellow" + notpassed="red" + + # If the user do not ask for his own colormap + # if not context["user_defined_colormaps"]: + # # A palette that goes: purple, orange, white + # percs = [45, 39, 33, 27, 21, 57, 63, 62, 98, 97, 133, 132, 138, 173, 172, 208, 214, 220, 226, 228, 229, 230, 231, 255] + # context["colormaps"]["Scale"] = percs + + return context,[ + # Passed + [ r"^\s*[0-9]+/[0-9]+ Test\s+#[0-9]+: (.*)\s+\.+\s+(Passed)", "blue,"+passed], + [ r"^\s*[0-9]+/[0-9]+ Test\s+#[0-9]+: (.*)\s+\.+(\*{3}Not Run.*)\s+.*", "blue,"+notrun], + [ r"^\s*[0-9]+/[0-9]+ Test\s+#[0-9]+: (.*)\s+\.+(.*\*{3}.*)\s+.*", "blue,"+notpassed], + ] diff --git a/colout/colout_django.py b/colout/colout_django.py new file mode 100644 index 0000000..fcacc00 --- /dev/null +++ b/colout/colout_django.py @@ -0,0 +1,36 @@ + +def theme(context): + return context,[ + # Waiting + ["^Waiting for .*$", "red", "bold"], + [".*Sending.*", "green"], + # Watches + [r"^(Watching) (\S*) (.*)", "yellow", "bold,bold,normal"], + [".*reloading.$","yellow"], + # File from python/lib + [r"^(File) (/.*/lib/python[^/]*/site-packages/)([^/]*)\S* (first seen) (with mtime [0-9]*.*)$", + "blue,blue,white,blue,blue", "bold,normal,bold,bold,normal"], + # File from app (last 3 name highlighted) + [r"^(File) (/\S*/)(\S*/\S*/)(\S*) (first seen) (with mtime [0-9]*.*)$", + "magenta,magenta,white,white,magenta,magenta", "bold,normal,normal,bold,bold,normal"], + # SQL + ["(.*)(SELECT)(.*)(FROM)(.*)", + "green", "normal,bold,normal,bold,normal"], + ["(.*)(SELECT)(.*)(FROM)(.*)(WHERE)(.*)", + "green", "normal,bold,normal,bold,normal,bold,normal"], + # HTTP + [r"\"(GET) (\S*) (HTTP\S*)\" ([0-9]+) (.*)$", + "green,white,green,green,green", "bold,bold,normal,bold,normal"], + # Errors + ["(Exception) (while .*) '(.*)' (in) (.*) '(.*)'", "red,red,white,red,red,white", "bold,normal,bold,bold,normal,bold"], + ["(.*Error): (.*) '(.*)'", "red,red,white", "bold,normal,bold"], + [r"(django[^:\s]*)\.([^.:\s]*): (.*)", "red","normal,bold,normal"], + ["Traceback.*:","yellow"], + ["During handling.*","yellow"], + # File, line, in + [ + r"^\s{2}(File \")(/*.*?/)*([^/:]+)(\", line) ([0-9]+)(, in) (.*)$", + "blue, none, white,blue, yellow,blue", + "normal,normal,bold, normal,normal,bold" + ], + ] diff --git a/colout/colout_g++.py b/colout/colout_g++.py index d10e320..a5525ef 100644 --- a/colout/colout_g++.py +++ b/colout/colout_g++.py @@ -3,11 +3,13 @@ def default_gettext( msg ): return msg -def theme(): +def theme(context): import os import gettext import locale + section="blue" + # get g++ version gv = os.popen("g++ -dumpversion").read().strip() @@ -24,26 +26,47 @@ def theme(): enc = locale.getpreferredencoding() if "UTF" in enc: # gcc will use unicode quotes - qo = "‘" - qc = "’" + qo = "[‘`]" + qc = "[’']" else: # rather than ascii ones - qo = "'" + qo = "['`]" qc = "'" - return [ + return context,[ + # Command line + [ r"[/\s]([cg]\+\+-*[0-9]*\.*[0-9]*)", "white", "bold" ], + [ r"\s(\-D)(\s*[^\s]+)", "none,green", "normal,bold" ], + [ r"\s(-g)", "green", "normal" ], + [ r"\s-O[0-4]", "green", "normal" ], + [ r"\s-[Wf][^\s]*", "magenta", "normal" ], + [ r"\s-pedantic", "magenta", "normal" ], + [ r"\s(-I)(/*[^\s]+/)([^/\s]+)", "none,blue", "normal,normal,bold" ], + [ r"\s(-L)(/*[^\s]+/)([^/\s]+)", "none,cyan", "normal,normal,bold" ], + [ r"\s(-l)([^/\s]+)", "none,cyan", "normal,bold" ], + [ r"\s-[oc]", "red", "bold" ], + [ r"\s(-+std(?:lib)?)=?([^\s]+)", "red", "normal,bold" ], + + # Important messages [ _("error: "), "red", "bold" ], + [ _("fatal error: "), "red", "bold" ], [ _("warning: "), "magenta", "bold" ], - [ _("note: "), "blue", "bold" ], + [ _("undefined reference to "), "red", "bold" ], # [-Wflag] - [ "\[-W.*\]", "magenta"], + [ r"\[-W.*\]", "magenta"], # Highlight message start: # path file ext : line : col … [ "(/.*?)/([^/:]+): (In .*)"+qo, - "green", + section, "normal,normal,bold" ], + [ "(/.*?)/([^/:]+): (At .*)", + section, + "normal,normal,bold" ], + + [ _("In file included from"), section ], + # Highlight locations: # path file ext : line : col … [ "(/.*?)/([^/:]+):([0-9]+):*([0-9]*)(.*)", @@ -51,6 +74,12 @@ def theme(): "normal,normal,normal,normal" ], # source code in single quotes - [ qo+"(.*?)"+qc, "Cpp", "monokai" ] + [ qo+"(.*?)"+qc, "Cpp", "monokai" ], + + # source code after a "note: candidate are/is:" + [ _("note: ")+"((?!.*("+qo+"|"+qc+")).*)$", "Cpp", "monokai" ], + # [ _("note: ")+"(candidate:)(.*)$", "green,Cpp", "normal,monokai" ], + # after the code part, to avoid matching ANSI escape chars + [ _("note: "), "green", "normal" ] ] diff --git a/colout/colout_javac.py b/colout/colout_javac.py new file mode 100644 index 0000000..92e5a2c --- /dev/null +++ b/colout/colout_javac.py @@ -0,0 +1,15 @@ +#encoding: utf-8 + +def theme(context): + style="monokai" + return context,[ + [ r"^(.*\.java):([0-9]+):\s*(warning:.*)$", "white,yellow,magenta", "normal,normal,bold" ], + [ r"^(.*\.java):([0-9]+):(.*)$", "white,yellow,red", "normal,normal,bold" ], + [ r"^(symbol|location)\s*:\s*(.*)$", "blue,Java", "bold,"+style ], + [ r"^(found)\s*:\s*(.*)", "red,Java", "bold,"+style ], + [ r"^(required)\s*:\s*(.*)", "green,Java", "bold,"+style ], + [ r"^\s*\^$", "cyan", "bold" ], + [ r"^\s+.*$", "Java", style ], + [ "[0-9]+ error[s]*", "red", "bold" ], + [ "[0-9]+ warning[s]*", "magenta", "bold" ], + ] diff --git a/colout/colout_json.py b/colout/colout_json.py index 724362f..2f9898e 100644 --- a/colout/colout_json.py +++ b/colout/colout_json.py @@ -1,8 +1,13 @@ -def theme(): - return [ - [ '[][{}]' ], - [ '[:,]', "blue" ], - [ '".*"', "green" ] +def theme(context): + # This theme expect a formatted JSON input, with items spread across lines. + # See tools like "python -m json.tool" or "json_xs" + return context,[ + [ r'[\[\]{}],*\s*\n' ], + [ '" (:) ', "yellow" ], + [ r'[\]}"](,)', "yellow" ], + [ r"\"(-*[0-9]+\.*[0-9]*e*-*[0-9]*)\"", "blue" ], + [ '"(.*)"', "green" ], + [ """["']""", "cyan" ] ] diff --git a/colout/colout_latex.py b/colout/colout_latex.py new file mode 100644 index 0000000..5567fbd --- /dev/null +++ b/colout/colout_latex.py @@ -0,0 +1,30 @@ + +def theme(context): + return context,[ + # LaTeX + ["This is .*TeX.*$", "white", "bold"], + ["(LaTeX Warning): (.*) `(.*)' on page [0-9] (.*) on input line [0-9]+.$", + "magenta,magenta,white,magenta", "normal,bold,normal" ], + ["(LaTeX Warning): (.*)", "magenta", "normal,bold" ], + ["(LaTeX Error): (.*)", "red", "normal,bold" ], + [r"^(.*\.tex):([0-9]+): (.*)", "white,yellow,red", "normal,normal,bold" ], + # ["on (page [0-9]+)", "yellow", "normal" ], + ["on input (line [0-9]+)", "yellow", "normal" ], + ["^! .*$", "red", "bold"], + [r"(.*erfull) ([^\s]+).* in [^\s]+ at (lines [0-9]+--[0-9]+)", + "magenta,magenta,yellow", "normal"], + [r"\\[^\s]+\s", "white", "bold"], + [r"^l\.([0-9]+) (.*)", "yellow,tex"], + [r"^\s+(.*)", "tex"], + [r"(Output written on) (.*) \(([0-9]+ pages), [0-9]+ bytes\).", + "blue,white,blue", "normal,bold,normal"], + ["WARNING.*", "magenta", "normal"], + ["[wW]arning.*", "magenta", "normal"], + ["No pages of output", "red", "bold"], + + # BiBTeX + ["^(I couldn't) (.*)", "red", "normal,bold"], + ["(I found) no (.*)", "red"], + ["^---(line [0-9]+) of file (.*)", "yellow,white", "normal"], + ] + diff --git a/colout/colout_ninja.py b/colout/colout_ninja.py new file mode 100644 index 0000000..34dd9ec --- /dev/null +++ b/colout/colout_ninja.py @@ -0,0 +1,19 @@ + +import colout_cmake + +def theme(context): + # Ninja theme + + # Inherit from the CMake theme + context,th = colout_cmake.theme(context) + + # Because Ninja note progress as a fraction, we do not want the scale of a percentage + context["scale"] = (0,1) + + # Link (ninja) + th.append( [ r"^\[[0-9/]+\]\s?(Linking .* )(library|executable) (.*/)*(.+(\.[aso]+)*)$", + "blue", "normal,normal,bold" ] ) + # progress percentage (ninja) + th.append( [ r"^(\[[0-9]+/[0-9]+\])","Scale" ] ) + + return context,th diff --git a/colout/colout_perm.py b/colout/colout_perm.py index 3d2ed6a..048230c 100644 --- a/colout/colout_perm.py +++ b/colout/colout_perm.py @@ -1,8 +1,8 @@ -def theme(): - p="([rwxs-])" - reg="^([d-])"+p*9+"\s.*$" +def theme(context): + p="([-rwxsStT])" + reg=r"^([-dpcCDlMmpPs?])"+p*9+r"\s.*$" colors="blue"+",green"*3+",yellow"*3+",red"*3 styles="normal"+ ",normal,italic,bold"*3 - return [ [reg, colors, styles] ] + return context,[ [reg, colors, styles] ] diff --git a/colout/colout_python.py b/colout/colout_python.py new file mode 100644 index 0000000..40e4b34 --- /dev/null +++ b/colout/colout_python.py @@ -0,0 +1,20 @@ + +def theme(context): + return context,[ + # traceback header + ["^Traceback .*$", "blue" ], + # File, line, in + [ + r"^\s{2}(File \")(/*.*?/)*([^/:]+)(\", line) ([0-9]+)(, in) (.*)$", + "blue, none, white,blue, yellow,blue", + "normal,normal,bold, normal,normal,bold" + ], + # [r"^\s{2}File \"(.*)\", line ([0-9]+), in (.*)$", "white,yellow,white", "normal,normal,bold" ], + # Error name + ["^([A-Za-z]*Error):*", "red", "bold" ], + ["^([A-Za-z]*Exception):*", "red", "bold" ], + # any quoted things + [r"Error.*['\"](.*)['\"]", "magenta" ], + # python code + [r"^\s{4}.*$", "Python", "monokai" ], + ] diff --git a/colout/colout_slurm.py b/colout/colout_slurm.py new file mode 100644 index 0000000..9f5af8d --- /dev/null +++ b/colout/colout_slurm.py @@ -0,0 +1,117 @@ + +def theme(context): + # SLURM's states (from squeue manual). + + col_width = 9 + + COMPLETED =r"\bCOMPLETED" + PENDING =r"\bPENDING" + RUNNING =r"\bRUNNING" + CONFIGURING =r"\bCONFIGURING" + COMPLETING =r"\bCOMPLETING" + FAILED =r"\bFAILED" + DEADLINE =r"\bDEADLINE" + OUT_OF_MEMORY=r"\bOUT_OF_MEMORY" + TIMEOUT =r"\bTIMEOUT" + CANCELLED =r"\bCANCELLED" + BOOT_FAIL =r"\bBOOT_FAIL" + NODE_FAIL =r"\bNODE_FAIL" + PREEMPTED =r"\bPREEMPTED" + RESV_DEL_HOLD=r"\bRESV_DEL_HOLD" + REQUEUE_FED =r"\bREQUEUE_FED" + REQUEUE_HOLD =r"\bREQUEUE_HOLD" + REQUEUED =r"\bREQUEUED" + RESIZING =r"\bRESIZING" + REVOKED =r"\bREVOKED" + SIGNALING =r"\bSIGNALING" + SPECIAL_EXIT =r"\bSPECIAL_EXIT" + STAGE_OUT =r"\bSTAGE_OUT" + STOPPED =r"\bSTOPPED" + SUSPENDED =r"\bSUSPENDED" + + return context,[ + + ## No problem: greens + + #Job has terminated all processes on all nodes with an exit code of zero. + [r"\bCD\b", "22"], + [COMPLETED[0:col_width]+r"\w*\b", "22"], + #Job is awaiting resource allocation. + [r"\bPD\b", "28"], + [PENDING[0:col_width]+r"\w*\b", "28"], + #Job currently has an allocation. + [r"\bR\b", "34"], + [RUNNING[0:col_width]+r"\w*\b", "34"], + #Job has been allocated resources, but are waiting for them to become ready for use (e.g. booting). + [r"\bCF\b", "58"], + [CONFIGURING[0:col_width]+r"\w*\b", "58"], + #Job is in the process of completing. Some processes on some nodes may still be active. + [r"\bCG\b", "23"], + [COMPLETING[0:col_width]+r"\w*\b", "23"], + + ## Problem for the user: bold reds + + #Job terminated with non-zero exit code or other failure condition. + [r"\bF\b", "196"], + [FAILED[0:col_width]+r"\w*\b", "196", "bold"], + #Job terminated on deadline. + [r"\bDL\b", "160"], + [DEADLINE[0:col_width]+r"\w*\b", "160", "bold"], + #Job experienced out of memory error. + [r"\bOO\b", "197"], + [OUT_OF_MEMORY[0:col_width]+r"\w*\b", "197", "bold"], + #Job terminated upon reaching its time limit. + [r"\bTO\b", "161"], + [TIMEOUT[0:col_width]+r"\w*\b", "161", "bold"], + + ## Problem for the sysadmin: oranges + + #Job was explicitly cancelled by the user or system administrator. The job may or may not have been initiated. + [r"\bCA\b", "202"], + [CANCELLED[0:col_width]+r"\w*\b", "202", "bold"], + #Job terminated due to launch failure, typically due to a hardware failure (e.g. unable to boot the node or block and the job can not be requeued). + [r"\bBF\b", "166"], + [BOOT_FAIL[0:col_width]+r"\w*\b", "166"], + #Job terminated due to failure of one or more allocated nodes. + [r"\bNF\b", "208"], + [NODE_FAIL[0:col_width]+r"\w*\b", "208"], + + ## Non-blocking events: blues + + #Job terminated due to preemption. + [r"\bPR\b", "105"], + [PREEMPTED[0:col_width]+r"\w*\b", "105", "bold"], + #Job is being held after requested reservation was deleted. + [r"\bRD\b", "25"], + [RESV_DEL_HOLD[0:col_width]+r"\w*\b", "25"], + #Job is being requeued by a federation. + [r"\bRF\b", "26"], + [REQUEUE_FED[0:col_width]+r"\w*\b", "26"], + #Held job is being requeued. + [r"\bRH\b", "27"], + [REQUEUE_HOLD[0:col_width]+r"\w*\b", "27"], + #Completing job is being requeued. + [r"\bRQ\b", "31"], + [REQUEUED[0:col_width]+r"\w*\b", "31"], + #Job is about to change size. + [r"\bRS\b", "32"], + [RESIZING[0:col_width]+r"\w*\b", "32"], + #Sibling was removed from cluster due to other cluster starting the job. + [r"\bRV\b", "33"], + [REVOKED[0:col_width]+r"\w*\b", "33"], + #Job is being signaled. + [r"\bSI\b", "37"], + [SIGNALING[0:col_width]+r"\w*\b", "37"], + #The job was requeued in a special state. This state can be set by users, typically in EpilogSlurmctld, if the job has terminated with a particular exit value. + [r"\bSE\b", "38"], + [SPECIAL_EXIT[0:col_width]+r"\w*\b", "38"], + #Job is staging out files. + [r"\bSO\b", "39"], + [STAGE_OUT[0:col_width]+r"\w*\b", "39"], + #Job has an allocation, but execution has been stopped with SIGSTOP signal. CPUS have been retained by this job. + [r"\bST\b", "44"], + [STOPPED[0:col_width]+r"\w*\b", "44"], + #Job has an allocation, but execution has been suspended and CPUs have been released for other jobs. + [r"\bS\b", "45"], + [SUSPENDED[0:col_width]+r"\w*\b", "45"], + ] diff --git a/colout/colout_valgrind.py b/colout/colout_valgrind.py new file mode 100644 index 0000000..7fc33a2 --- /dev/null +++ b/colout/colout_valgrind.py @@ -0,0 +1,33 @@ +#encoding: utf-8 + +def theme(context): + + return context, [ + # section title + [r"^(==[0-9]+==\s{1})(Memcheck|Copyright|Using)(.*)$","blue",""], + [r"^(==[0-9]+==\s{1})(Warning)(.*)$","magenta",""], + [r"^(==[0-9]+==\s{1}Command: )(\S*)(.*)$","green,white","normal,bold,normal"], + [r"^(==[0-9]+==\s{1})(HEAP SUMMARY:)(.*)$","green",""], + [r"^(==[0-9]+==\s{1})(All heap blocks were freed)(.*)$","green",""], + [r"^(==[0-9]+==\s{1})(.*[rR]erun.*)$","blue",""], + [r"^(==[0-9]+==\s{1})(Use --.*)$","blue",""], + [r"^(==[0-9]+==\s{1}\S+.*)$","red",""], + # section explanation + [r"^==[0-9]+==\s{2}(\S+.*)$","orange",""], + # locations adresses + [r"^==[0-9]+==\s{4}([atby]{2}) (0x0): (\?{3})", + "blue,yellow,red", "normal,normal,bold"], + [r"^==[0-9]+==\s{4}([atby]{2}) (0x)([^:]*:) (\S+)", + "blue,blue,blue,none", "normal"], + # locations: library + [r"\(in (.*)\)", "cyan", "normal"], + # locations: file + [r"\(([^\.]*\.[^:]+):([0-9]+)\)", "white,yellow", "bold,normal"], + # leak summary + [r"^==[0-9]+==\s{4}(definitely lost): .* (in) .*","red","bold"], + [r"^==[0-9]+==\s{4}(indirectly lost): .* (in) .*","orange","bold"], + [r"^==[0-9]+==\s{6}(possibly lost): .* (in) .*","yellow","bold"], + [r"^==[0-9]+==\s{4}(still reachable): .* (in) .*","green","bold"], + [r"^==[0-9]+==\s{9}(suppressed): .* (in) .*","cyan","bold"], + ] + diff --git a/colout/colout_vivado.py b/colout/colout_vivado.py new file mode 100644 index 0000000..aa74216 --- /dev/null +++ b/colout/colout_vivado.py @@ -0,0 +1,26 @@ + +def theme(context): + # Theme for coloring AMD/Xilinx Vivado IDE synthesis and implementation output + return context,[ + [ r"^\s*\*+.+$", "green" ], + [ "^#.+", "green" ], + + [ "^.+ Checksum: .+$", "green" ], + + [ r"^.+Time \(s\).+", "green" ], + [ r"^Time \(s\).+", "green" ], + + [ r"Estimated Timing Summary \|.+\|.+\|", "cyan", "bold" ], + [ r"Intermediate Timing Summary \|.+\|.+\|", "cyan", "bold" ], + + [ "^INFO:", "white", "bold" ], + [ "^WARNING:.+$", "yellow" ], + [ "^CRITICAL WARNING:.+$", "red" ], + [ "^ERROR:.+$", "red" ], + + [ "^Phase [0-9]+.[0-9]+.[0-9]+.[0-9]+.+$", "magenta", "bold" ], + [ "^Phase [0-9]+.[0-9]+.[0-9]+.+$", "magenta", "bold" ], + [ "^Phase [0-9]+.[0-9]+.+$", "magenta", "bold" ], + [ "^Phase [0-9]+.+$", "magenta", "bold" ] + ] + diff --git a/colout/example.gdbinit b/colout/example.gdbinit new file mode 100644 index 0000000..e4c3116 --- /dev/null +++ b/colout/example.gdbinit @@ -0,0 +1,104 @@ + +# 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 + +# A yellow prompt +set prompt \033[0;33mgdb>>>\033[0m + +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-break + # Don't forget to run the command in the background + shell cat /tmp/coloutPipe | colout "(Breakpoint) ([0-9]+) at (0x\S+): file (.+/)([^/]+), line ([0-9]+)." blue,red,cyan,none,white,yellow normal,bold,normal,normal,bold,normal & + # You should start to consume the pipe before actually redirecting the command output into it. + logging_on +end +define hookpost-break + logging_off +end + + +define hook-run + shell cat /tmp/coloutPipe | colout "^(Breakpoint) ([0-9]+),*\s+(0x\S+ )*(in )*(\S+) (\(.*\)) at (.*/)?(?:$|(.+?)(?:(\.[^.]*)|)):([0-9]+)" red,red,blue,none,green,cpp,none,white,white,yellow normal,bold,normal,normal,bold,normal,normal,bold,bold,bold | colout "^(Starting program): (.*/)?(?:$|(.+?)(?:(\.[^.]*)|)):([0-9]+)" green,none,white,white,yellow normal,normal,bold,bold,bold | colout "^[0-9]+\s+(.*)$" Cpp & + logging_on +end +define hookpost-run + logging_off +end + + +define hook-continue + shell cat /tmp/coloutPipe | colout "^(Program received signal )(.*)(,.*)$" yellow,red,yellow bold | colout "^(Breakpoint) ([0-9]+),*\s+(0x\S+ )*(in )*(\S+) (\(.*\)) at (.*/)?(?:$|(.+?)(?:(\.[^.]*)|)):([0-9]+)" red,red,blue,none,green,cpp,none,white,white,yellow normal,bold,normal,normal,bold,normal,normal,bold,bold,bold | colout "^[0-9]+\s+(.*)$" Cpp & + logging_on +end +define hookpost-continue + logging_off +end + + +# Full syntax highlighting for the `list` command. +define hook-list + shell cat /tmp/coloutPipe | colout --all --source Cpp & + logging_on +end +# Don't forget the hookpost- or next coloring commands will fail. +define hookpost-list + logging_off +end + + +define hook-backtrace + # match the [path]file[.ext]: (.*/)?(?:$|(.+?)(?:(\.[^.]*)|)) + shell cat /tmp/coloutPipe | colout "^(#)([0-9]+)\s+(0x\S+ )*(in )*(\S+) (\(.*\)) at (.*/)?(?:$|(.+?)(?:(\.[^.]*)|)):([0-9]+)" red,red,blue,none,green,cpp,none,white,white,yellow normal,bold,normal,normal,bold,normal,normal,bold,bold,bold & + logging_on +end +define hookpost-backtrace + logging_off +end + + +define info hook-breakpoints + shell cat /tmp/coloutPipe | colout "^([0-9]+)" red bold | colout "\sy\s" green | colout "\sn\s" red | colout "breakpoint" green normal | colout "watchpoint" orange normal | colout "\s0x\S+\s" blue normal | colout "(.*/)?(?:$|(.+?)(?:(\.[^.]*)|)):([0-9]+)$" none,white,white,yellow normal,bold & + logging_on +end +define info hookpost-breakpoints + logging_off +end + + +define info hook-line + shell cat /tmp/coloutPipe | colout "^Line ([0-9]+) of \"(.*/)?(?:$|(.+?)(?:(\.[^.]*)|))\"" yellow,none,white,white bold | colout "(0x\S+) <(\S+)\+([0-9]+)>" blue,green,blue normal & + logging_on +end +define info hookpost-line + logging_off +end + + +# Don't forget to clean the adhoc pipe. +define hook-quit + shell rm -f /tmp/coloutPipe +end + diff --git a/colout/jet72.gpl b/colout/jet72.gpl new file mode 100644 index 0000000..ac69f16 --- /dev/null +++ b/colout/jet72.gpl @@ -0,0 +1,77 @@ +GIMP Palette +Name: MATLAB Jet (72) +Columns: 3 +# + 0 0 127 0 + 0 0 127 #1 + 0 0 141 #2 + 0 0 155 #3 + 0 0 169 #4 + 0 0 183 #5 + 0 0 198 #6 + 0 0 212 #7 + 0 0 226 #8 + 0 0 240 #9 + 0 0 255 #10 + 0 14 255 #11 + 0 28 255 #12 + 0 42 255 #13 + 0 56 255 #14 + 0 70 255 #15 + 0 84 255 #16 + 0 98 255 #17 + 0 112 255 #18 + 0 127 255 #19 + 0 141 255 #20 + 0 155 255 #21 + 0 169 255 #22 + 0 183 255 #23 + 0 198 255 #24 + 0 212 255 #25 + 0 226 255 #26 + 0 240 255 #27 + 0 255 255 #28 + 14 255 240 #29 + 28 255 226 #30 + 42 255 212 #31 + 56 255 198 #32 + 70 255 183 #33 + 84 255 169 #34 + 98 255 155 #35 +112 255 141 #36 +127 255 127 #37 +141 255 112 #38 +155 255 98 #39 +169 255 84 #40 +183 255 70 #41 +198 255 56 #42 +212 255 42 #43 +226 255 28 #44 +240 255 14 #45 +255 255 0 #46 +255 240 0 #47 +255 226 0 #48 +255 212 0 #49 +255 198 0 #50 +255 183 0 #51 +255 169 0 #52 +255 155 0 #53 +255 141 0 #54 +255 127 0 #55 +255 112 0 #56 +255 98 0 #57 +255 84 0 #58 +255 70 0 #59 +255 56 0 #60 +255 42 0 #61 +255 28 0 #62 +255 14 0 #63 +255 0 0 #64 +240 0 0 #65 +226 0 0 #66 +212 0 0 #67 +198 0 0 #68 +183 0 0 #69 +169 0 0 #70 +155 0 0 #71 +141 0 0 #72 diff --git a/colout_logo.svg b/colout_logo.svg new file mode 100644 index 0000000..8db7984 --- /dev/null +++ b/colout_logo.svg @@ -0,0 +1,612 @@ + + + + + colout logo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + colout logo + 2022-08-31 + + + nojhan + + + + + + diff --git a/example.gdbinit b/example.gdbinit new file mode 100644 index 0000000..b40a960 --- /dev/null +++ b/example.gdbinit @@ -0,0 +1,132 @@ + +set confirm off + +# Reversed yellow >>>, underlined green frame name, yellow »»» +set extended-prompt \[\e[7;33m\]>>>\[\e[0m\]\[\] \[\e[4;32m\]\f\[\e[0m\]\[\]\[\e[0;33m\] \n»»» \[\e[0m\] + + +# 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-break + # Don't forget to run the command in the background + shell cat /tmp/coloutPipe | colout "(Breakpoint) ([0-9]+) at (0x\S+): file (.+/)([^/]+), line ([0-9]+)." blue,red,cyan,none,white,yellow normal,bold,normal,normal,bold,normal & + # You should start to consume the pipe before actually redirecting the command output into it. + logging_on +end +define hookpost-break + logging_off +end + + +define hook-run + shell cat /tmp/coloutPipe | colout "^(Program received signal )(.+), (.+).$" yellow,red,yellow normal,bold | colout "^(Breakpoint) ([0-9]+),*\s+(0x\S+ )*(in )*(\S+) (\(.*\)) at (.*/)?(?:$|(.+?)(?:(\.[^.]*)|)):([0-9]+)" red,red,blue,none,green,cpp,none,white,white,yellow normal,bold,normal,normal,bold,normal,normal,bold,bold,bold | colout "^(Starting program): (.*/)?(?:$|(.+?)(?:(\.[^.]*)|)):([0-9]+)" green,none,white,white,yellow normal,normal,bold,bold,bold | colout "^[0-9]+\s+(.*)$" Cpp & + logging_on +end +define hookpost-run + logging_off +end + + +define hook-continue + shell cat /tmp/coloutPipe | colout "^(Program received signal )(.*)(,.*)$" yellow,red,yellow bold | colout "^(Breakpoint) ([0-9]+),*\s+(0x\S+ )*(in )*(\S+) (\(.*\)) at (.*/)?(?:$|(.+?)(?:(\.[^.]*)|)):([0-9]+)" red,red,blue,none,green,cpp,none,white,white,yellow normal,bold,normal,normal,bold,normal,normal,bold,bold,bold | colout "^[0-9]+\s+(.*)$" Cpp & + logging_on +end +define hookpost-continue + logging_off +end + + +# Full syntax highlighting for the `list` command. +define hook-list + #shell cat /tmp/coloutPipe | colout --all --source cpp & + shell cat /tmp/coloutPipe | colout "^([0-9]+)\s*(.*)$" red,Cpp & + logging_on +end +# Don't forget the hookpost- or next coloring commands will fail. +define hookpost-list + logging_off +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 + + +define info hook-breakpoints + shell cat /tmp/coloutPipe | colout "^([0-9]+)" red bold | colout "\sy\s" green | colout "\sn\s" red | colout "breakpoint" green normal | colout "watchpoint" orange normal | colout "\s0x\S+\s" blue normal | colout "(.*/)?(?:$|(.+?)(?:(\.[^.]*)|)):([0-9]+)$" none,white,white,yellow normal,bold & + logging_on +end +define info hookpost-breakpoints + logging_off +end + + +define info hook-line + shell cat /tmp/coloutPipe | colout "^Line ([0-9]+) of \"(.*/)?(?:$|(.+?)(?:(\.[^.]*)|))\"" yellow,none,white,white bold | colout "(0x\S+) <(\S+)\+([0-9]+)>" blue,green,blue normal & + logging_on +end +define info hookpost-line + logging_off +end + + +define hook-frame + #shell cat /tmp/coloutPipe | colout "^(#)([0-9]+)\s+(0x\S+ )*(in )*(.*) (at) (.*/)?(?:$|(.+?)(?:(\.[^.]*)|)):([0-9]+)" red,red,blue,red,green,red,magenta,none,white,white,yellow normal,bold,normal,normal,bold,normal,normal,bold,bold,bold | colout "^([0-9]+)\s+(.*)$" yellow,Cpp & + 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 & + logging_on +end +define hookpost-frame + logging_off +end + +# Don't forget to clean the adhoc pipe. +define hook-quit + set confirm off + shell rm -f /tmp/coloutPipe +end + +define hook-display + shell cat /tmp/coloutPipe | colout "^([0-9]+)(:) (.+?) (=) " red,blue,white,blue normal,normal,bold,normal | colout "(@)(0x\S+)(:)" red,blue,red normal & + logging_on +end +define hookpost-display + logging_off +end + + diff --git a/requirements-build.txt b/requirements-build.txt new file mode 100644 index 0000000..d50412d --- /dev/null +++ b/requirements-build.txt @@ -0,0 +1,3 @@ +tox +tox-wheel +twine diff --git a/run-tests.py b/run-tests.py deleted file mode 100644 index e69de29..0000000 diff --git a/setup.py b/setup.py index cd9b398..6bf5211 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python -#coding=utf-8 +#!/usr/bin/env python3 import os import sys @@ -10,45 +9,51 @@ except ImportError: from distutils.core import setup if sys.argv[-1] == 'publish': - os.system('python3 setup.py sdist upload') + os.system('python setup.py bdist_wheel --universal upload') sys.exit() packages = ['colout'] -requires = ['argparse', 'pygments', 'babel'] +requires = ['pygments', 'babel'] -classifiers = """\ -Development Status :: 4 - Beta +setup_requires = ['setuptools_scm'] + +classifiers = """ Environment :: Console -Intended Audience :: End Users/Desktop +Development Status :: 5 - Production/Stable License :: OSI Approved :: GNU General Public License v3 (GPLv3) -Operating System :: OS Independent -Programming Language :: Python -Programming Language :: Python :: 2.5 -Programming Language :: Python :: 2.6 -Programming Language :: Python :: 2.7 +Operating System :: POSIX +Operating System :: POSIX :: Linux Programming Language :: Python :: 3 -Topic :: Text Processing +Programming Language :: Python :: 3.5 +Programming Language :: Python :: 3.6 +Programming Language :: Python :: 3.7 +Programming Language :: Python :: 3.8 Topic :: Utilities -Topic :: Software Development :: Libraries :: Python Modules -""" +Topic :: Text Processing +Topic :: Text Processing :: Filters +""".strip().split('\n') setup( name='colout', - version='0.1', - description='Color Up Arbitrary Command Ouput.', - long_description=open('README').read(), - author='Nojhan', + use_scm_version=True, + classifiers=classifiers, + description='Color Up Arbitrary Command Output.', + entry_points={ + 'console_scripts': ['colout=colout.colout:main'], + }, + long_description=open(os.path.join(os.path.dirname(__file__), 'README.md')).read(), + long_description_content_type='text/markdown;variant=CommonMark', + author='nojhan', author_email='nojhan@nojhan.net', url='http://nojhan.github.com/colout/', - download_url = 'https://pypi.python.org/packages/source/c/colout/colout-0.1.tar.gz', packages=packages, - package_data={'': ['LICENSE']}, + package_data={'': ['LICENSE', 'README.md']}, package_dir={'colout': 'colout'}, - scripts=['bin/colout'], + python_requires='>=3.5', + setup_requires=setup_requires, include_package_data=True, install_requires=requires, - license="GPL-3", - classifiers = filter(None, classifiers.split("\n")), + license='GPLv3', zip_safe=False, ) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..ff4e1ca --- /dev/null +++ b/tox.ini @@ -0,0 +1,15 @@ +[tox] +envlist=py35,py36,py37,py38,cov + +[testenv] +wheel = true +deps= + pytest + pytest-cov + pytest-xdist +setenv= + py{35,36,37,38}: COVERAGE_FILE=.coverage.{envname} +commands= + py{35,36,37,38}: python -m pytest --cov=colout --cov-report=term-missing --no-cov-on-fail + cov: coverage combine + cov: coverage html --fail-under=85