use std::fmt::{
self,
Display
};
use std::io::Write;
use std::mem::{
needs_drop,
size_of
};
use std::str::from_utf8;
use crate::{
Conciliator,
Buffer,
Print
};
use crate::data::tree;
use crate::term::{
ColorCode,
EmitEscapes
};
const CUSTOM_PACKED_TAGS: Option<&str> = option_env!("CONCILIATOR_TAGS");
const DEFAULT_TAGS: Tags = Tags {
status: ">",
info: "+",
warn: "WARN",
error: "ERROR"
};
const CUSTOM_PACKED_PALETTE: Option<&str> = option_env!("CONCILIATOR_PALETTE");
const DEFAULT_PALETTE: Palette = Palette {
tag: ColorCode::White,
alpha: ColorCode::Blue,
beta: ColorCode::Green,
gamma: ColorCode::Red,
delta: ColorCode::IntenseRed,
zeta: ColorCode::Yellow,
iota: ColorCode::Cyan,
omega: ColorCode::IntenseBlack
};
static mut PALETTE: Palette = match Palette::unpack(CUSTOM_PACKED_PALETTE) {
Some(custom_palette) => custom_palette,
None => DEFAULT_PALETTE
};
pub(crate) static TAGS: Tags = match Tags::unpack(CUSTOM_PACKED_TAGS) {
Some(custom_tags) => custom_tags,
None => DEFAULT_TAGS
};
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct Palette {
pub tag: ColorCode,
pub alpha: ColorCode,
pub beta: ColorCode,
pub gamma: ColorCode,
pub delta: ColorCode,
pub zeta: ColorCode,
pub iota: ColorCode,
pub omega: ColorCode
}
pub struct Tags {
pub status: &'static str,
pub info: &'static str,
pub warn: &'static str,
pub error: &'static str
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Color {
Alpha,
Beta,
Gamma,
Delta,
Zeta,
Iota,
Omega
}
pub fn get_palette() -> Palette {
unsafe {PALETTE}
}
pub unsafe fn set_palette(new: Palette) {
assert!(!needs_drop::<Palette>());
assert!(size_of::<ColorCode>() == 1);
assert!(size_of::<Palette>() == 8);
PALETTE = new;
}
pub trait Paint: EmitEscapes {
fn tag(&mut self, color: Color, tag: &str) -> &mut Self {
let tag_color = unsafe {PALETTE.tag};
self.set_bold().unwrap();
self.set_color(tag_color).unwrap();
write!(self, "[ ").unwrap();
self.set_color(color.get_from_static()).unwrap();
self.write_all(tag.as_bytes()).unwrap();
self.set_color(tag_color).unwrap();
write!(self, " ] ").unwrap();
self.reset().unwrap();
self
}
fn push_plain<T: Display + ?Sized>(&mut self, thing: &T) -> &mut Self {
write!(self, "{thing}").unwrap();
self
}
fn push_bold<T: Display + ?Sized>(&mut self, thing: &T) -> &mut Self {
self.set_bold().unwrap();
write!(self, "{thing}").unwrap();
self.reset().unwrap();
self
}
fn push_alpha<T: Display + ?Sized>(&mut self, thing: &T) -> &mut Self {
self.push_with_color(Color::Alpha, thing)
}
fn push_beta<T: Display + ?Sized>(&mut self, thing: &T) -> &mut Self {
self.push_with_color(Color::Beta, thing)
}
fn push_gamma<T: Display + ?Sized>(&mut self, thing: &T) -> &mut Self {
self.push_with_color(Color::Gamma, thing)
}
fn push_delta<T: Display + ?Sized>(&mut self, thing: &T) -> &mut Self {
self.push_with_color(Color::Delta, thing)
}
fn push_zeta<T: Display + ?Sized>(&mut self, thing: &T) -> &mut Self {
self.push_with_color(Color::Zeta, thing)
}
fn push_iota<T: Display + ?Sized>(&mut self, thing: &T) -> &mut Self {
self.push_with_color(Color::Iota, thing)
}
fn push_omega<T: Display + ?Sized>(&mut self, thing: &T) -> &mut Self {
self.push_with_color(Color::Omega, thing)
}
fn push_alpha_bold<T: Display + ?Sized>(&mut self, thing: &T) -> &mut Self {
self.push_with_color_bold(Color::Alpha, thing)
}
fn push_beta_bold<T: Display + ?Sized>(&mut self, thing: &T) -> &mut Self {
self.push_with_color_bold(Color::Beta, thing)
}
fn push_gamma_bold<T: Display + ?Sized>(&mut self, thing: &T) -> &mut Self {
self.push_with_color_bold(Color::Gamma, thing)
}
fn push_delta_bold<T: Display + ?Sized>(&mut self, thing: &T) -> &mut Self {
self.push_with_color_bold(Color::Delta, thing)
}
fn push_zeta_bold<T: Display + ?Sized>(&mut self, thing: &T) -> &mut Self {
self.push_with_color_bold(Color::Zeta, thing)
}
fn push_iota_bold<T: Display + ?Sized>(&mut self, thing: &T) -> &mut Self {
self.push_with_color_bold(Color::Iota, thing)
}
fn push_omega_bold<T: Display + ?Sized>(&mut self, thing: &T) -> &mut Self {
self.push_with_color_bold(Color::Omega, thing)
}
fn push_with_color<T: Display + ?Sized>(
&mut self,
color: Color,
thing: &T)
-> &mut Self
{
self.write_with_color(color.get_from_static(), thing);
self
}
fn push_with_color_bold<T: Display + ?Sized>(
&mut self,
color: Color,
thing: &T)
-> &mut Self
{
self.write_with_color_bold(color.get_from_static(), thing);
self
}
fn push_with_any_color<T: Display + ?Sized>(
&mut self,
color: ColorCode,
thing: &T)
-> &mut Self
{
self.write_with_color(color, thing);
self
}
fn push_with_any_color_bold<T: Display + ?Sized>(
&mut self,
color: ColorCode,
thing: &T)
-> &mut Self
{
self.write_with_color_bold(color, thing);
self
}
}
impl<T: EmitEscapes> Paint for T {}
impl Color {
pub fn get_from_static(self) -> ColorCode {
use Color::*;
unsafe {
match self {
Alpha => PALETTE.alpha,
Beta => PALETTE.beta,
Gamma => PALETTE.gamma,
Delta => PALETTE.delta,
Zeta => PALETTE.zeta,
Iota => PALETTE.iota,
Omega => PALETTE.omega,
}
}
}
}
impl Buffer {
pub(crate) fn tag_open(&mut self) -> &mut Self {
self.push_with_any_color_bold(unsafe {PALETTE.tag}, "[")
}
pub(crate) fn tag_close(&mut self) -> &mut Self {
self.push_with_any_color_bold(unsafe {PALETTE.tag}, "] ")
}
}
impl Palette {
fn get(self, color: Color) -> ColorCode {
use Color::*;
match color {
Alpha => self.alpha,
Beta => self.beta,
Gamma => self.gamma,
Delta => self.delta,
Zeta => self.zeta,
Iota => self.iota,
Omega => self.omega,
}
}
fn write_tag(self, buffer: &mut Buffer, color: Color, tag: &str) {
buffer.set_bold().unwrap();
buffer.set_color(self.tag).unwrap();
write!(buffer, "[ ").unwrap();
buffer.set_color(self.get(color)).unwrap();
buffer.write_all(tag.as_bytes()).unwrap();
buffer.set_color(self.tag).unwrap();
write!(buffer, " ] ").unwrap();
buffer.reset().unwrap();
}
const fn unpack(packed: Option<&str>) -> Option<Self> {
let mut codes = [None; 8];
let Some(mut unparsed) = packed else {return None};
let mut i = 0;
while i < (codes.len() - 1) {
let Some((s, rest)) = split_once(unparsed, ", ") else {return None};
codes[i] = ColorCode::from_str(s);
i += 1;
unparsed = rest;
}
codes[7] = ColorCode::from_str(unparsed);
let [
Some(tag),
Some(alpha),
Some(beta),
Some(gamma),
Some(delta),
Some(zeta),
Some(iota),
Some(omega)
] = codes else {return None};
Some(Self {tag, alpha, beta, gamma, delta, zeta, iota, omega})
}
}
impl fmt::Debug for Palette {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { tag, alpha, beta, gamma, delta, zeta, iota, omega } = self;
let colors = [ tag, alpha, beta, gamma, delta, zeta, iota, omega ];
if f.alternate() {
write!(f, "{}", tag.as_number())?;
for color in &colors[1..] {
write!(f, ", {}", color.as_number())?;
}
}
else {
write!(f, "{tag:?}")?;
for color in &colors[1..] {
write!(f, ", {color:?}")?;
}
}
Ok(())
}
}
impl Print for Palette {
fn print<C: Conciliator + ?Sized>(self, con: &C) {
let omega = self.get(Color::Omega);
self.write_tag(&mut con.get_line(), Color::Omega, tree::ROOT);
let main_colors = [
(Color::Alpha, TAGS.status),
(Color::Beta, TAGS.info),
(Color::Gamma, TAGS.warn),
(Color::Delta, TAGS.error),
];
for (color, tag) in main_colors {
let code = self.get(color);
let name = format!("{color:?}");
let mut line = con.get_line();
line
.push(" ")
.push_with_any_color(omega, tree::KNOT);
self.write_tag(&mut line, color, tag);
line.push("\t")
.push_with_any_color(code, "███")
.push(" ")
.push_with_any_color_bold(code, &format_args!("{name:6}"))
.push(format_args!("({code:?})"));
}
con.get_line()
.push(" ")
.push_with_any_color_bold(omega, tree::STEM);
for color in [Color::Iota, Color::Zeta] {
let code = self.get(color);
let name = format!("{color:?}");
con.get_line()
.push(" ")
.push_with_any_color(omega, tree::KNOT)
.push_with_any_color(code, " spare ")
.push("\t")
.push_with_any_color(code, "███")
.push(" ")
.push_with_any_color_bold(code, &format_args!("{name:6}"))
.push(format_args!("({code:?})"));
}
con.get_line()
.push(" ")
.push_with_any_color_bold(omega, tree::TAIL)
.push_with_any_color(omega, " branches")
.push("\t")
.push_with_any_color(omega, "███")
.push(" ")
.push_with_any_color_bold(omega, "Omega")
.push(format_args!(" ({omega:?})"));
}
}
const fn slice_eq(mut a: &[u8], mut b: &[u8]) -> bool {
if a.len() != b.len() {return false;}
while let ([a_byte, a_rest @ ..], [b_byte, b_rest @ ..]) = (a, b) {
if *a_byte != *b_byte {return false;}
(a, b) = (a_rest, b_rest);
}
true
}
const fn strip_prefix<'a>(s: &'a str, prefix: &str) -> Option<&'a str> {
const fn is_char_boundary(s: &str, index: usize) -> bool {
if index == 0 || index == s.len() {true}
else if index > s.len() {false}
else {is_utf8_char_boundary(s.as_bytes()[index])}
}
const fn is_utf8_char_boundary(byte: u8) -> bool {
(byte as i8) >= -0x40
}
if prefix.len() > s.len() {return None;}
if !is_char_boundary(s, prefix.len()) {return None;}
let (maybe_prefix, rest) = s.as_bytes().split_at(prefix.len());
if !slice_eq(prefix.as_bytes(), maybe_prefix) {None}
else {
match from_utf8(rest) {
Ok(stripped) => Some(stripped),
Err(_err) => None
}
}
}
const fn split_once<'a>(s: &'a str, delim: &str) -> Option<(&'a str, &'a str)> {
let haystack = s.as_bytes();
let needle = delim.as_bytes();
if haystack.len() < needle.len() {return None;}
let mut index = 0;
while index + needle.len() <= haystack.len() {
let (prefix, rest) = haystack.split_at(index);
let (maybe_needle, rest) = rest.split_at(needle.len());
index += 1;
if !slice_eq(maybe_needle, needle) {continue;}
let (Ok(prefix), Ok(rest)) = (from_utf8(prefix), from_utf8(rest)) else {
return None;
};
return Some((prefix, rest));
}
None
}
impl Tags {
const fn unpack(packed: Option<&'static str>) -> Option<Self> {
let Some(packed) = packed else {return None};
let Some(rest) = strip_prefix(packed, "[ Tags ], [ ") else {
return None
};
let Some((status, rest)) = split_once(rest, " ], [ ") else {
return None
};
let Some((info, rest)) = split_once(rest, " ], [ ") else {return None};
let Some((warn, rest)) = split_once(rest, " ], [ ") else {return None};
let Some((error, rest)) = split_once(rest, " ]") else {return None};
if !rest.is_empty() {return None;}
Some(Tags {status, info, warn, error})
}
}
#[test]
fn change_palette() {
let con = crate::init();
con.status("Test").push_alpha("123");
let old = get_palette();
let new_palette = Palette {
tag: ColorCode::IntenseWhite,
alpha: ColorCode::Green,
beta: ColorCode::Blue,
gamma: ColorCode::IntenseRed,
delta: ColorCode::Red,
zeta: ColorCode::Magenta,
iota: ColorCode::Cyan,
omega: ColorCode::IntenseBlack
};
assert_ne!(old, new_palette);
unsafe {set_palette(new_palette);}
assert_eq!(new_palette, get_palette());
con.status("Test").push_alpha("123");
}
#[test]
fn slice_eq_test() {
let tests: [(&[u8], &[u8], bool); 7] = [
(b"abc", b"abc", true),
(b"", b"", true),
(b"", b" ", false),
(b" ", b"", false),
(b"abc", b"123", false),
(b"abc", b"abc123", false),
(b"abc123", b"abc", false)
];
for (a, b, result) in tests {
assert_eq!(slice_eq(a, b), result);
}
}
#[test]
fn split_once_test() {
let tests = [
("x ] abc", " ] ", Some(("x", "abc"))),
("x ]abc", " ] ", None),
("[123] ] abc", " ] ", Some(("[123]", "abc"))),
("x ]", " ]", Some(("x", ""))),
];
for (s, delim, result) in tests {
assert_eq!(split_once(s, delim), result);
}
}
#[test]
fn unpack_tags() {
const PACKED_TAGS: &str = "[ Tags ], [ > ], [ + ], [ WARN ], [ ERROR ]";
const UNPACKED: Tags = match Tags::unpack(Some(PACKED_TAGS)) {
Some(t) => t,
None => panic!("unpacking failed")
};
let Tags { status, info, warn, error } = UNPACKED;
assert_eq!(DEFAULT_TAGS.status, status);
assert_eq!(DEFAULT_TAGS.info, info);
assert_eq!(DEFAULT_TAGS.warn, warn);
assert_eq!(DEFAULT_TAGS.error, error);
}
#[test]
fn unpack_palette() {
const PACKED_PALETTE: &str =
"White, Blue, Green, Red, IntenseRed, Yellow, Cyan, IntenseBlack";
const UNPACKED: Palette = match Palette::unpack(Some(PACKED_PALETTE)) {
Some(t) => t,
None => panic!("unpacking failed")
};
assert_eq!(DEFAULT_PALETTE, UNPACKED);
const NUM_PACKED: &str = "7, 4, 2, 1, 9, 3, 6, 8";
const NUM_UNPACKED: Palette = match Palette::unpack(Some(NUM_PACKED)) {
Some(t) => t,
None => panic!("unpacking failed")
};
assert_eq!(DEFAULT_PALETTE, NUM_UNPACKED);
}