Implement human-readable output
authorMegaBrutal <megabrutal+github@megabrutal.com>
Mon, 20 Feb 2023 02:15:00 +0000 (03:15 +0100)
committerMegaBrutal <megabrutal+github@megabrutal.com>
Mon, 20 Feb 2023 02:15:00 +0000 (03:15 +0100)
src/main.rs

index 792b64cce27da46d5ddb9217f11f20c7f29d5a81..6e87bad1633f1fbe0a378ecaa9914858d1d1417d 100644 (file)
@@ -7,6 +7,30 @@ use serde::{Serialize, Serializer};
 use a2s::A2SClient;
 use crate::HLQueryError::{IOError,A2SError};
 
+macro_rules! write_datapoint {
+    ($formatter:expr,$pad:expr,$caption:expr,$var:expr) => {
+        writeln!($formatter, "{:<pad$} {}", $caption, $var, pad = $pad)
+    }
+}
+
+macro_rules! implement_displaydebug {
+    ($foreigntype:path,$localtype:ident) => {
+        struct $localtype<'a>(&'a $foreigntype);
+
+        impl Display for $localtype<'_> {
+            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+                write!(f, "{:?}", self.0)
+            }
+        }
+    }
+}
+
+implement_displaydebug!(a2s::info::ServerOS, ServerOS);
+implement_displaydebug!(a2s::info::ServerType, ServerType);
+implement_displaydebug!(Option<a2s::info::TheShip>, TheShip);
+implement_displaydebug!(Option<a2s::info::SourceTVInfo>, SourceTVInfo);
+implement_displaydebug!(Option<a2s::players::TheShipPlayer>, TheShipPlayer);
+
 #[derive(Parser)]
 #[command(name = "HLQuery")]
 #[command(author = "MegaBrutal")]
@@ -17,6 +41,10 @@ struct Cli {
     #[arg(short, long)]
     json: bool,
 
+    /// Print output in Rust debug format
+    #[arg(short, long)]
+    rust: bool,
+
     /// Pretty-print JSON or Rust objects
     #[arg(short, long)]
     pretty: bool,
@@ -24,7 +52,6 @@ struct Cli {
     addresses: Vec<String>
 }
 
-
 #[derive(Debug, Serialize)]
 struct HLQueryResult {
     address:    SocketAddrV4,
@@ -44,6 +71,70 @@ impl HLQueryResult {
     }
 }
 
+const PAD_WIDTH: usize = 25;
+
+impl Display for HLQueryResult {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write_datapoint!(f, PAD_WIDTH, "Address:", self.address)?;
+        match &self.info {
+            Ok(info) => {
+                writeln!(f, "\nServer info:")?;
+                write_datapoint!(f, PAD_WIDTH, "Server name:", info.name)?;
+                write_datapoint!(f, PAD_WIDTH, "Game:", info.game)?;
+                write_datapoint!(f, PAD_WIDTH, "Folder:", info.folder)?;
+                write_datapoint!(f, PAD_WIDTH, "Current map:", info.map)?;
+                write_datapoint!(f, PAD_WIDTH, "Protocol version:", info.protocol)?;
+                write_datapoint!(f, PAD_WIDTH, "Steam App ID:", info.app_id)?;
+                write_datapoint!(f, PAD_WIDTH, "Number of players:", format!("{}/{}", info.players, info.max_players))?;
+                write_datapoint!(f, PAD_WIDTH, "Number of bots:", info.bots)?;
+                write_datapoint!(f, PAD_WIDTH, "Server type:", ServerType(&info.server_type))?;
+                write_datapoint!(f, PAD_WIDTH, "Server OS:", ServerOS(&info.server_os))?;
+                write_datapoint!(f, PAD_WIDTH, "Password protected:", if info.visibility { "Private" } else { "Public" })?;
+                write_datapoint!(f, PAD_WIDTH, "VAC enabled:", info.vac)?;
+                write_datapoint!(f, PAD_WIDTH, "The Ship mode:", TheShip(&info.the_ship))?;
+                write_datapoint!(f, PAD_WIDTH, "Server version:", info.version)?;
+                write_datapoint!(f, PAD_WIDTH, "Extra Data Flag:", info.edf)?;
+                write_datapoint!(f, PAD_WIDTH, "Server port:", info.extended_server_info.port.map_or("Unknown".to_string(), |i| i.to_string()))?;
+                write_datapoint!(f, PAD_WIDTH, "Steam ID:", info.extended_server_info.steam_id.map_or("Unknown".to_string(), |i| i.to_string()))?;
+                write_datapoint!(f, PAD_WIDTH, "Keywords:", info.extended_server_info.keywords.clone().unwrap_or("Unknown".to_string()))?;
+                write_datapoint!(f, PAD_WIDTH, "Game ID:", info.extended_server_info.game_id.map_or("Unknown".to_string(), |i| i.to_string()))?;
+                write_datapoint!(f, PAD_WIDTH, "Source TV port:", SourceTVInfo(&info.source_tv))?;
+            },
+            Err(e) =>
+                writeln!(f, "Failed to query server info:\t{}", e)?
+        }
+
+        match &self.rules {
+            Ok(rules) => {
+                writeln!(f, "\nGame rules (CVARs):")?;
+                for rule in rules {
+                    write_datapoint!(f, PAD_WIDTH, rule.name, format!("\"{}\"", rule.value))?;
+                }
+            }
+            Err(e) =>
+                writeln!(f, "Failed to query game rules:\t{}", e)?
+        }
+
+        match &self.players {
+            Ok(players) => {
+                writeln!(f, "\nPlayers:")?;
+                writeln!(f, "\t{:>3} players online.", players.len())?;
+                for player in players {
+                    writeln!(f, "{}: {:<30} {:>4} {:>10} {}",
+                        player.index,
+                        player.name,
+                        player.score,
+                        player.duration,
+                        TheShipPlayer(&player.the_ship))?;
+                }
+                Ok(())
+            }
+            Err(e) =>
+                writeln!(f, "Failed to query players:\t{}", e)
+        }
+    }
+}
+
 #[derive(Debug, Serialize)]
 struct HLQuery {
     input:      String,
@@ -57,6 +148,23 @@ impl HLQuery {
     }
 }
 
+impl Display for HLQuery {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write_datapoint!(f, PAD_WIDTH, "Input:", self.input)?;
+        match &self.result {
+            Ok(v) => {
+                write_datapoint!(f, PAD_WIDTH, "Number of addresses:", format!("{}\n", v.len()))?;
+                for r in v {
+                    writeln!(f, "{r}\n")?;
+                };
+                Ok(())
+            },
+            Err(e) =>
+                writeln!(f, "Failed to execute query:\t{e}\n")
+        }
+    }
+}
+
 #[derive(Debug)]
 enum HLQueryError {
     IOError(std::io::Error),
@@ -126,7 +234,7 @@ fn main() {
             println!("{}", serde_json::to_string(&query_results).unwrap());
         }
     }
-    else {
+    else if cli.rust {
         if cli.pretty {
             println!("{:#?}", query_results);
         }
@@ -134,4 +242,9 @@ fn main() {
             println!("{:?}", query_results);
         }
     }
+    else {
+        for query in query_results {
+            println!("{}", query);
+        }
+    }
 }