Skip to content

Commit c5bf2bd

Browse files
committed
query: add filter options for clver, buildnum, gamedir and map
1 parent 015d817 commit c5bf2bd

File tree

1 file changed

+116
-9
lines changed

1 file changed

+116
-9
lines changed

query/src/cli.rs

Lines changed: 116 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
// SPDX-License-Identifier: GPL-3.0-only
22
// SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com>
33

4-
use std::process;
4+
use std::{
5+
fmt::{self, Write},
6+
process,
7+
str::FromStr,
8+
};
59

610
use getopts::Options;
711

8-
use xash3d_protocol as proto;
12+
use xash3d_protocol::{self as proto, filter::Version};
913

1014
const BIN_NAME: &str = env!("CARGO_BIN_NAME");
1115
const PKG_NAME: &str = env!("CARGO_PKG_NAME");
@@ -14,6 +18,98 @@ const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
1418
const DEFAULT_HOST: &str = "mentality.rip";
1519
const DEFAULT_PORT: u16 = 27010;
1620

21+
const DEFAULT_CLIENT_BUILDNUM: u32 = 4000;
22+
23+
struct Filter {
24+
clver: Option<Version>,
25+
buildnum: Option<u32>,
26+
protocol: Option<u8>,
27+
gamedir: Option<String>,
28+
map: Option<String>,
29+
}
30+
31+
fn filter_opt<T: FromStr, F: Fn(&T) -> bool>(
32+
matches: &getopts::Matches,
33+
name: &str,
34+
dst: &mut Option<T>,
35+
f: F,
36+
) {
37+
if let Some(s) = matches.opt_str(name) {
38+
if s == "none" {
39+
*dst = None;
40+
return;
41+
}
42+
match s.parse() {
43+
Ok(v) => {
44+
if f(&v) {
45+
*dst = Some(v);
46+
}
47+
}
48+
Err(_) => {
49+
eprintln!("Invalid value for --{name}: {s}");
50+
process::exit(1);
51+
}
52+
}
53+
}
54+
}
55+
56+
impl Filter {
57+
fn opt_get(&mut self, matches: &getopts::Matches) -> String {
58+
let mut out = String::new();
59+
60+
if let Some(s) = matches.opt_str("filter") {
61+
if s.contains("\\clver\\") {
62+
self.clver = None;
63+
}
64+
if s.contains("\\buildnum\\") {
65+
self.buildnum = None;
66+
}
67+
out = s;
68+
}
69+
70+
filter_opt(matches, "filter-clver", &mut self.clver, |_| true);
71+
filter_opt(matches, "filter-buildnum", &mut self.buildnum, |_| true);
72+
filter_opt(matches, "filter-gamedir", &mut self.gamedir, |s| s != "all");
73+
filter_opt(matches, "filter-map", &mut self.map, |s| s != "map");
74+
75+
write!(&mut out, "{self}").unwrap();
76+
out
77+
}
78+
}
79+
80+
impl fmt::Display for Filter {
81+
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
82+
if let Some(clver) = self.clver {
83+
write!(fmt, "\\clver\\{clver}")?;
84+
}
85+
if let Some(buildnum) = self.buildnum {
86+
write!(fmt, "\\buildnum\\{buildnum}")?;
87+
}
88+
if let Some(protocol) = self.protocol {
89+
write!(fmt, "\\protocol\\{protocol}")?;
90+
}
91+
if let Some(gamedir) = &self.gamedir {
92+
write!(fmt, "\\gamedir\\{gamedir}")?;
93+
}
94+
if let Some(map) = &self.map {
95+
write!(fmt, "\\map\\{map}")?;
96+
}
97+
Ok(())
98+
}
99+
}
100+
101+
impl Default for Filter {
102+
fn default() -> Self {
103+
Self {
104+
clver: Some(proto::CLIENT_VERSION),
105+
buildnum: Some(DEFAULT_CLIENT_BUILDNUM),
106+
protocol: None,
107+
gamedir: None,
108+
map: None,
109+
}
110+
}
111+
}
112+
17113
#[derive(Debug)]
18114
pub struct Cli {
19115
pub masters: Vec<Box<str>>,
@@ -42,7 +138,7 @@ impl Default for Cli {
42138
json: false,
43139
debug: false,
44140
force_color: false,
45-
filter: format!("\\gamedir\\valve\\clver\\{}", proto::CLIENT_VERSION),
141+
filter: String::new(),
46142
key: None,
47143
}
48144
}
@@ -79,7 +175,7 @@ pub fn parse() -> Cli {
79175
"master address to connect [default: {}]",
80176
cli.masters.join(",")
81177
);
82-
opts.optopt("m", "master", &help, "LIST");
178+
opts.optopt("M", "master", &help, "LIST");
83179
let help = format!(
84180
"time to wait results from masters [default: {}]",
85181
cli.master_timeout
@@ -102,8 +198,21 @@ pub fn parse() -> Cli {
102198
opts.optflag("d", "debug", "output debug");
103199
opts.optflag("F", "force-color", "force colored output");
104200
opts.optflag("k", "key", "send challenge key to master");
105-
let help = format!("query filter [default: {:?}]", cli.filter);
201+
202+
// Filter options
203+
let mut filter = Filter::default();
204+
let help = format!("query filter [default: {filter}]");
106205
opts.optopt("f", "filter", &help, "FILTER");
206+
let default = filter.clver.unwrap();
207+
let help = format!("set query filter clver [default: {default}]");
208+
opts.optopt("V", "filter-clver", &help, "VERSION");
209+
let default = filter.buildnum.unwrap();
210+
let help = format!("set query filter buildnum [default: {default}]");
211+
opts.optopt("b", "filter-buildnum", &help, "BUILDNUM");
212+
let help = "set query filter gamedir [default: all]";
213+
opts.optopt("g", "filter-gamedir", help, "GAMEDIR");
214+
let help = "set query filter map [default: all]";
215+
opts.optopt("m", "filter-map", help, "MAP");
107216

108217
let matches = match opts.parse(&args[1..]) {
109218
Ok(m) => m,
@@ -160,7 +269,7 @@ pub fn parse() -> Cli {
160269
match i.parse() {
161270
Ok(i) => cli.protocol.push(i),
162271
Err(_) => {
163-
eprintln!("Invalid protocol version: {}", i);
272+
eprintln!("Invalid protocol version: {i}");
164273
error = true;
165274
}
166275
}
@@ -171,9 +280,7 @@ pub fn parse() -> Cli {
171280
}
172281
}
173282

174-
if let Some(s) = matches.opt_str("filter") {
175-
cli.filter = s;
176-
}
283+
cli.filter = filter.opt_get(&matches);
177284

178285
if matches.opt_present("key") {
179286
let key = fastrand::u32(..);

0 commit comments

Comments
 (0)