No description
Find a file
2025-12-24 14:09:18 +00:00
example_result Revised the color parser 2025-10-28 06:15:25 +00:00
examples Change Proc name from compile() to parse() 2025-11-24 10:38:04 +00:00
src Fixed RGB IndexDefect Bug 2025-12-24 14:09:18 +00:00
tests Change Proc name from compile() to parse() 2025-11-24 10:38:04 +00:00
ERR_AND_UPDATES.md Change Proc name from compile() to parse() 2025-11-24 10:38:04 +00:00
LICENSE Create LICENSE 2025-10-12 22:01:32 +00:00
README.md Fix RGB IndexDefect Bug 2025-12-24 13:45:23 +00:00
spectra.nimble Change Proc name from compile() to parse() 2025-11-24 10:38:04 +00:00

Spectra

Spectra is a high performance library for terminal text coloring and formatting.

Installation

nimble install spectra

Features

  • It supports granular resets, Only what you close resets (foreground, background, or text styles) as well as one-for-all reset (reset).
  • Support for multiple color systems {basic ANSI colors, hex colors and 256-color Palette}.
  • Spectra uses [ ]-enclosed syntax but its not an owned syntax. Users are free to use "[ ]" for anything of their choice without needing escapes, only if its content does not count as spectra tag/color.

Spectra has true color support, depending on your terminal's true color support. This is achieved through its hex and RGB colors.

When true-color is not available, hex and RGB colors will not be rendered

Limitation

Spectra supports Unix-like systems including Linux and macOS. Windows is not supported due to different terminal architecture.

Usage

Spectra supports more than one color tags in its square brackets bounded syntax (but one bad nut spoils all).

It means spectra accepts this

echo parse("[fg=white][bold italic strike fg=cyan]Hello World[reset]").apply()
#This will work perfectly 

but a typo goes unforgiven

echo parse("[fg=white][bld italic strike fg=cyan]Hello World[reset]").apply()

#"bld" is the bad nut, so all other tags enclosed in same [] are treated as literals.
#hence it prints "[bld italic strike fg=cyan]Hello World" colored white due to the first color (fg=white). 

#Because the tool sees it as an escape, not a typo

Importing the package

import spectra

Precompilation or Precomputation:

Spectra is color template-first.

Reasons template-first coloring is preferred:

  • DRY Principle (Define once, use anywhere)
  • Pay parsing overhead cost once by precompiling (Performance boost)

Worried about manual interpolation or string concatenation?? Just use indices (square bracket bounded numbers). See the example below.

let test = parse("[bold fg=red]Hello [0][fg=cyan blink][1][reset]")
                                      ^                 ^             
                                      |                 |
                                      Indices/Placeholders 0 and 1 are slots awaiting dynamic input from apply()  


for i in 0..1000000:
  echo test.apply("world", $i)

#[0] and [1] are positional indices(index), which are used by "apply()" for interpolation.
#Based on the parameters of apply(), [0]  ==> "world" and [1] ==> i

Usage Examples

import spectra

#Multiple styles in one tag
echo parse("[bold italic fg=red]Important![reset]").apply()


#Granular reset control
echo parse("[bold fg=red bg=blue]Alert[bold=reset] Still colored[fg=reset bg=reset] Normal").apply()


#Hex colors
echo parse("[fg=#FF0000]Red text[fg=#00FF00]Green text[reset]").apply()

#RGB colors
echo parse("[fg=rgb(255,0,0)]Red text[fg=rgb(0,255,0)]Green text[reset]").apply()


#256 colors
echo parse("[fg=202]Orange text[fg=45]Blue text[reset]").apply()

#Spectra for other languages through interpolation. Express Precompilation flexibility
let lang_temp = parse("[fg=red][0]: [fg=green][1][reset]")

echo lang_temp.apply("Error", "File not found") #English
echo lang_temp.apply("Erreur", "Fichier non trouve") #French


#Define once, use anywhere
let help_temp = parse("[bold fg=cyan][0][fg=green], [fg=cyan][1][bold=reset fg=green]: [fg=yellow][2][reset]")

echo help_temp.apply("-h", "--help", "Show help and exit")
echo help_temp.apply("-v", "--version", "Show version")
echo help_temp.apply("-r", "--recursive", "Run recursively")

Coloring a text

echo parse("[bold][fg=yellow]Hello Word[reset]").apply() 

echo parse("[bold fg=red]Error:[bold=reset] File not found[reset]").apply()

#hex color
echo parse("[fg=#FF0000 underline]DANGER[fg=reset underline=reset]").apply()

# 256 color support
let btnTemplate  = parse("[fg=255 bg=24][0][reset]")

echo btnTemplate.apply("┌────────────────────┐")
echo btnTemplate.apply("│       Submit       │")
echo btnTemplate.apply("└────────────────────┘")

Escapes

#Normally no escapes are required if contents in "[ ]" are not colors or styles
#For escape when contents are colors or styles, use apply()

#EXAMPLE
echo parse("[fg=green bold]Hey Fred, be [0] and [1] hard[reset]").apply("[bold]", "[strike]") #Safe for escapes too

Color Toggling


#default color toggling
# automatically switches off color when output is redirected or when terminal has NO_COLOR on
newColorToggle()


newColorToggle(true) #Color always stays on
newColorToggle(false) #Color always off


#EXAMPLES
let toggle = newColorToggle()

let help_temp = toggle.parse("[bold fg=cyan][0][fg=green], [fg=cyan][1][bold=reset fg=green]: [fg=yellow][2][reset]")

echo help_temp.apply("-h", "--help", "Show help and exit")
echo help_temp.apply("-v", "--version", "Show version")
echo help_temp.apply("-r", "--recursive", "Run recursively")

#OR
## IMPLICIT newColorToggle() CALL
##================================
#still works without explicitly calling colorToggle
#newColorToggle() is implicitly called by "parse()" 
#when used implicitly, each "parse()" call creates a newColorToggle()
let help_temp = parse("[bold fg=cyan][0][fg=green], [fg=cyan][1][bold=reset fg=green]: [fg=yellow][2]")
let test_temp = parse("[bold fg=green]Test Me[reset]")

#both test_temp and help_temp create newColorToggle() implicitly but they do not share. Each call creates its own

echo help_temp.apply("-h", "--help", "Show help and exit")
echo help_temp.apply("-v", "--version", "Show version")
echo help_temp.apply("-r", "--recursive", "Run recursively")




#respect for no-color flag
let no_color = newColorToggle(not paramStr(1) == "--no-color")
echo parse(no_color, "[fg=green]Ready[reset]").apply()

Spectra In Action

#EXAMPLE ONE
let itemTemp = parse("[0], [fg=cyan][1][reset]")

let fruits = @["Apple", "Watermelon", "Grapes", "Banana"]
for i, fruit in fruits:
  echo itemTemp.apply($(i+1), fruit)


#EXAMPLE TWO
let temp_template = parse("Temperature: [0]")

proc showTemp(temp: int) =
  let color = if temp > 25: "[fg=red]" elif temp < 10: "[fg=blue]" else: "[fg=green]"
  let comp = parse(color & "[0]°C[reset]")
  echo temp_template.apply(comp.apply($temp))

showTemp(25)
showTemp(14)
showTemp(4)

#EXAMPLE THREE
let loginTemp = parse("""
Username: [fg=cyan][0][reset]
Password: [fg=yellow][1][reset]
""")

echo loginTemp.apply("Jay Pal", "********")

For Projects

Style File (Define spectra colors upfront)

 # styles.nim
 import spectra, terminal

let toggle = newColorToggle(not noColor and stdout.isatty())

let help* = (
  header: toggle.parse( "[bold fg=cyan][0][reset]"),
  description: toggle.parse("[bold fg=magenta][0][reset]"),
  section: toggle.parse("\n[bold fg=blue][[fg=cyan][0][fg=blue]][reset]\n"),
  flag: toggle.parse("\t[bold fg=#FF6600][0][fg=green], [fg=#FF6600][1] [fg=green]: [2][reset]")
)

Help File (Applying defined colors)

# help.nim
import strutils, strformat
include styles


proc helpFunc*() =
  echo help.description.apply(" cp - copy files and directories.")
  echo help.description.apply("Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.")
  
  echo help.header.apply("\nUsage: cp [OPTIONS] SOURCE DEST")
  
  echo help.section.apply("OPTIONS")
  echo help.flag.apply("-h", "--help", "display this help and exit")
  echo help.flag.apply("-v", "--version", "output version information and exit")
  echo help.flag.apply("", "--verbose", "explain what is being done")
  echo help.flag.apply("-l", "--link", "hard link files instead of copying")

Beauty Of Spectra

#This example is to express how spectra can be used for ascii arts

# Create gradient-like effects with multiple colors
#to recreate the unicode block below
#for linux, enter "Ctrl + Shift + u" and then type "2592" and press enter
echo parse("""
[fg=#FF0000][0][fg=#FF3300][0][fg=#FF6600][0][fg=#FF9900][0][fg=#FFCC00][0][reset]
[fg=#CC0000][0][fg=#CC3300][0][fg=#CC6600][0][fg=#CC9900][0][fg=#CCCC00][0][reset]
[fg=#990000][0][fg=#993300][0][fg=#996600][0][fg=#999900][0][fg=#99CC00][0][reset]
""").apply("▓".repeat(3))



# Simple colored blocks and patterns
#to recreate the unicode block below
#for linux, enter "Ctrl + Shift + u" and then type "2588" and press enter

let blkTemp = parse("[fg=red][0][fg=green][0][fg=blue][0][reset]")
let lastTemp = parse("[fg=yellow][0][fg=magenta][0][fg=cyan][0][reset]\n")

echo blkTemp.apply("█".repeat(8))
echo blkTemp.apply("█".repeat(8))
echo lastTemp.apply("█".repeat(8))



echo parse("""
[fg=red][0][fg=#FF6600][0][fg=yellow][0][fg=green][0][fg=blue][0][fg=#6600FF][0][fg=magenta][0][reset]
""").apply("▄".repeat(5))


#ctrl+ shift+u + 250c = ┌
#ctrl+ shift+u + 2514 = └
#ctrl+ shift+u + 2518 = ┘
#ctrl+ shift+u + 2510 = ┐
#ctrl+ shift+u + 2500 = ─
#ctrl+ shift+u + 2502 = │

let btnTemplate = parse("[fg=255 bg=24][0][1][2][reset]")

echo btnTemplate.apply("┌", "─".repeat(12), "┐")
echo btnTemplate.apply("│", "   Submit   ", "│")
echo btnTemplate.apply("└", "─".repeat(12), "┘")

Results

beauty

Spectra Syntax Reference On Terminal

import spectra
#view color syntax and corresponding output
listColors()

#view style syntax and corresponding output
listStyles()

Spectra Syntax Reference

Basic Colors

Foreground Colors

Command Effect
fg=black Black text
fg=red Red text
fg=green Green text
fg=yellow Yellow text
fg=blue Blue text
fg=magenta Magenta text
fg=cyan Cyan text
fg=white White text
fg=darkgray Dark gray text
fg=lightred Light red text
fg=lightgreen Light green text
fg=lightyellow Light yellow text
fg=lightblue Light blue text
fg=lightmagenta Light magenta text
fg=lightcyan Light cyan text
fg=lightwhite Light white text

Background Colors

Command Effect
bg=black Black background
bg=red Red background
bg=green Green background
bg=yellow Yellow background
bg=blue Blue background
bg=magenta Magenta background
bg=cyan Cyan background
bg=white White background
bg=darkgray Dark gray background
bg=lightred Light red background
bg=lightgreen Light green background
bg=lightyellow Light yellow background
bg=lightblue Light blue background
bg=lightmagenta Light magenta background
bg=lightcyan Light cyan background
bg=lightwhite Light white background

Text Styles

Command Effect
bold Bold/bright text
dim Dim/faint text
italic Italic text
underline=single Single underlined text
underline=double Double underlined text
blink=slow Slow blinking text
blink=fast Fast blinking text
reverse Reverse video (swap foreground and background colors)
hidden Hidden text
strike Strikethrough text

Reset Commands

Command Effect
reset Reset all colors and styles
fg=reset Reset foreground color only
bg=reset Reset background color only
bold=reset Reset bold style only
dim=reset Reset dim style only
italic=reset Reset italic style only
underline=reset Reset underline style only
blink=reset Reset blink style only
blinkfast=reset Reset fast blink style only
reverse=reset Reset reverse style only
hidden=reset Reset hidden style only
strike=reset Reset strikethrough style only

Advanced Features

Command Effect
fg=#RRGGBB Hex color for foreground
bg=#RRGGBB Hex color for background
fg=rgb(RR,GG,BB) RGB color for foreground
bg=rgb(RR,GG,BB) RGB color for background
fg=NNN 256-color palette (0-255) for foreground
bg=NNN 256-color palette (0-255) for background

Parsing Overhead Benchmark

I decided to test the speed of spectra parsing.

import spectra, strformat, times


let iterations = 1000000

#for spectra with precomputation
let comp = parse("[bold fg=cyan italic] Processing [fg=yellow underline][0][fg=green strike] from [dim blinkfast   fg=#FFFFFF] file 1 [reverse fg=254]to end[reset]")

let fStartTime = cpuTime()
for i in 0..iterations:
  discard comp.apply($i)
let fEndTime = cpuTime()

#for spectra without precomputation (simulating naive color libraries)
#Reparse per loop
let sStartTime = cpuTime()
for i in 0..iterations:
  discard parse("[bold fg=cyan italic] Processing [fg=yellow underline][0][fg=green strike] from [dim blinkfast fg=#FFFFFF] file 1 [reverse fg=254]to end[reset]").apply($i)
let sEndTime = cpuTime()

#Checking without spectra
let tStartTime = cpuTime()
for i in 0..iterations:
  discard fmt "Processing {i} from file 1  to end."
let tEndTime = cpuTime()




echo "First Loop Duration [Spectra]: ", fEndTime-fStartTime, "sec"
echo "Second Loop Duration [Parse Per Loop]: ", sEndTime-sStartTime, " sec"
echo "Third Loop Duration [Pure Nim]: ", tEndTime-tStartTime, " sec"

Output

First Loop Duration [Spectra]: 3.048232739sec
Second Loop Duration [Parse Per Loop]: 163.822929514 sec
Third Loop Duration [Pure Nim]: 1.254006722999975 sec

Note

The benchmark is to show how low spectra interpolation overhead is and how effiecient it is in loops.

By default spectra is more than 48x faster than most naive color libraries for hot loops (all thanks to precomputation)

Test for yourself if you doubt this benchmark

Contributing

You can help improve Spectra by:

  • Trying to use it and giving feedback
  • Test the programs under different MacOS versions or Linux distributions
  • Help improving and extending the code
  • Adding Windows support

SPECTRA CREED

We believe terminal coloring should be predictable, fast and explicit. We optimize for productiona performance over development convenience, and for runtime efficiency over typo forgiveness. Our users value speed and reliability above all else.