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
610use getopts:: Options ;
711
8- use xash3d_protocol as proto;
12+ use xash3d_protocol:: { self as proto, filter :: Version } ;
913
1014const BIN_NAME : & str = env ! ( "CARGO_BIN_NAME" ) ;
1115const PKG_NAME : & str = env ! ( "CARGO_PKG_NAME" ) ;
@@ -14,6 +18,98 @@ const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
1418const DEFAULT_HOST : & str = "mentality.rip" ;
1519const 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 ) ]
18114pub 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