server description now printed with coloring

This commit is contained in:
LordMZTE 2021-09-07 15:08:46 +02:00
parent 258899cf61
commit f015c85c60
3 changed files with 100 additions and 25 deletions

View File

@ -7,25 +7,25 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.40"
anyhow = "1.0.43"
base64 = "0.13.0"
clap = "2.33.3"
crossterm = "0.21.0"
image = "0.23.14"
itertools = "0.10.0"
serde_json = "1.0.64"
itertools = "0.10.1"
serde_json = "1.0.67"
smart-default = "0.6.0"
structopt = "0.3.21"
structopt = "0.3.23"
term_size = "0.3.2"
termcolor = "1.1.2"
unicode-width = "0.1.8"
viuer = "0.4.0"
viuer = "0.5.2"
[dependencies.async-minecraft-ping]
git = "https://github.com/LordMZTE/async-minecraft-ping.git"
tag = "v0.2.5"
[dependencies.tokio]
version = "1.5.0"
version = "1.11.0"
features = ["rt-multi-thread", "macros", "time"]
[build-dependencies]

View File

@ -3,9 +3,13 @@ extern crate smart_default;
use crate::output::Table;
use anyhow::{anyhow, bail, Context};
use crossterm::{
style::{Attribute, Color, Print, ResetColor, SetAttribute, SetForegroundColor},
ExecutableCommand,
};
use image::{DynamicImage, ImageFormat};
use itertools::Itertools;
use std::io::Cursor;
use std::io::{self, Cursor, Write};
pub mod output;
@ -26,18 +30,85 @@ macro_rules! none_if_empty {
}};
}
pub fn remove_formatting(s: &str) -> String {
let chars = s.char_indices().rev();
let mut buf = s.to_owned();
for c in chars {
if c.1 == '§' {
buf.remove(c.0);
if c.0 < buf.len() {
buf.remove(c.0);
/// Print mincraft-formatted text to `out` using crossterm
pub fn print_mc_formatted(s: &str, mut out: impl Write) -> io::Result<()> {
macro_rules! exec {
(fg, $color:ident) => {
exec!(SetForegroundColor(Color::$color))
};
(at, $attr:ident) => {
exec!(SetAttribute(Attribute::$attr))
};
($action:expr) => {{
out.execute($action)?;
}};
}
let mut splits = s.split('§');
if let Some(n) = splits.next() {
exec!(Print(n));
}
let mut empty = true;
for split in splits {
empty = false;
if let Some(c) = split.chars().next() {
match c {
// Colors
'0' => exec!(fg, Black),
'1' => exec!(fg, DarkBlue),
'2' => exec!(fg, DarkGreen),
'3' => exec!(fg, DarkCyan),
'4' => exec!(fg, DarkRed),
'5' => exec!(fg, DarkMagenta),
'6' => exec!(fg, DarkYellow),
'7' => exec!(fg, Grey),
'8' => exec!(fg, DarkGrey),
'9' => exec!(fg, Blue),
'a' => exec!(fg, Green),
'b' => exec!(fg, Cyan),
'c' => exec!(fg, Red),
'd' => exec!(fg, Magenta),
'e' => exec!(fg, Yellow),
'f' => exec!(fg, White),
// Formatting
// Obfuscated. This is the closest thing, althogh not many terminals support it.
'k' => exec!(at, RapidBlink),
'l' => exec!(at, Bold),
'm' => exec!(at, CrossedOut),
'n' => exec!(at, Underlined),
'o' => exec!(at, Italic),
'r' => exec!(ResetColor),
_ => {},
}
exec!(Print(&split[1..]));
}
}
buf
// no need to reset color if there were no escape codes.
if !empty {
exec!(ResetColor);
}
Ok(())
}
pub fn mc_formatted_to_ansi(s: &str) -> io::Result<String> {
let mut bytes = Vec::new();
let mut c = Cursor::new(&mut bytes);
print_mc_formatted(s, &mut c)?;
// this shouldn't be able to fail, as we started of with a valid utf8 string.
#[cfg(debug_assertions)]
let out = String::from_utf8(bytes).unwrap();
#[cfg(not(debug_assertions))]
let out = unsafe { String::from_utf8_unchecked(bytes) };
Ok(out)
}
/// formats a iterator to a readable list
@ -65,13 +136,13 @@ pub fn get_table<'a>(
/// parses a base64 formatted image
pub fn parse_base64_image(data: String) -> anyhow::Result<DynamicImage> {
let (header, data) = data
.split_once(",")
.split_once(',')
.context("Couldn't parse base64 image due to missing format header.")?;
let (data_type, image_format) = header
.split_once("/")
.split_once('/')
.context("Failed to parse base64 image, header has invalid format.")?;
let image_format = image_format
.split(";")
.split(';')
.next()
.context("Failed to parse base64 image, header has invalid format.")?;

View File

@ -6,7 +6,7 @@ use structopt::StructOpt;
use time::{Duration, Instant};
use tokio::time;
use mcstat::{get_table, none_if_empty, output::Table, parse_base64_image, remove_formatting};
use mcstat::{get_table, mc_formatted_to_ansi, none_if_empty, output::Table, parse_base64_image};
#[derive(Debug, StructOpt)]
#[structopt(
@ -197,25 +197,29 @@ fn format_table(
table.max_block_width = w;
}
if let Some(s) = none_if_empty!(remove_formatting(&response.description.get_text())) {
if let Some(s) = none_if_empty!(mc_formatted_to_ansi(response.description.get_text())
.unwrap_or_else(|e| format!("Error: {}", e)))
{
table.big_entry("Description", s);
}
if let ServerDescription::Big(big_desc) = &response.description {
let desc = &big_desc.extra;
let txt = desc.into_iter().map(|p| p.text.clone()).collect::<String>();
let txt = desc.iter().map(|p| p.text.clone()).collect::<String>();
if let Some(s) = none_if_empty!(txt) {
table.big_entry("Extra Description", s);
}
}
if let Some(s) = none_if_empty!(remove_formatting(&player_sample)) {
if let Some(s) = none_if_empty!(
mc_formatted_to_ansi(&player_sample).unwrap_or_else(|e| format!("Error: {}", e))
) {
table.big_entry("Player Sample", s);
}
table.blank();
if let Some(s) = none_if_empty!(remove_formatting(&response.version.name)) {
if let Some(s) = none_if_empty!(&response.version.name) {
table.small_entry("Server Version", s);
}