spectra

Spectra is a simple text coloring, styling and formatting library(terminal coloring).

Pure Nim score 15/100 · tests present · no docs generated

Summary

Latest Version Unknown
License MIT
CI Status Failing
Downloads 0
Last Indexed 2026-07-21 05:23

Installation

nimble install spectra
choosenim install spectra
git clone https://github.com/ph4mished/spectra

OS Compatibility

Platform Linux macOS Windows FreeBSD OpenBSD NetBSD Android iOS WASM Embedded
spectra - - - - - - -

Source

Repository https://github.com/ph4mished/spectra
Homepage https://github.com/ph4mished/spectra
Registry Source nimble_official

README

Spectra

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

Installation

nimble install spectra

Features

  • Multiple Color Systems: Named colors, hex codes, RGB, 256-color palette
  • TrueColor Detection: Automatic detection of terminal truecolor support
  • Terminal Safe: Graceful fallbacks when color not supported(no-color fallback)
  • Simple API: Easy-to-use functions for text styling and coloring.
  • Comprehensive Styles: Bold, italic, underline, blink, reverse, hidden, strike-through
  • Granular Resets: Individual and full reset codes for precise control
  • No Escape: Texts in [] that aren't colors/styles are left as it is.

Core Concepts

Template System

The library follows a template-first approach: parse color templates once with or without placeholders([0], [1], etc), then reuse them with different data to replace placeholders. Placeholders are like slots

Color Toggling

Respects the NO_COLOR environment variable and detects when output is redirected. It can be manually controlled to suit user preference.


Quick Start

import strformat, spectra


# Parse and use color codes directly
let red = parseColor("fg=red")
let bold = parseColor("bold")
let reset = parseColor("reset")

echo fmt"{red}{bold}This is red and bold!{reset}"

# Check if a color is supported
if isSupportedColor("fg=#FF0000"):
  echo "Hex colors are supported!"


# Or use the main functions
parse("[fg=blue]Hello in blue![reset]").apply()
parse("[bg=yellow fg=black bold]Bold black text on yellow background.[reset]").apply()


# Or pre-parse the color template with placeholders for reuse. 
# This is the heart of the library's performance.

# Parse once
let temp = parse("[fg=red bold]Error: [0][reset]")

# Reuse multiple times
echo temp.apply("File not found")
echo temp.apply("Permission denied")
echo temp.apply("Network timeout")

Complete Usage Examples

Basic Template with Placeholders

import strformat, spectra


# Simple template with one placeholder
let greeting = parse("[fg=green]Hello, [0][reset]!")

echo greeting.apply("Alice")
echo greeting.apply("Bob")
echo greeting.apply("World")

# Complex template with multiple placeholders
let logTemplate = parse("[0] [fg=blue][1][reset]: [fg=yellow][2][reset]")

# Different log levels
echo logTemplate.apply("[INFO]", "main", "Application started")
echo logTemplate.apply("[WARN]", "auth", "Token expiring soon")
echo logTemplate.apply("[ERROR]", "db", "Connection failed")

Basic Text Coloring

import spectra

# Simple colored text
parse("[fg=green]Success message![reset]").apply()
parse("[fg=red bold]Error: Something went wrong![reset]").apply()
parse("[fg=cyan italic]Info message[reset]").apply()

# Background colors
parse("[bg=blue fg=white]White text on blue background[reset]").apply()
parse("[bg=lgreen fg=black]Black text on light green[reset]").apply()

Advanced Color Formats

import spectra

# Hex colors (requires truecolor support)
parse("[fg=#FF5733]Orange hex color[reset]").apply()
parse("[bg=#3498db]Blue background[reset]").apply()

# RGB colors
parse("[fg=rgb(255,105,180)]Hot pink text[reset]").apply()
parse("[bg=rgb(50,205,50)]Lime green background[reset]").apply()

# 256-color palette
parse("[fg=214]Orange from 256-color palette[reset]").apply()
parse("[bg=196]Red background from palette[reset]").apply()

Text Styles

import spectra

# Combine styles
parse("[bold underline=single] Bold and underlined[reset]").apply()
parse("[italic dim]", "Dim italic text. [italic=reset dim=reset][strike]Strikethrough  text only[reset]").apply()
parse("[blink=slow hidden]Slow blinking hidden text[reset]").apply()

# Reset specific attributes
parse("[bold fg=blue]Blue bold text. [bold=reset]No longer bold, but still blue. [fg=reset]No color, but other styles remain[reset]").apply()

Color Toggling

import os, spectra


# Create color toggle - respects NO_COLOR env var and when output is redirected by default
let toggle = color.newColorToggle()

# Parse templates using the toggle
let successTemplate = toggle.parse("[fg=green]✓ [0][reset]")
let errorTemplate = toggle.parse("[fg=red]✗ [0][reset]")

# These will only show colors if appropriate
echo successTemplate.apply("Operation completed")
echo errorTemplate.apply("Operation failed")

# Manual control
let forceColors = color.newColorToggle(true)   # Always show colors
let noColors = color.newColorToggle(false)     # Never show colors

# Use in CLI applications
let useColor = getEnv("NO_COLOR") == ""
let appToggle = color.newColorToggle(useColor)

let helpTemplate = appToggle.parse("[bold fg=cyan][0][reset] [fg=green][1][reset]")
echo helpTemplate.apply("Usage:", "myapp [options]")

Advanced Template Examples

import os, strformat, spectra

# Status indicator with conditional colors
let statusTemplate = parse("[0] : [1][reset]")

type Item* = object
  name*: string
  status*: string

var 
  items: seq[Item]
  statusColor: string

items.add(Item(name: "Database", status: "Online"))
items.add(Item(name: "API Server", status: "Offline"))
items.add(Item(name: "Cache", status: "Degraded"))

for item in items:
  case item.status:
  of "Online":
    statusColor = "[fg=green bold]"
  of "Offline":
    statusColor = "[fg=red bold]"
  else:
    statusColor = "[fg=yellow]"

  let statusColored = parse(statusColor & item.status).apply()
  echo statusTemplate.apply(item.name, statusColored)

# Progress bar template
let progressTemplate = parse("[fg=cyan][0][reset]/[fg=cyan][1][reset] [fg=green][2][reset]%")

let total = 100
for i in 0..total:
  let percent = i * 100 / total
  stdout.write("\r" & progressTemplate.apply($i, $total, $percent))
  stdout.flushFile()
  sleep(100)
echo "\n"

Building Complex UIs

import strutils, spectra

# Table with colored headers
let headerTemplate = parse("[bold fg=cyan][0][reset]")
let rowTemplate = parse("[0]  [fg=yellow][1][reset]  [fg=green][2][reset]")

echo headerTemplate.apply("─".repeat(40))
echo headerTemplate.apply("USER MANAGEMENT")
echo headerTemplate.apply("─".repeat(40))

echo rowTemplate.apply("Alice", "admin", "active")
echo rowTemplate.apply("Bob", "user", "active")
echo rowTemplate.apply("Charlie", "guest", "inactive")

# Nested templates
let errorTemplate = parse("[bold fg=red][0][reset]: [1]")
let suggestionTemplate = parse("[fg=yellow]Suggestion: [0][reset]")


type Err = object
  code: string
  msg: string
  suggestion: string


var errors: seq[Err]
errors.add(Err(code: "E001", msg: "File not found", suggestion: "Check the file path"))
errors.add(Err(code: "E002", msg: "Permission denied", suggestion: "Run with sudo or check permissions"))
errors.add(Err(code: "E003", msg: "Out of memory", suggestion: "Close other applications"))

for err in errors:
  echo errorTemplate.apply(err.code, err.msg)
  echo "  " & suggestionTemplate.apply(err.suggestion)
  echo "\n" 

Project Structure Example

# file: styles.nim - Define your color scheme

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]")
)
include styles

# file: help.nim - Use the color templates

import strutils, strformat


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")
import help

# file: main.nim - Main application
echo helpFunc()

CLI Applications

import os, spectra

# Best practice for CLI applications

# Check for --no-color flag

let noColorFlag: bool = false

for arg in commandLineParams():
  if arg == "--no-color":
    noColorFlag = true
    break

# Respect both flag and environment variable
let useColor = !noColorFlag and getEnv("NO_COLOR") == ""

# Create toggle
let toggle = color.newColorToggle(useColor)

# All templates use this toggle
    templates := struct {
        Success color.CompiledTemplate
        Error   color.CompiledTemplate
        Header  color.CompiledTemplate
    }{
        Success: toggle.parse("[fg=green]✓ [0][reset]"),
        Error:   toggle.parse("[fg=red]✗ [0][reset]"),
        Header:  toggle.parse("[bold][0][reset]"),
    }

# Use templates - they'll respect the toggle
echo templates.header.apply("My Application"))
echo templates.success.apply("Started successfully"))

# If --no-color was used or NO_COLOR is set,
# outputs will be plain text without escape codes
}

Error Handling in Templates

import ../src/spectra, tables

# Template for showing validation errors
let validationTemplate = parse("[fg=red]• [0]: [1][reset]")

let errors = {
  "username": "Must be at least 3 characters",
  "email":    "Invalid email format",
  "password": "Must contain uppercase and numbers"
}.toTable

echo parse("[bold fg=yellow]Validation Errors:[reset]").apply()
for field, message in errors:
  echo validationTemplate.apply(field, message)


# Template with conditional formatting
let scoreTemplate = parse("[0]: [1]")

type Score = object
  name: string
  score: int

var scores: seq[Score]

scores.add(Score(name: "Alice", score: 95))
scores.add(Score(name: "Bob", score: 75))
scores.add(Score(name: "Charlie", score: 45))
scores.add(Score(name: "Diana", score: 60))


for s in scores:
  var scoreColor: string
  if s.score >= 90:
    scoreColor = "[fg=green bold]"
  elif s.score >= 70:
    scoreColor = "[fg=yellow]"
  else:
    scoreColor = "[fg=red]"
  let coloredScore = parse(scoreColor & $s.score & "[reset]").apply()
  echo scoreTemplate.apply(s.name, coloredScore)

Pattern to avoid

# Good pattern

proc init() =
  let toggle = color.newColorToggle()
  let success = toggle.parse("[fg=green] [0][reset]")
  let error = toggle.parse("[fg=red] [0][reset]")


# Bad pattern (parsing in hot loop)

proc processItems(items: seq[string]) =
  for item in items:
    # DON'T DO THIS - parses every iteration!
    let tmpl = parse("[fg=blue]" & item & "[reset]")
    echo tmpl.apply())

Performance Comparison

import times, strformat, spectra



const iterations = 1000000

# Method 1: parse once, apply many
let temp = parse("[bold fg=red][0][reset] [fg=green][1][reset]")

var start = cpuTime()
for i in 0..iterations:
  temp.apply(fmt "Item{i}", "Value{i}")

echo fmt "Template reuse: {cpuTime() - start}"


# Method 2: parse every time
start = cpuTime()
for i in 0..iterations:
  parse(fmt"[bold fg=red]Item{i}[reset] [fg=green]Value{i}[reset]").apply()

echo fmt "Parse every time: {cpuTime() - start}"


# Method 3: Manual concatenation
start = cpuTime()
for i in 0..iterations:
  let parseCol = parseColor("fg=red bold") & fmt"Item{i}" & parseColor("reset") & " " & parseColor("fg=green") & fmt"Value{i}" & parseColor("reset")

echo fmt "Manual concatenation: {cpuTime() - start}"

Performance Comparison Result

Template reuse: 5.38109636
Parse every time: 48.491620272999995
Manual concatenation: 20.403113159999997

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=lred | Light red text | | fg=lgreen | Light green text | | fg=lyellow | Light yellow text | | fg=lblue | Light blue text | | fg=lmagenta | Light magenta text | | fg=lcyan | Light cyan text | | fg=lwhite | 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=lred | Light red background | | bg=lgreen | Light green background | | bg=lyellow | Light yellow background | | bg=lblue | Light blue background | | bg=lmagenta | Light magenta background | | bg=lcyan | Light cyan background | | bg=lwhite | 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
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

Tips and Best Practices

  1. Parse Once: Always parse templates at initialization, not in loops
  2. Use Toggles: Respect user preferences with color toggling
  3. Template Reuse: Create templates for consistent styling
  4. Placeholder Limits: The current implementation supports [0] through [999]
  5. Testing: Test both color and no-color outputs

Limitations

  1. Terminal Dependency: Colors only work in terminals that support ANSI escape codes(Unix/Linux platform)
  2. TrueColor Requirement: Hex and RGB colors require terminal with truecolor support
  3. Style Support: Some styles (blink, double underline) may not work in all terminals
  4. Color Detection: Fallback from truecolor to 256-color not yet implemented
  5. Windows: May require additional setup on Windows terminals

Platform Support

  • Linux/macOS terminals (full support)
  • Windows Terminal/WSL (good support probably)
  • Legacy Windows CMD (not supported)
  • iTerm2, GNOME Terminal, Kitty (not tested yet)

Contributing

We welcome contributions! Here's how you can help:

  1. Report Bugs: Open an issue with reproduction steps
  2. Suggest Features: Share your ideas for improvements
  3. Submit PRs:
  4. Fork the repository
  5. Create a feature branch
  6. Add tests for your changes
  7. Ensure code follows Nim conventions
  8. Submit a pull request

Development Setup

# Clone the repository
git clone https://github.com/ph4mished/spectra.git
cd spectra

# Run tests
cd tests
nim c -r test1.ni && nim c -r test2

Areas Needing Improvement

  1. Better Windows compatibility
  2. 256-color fallback for truecolor
  3. Performance optimization

License

MIT License - see LICENSE file for details.

Acknowledgments

  • ANSI escape code specifications
  • The Nim community for testing and feedback
  • All contributors who have helped improve this library

Note: Always test color output in different terminals to ensure compatibility with your users' environments. Consider providing a --no-color flag in your applications for users who prefer plain text.

color

Spectra has a port in Golang called color