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,
|
"Taskfile.yaml": true,
|
||||||
".cocorc": true,
|
".cocorc": true,
|
||||||
".task": true,
|
".task": true,
|
||||||
".cargo": true,
|
// ".cargo": true,
|
||||||
// "Cargo.toml": true,
|
// "Cargo.toml": true,
|
||||||
|
|
||||||
// 📦
|
// 📦
|
||||||
@ -12,7 +12,7 @@
|
|||||||
"target": true,
|
"target": true,
|
||||||
|
|
||||||
// 📝 readmes
|
// 📝 readmes
|
||||||
"**/**/README.md": true,
|
// "**/**/README.md": true,
|
||||||
"LICENSE": 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 = 0
|
||||||
debug-assertions = false
|
debug-assertions = false
|
||||||
|
|
||||||
[dependencies]
|
[registry]
|
||||||
eyre = { version = "0.6.12", default-features = false, features = ["auto-install"] }
|
default = "gitea"
|
||||||
indoc = "2.0.4"
|
|
||||||
|
|
||||||
[[bin]]
|
[registries.gitea]
|
||||||
name = "lool"
|
index = "sparse+http://lugit.local/api/packages/lucodear/cargo/"
|
||||||
path = "src/main.rs"
|
|
||||||
|
[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
|
- python check_size.py
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
desc: 🚀 format lool
|
desc: 🎨 format lool
|
||||||
cmds:
|
cmds:
|
||||||
- cargo +nightly fmt --all
|
- 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