server description now printed with coloring
This commit is contained in:
parent
258899cf61
commit
f015c85c60
3 changed files with 100 additions and 25 deletions
14
Cargo.toml
14
Cargo.toml
|
@ -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]
|
||||
|
|
97
src/lib.rs
97
src/lib.rs
|
@ -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.")?;
|
||||
|
||||
|
|
14
src/main.rs
14
src/main.rs
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue