mirror of
https://github.com/LordMZTE/mcstat.git
synced 2024-05-04 05:41:11 +02:00
mcstat cleanup
This commit is contained in:
parent
56f1f7ac9b
commit
82f65ed721
15
Cargo.toml
15
Cargo.toml
|
@ -7,15 +7,16 @@ edition = "2018"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "^2.33", features = ["yaml"] }
|
anyhow = "1.0.32"
|
||||||
image-base64 = "0.1.0"
|
|
||||||
image = "0.23.9"
|
|
||||||
asciify = "0.1.6"
|
asciify = "0.1.6"
|
||||||
async-minecraft-ping = { git = "https://github.com/LordMZTE/async-minecraft-ping.git", tag = "v0.2.4" }
|
async-minecraft-ping = { git = "https://github.com/LordMZTE/async-minecraft-ping.git", tag = "v0.2.4" }
|
||||||
tokio = { version = "0.2.22", features = ["full"] }
|
clap = { version = "^2.33", features = ["yaml"] }
|
||||||
|
image = "0.23.9"
|
||||||
|
image-base64 = "0.1.0"
|
||||||
itertools = "0.9.0"
|
itertools = "0.9.0"
|
||||||
termcolor = "1"
|
|
||||||
anyhow = "1.0.32"
|
|
||||||
serde_json = "1.0.58"
|
serde_json = "1.0.58"
|
||||||
term_size = "0.3.2"
|
|
||||||
smart-default = "0.6.0"
|
smart-default = "0.6.0"
|
||||||
|
term_size = "0.3.2"
|
||||||
|
termcolor = "1"
|
||||||
|
tokio = { version = "0.2.22", features = ["full"] }
|
||||||
|
unicode-width = "0.1.8"
|
||||||
|
|
40
src/lib.rs
40
src/lib.rs
|
@ -3,6 +3,8 @@ extern crate smart_default;
|
||||||
|
|
||||||
use asciify::AsciiBuilder;
|
use asciify::AsciiBuilder;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use crate::output::Table;
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
pub mod output;
|
pub mod output;
|
||||||
|
|
||||||
|
@ -12,13 +14,15 @@ pub mod output;
|
||||||
/// trait
|
/// trait
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! none_if_empty {
|
macro_rules! none_if_empty {
|
||||||
($x:expr) => {
|
($x:expr) => {{
|
||||||
if $x.is_empty() {
|
// let binding to avoid copying
|
||||||
|
let x = $x;
|
||||||
|
if x.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some($x)
|
Some(x)
|
||||||
}
|
}
|
||||||
};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AsciiConfig {
|
pub struct AsciiConfig {
|
||||||
|
@ -58,23 +62,15 @@ pub fn get_table<'a>(
|
||||||
entries: impl Iterator<Item = (&'a str, &'a str)> + Clone,
|
entries: impl Iterator<Item = (&'a str, &'a str)> + Clone,
|
||||||
second_column: bool,
|
second_column: bool,
|
||||||
) -> String {
|
) -> String {
|
||||||
// the width at which | characters should be placed this is the length of the
|
if second_column {
|
||||||
// longest entry
|
let mut table = Table::new();
|
||||||
let max_width = if second_column {
|
for entry in entries {
|
||||||
entries.clone().map(|m| m.0.len()).max().unwrap_or_default()
|
table.small_entry(entry.0, entry.1);
|
||||||
|
}
|
||||||
|
let mut cursor = Cursor::new(Vec::<u8>::new());
|
||||||
|
table.print(&mut cursor).unwrap();
|
||||||
|
String::from_utf8(cursor.into_inner()).unwrap()
|
||||||
} else {
|
} else {
|
||||||
// this will not be used in case second_column is off so we just use 0
|
entries.map(|x| x.0).intersperse("\n").collect()
|
||||||
0
|
}
|
||||||
};
|
|
||||||
|
|
||||||
entries
|
|
||||||
.map(|m| {
|
|
||||||
if second_column {
|
|
||||||
format!("{: <width$} | {}", m.0, m.1, width = max_width)
|
|
||||||
} else {
|
|
||||||
m.0.to_owned()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.intersperse("\n".to_owned())
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
173
src/main.rs
173
src/main.rs
|
@ -1,6 +1,4 @@
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use time::Duration;
|
|
||||||
use tokio::time;
|
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use asciify::AsciiBuilder;
|
use asciify::AsciiBuilder;
|
||||||
|
@ -8,11 +6,15 @@ use async_minecraft_ping::{ConnectionConfig, ServerDescription, StatusResponse};
|
||||||
use clap::{load_yaml, App};
|
use clap::{load_yaml, App};
|
||||||
use image::ImageFormat;
|
use image::ImageFormat;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use mcstat::{get_table, none_if_empty, output::Table, remove_formatting, AsciiConfig};
|
|
||||||
use termcolor::{Buffer, BufferWriter, ColorChoice, WriteColor};
|
use termcolor::{Buffer, BufferWriter, ColorChoice, WriteColor};
|
||||||
|
use time::Duration;
|
||||||
|
use tokio::time;
|
||||||
|
|
||||||
|
use mcstat::{get_table, none_if_empty, output::Table, remove_formatting, AsciiConfig};
|
||||||
|
|
||||||
/// this message is used if getting a value from the arguments fails
|
/// this message is used if getting a value from the arguments fails
|
||||||
const ARGUMENT_FAIL_MESSAGE: &str = "failed to get value from args";
|
const ARGUMENT_FAIL_MESSAGE: &str = "failed to get value from args";
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
let yaml = load_yaml!("args.yml");
|
let yaml = load_yaml!("args.yml");
|
||||||
|
@ -93,88 +95,13 @@ async fn main() -> Result<()> {
|
||||||
println!("This server has mods. To show them use the -m argument\n")
|
println!("This server has mods. To show them use the -m argument\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
let player_sample = response
|
format_table(
|
||||||
.players
|
&response,
|
||||||
.sample
|
matches.is_present("mods"),
|
||||||
.as_ref()
|
matches.is_present("modversions"),
|
||||||
.unwrap_or(&vec![])
|
matches.is_present("channels"),
|
||||||
.iter()
|
)
|
||||||
.map(|p| p.name.as_str())
|
.stdout()?;
|
||||||
.intersperse("\n")
|
|
||||||
.collect::<String>();
|
|
||||||
|
|
||||||
let mut table = Table::new();
|
|
||||||
|
|
||||||
if let Some((w, _)) = term_size::dimensions() {
|
|
||||||
table.max_block_width = w;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.opt_big_entry(
|
|
||||||
"Description",
|
|
||||||
none_if_empty!(remove_formatting(&response.description.get_text())),
|
|
||||||
);
|
|
||||||
|
|
||||||
table.opt_big_entry("Extra Description", {
|
|
||||||
if let ServerDescription::Big(big_desc) = &response.description {
|
|
||||||
let desc = &big_desc.extra;
|
|
||||||
if desc.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(desc.into_iter().map(|p| p.text.clone()).collect::<String>())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table.opt_big_entry(
|
|
||||||
"Player Sample",
|
|
||||||
none_if_empty!(remove_formatting(&player_sample)),
|
|
||||||
);
|
|
||||||
|
|
||||||
table.blank();
|
|
||||||
|
|
||||||
table.opt_small_entry(
|
|
||||||
"Server Version",
|
|
||||||
none_if_empty!(remove_formatting(&response.version.name)),
|
|
||||||
);
|
|
||||||
table.small_entry("Online Players", &response.players.online);
|
|
||||||
table.small_entry("Max Players", &response.players.max);
|
|
||||||
table.small_entry("Protocol Version", &response.version.protocol);
|
|
||||||
|
|
||||||
table.blank();
|
|
||||||
|
|
||||||
table.opt_big_entry(
|
|
||||||
"Mods",
|
|
||||||
if let (Some(mod_list), true) = (response.forge_mod_info(), matches.is_present("mods")) {
|
|
||||||
Some(get_table(
|
|
||||||
mod_list
|
|
||||||
.iter()
|
|
||||||
.sorted_by(|a, b| a.modid.cmp(&b.modid))
|
|
||||||
.map(|m| (&*m.modid, &*m.version)),
|
|
||||||
matches.is_present("modversions"),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
table.opt_big_entry(
|
|
||||||
"Forge Channels",
|
|
||||||
if let (true, Some(fd)) = (matches.is_present("channels"), response.forge_data) {
|
|
||||||
Some(get_table(
|
|
||||||
fd.channels
|
|
||||||
.iter()
|
|
||||||
.sorted_by(|a, b| a.res.cmp(&b.res))
|
|
||||||
.map(|c| (&*c.res, &*c.version)),
|
|
||||||
true,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
table.stdout()?;
|
|
||||||
|
|
||||||
if let Some(img) = image {
|
if let Some(img) = image {
|
||||||
println!("\n{}", img.await??);
|
println!("\n{}", img.await??);
|
||||||
|
@ -220,3 +147,79 @@ async fn asciify_base64_image(favicon: String, config: AsciiConfig) -> Result<St
|
||||||
|
|
||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn format_table(response: &StatusResponse, mods: bool, modversions: bool, channels: bool) -> Table {
|
||||||
|
let player_sample = response
|
||||||
|
.players
|
||||||
|
.sample
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&vec![])
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.name.as_str())
|
||||||
|
.intersperse("\n")
|
||||||
|
.collect::<String>();
|
||||||
|
|
||||||
|
let mut table = Table::new();
|
||||||
|
|
||||||
|
if let Some((w, _)) = term_size::dimensions() {
|
||||||
|
table.max_block_width = w;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(s) = none_if_empty!(remove_formatting(&response.description.get_text())) {
|
||||||
|
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>();
|
||||||
|
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)) {
|
||||||
|
table.big_entry("Player Sample", s);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.blank();
|
||||||
|
|
||||||
|
if let Some(s) = none_if_empty!(remove_formatting(&response.version.name)) {
|
||||||
|
table.small_entry("Server Version", s);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.small_entry("Online Players", &response.players.online);
|
||||||
|
table.small_entry("Max Players", &response.players.max);
|
||||||
|
table.small_entry("Protocol Version", &response.version.protocol);
|
||||||
|
|
||||||
|
table.blank();
|
||||||
|
|
||||||
|
if let (Some(mod_list), true) = (response.forge_mod_info(), mods) {
|
||||||
|
let txt = get_table(
|
||||||
|
mod_list
|
||||||
|
.iter()
|
||||||
|
.sorted_by(|a, b| a.modid.cmp(&b.modid))
|
||||||
|
.map(|m| (&*m.modid, &*m.version)),
|
||||||
|
modversions,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(s) = none_if_empty!(txt) {
|
||||||
|
table.big_entry("Mods", s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (true, Some(fd)) = (channels, &response.forge_data) {
|
||||||
|
let txt = get_table(
|
||||||
|
fd.channels
|
||||||
|
.iter()
|
||||||
|
.sorted_by(|a, b| a.res.cmp(&b.res))
|
||||||
|
.map(|c| (&*c.res, &*c.version)),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(s) = none_if_empty!(txt) {
|
||||||
|
table.big_entry("Forge Channels", s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ use std::{
|
||||||
cmp::{max, min},
|
cmp::{max, min},
|
||||||
io::{self, Write},
|
io::{self, Write},
|
||||||
};
|
};
|
||||||
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
#[derive(SmartDefault)]
|
#[derive(SmartDefault)]
|
||||||
pub struct Table {
|
pub struct Table {
|
||||||
|
@ -34,7 +35,7 @@ impl Table {
|
||||||
|
|
||||||
pub fn small_entry(&mut self, name: impl ToString, val: impl ToString) {
|
pub fn small_entry(&mut self, name: impl ToString, val: impl ToString) {
|
||||||
let name = name.to_string();
|
let name = name.to_string();
|
||||||
self.set_small_width(name.len());
|
self.set_small_width(name.width());
|
||||||
|
|
||||||
self.entries
|
self.entries
|
||||||
.push(Box::new(SmallTableEntry(name, val.to_string())));
|
.push(Box::new(SmallTableEntry(name, val.to_string())));
|
||||||
|
@ -48,21 +49,6 @@ impl Table {
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn opt_small_entry(&mut self, name: impl ToString, val: Option<impl ToString>) {
|
|
||||||
let name = name.to_string();
|
|
||||||
self.set_small_width(name.len());
|
|
||||||
|
|
||||||
self.entries.push(Box::new(OptSmallTableEntry(
|
|
||||||
val.map(|t| SmallTableEntry(name, t.to_string())),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn opt_big_entry(&mut self, name: impl ToString, val: Option<impl ToString>) {
|
|
||||||
self.entries.push(Box::new(OptBigTableEntry(val.map(|t| {
|
|
||||||
BigTableEntry::new(name.to_string(), t.to_string(), self.max_block_width)
|
|
||||||
}))));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_small_width(&mut self, width: usize) {
|
fn set_small_width(&mut self, width: usize) {
|
||||||
if width > self.small_entry_width {
|
if width > self.small_entry_width {
|
||||||
self.small_entry_width = width;
|
self.small_entry_width = width;
|
||||||
|
@ -111,44 +97,20 @@ impl BigTableEntry {
|
||||||
pub fn new(name: String, val: String, maxwidth: usize) -> Self {
|
pub fn new(name: String, val: String, maxwidth: usize) -> Self {
|
||||||
let val_width = min(
|
let val_width = min(
|
||||||
max(
|
max(
|
||||||
val.lines().map(|s| s.len()).max().unwrap_or_default(),
|
val.lines().map(|s| s.width()).max().unwrap_or_default(),
|
||||||
name.len() + 4,
|
name.width() + 4,
|
||||||
),
|
),
|
||||||
maxwidth,
|
maxwidth,
|
||||||
);
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
width: max(name.len(), val_width),
|
width: max(name.width(), val_width),
|
||||||
name,
|
name,
|
||||||
val,
|
val,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct OptSmallTableEntry(Option<SmallTableEntry>);
|
|
||||||
|
|
||||||
impl TableEntry for OptSmallTableEntry {
|
|
||||||
fn print(&self, out: &mut dyn Write, table: &Table) -> io::Result<()> {
|
|
||||||
if let Some(entry) = &self.0 {
|
|
||||||
entry.print(out, table)
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct OptBigTableEntry(Option<BigTableEntry>);
|
|
||||||
|
|
||||||
impl TableEntry for OptBigTableEntry {
|
|
||||||
fn print(&self, out: &mut dyn Write, table: &Table) -> io::Result<()> {
|
|
||||||
if let Some(entry) = &self.0 {
|
|
||||||
entry.print(out, table)
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct BlankTableEntry;
|
pub struct BlankTableEntry;
|
||||||
|
|
||||||
impl TableEntry for BlankTableEntry {
|
impl TableEntry for BlankTableEntry {
|
||||||
|
|
Loading…
Reference in a new issue