package spectrum
Install
Dune Dependency
Authors
Maintainers
Sources
md5=5f07823399866f856121eefed75dc72c
sha512=06992c7673fe680be8d54727b550e176b1b141bff5e3b8e3c2ef06795e5190ea18ef3190ba822b78c3bb48cefa4971f3ca199dbbbfa644fc95f8f32941a855bd
Description
Using OCaml Format module's 'semantic tags' with named colours and CSS-style hex colours.
Published: 10 Jan 2022
README
spectrum
Library for colour and formatting in the terminal.
Using OCaml Format module's "semantic tags" feature, with tags defined for named colours from the xterm 256-color palette, as well as 24-bit colours via CSS-style hex codes and RGB or HSL values.
It's inspired by the examples given in Format Unraveled, a paper by Richard Bonichon & Pierre Weis, which also explains the cleverness behind OCaml's (mostly) type-safe format string system.
Installation
It's released on opam, so:
opam install spectrum
Usage
The basic usage looks like:
Spectrum.Printer.printf "@{<green>%s@}\n" "Hello world ๐";;
The pattern is @{<TAG-NAME>CONTENT@}
. So in the example above green
is matching one of the 256 xterm color names. Tag names are case-insensitive.
Tags
You can have arbitrarily nested tags, e.g.:
Spectrum.Printer.printf "@{<green>%s @{<bold>%s@} %s@}\n" "Hello" "world" "I'm here";;
Which should look like:
Here the tag bold
is used to output one the ANSI style codes. Spectrum defines tags for:
bold
dim
italic
underline
blink
rapid-blink
inverse
hidden
strikethru
As well as the named palette colours you can directly specify an arbitrary colour using short or long CSS-style hex codes:
Spectrum.Printer.printf "@{<#f0c090>%s@}\n" "Hello world ๐";;
Spectrum.Printer.printf "@{<#f00>%s@}\n" "RED ALERT";;
...or CSS-style RGB/HSL formats:
Spectrum.Printer.printf "@{<rgb(240 192 144)>%s@}\n" "Hello world ๐";;
Spectrum.Printer.printf "@{<hsl(60 100% 50%)>%s@}\n" "YELLOW ALERT";;
By default you are setting the "foreground" colour, i.e. the text colour.
Any colour tag can be prefixed with a foreground fg:
or background bg:
qualifier, e.g.:
Spectrum.Printer.printf "@{<bg:#f00>%s@}\n" "RED ALERT";;
Finally, Spectrum also supports compound tags in comma-separated format, e.g.:
Spectrum.Printer.printf "@{<bg:#f00,bold,yellow>%s@}\n" "RED ALERT";;
Interface
Spectrum provides two versions of the main module:
The default is
Spectrum.Printer
and it will raise an exception if your tags are invalid (i.e. malformed or unrecognised colour name, style name).Alternatively
Spectrum.Printer.Noexn
will swallow any errors, invalid tags will simply have no effect on the output string.
Both modules expose the same interface:
(** equivalent to [Format.fprintf] *)
val fprintf :
Format.formatter -> ('a, Format.formatter, unit, unit) format4 -> 'a
(** equivalent to [Format.printf] *)
val printf : ('a, Format.formatter, unit, unit) format4 -> 'a
(** equivalent to [Format.eprintf] *)
val eprintf : ('a, Format.formatter, unit, unit) format4 -> 'a
(** equivalent to [Format.sprintf] *)
val sprintf : ('a, Format.formatter, unit, string) format4 -> 'a
As you can see in the examples in the previous section, Spectrum.Printer.printf
works just like Format.printf
from the OCaml stdlib, and fprintf
, eprintf
and sprintf
also work just like their Format
counterparts.
Capabilities detection
I've ported the logic from the https://github.com/chalk/supports-color/ nodejs lib, which performs some heuristics based on env vars to determine what level of color support is available.
In most cases you can also override the detected by setting the FORCE_COLOR
env var.
The following method is provided:
Spectrum.Capabilities.supported_color_levels () -> color_level_info
type color_level_info = {
stdout : color_level;
stderr : color_level;
}
The following levels are recognised:
type color_level =
| Unsupported (* FORCE_COLOR=0 or FORCE_COLOR=false *)
| Basic (* FORCE_COLOR=1 or FORCE_COLOR=true *)
| Eight_bit (* FORCE_COLOR=2 *)
| True_color (* FORCE_COLOR=3 *)
Unsupported
: probably best not to use colors or stylingBasic
: supports 16 colors, i.e. the 8 basic colors plus "bright" version of each. They are equivalent to the first eight colours of the xterm 256-color set, with bright version accessed by setting the style to bold. So the available colour name tags are:black
red
green
yellow
blue
magenta
cyan
light-gray
(i.e. white)
Eight_bit
: supports the xterm 256-color palette, CSS hex codes likely won't work.True_color
: should support everything
Alternatives
AFAICT the main lib for this in the OCaml world is ANSITerminal
. It supports more than just colour and styles, providing tools for other things you might need in a terminal app like interacting with the cursor. It doesn't use "semantic tags", but provides analogs of the *printf
functions which now take a list of styles as the first arg, with that styling applied to the formatted string as a whole. For named colours it supports only the basic set of eight i.e. those which should be supported by any terminal.
There is also Fmt
. Unfortunately I couldn't work out how to use it from reading the docs, which don't give any examples. I think it may also integrate with Cmdliner
somehow, which could be handy. It appears to support the eight basic colours and styles and exposes a val styled : style -> 'a t -> 'a t
signature (where 'a t
is "the type for formatters of values of type 'a.
" ๐คทโโ๏ธ ), which looks similar to ANSITerminal but only applying a single style at a time i.e. no bold+red. (I guess you can do that by nesting function calls though).
In other languages there are libs like colored (Python) and chalk (JS) ...the latter being one of the most comprehensive I've seen.
Update:
I worked out how to use Fmt
, which is like this:
Fmt.set_style_renderer Fmt.stdout Fmt.(`Ansi_tty);;
Fmt.styled Fmt.(`Fg `Red) Fmt.string Fmt.stdout "wtf\n";;
Fmt.styled Fmt.(`Bg `Blue) Fmt.int Fmt.stdout 999;;
Changelog
0.5.0
support CSS-style
rgb(...)
andhsl(...)
color tags
0.4.0
port the terminal colour capabilities detection from chalk.js
0.3.0
expose separate
Exn
andNoexn
interfacesfix for buffer interaction issue (tests broke when updating dep
Fmt.0.9.0
) ...probably affected most uses ofsprintf_into
replace
sprintf_into
kludge with a workingsprintf
implementation
0.2.0
first viable version
TODOs
use the actual xterm colour names (seems like the ones I have came from some lib that changed some of them)
tests for all methods (
sprintf
and the lexer are tested currently)add other
Format
methods likedprintf
etc?publish the printer and capabilities-detection as separate opam modules?
expose variant types for use with explicit
mark_open_stag
and close calls?auto coercion to nearest supported colour, for high res colours on unsupported terminals, as per
chalk
don't output any codes if level is
Unsupported
Dependencies (6)
-
ppx_deriving
>= "5.2"
-
opam-state
>= "2.1" & < "2.2"
-
pcre
>= "7.5"
-
color
>= "0.2"
-
ocaml
>= "4.10"
-
dune
>= "2.8"
Dev Dependencies (3)
-
odoc
with-doc
-
junit_alcotest
with-test & >= "2.0"
-
alcotest
with-test & >= "1.4"
Used by
None
Conflicts
None