feat: ✨ cli colors
This commit is contained in:
parent
58ec1de9e1
commit
41944ba7ee
15
.github/img/logo.svg
vendored
Normal file
15
.github/img/logo.svg
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 142 64">
|
||||
<style>
|
||||
.a { fill: #000000; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.a { fill: #ffffff; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<g>
|
||||
<path class="a" d="M0,0H20.42877V61.64158h4.66951v1.51648H.33347V61.64158H4.91962V1.516H0Z" />
|
||||
<path class="a" d="M32.68564,58.6096Q27.34846,53.22312,27.34919,43.157q0-10.06074,5.7116-15.49373,5.71016-5.432,16.051-5.432,10.33773,0,15.42578,5.01055Q69.6225,32.25308,69.624,42.52651,69.624,63.999,48.44489,64,38.02064,64,32.68564,58.6096Zm20.929-12.04176V39.41033a57.79486,57.79486,0,0,0-.87584-11.916q-.87535-3.74542-4.04378-3.74714a4.16126,4.16126,0,0,0-2.96,1.05243,6.48776,6.48776,0,0,0-1.62614,3.495,64.70357,64.70357,0,0,0-.7503,11.5366v6.56814q0,9.18111.54189,11.03143a18.941,18.941,0,0,0,1.12545,3.032,3.54892,3.54892,0,0,0,3.58575,2.02164q3.252,0,4.25219-3.79071Q53.61486,56.00056,53.61461,46.56784Z" />
|
||||
<path class="a" d="M77.96259,58.6096q-5.3379-5.38648-5.33742-15.45262,0-10.06074,5.7116-15.49373,5.71161-5.432,16.051-5.432,10.33846,0,15.42675,5.01055,5.08344,5.01129,5.08538,15.28472Q114.89994,63.999,93.72087,64,83.29808,64,77.96259,58.6096Zm20.929-12.04176V39.41033a57.82133,57.82133,0,0,0-.87632-11.916q-.87536-3.74542-4.04427-3.74714a4.16,4.16,0,0,0-2.95952,1.05243,6.48414,6.48414,0,0,0-1.62566,3.495,64.70357,64.70357,0,0,0-.7503,11.5366v6.56814q0,9.18111.54092,11.03143a18.934,18.934,0,0,0,1.12642,3.032,3.54712,3.54712,0,0,0,3.58478,2.02164q3.25275,0,4.25268-3.79071Q98.892,56.00056,98.89156,46.56784Z" />
|
||||
<path class="a" d="M116.90172,0h20.42876V61.64158H142v1.51648H117.23518V61.64158h4.58615V1.516h-4.91961Z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@ -4,7 +4,7 @@
|
||||
"Taskfile.yaml": true,
|
||||
".cocorc": true,
|
||||
".task": true,
|
||||
".cargo": true,
|
||||
// ".cargo": true,
|
||||
// "Cargo.toml": true,
|
||||
|
||||
// 📦
|
||||
@ -12,7 +12,7 @@
|
||||
"target": true,
|
||||
|
||||
// 📝 readmes
|
||||
"**/**/README.md": true,
|
||||
// "**/**/README.md": true,
|
||||
"LICENSE": true,
|
||||
|
||||
// 🗑️
|
||||
|
||||
39
Cargo.lock
generated
Normal file
39
Cargo.lock
generated
Normal file
@ -0,0 +1,39 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||
|
||||
[[package]]
|
||||
name = "eyre"
|
||||
version = "0.6.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec"
|
||||
dependencies = [
|
||||
"indenter",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indenter"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
||||
|
||||
[[package]]
|
||||
name = "lool"
|
||||
version = "0.0.0-alpha.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"eyre",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
17
Cargo.toml
17
Cargo.toml
@ -16,10 +16,15 @@ overflow-checks = false
|
||||
debug = 0
|
||||
debug-assertions = false
|
||||
|
||||
[dependencies]
|
||||
eyre = { version = "0.6.12", default-features = false, features = ["auto-install"] }
|
||||
indoc = "2.0.4"
|
||||
[registry]
|
||||
default = "gitea"
|
||||
|
||||
[[bin]]
|
||||
name = "lool"
|
||||
path = "src/main.rs"
|
||||
[registries.gitea]
|
||||
index = "sparse+http://lugit.local/api/packages/lucodear/cargo/"
|
||||
|
||||
[lib]
|
||||
path = "lib/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2.5.0"
|
||||
eyre = { version = "0.6.12", default-features = false }
|
||||
|
||||
20
README.md
Normal file
20
README.md
Normal file
@ -0,0 +1,20 @@
|
||||
<p align="center"><img src=".github/img/logo.svg" height="128"></p>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
|
||||
<p align="center"><b>𝐥𝐨𝐨𝐥</b> is a tool-box library with common utilities for <b>𝚕𝚞𝚌𝚘𝚍𝚎.𝚊𝚛</b> projects.
|
||||
</p>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
|
||||
# Features
|
||||
|
||||
- [ ] **logging**: A simple logging utility.
|
||||
- [ ] **cli**: Command-line interface utilities:
|
||||
- [ ] **argparse**: A simple wrapper around `argparse`.
|
||||
- [ ] **color**: `foo.<color>()` like extension methods for strings.
|
||||
- [ ] **case**: Case conversion utilities.
|
||||
@ -20,6 +20,16 @@ tasks:
|
||||
- python check_size.py
|
||||
|
||||
fmt:
|
||||
desc: 🚀 format lool
|
||||
desc: 🎨 format lool
|
||||
cmds:
|
||||
- cargo +nightly fmt --all
|
||||
|
||||
lint:
|
||||
desc: 🧶 lint lool
|
||||
cmds:
|
||||
- cargo clippy --fix
|
||||
|
||||
test:
|
||||
desc: 🧪 test lool
|
||||
cmds:
|
||||
- cargo nextest run
|
||||
|
||||
38
examples/stylizer.rs
Normal file
38
examples/stylizer.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use eyre::{set_hook, DefaultHandler, Result};
|
||||
use lool::cli::colors::{stylize, Stylize};
|
||||
|
||||
fn setup_eyre() {
|
||||
let _ = set_hook(Box::new(DefaultHandler::default_with));
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
setup_eyre();
|
||||
|
||||
let red_bold = stylize("[red+bold]", "red+bold");
|
||||
let alt_red_bold = stylize(stylize("alt [red+bold]", "red"), "+bold");
|
||||
|
||||
let red_bold_italic = stylize("[red+bold|italic]", "red+bold|italic");
|
||||
let alt_red_bold_italic = stylize(stylize(stylize("alt [red+bold|italic]", "red"), "+bold"), "+italic");
|
||||
|
||||
let red_on_blue = stylize("[white on blue]", "white on blue");
|
||||
let rgb = stylize("[#3a95ef]", "#3a95ef");
|
||||
let rgb_on_rgb = stylize("[#3a95ef on #c174dd]", "#3a95ef on #c174dd");
|
||||
|
||||
println!("pre {} post", red_bold);
|
||||
println!("pre {} post", alt_red_bold);
|
||||
println!("pre {} post", red_bold_italic);
|
||||
println!("pre {} post", alt_red_bold_italic);
|
||||
|
||||
println!("pre {} post", red_on_blue);
|
||||
println!("pre {} post", rgb);
|
||||
println!("pre {} post", rgb_on_rgb);
|
||||
|
||||
println!("pre {} post", "[green]".stl("green").stl("+bold"));
|
||||
println!("pre {} post", "[green+bold]".stl("green+bold"));
|
||||
|
||||
println!("pre {} post", "[.blue()]".blue());
|
||||
println!("pre {} post", "[.blue().bold()]".blue().bold());
|
||||
println!("pre {} post", "[.blue().on_red().bold()]".blue().on_red().bold());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
7
lib/cli/colors.rs
Normal file
7
lib/cli/colors.rs
Normal file
@ -0,0 +1,7 @@
|
||||
mod style;
|
||||
mod stylizer;
|
||||
|
||||
pub use stylizer::{stylize, Stylize};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
148
lib/cli/colors/style.rs
Normal file
148
lib/cli/colors/style.rs
Normal file
@ -0,0 +1,148 @@
|
||||
use bitflags::bitflags;
|
||||
use eyre::Context;
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// The 8 standard colors.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Color {
|
||||
Black,
|
||||
Red,
|
||||
Green,
|
||||
Yellow,
|
||||
Blue,
|
||||
Magenta,
|
||||
Cyan,
|
||||
White,
|
||||
BrightBlack,
|
||||
BrightRed,
|
||||
BrightGreen,
|
||||
BrightYellow,
|
||||
BrightBlue,
|
||||
BrightMagenta,
|
||||
BrightCyan,
|
||||
BrightWhite,
|
||||
TrueColor { r: u8, g: u8, b: u8 },
|
||||
}
|
||||
|
||||
impl Color {
|
||||
pub fn to_fg_str(&self) -> Cow<'static, str> {
|
||||
match *self {
|
||||
Color::Black => "30".into(),
|
||||
Color::Red => "31".into(),
|
||||
Color::Green => "32".into(),
|
||||
Color::Yellow => "33".into(),
|
||||
Color::Blue => "34".into(),
|
||||
Color::Magenta => "35".into(),
|
||||
Color::Cyan => "36".into(),
|
||||
Color::White => "37".into(),
|
||||
Color::BrightBlack => "90".into(),
|
||||
Color::BrightRed => "91".into(),
|
||||
Color::BrightGreen => "92".into(),
|
||||
Color::BrightYellow => "93".into(),
|
||||
Color::BrightBlue => "94".into(),
|
||||
Color::BrightMagenta => "95".into(),
|
||||
Color::BrightCyan => "96".into(),
|
||||
Color::BrightWhite => "97".into(),
|
||||
Color::TrueColor { r, g, b } => format!("38;2;{};{};{}", r, g, b).into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_bg_str(&self) -> Cow<'static, str> {
|
||||
match *self {
|
||||
Color::Black => "40".into(),
|
||||
Color::Red => "41".into(),
|
||||
Color::Green => "42".into(),
|
||||
Color::Yellow => "43".into(),
|
||||
Color::Blue => "44".into(),
|
||||
Color::Magenta => "45".into(),
|
||||
Color::Cyan => "46".into(),
|
||||
Color::White => "47".into(),
|
||||
Color::BrightBlack => "100".into(),
|
||||
Color::BrightRed => "101".into(),
|
||||
Color::BrightGreen => "102".into(),
|
||||
Color::BrightYellow => "103".into(),
|
||||
Color::BrightBlue => "104".into(),
|
||||
Color::BrightMagenta => "105".into(),
|
||||
Color::BrightCyan => "106".into(),
|
||||
Color::BrightWhite => "107".into(),
|
||||
Color::TrueColor { r, g, b } => format!("48;2;{};{};{}", r, g, b).into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str(s: &str) -> eyre::Result<Color> {
|
||||
let color = match s {
|
||||
"" => None,
|
||||
"black" => Some(Color::Black),
|
||||
"red" => Some(Color::Red),
|
||||
"green" => Some(Color::Green),
|
||||
"yellow" => Some(Color::Yellow),
|
||||
"blue" => Some(Color::Blue),
|
||||
"magenta" => Some(Color::Magenta),
|
||||
"cyan" => Some(Color::Cyan),
|
||||
"white" => Some(Color::White),
|
||||
"bright-black" => Some(Color::BrightBlack),
|
||||
"bright-red" => Some(Color::BrightRed),
|
||||
"bright-green" => Some(Color::BrightGreen),
|
||||
"bright-yellow" => Some(Color::BrightYellow),
|
||||
"bright-blue" => Some(Color::BrightBlue),
|
||||
"bright-magenta" => Some(Color::BrightMagenta),
|
||||
"bright-cyan" => Some(Color::BrightCyan),
|
||||
"bright-white" => Some(Color::BrightWhite),
|
||||
s if s.starts_with("#") => {
|
||||
let s = &s[1..];
|
||||
let r = u8::from_str_radix(&s[0..2], 16).context("Error parsing RGB color")?;
|
||||
let g = u8::from_str_radix(&s[2..4], 16).context("Error parsing RGB color")?;
|
||||
let b = u8::from_str_radix(&s[4..6], 16).context("Error parsing RGB color")?;
|
||||
Some(Color::TrueColor { r, g, b })
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
color.ok_or_else(|| eyre::eyre!("Invalid color: '{}'", s))
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct StyleAttributes: u8 {
|
||||
const BOLD = 0b00000001;
|
||||
const DIMMED = 0b00000010;
|
||||
const ITALIC = 0b00000100;
|
||||
const UNDERLINE = 0b00001000;
|
||||
const BLINK = 0b00010000;
|
||||
const REVERSED = 0b00100000;
|
||||
const HIDDEN = 0b01000000;
|
||||
const STRIKETHROUGH = 0b10000000;
|
||||
}
|
||||
}
|
||||
|
||||
impl StyleAttributes {
|
||||
pub fn to_ansi_codes(&self) -> Vec<&'static str> {
|
||||
let mut v = Vec::new();
|
||||
if self.contains(StyleAttributes::BOLD) {
|
||||
v.push("1");
|
||||
}
|
||||
if self.contains(StyleAttributes::DIMMED) {
|
||||
v.push("2");
|
||||
}
|
||||
if self.contains(StyleAttributes::ITALIC) {
|
||||
v.push("3");
|
||||
}
|
||||
if self.contains(StyleAttributes::UNDERLINE) {
|
||||
v.push("4");
|
||||
}
|
||||
if self.contains(StyleAttributes::BLINK) {
|
||||
v.push("5");
|
||||
}
|
||||
if self.contains(StyleAttributes::REVERSED) {
|
||||
v.push("7");
|
||||
}
|
||||
if self.contains(StyleAttributes::HIDDEN) {
|
||||
v.push("8");
|
||||
}
|
||||
if self.contains(StyleAttributes::STRIKETHROUGH) {
|
||||
v.push("9");
|
||||
}
|
||||
v
|
||||
}
|
||||
}
|
||||
187
lib/cli/colors/stylizer.rs
Normal file
187
lib/cli/colors/stylizer.rs
Normal file
@ -0,0 +1,187 @@
|
||||
use super::style::{Color, StyleAttributes as StyleBitflags};
|
||||
use eyre::Result;
|
||||
|
||||
pub mod instructions {
|
||||
use super::{Result, StyleBitflags, Color};
|
||||
use bitflags::parser::{from_str, ParseError};
|
||||
|
||||
pub struct StyledString {
|
||||
pub fg: Option<Color>,
|
||||
pub bg: Option<Color>,
|
||||
pub attrs: StyleBitflags,
|
||||
}
|
||||
|
||||
impl Default for StyledString {
|
||||
fn default() -> Self {
|
||||
StyledString {
|
||||
fg: None,
|
||||
bg: None,
|
||||
attrs: StyleBitflags::empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// parses the instruction into a StyledString
|
||||
pub fn parse(instructions: &str) -> Result<StyledString> {
|
||||
let mut styled_string = StyledString::default();
|
||||
|
||||
if instructions.starts_with("+") {
|
||||
// only attributes
|
||||
styled_string.attrs = attributes_from_str(instructions.trim_start_matches('+')).map_err(|e| eyre::eyre!(e))?;
|
||||
return Ok(styled_string);
|
||||
}
|
||||
|
||||
// try to separate the colors from the attributes
|
||||
let parts = instructions.split('+').collect::<Vec<_>>();
|
||||
|
||||
match parts.len() {
|
||||
1 => {
|
||||
// no attributes
|
||||
let (fg, bg) = parse_color_instruction(parts[0].trim())?;
|
||||
styled_string.fg = fg;
|
||||
styled_string.bg = bg;
|
||||
}
|
||||
2 => {
|
||||
// attributes
|
||||
let (fg, bg) = parse_color_instruction(parts[0].trim())?;
|
||||
styled_string.fg = fg;
|
||||
styled_string.bg = bg;
|
||||
styled_string.attrs = attributes_from_str(parts[1]).map_err(|e| eyre::eyre!(e))?;
|
||||
}
|
||||
_ => {
|
||||
return Err(eyre::eyre!("Invalid instruction: {}", instructions));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(styled_string)
|
||||
}
|
||||
|
||||
/// Parses the color instruction into a tuple of fg and bg colors.
|
||||
pub fn parse_color_instruction(instruction: &str) -> Result<(Option<Color>, Option<Color>)> {
|
||||
if instruction.starts_with("on ") {
|
||||
// If instruction starts with "on ", bg color is provided
|
||||
return Ok((None, Some(Color::from_str(&instruction[3..])?)));
|
||||
}
|
||||
|
||||
let colors: Vec<&str> = instruction.split(" on ").collect();
|
||||
match colors.len() {
|
||||
1 => {
|
||||
// Only fg color is provided
|
||||
Ok((Some(Color::from_str(colors[0])?), None))
|
||||
}
|
||||
2 => {
|
||||
// Both fg and bg colors are provided
|
||||
Ok((Some(Color::from_str(colors[0])?), Some(Color::from_str(colors[1])?)))
|
||||
}
|
||||
_ => Err(eyre::eyre!("Invalid color instruction: {}", instruction)),
|
||||
}
|
||||
}
|
||||
|
||||
fn attributes_from_str (s: &str) -> Result<StyleBitflags, ParseError> {
|
||||
from_str(s.to_uppercase().as_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// 🧉 » Stylize Trait
|
||||
/// --
|
||||
///
|
||||
/// `Trait` that extends `String` and `str` with the ability to stylize them
|
||||
/// with ANSI color and attrs, using methods that return a new string with the given style.
|
||||
pub trait Stylize {
|
||||
/// Basic styling method, receives a styling instruction
|
||||
/// see the `stylize` function for more information
|
||||
fn stl(&self, instruction: &str) -> String;
|
||||
fn black(&self) -> String { self.stl("black") }
|
||||
fn red(&self) -> String { self.stl("red") }
|
||||
fn green(&self) -> String { self.stl("green") }
|
||||
fn yellow(&self) -> String { self.stl("yellow") }
|
||||
fn blue(&self) -> String { self.stl("blue") }
|
||||
fn magenta(&self) -> String { self.stl("magenta") }
|
||||
fn cyan(&self) -> String { self.stl("cyan") }
|
||||
fn white(&self) -> String { self.stl("white") }
|
||||
fn bright_black(&self) -> String { self.stl("bright-black") }
|
||||
fn bright_red(&self) -> String { self.stl("bright-red") }
|
||||
fn bright_green(&self) -> String { self.stl("bright-green") }
|
||||
fn bright_yellow(&self) -> String { self.stl("bright-yellow") }
|
||||
fn bright_blue(&self) -> String { self.stl("bright-blue") }
|
||||
fn bright_magenta(&self) -> String { self.stl("bright-magenta") }
|
||||
fn bright_cyan(&self) -> String { self.stl("bright-cyan") }
|
||||
fn bright_white(&self) -> String { self.stl("bright-white") }
|
||||
fn rgb(&self, r: u8, g: u8, b: u8) -> String { self.stl(&format!("#{:02X}{:02X}{:02X}", r, g, b)) }
|
||||
fn on_black(&self) -> String { self.stl("on black") }
|
||||
fn on_red(&self) -> String { self.stl("on red") }
|
||||
fn on_green(&self) -> String { self.stl("on green") }
|
||||
fn on_yellow(&self) -> String { self.stl("on yellow") }
|
||||
fn on_blue(&self) -> String { self.stl("on blue") }
|
||||
fn on_magenta(&self) -> String { self.stl("on magenta") }
|
||||
fn on_cyan(&self) -> String { self.stl("on cyan") }
|
||||
fn on_white(&self) -> String { self.stl("on white") }
|
||||
fn on_bright_black(&self) -> String { self.stl("on bright-black") }
|
||||
fn on_bright_red(&self) -> String { self.stl("on bright-red") }
|
||||
fn on_bright_green(&self) -> String { self.stl("on bright-green") }
|
||||
fn on_bright_yellow(&self) -> String { self.stl("on bright-yellow") }
|
||||
fn on_bright_blue(&self) -> String { self.stl("on bright-blue") }
|
||||
fn on_bright_magenta(&self) -> String { self.stl("on bright-magenta") }
|
||||
fn on_bright_cyan(&self) -> String { self.stl("on bright-cyan") }
|
||||
fn on_bright_white(&self) -> String { self.stl("on bright-white") }
|
||||
fn on_rgb(&self, r: u8, g: u8, b: u8) -> String { self.stl(&format!("on #{:02X}{:02X}{:02X}", r, g, b)) }
|
||||
fn bold(&self) -> String { self.stl("+bold") }
|
||||
fn dim(&self) -> String { self.stl("+dim") }
|
||||
fn italic(&self) -> String { self.stl("+italic") }
|
||||
fn underline(&self) -> String { self.stl("+underline") }
|
||||
fn blink(&self) -> String { self.stl("+blink") }
|
||||
fn reverse(&self) -> String { self.stl("+reverse") }
|
||||
fn hidden(&self) -> String { self.stl("+hidden") }
|
||||
fn strikethrough(&self) -> String { self.stl("+strikethrough") }
|
||||
}
|
||||
|
||||
impl Stylize for str {
|
||||
fn stl(&self, instruction: &str) -> String {
|
||||
stylize(self, instruction)
|
||||
}
|
||||
}
|
||||
|
||||
impl Stylize for String {
|
||||
fn stl(&self, instruction: &str) -> String {
|
||||
stylize(self, instruction)
|
||||
}
|
||||
}
|
||||
|
||||
/// 🧉 » stylize fn
|
||||
/// --
|
||||
///
|
||||
/// Stylizes a string with optional ANSI color and attributes.
|
||||
///
|
||||
pub fn stylize<S: AsRef<str>>(s: S, instructions: &str) -> String {
|
||||
let styled_string = instructions::parse(instructions);
|
||||
|
||||
if styled_string.is_err() {
|
||||
return s.as_ref().to_string();
|
||||
}
|
||||
|
||||
let styled_string = styled_string.unwrap();
|
||||
|
||||
let mut formatted = String::new();
|
||||
|
||||
if let Some(fg) = styled_string.fg {
|
||||
formatted.push_str(&format!("\x1b[{}m", fg.to_fg_str()));
|
||||
}
|
||||
|
||||
if let Some(bg) = styled_string.bg {
|
||||
formatted.push_str(&format!("\x1b[{}m", bg.to_bg_str()));
|
||||
}
|
||||
|
||||
if !styled_string.attrs.is_empty() {
|
||||
let ansi_codes = styled_string.attrs.to_ansi_codes();
|
||||
|
||||
if !ansi_codes.is_empty() {
|
||||
formatted.push_str(&format!("\x1b[{}m", ansi_codes.join(";")));
|
||||
}
|
||||
}
|
||||
|
||||
// Append the original string and clear the style
|
||||
formatted.push_str(s.as_ref());
|
||||
formatted.push_str("\x1b[0m");
|
||||
|
||||
formatted
|
||||
}
|
||||
1
lib/cli/colors/tests/mod.rs
Normal file
1
lib/cli/colors/tests/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
mod stylizer;
|
||||
103
lib/cli/colors/tests/stylizer.rs
Normal file
103
lib/cli/colors/tests/stylizer.rs
Normal file
@ -0,0 +1,103 @@
|
||||
#[cfg(test)]
|
||||
mod parse_instruction {
|
||||
use {
|
||||
crate::cli::colors::{
|
||||
stylizer::instructions::{parse, parse_color_instruction}, style::{Color, StyleAttributes}
|
||||
}, eyre::{set_hook, DefaultHandler}
|
||||
};
|
||||
|
||||
|
||||
fn setup_eyre() {
|
||||
let _ = set_hook(Box::new(DefaultHandler::default_with));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_color_instruction() -> eyre::Result<()> {
|
||||
setup_eyre();
|
||||
|
||||
assert_eq!(parse_color_instruction("red")?, (Some(Color::Red), None));
|
||||
assert_eq!(parse_color_instruction("#FF0000")?, (Some(Color::TrueColor { r: 255, g: 0, b: 0 }), None));
|
||||
assert_eq!(parse_color_instruction("on blue")?, (None, Some(Color::Blue)));
|
||||
assert_eq!(parse_color_instruction("on #0000FF")?, (None, Some(Color::TrueColor { r: 0, g: 0, b: 255 })));
|
||||
assert_eq!(parse_color_instruction("red on blue")?, (Some(Color::Red), Some(Color::Blue)));
|
||||
assert_eq!(parse_color_instruction("#FF0000 on #0000FF")?, (Some(Color::TrueColor { r: 255, g: 0, b: 0 }), Some(Color::TrueColor { r: 0, g: 0, b: 255 })));
|
||||
assert!(parse_color_instruction("red on blue on green").is_err());
|
||||
assert!(parse_color_instruction("red on").is_err());
|
||||
assert!(parse_color_instruction("on").is_err());
|
||||
assert!(parse_color_instruction("on red blue").is_err());
|
||||
assert!(parse_color_instruction("red on blue on green").is_err());
|
||||
assert!(parse_color_instruction("invalid").is_err());
|
||||
assert!(parse_color_instruction("red on invalid").is_err());
|
||||
assert!(parse_color_instruction("invalid on blue").is_err());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_instructions() -> eyre::Result<()> {
|
||||
|
||||
setup_eyre();
|
||||
|
||||
let styled_string = parse("red on blue")?;
|
||||
assert_eq!(styled_string.fg, Some(Color::Red));
|
||||
assert_eq!(styled_string.bg, Some(Color::Blue));
|
||||
assert_eq!(styled_string.attrs, StyleAttributes::empty());
|
||||
|
||||
let styled_string = parse("on blue")?;
|
||||
assert_eq!(styled_string.fg, None);
|
||||
assert_eq!(styled_string.bg, Some(Color::Blue));
|
||||
assert_eq!(styled_string.attrs, StyleAttributes::empty());
|
||||
|
||||
let styled_string = parse("red")?;
|
||||
assert_eq!(styled_string.fg, Some(Color::Red));
|
||||
assert_eq!(styled_string.bg, None);
|
||||
assert_eq!(styled_string.attrs, StyleAttributes::empty());
|
||||
|
||||
// ##RRGGBB
|
||||
let styled_string = parse("#FF0000 on #0000FF")?;
|
||||
assert_eq!(styled_string.fg, Some(Color::TrueColor { r: 255, g: 0, b: 0 }));
|
||||
assert_eq!(styled_string.bg, Some(Color::TrueColor { r: 0, g: 0, b: 255 }));
|
||||
assert_eq!(styled_string.attrs, StyleAttributes::empty());
|
||||
|
||||
let styled_string = parse("red on blue+bold")?;
|
||||
assert_eq!(styled_string.fg, Some(Color::Red));
|
||||
assert_eq!(styled_string.bg, Some(Color::Blue));
|
||||
assert_eq!(styled_string.attrs, StyleAttributes::BOLD);
|
||||
|
||||
// ##RRGGBB
|
||||
let styled_string = parse("#FF0000 on #0000FF+bold")?;
|
||||
assert_eq!(styled_string.fg, Some(Color::TrueColor { r: 255, g: 0, b: 0 }));
|
||||
assert_eq!(styled_string.bg, Some(Color::TrueColor { r: 0, g: 0, b: 255 }));
|
||||
assert_eq!(styled_string.attrs, StyleAttributes::BOLD);
|
||||
|
||||
let styled_string = parse("red on blue+bold|underline")?;
|
||||
assert_eq!(styled_string.fg, Some(Color::Red));
|
||||
assert_eq!(styled_string.bg, Some(Color::Blue));
|
||||
assert_eq!(styled_string.attrs, StyleAttributes::BOLD | StyleAttributes::UNDERLINE);
|
||||
|
||||
let styled_string = parse("red on #0000FF+bold|underline|italic")?;
|
||||
assert_eq!(styled_string.fg, Some(Color::Red));
|
||||
assert_eq!(styled_string.bg, Some(Color::TrueColor { r: 0, g: 0, b: 255 }));
|
||||
assert_eq!(styled_string.attrs, StyleAttributes::BOLD | StyleAttributes::UNDERLINE | StyleAttributes::ITALIC);
|
||||
|
||||
let styled_string = parse("+bold")?;
|
||||
assert_eq!(styled_string.fg, None);
|
||||
assert_eq!(styled_string.bg, None);
|
||||
assert_eq!(styled_string.attrs, StyleAttributes::BOLD);
|
||||
|
||||
let styled_string = parse("on red+bold")?;
|
||||
assert_eq!(styled_string.fg, None);
|
||||
assert_eq!(styled_string.bg, Some(Color::Red));
|
||||
assert_eq!(styled_string.attrs, StyleAttributes::BOLD);
|
||||
|
||||
let styled_string = parse("red+bold")?;
|
||||
assert_eq!(styled_string.fg, Some(Color::Red));
|
||||
assert_eq!(styled_string.bg, None);
|
||||
assert_eq!(styled_string.attrs, StyleAttributes::BOLD);
|
||||
|
||||
let styled_string = parse("red on blue+bold")?;
|
||||
assert_eq!(styled_string.fg, Some(Color::Red));
|
||||
assert_eq!(styled_string.bg, Some(Color::Blue));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
1
lib/cli/mod.rs
Normal file
1
lib/cli/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod colors;
|
||||
1
lib/lib.rs
Normal file
1
lib/lib.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod cli;
|
||||
@ -1,7 +0,0 @@
|
||||
use eyre::Result;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
println!("Hello World");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user