diff --git a/.github/img/logo.svg b/.github/img/logo.svg
new file mode 100644
index 0000000..ab108c6
--- /dev/null
+++ b/.github/img/logo.svg
@@ -0,0 +1,15 @@
+
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index a5ad631..e074239 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -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,
// ๐๏ธ
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..08bde58
--- /dev/null
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 24099fe..85e07f7 100644
--- a/Cargo.toml
+++ b/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 }
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..af25e1d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,20 @@
+

+
+
+
+
+
+๐ฅ๐จ๐จ๐ฅ is a tool-box library with common utilities for ๐๐๐๐๐๐.๐๐ projects.
+
+
+
+
+
+
+# Features
+
+- [ ] **logging**: A simple logging utility.
+- [ ] **cli**: Command-line interface utilities:
+ - [ ] **argparse**: A simple wrapper around `argparse`.
+ - [ ] **color**: `foo.()` like extension methods for strings.
+- [ ] **case**: Case conversion utilities.
diff --git a/Taskfile.yaml b/Taskfile.yaml
index 08eb026..3ce2173 100644
--- a/Taskfile.yaml
+++ b/Taskfile.yaml
@@ -20,6 +20,16 @@ tasks:
- python check_size.py
fmt:
- desc: ๐ format lool
+ desc: ๐จ format lool
cmds:
- - cargo +nightly fmt --all
\ No newline at end of file
+ - cargo +nightly fmt --all
+
+ lint:
+ desc: ๐งถ lint lool
+ cmds:
+ - cargo clippy --fix
+
+ test:
+ desc: ๐งช test lool
+ cmds:
+ - cargo nextest run
diff --git a/examples/stylizer.rs b/examples/stylizer.rs
new file mode 100644
index 0000000..0c032e7
--- /dev/null
+++ b/examples/stylizer.rs
@@ -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(())
+}
\ No newline at end of file
diff --git a/lib/cli/colors.rs b/lib/cli/colors.rs
new file mode 100644
index 0000000..a230d48
--- /dev/null
+++ b/lib/cli/colors.rs
@@ -0,0 +1,7 @@
+mod style;
+mod stylizer;
+
+pub use stylizer::{stylize, Stylize};
+
+#[cfg(test)]
+mod tests;
\ No newline at end of file
diff --git a/lib/cli/colors/style.rs b/lib/cli/colors/style.rs
new file mode 100644
index 0000000..493bf6b
--- /dev/null
+++ b/lib/cli/colors/style.rs
@@ -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 {
+ 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
+ }
+}
diff --git a/lib/cli/colors/stylizer.rs b/lib/cli/colors/stylizer.rs
new file mode 100644
index 0000000..1652aff
--- /dev/null
+++ b/lib/cli/colors/stylizer.rs
@@ -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,
+ pub bg: Option,
+ 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 {
+ 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::>();
+
+ 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, Option)> {
+ 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 {
+ 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: 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
+}
diff --git a/lib/cli/colors/tests/mod.rs b/lib/cli/colors/tests/mod.rs
new file mode 100644
index 0000000..0f92e3b
--- /dev/null
+++ b/lib/cli/colors/tests/mod.rs
@@ -0,0 +1 @@
+mod stylizer;
\ No newline at end of file
diff --git a/lib/cli/colors/tests/stylizer.rs b/lib/cli/colors/tests/stylizer.rs
new file mode 100644
index 0000000..1b5912d
--- /dev/null
+++ b/lib/cli/colors/tests/stylizer.rs
@@ -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(())
+ }
+}
\ No newline at end of file
diff --git a/lib/cli/mod.rs b/lib/cli/mod.rs
new file mode 100644
index 0000000..40fd5c1
--- /dev/null
+++ b/lib/cli/mod.rs
@@ -0,0 +1 @@
+pub mod colors;
diff --git a/lib/lib.rs b/lib/lib.rs
new file mode 100644
index 0000000..5d863fb
--- /dev/null
+++ b/lib/lib.rs
@@ -0,0 +1 @@
+pub mod cli;
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
deleted file mode 100644
index 465a5e5..0000000
--- a/src/main.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-use eyre::Result;
-
-fn main() -> Result<()> {
- println!("Hello World");
-
- Ok(())
-}