Skip to content

Commit

Permalink
feat(color): implement colorful logging
Browse files Browse the repository at this point in the history
  • Loading branch information
QaidVoid committed Oct 14, 2024
1 parent 82644df commit 61d9ceb
Show file tree
Hide file tree
Showing 21 changed files with 618 additions and 160 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ chrono = { version = "0.4.38", features = ["serde"] }
clap = { version = "4.5.19", features = ["derive"] }
futures = "0.3.30"
image = { version = "0.25.2", default-features = false, features = ["png"] }
libc = "0.2.159"
reqwest = { version = "0.12.8", features = ["blocking", "http2", "rustls-tls", "stream"], default-features = false }
rmp-serde = "1.3.0"
serde = { version = "1.0.210", features = ["derive"] }
Expand Down
177 changes: 177 additions & 0 deletions src/core/color.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
use std::fmt::Display;

#[derive(Debug, Clone, Copy)]
pub enum Color {
Reset,
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
BrightBlack,
BrightRed,
BrightGreen,
BrightYellow,
BrightBlue,
BrightMagenta,
BrightCyan,
BrightWhite,
}

impl Color {
pub fn to_ansi_code(self, is_bg: bool) -> &'static str {
match self {
Color::Reset => "\x1B[0m",
Color::Black => {
if is_bg {
"\x1B[40m"
} else {
"\x1B[30m"
}
}
Color::Red => {
if is_bg {
"\x1B[41m"
} else {
"\x1B[31m"
}
}
Color::Green => {
if is_bg {
"\x1B[42m"
} else {
"\x1B[32m"
}
}
Color::Yellow => {
if is_bg {
"\x1B[43m"
} else {
"\x1B[33m"
}
}
Color::Blue => {
if is_bg {
"\x1B[44m"
} else {
"\x1B[34m"
}
}
Color::Magenta => {
if is_bg {
"\x1B[45m"
} else {
"\x1B[35m"
}
}
Color::Cyan => {
if is_bg {
"\x1B[46m"
} else {
"\x1B[36m"
}
}
Color::White => {
if is_bg {
"\x1B[47m"
} else {
"\x1B[37m"
}
}
Color::BrightBlack => {
if is_bg {
"\x1B[100m"
} else {
"\x1B[90m"
}
}
Color::BrightRed => {
if is_bg {
"\x1B[101m"
} else {
"\x1B[91m"
}
}
Color::BrightGreen => {
if is_bg {
"\x1B[102m"
} else {
"\x1B[92m"
}
}
Color::BrightYellow => {
if is_bg {
"\x1B[103m"
} else {
"\x1B[93m"
}
}
Color::BrightBlue => {
if is_bg {
"\x1B[104m"
} else {
"\x1B[94m"
}
}
Color::BrightMagenta => {
if is_bg {
"\x1B[105m"
} else {
"\x1B[95m"
}
}
Color::BrightCyan => {
if is_bg {
"\x1B[106m"
} else {
"\x1B[96m"
}
}
Color::BrightWhite => {
if is_bg {
"\x1B[107m"
} else {
"\x1B[97m"
}
}
}
}
}

pub trait ColorExt {
fn color(self, color: Color) -> String;
fn bg_color(self, color: Color) -> String;
fn bold(self) -> String;
}

impl<T: Display> ColorExt for T {
fn color(self, color: Color) -> String {
format!(
"{}{}{}",
color.to_ansi_code(false),
self,
Color::Reset.to_ansi_code(false)
)
}

fn bg_color(self, color: Color) -> String {
format!(
"{}{}{}",
color.to_ansi_code(true),
self,
Color::Reset.to_ansi_code(true)
)
}

fn bold(self) -> String {
format!(
"{}\x1B[1m{}{}",
Color::Reset.to_ansi_code(false),
self,
Color::Reset.to_ansi_code(false)
)
}
}
7 changes: 5 additions & 2 deletions src/core/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use std::{env, fs, path::PathBuf, sync::LazyLock};

use serde::{Deserialize, Serialize};

use crate::core::color::{Color, ColorExt};

use super::{constant::REGISTRY_PATH, util::home_config_path};

/// Application's configuration
Expand Down Expand Up @@ -52,8 +54,9 @@ impl Config {
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
fs::create_dir_all(&pkg_config).unwrap();
eprintln!(
"Config not found. Generating default config at {}",
config_path.to_string_lossy()
"{} Generating default config at {}",
"Config not found".color(Color::Red),
config_path.to_string_lossy().color(Color::Green)
);
Config::generate(config_path)
}
Expand Down
141 changes: 141 additions & 0 deletions src/core/grid.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use libc::{ioctl, winsize, STDOUT_FILENO, TIOCGWINSZ};
use std::mem;

pub struct Grid {
cols: usize,
col_widths: Vec<Option<usize>>,
col_ratios: Vec<f64>,
rows: Vec<Vec<String>>,
separator: String,
}

impl Grid {
pub fn builder(cols: usize) -> GridBuilder {
GridBuilder {
cols,
col_widths: vec![None; cols],
col_ratios: vec![1.0; cols],
separator: String::new(),
}
}

fn get_terminal_width() -> usize {
let mut w: winsize = unsafe { mem::zeroed() };

if unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut w) } == 0 {
if w.ws_col > 0 {
w.ws_col as usize
} else {
80
}
} else {
80
}
}

fn calculate_column_widths(&mut self) {
let mut total_fixed_width = 0;
let mut total_ratio = 0.0;

for (i, &width) in self.col_widths.iter().enumerate() {
if let Some(w) = width {
total_fixed_width += w;
} else {
total_ratio += self.col_ratios[i];
}
}

let terminal_width = Grid::get_terminal_width();

if total_fixed_width >= terminal_width {
panic!("Total fixed column widths exceed the terminal width");
}

let remaining_width = terminal_width.saturating_sub(total_fixed_width);

for (i, width) in self.col_widths.iter_mut().enumerate() {
if width.is_none() {
let ratio = self.col_ratios[i];
*width = Some((remaining_width as f64 * (ratio / total_ratio)).round() as usize);
}
}
}

pub fn row(&mut self, row: Vec<String>) -> &mut Self {
if row.len() != self.cols {
panic!("Row length must match the number of columns.");
}
self.rows.push(row);
self
}

fn truncate_row(row: &[String], widths: &[usize]) -> Vec<String> {
row.iter()
.enumerate()
.map(|(i, col)| {
let max_width = widths[i];
if col.len() > max_width {
format!("{}...", &col[..max_width.saturating_sub(3)])
} else {
col.clone()
}
})
.collect()
}

pub fn print(mut self) {
self.calculate_column_widths();

let column_widths: Vec<usize> = self.col_widths.iter().map(|w| w.unwrap_or(0)).collect();

for row in &self.rows {
let truncated_row = Grid::truncate_row(row, &column_widths);
for (i, col) in truncated_row.iter().enumerate() {
let width = column_widths[i];
print!("{:width$} ", col, width = width);
if i < self.cols - 1 {
print!("{}", self.separator)
}
}
println!();
}
}
}

pub struct GridBuilder {
cols: usize,
col_widths: Vec<Option<usize>>,
col_ratios: Vec<f64>,
separator: String,
}

impl GridBuilder {
pub fn set_width(mut self, col: usize, width: usize) -> Self {
if col < self.cols {
self.col_widths[col] = Some(width);
}
self
}

pub fn set_ratio(mut self, col: usize, ratio: f64) -> Self {
if col < self.cols {
self.col_ratios[col] = ratio;
}
self
}

pub fn set_separator(mut self, separator: String) -> Self {
self.separator = separator;
self
}

pub fn build(self) -> Grid {
Grid {
cols: self.cols,
col_widths: self.col_widths,
col_ratios: self.col_ratios,
rows: Vec::new(),
separator: self.separator,
}
}
}
27 changes: 27 additions & 0 deletions src/core/log.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#[macro_export]
macro_rules! warn {
($($arg:tt)*) => {
println!("{} {}", "[WARN]".color(Color::BrightYellow).bold(), format!($($arg)*))
};
}

#[macro_export]
macro_rules! info {
($($arg:tt)*) => {
println!("{} {}", "[INFO]".color(Color::BrightBlue).bold(), format!($($arg)*))
};
}

#[macro_export]
macro_rules! error {
($($arg:tt)*) => {
eprintln!("{} {}", "[ERROR]".color(Color::BrightRed).bold(), format!($($arg)*))
};
}

#[macro_export]
macro_rules! success {
($($arg:tt)*) => {
println!("{} {}", "[SUCCESS]".color(Color::BrightGreen).bold(), format!($($arg)*))
};
}
3 changes: 3 additions & 0 deletions src/core/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
pub mod color;
pub mod config;
pub mod constant;
pub mod grid;
pub mod log;
pub mod util;
Loading

0 comments on commit 61d9ceb

Please sign in to comment.