flag_rs/context.rs
1//! Context for passing data between commands
2//!
3//! The context module provides a way to pass data between parent and child
4//! commands, including parsed arguments, flags, and arbitrary typed values.
5
6use std::any::{Any, TypeId};
7use std::collections::HashMap;
8
9/// Context passed to command handlers
10///
11/// `Context` provides access to:
12/// - Command arguments
13/// - Parsed flag values
14/// - Arbitrary typed values for sharing state between commands
15///
16/// # Examples
17///
18/// ```
19/// use flag_rs::context::Context;
20/// use std::collections::HashMap;
21///
22/// // Create a context with arguments
23/// let mut ctx = Context::new(vec!["file1.txt".to_string(), "file2.txt".to_string()]);
24///
25/// // Access arguments
26/// assert_eq!(ctx.args(), &["file1.txt", "file2.txt"]);
27///
28/// // Set and retrieve flags
29/// ctx.set_flag("verbose".to_string(), "true".to_string());
30/// assert_eq!(ctx.flag("verbose"), Some(&"true".to_string()));
31///
32/// // Store typed values
33/// #[derive(Debug, PartialEq)]
34/// struct Config {
35/// api_key: String,
36/// }
37///
38/// ctx.set(Config { api_key: "secret".to_string() });
39/// let config = ctx.get::<Config>().unwrap();
40/// assert_eq!(config.api_key, "secret");
41/// ```
42pub struct Context {
43 args: Vec<String>,
44 flags: HashMap<String, String>,
45 values: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
46}
47
48impl Context {
49 /// Creates a new context with the given arguments
50 ///
51 /// # Arguments
52 ///
53 /// * `args` - The command-line arguments (without the command path)
54 pub fn new(args: Vec<String>) -> Self {
55 Self {
56 args,
57 flags: HashMap::new(),
58 values: HashMap::new(),
59 }
60 }
61
62 /// Returns a slice of the command arguments
63 ///
64 /// # Examples
65 ///
66 /// ```
67 /// use flag_rs::context::Context;
68 ///
69 /// let ctx = Context::new(vec!["file.txt".to_string()]);
70 /// assert_eq!(ctx.args(), &["file.txt"]);
71 /// ```
72 pub fn args(&self) -> &[String] {
73 &self.args
74 }
75
76 /// Returns a mutable reference to the command arguments
77 ///
78 /// # Examples
79 ///
80 /// ```
81 /// use flag_rs::context::Context;
82 ///
83 /// let mut ctx = Context::new(vec!["file.txt".to_string()]);
84 /// ctx.args_mut().push("another.txt".to_string());
85 /// assert_eq!(ctx.args().len(), 2);
86 /// ```
87 pub fn args_mut(&mut self) -> &mut Vec<String> {
88 &mut self.args
89 }
90
91 /// Gets the value of a flag by name
92 ///
93 /// # Arguments
94 ///
95 /// * `name` - The name of the flag
96 ///
97 /// # Returns
98 ///
99 /// Returns `Some(&String)` if the flag exists, `None` otherwise
100 pub fn flag(&self, name: &str) -> Option<&String> {
101 self.flags.get(name)
102 }
103
104 /// Sets a flag value
105 ///
106 /// # Arguments
107 ///
108 /// * `name` - The name of the flag
109 /// * `value` - The value to set
110 pub fn set_flag(&mut self, name: String, value: String) {
111 self.flags.insert(name, value);
112 }
113
114 /// Returns a reference to all flags
115 pub fn flags(&self) -> &HashMap<String, String> {
116 &self.flags
117 }
118
119 /// Gets a flag value as a boolean
120 ///
121 /// # Arguments
122 ///
123 /// * `name` - The name of the flag
124 ///
125 /// # Returns
126 ///
127 /// Returns `Some(bool)` if the flag exists and can be parsed as a boolean, `None` otherwise
128 ///
129 /// # Examples
130 ///
131 /// ```
132 /// use flag_rs::context::Context;
133 ///
134 /// let mut ctx = Context::new(vec![]);
135 /// ctx.set_flag("verbose".to_string(), "true".to_string());
136 /// ctx.set_flag("debug".to_string(), "false".to_string());
137 ///
138 /// assert_eq!(ctx.flag_bool("verbose"), Some(true));
139 /// assert_eq!(ctx.flag_bool("debug"), Some(false));
140 /// assert_eq!(ctx.flag_bool("missing"), None);
141 /// ```
142 pub fn flag_bool(&self, name: &str) -> Option<bool> {
143 self.flag(name)
144 .and_then(|v| match v.to_lowercase().as_str() {
145 "true" | "t" | "1" | "yes" | "y" => Some(true),
146 "false" | "f" | "0" | "no" | "n" => Some(false),
147 _ => None,
148 })
149 }
150
151 /// Gets a flag value as an integer
152 ///
153 /// # Arguments
154 ///
155 /// * `name` - The name of the flag
156 ///
157 /// # Returns
158 ///
159 /// Returns `Some(i64)` if the flag exists and can be parsed as an integer, `None` otherwise
160 ///
161 /// # Examples
162 ///
163 /// ```
164 /// use flag_rs::context::Context;
165 ///
166 /// let mut ctx = Context::new(vec![]);
167 /// ctx.set_flag("port".to_string(), "8080".to_string());
168 ///
169 /// assert_eq!(ctx.flag_int("port"), Some(8080));
170 /// assert_eq!(ctx.flag_int("missing"), None);
171 /// ```
172 pub fn flag_int(&self, name: &str) -> Option<i64> {
173 self.flag(name).and_then(|v| v.parse().ok())
174 }
175
176 /// Gets a flag value as a float
177 ///
178 /// # Arguments
179 ///
180 /// * `name` - The name of the flag
181 ///
182 /// # Returns
183 ///
184 /// Returns `Some(f64)` if the flag exists and can be parsed as a float, `None` otherwise
185 ///
186 /// # Examples
187 ///
188 /// ```
189 /// use flag_rs::context::Context;
190 ///
191 /// let mut ctx = Context::new(vec![]);
192 /// ctx.set_flag("ratio".to_string(), "0.75".to_string());
193 ///
194 /// assert_eq!(ctx.flag_float("ratio"), Some(0.75));
195 /// assert_eq!(ctx.flag_float("missing"), None);
196 /// ```
197 pub fn flag_float(&self, name: &str) -> Option<f64> {
198 self.flag(name).and_then(|v| v.parse().ok())
199 }
200
201 /// Gets a flag value as a string, returning a default if not present
202 ///
203 /// # Arguments
204 ///
205 /// * `name` - The name of the flag
206 /// * `default` - The default value to return if the flag is not set
207 ///
208 /// # Returns
209 ///
210 /// Returns the flag value if present, or the default value
211 ///
212 /// # Examples
213 ///
214 /// ```
215 /// use flag_rs::context::Context;
216 ///
217 /// let mut ctx = Context::new(vec![]);
218 /// ctx.set_flag("env".to_string(), "production".to_string());
219 ///
220 /// assert_eq!(ctx.flag_str_or("env", "development"), "production");
221 /// assert_eq!(ctx.flag_str_or("missing", "development"), "development");
222 /// ```
223 pub fn flag_str_or<'a>(&'a self, name: &str, default: &'a str) -> &'a str {
224 self.flag(name).map_or(default, String::as_str)
225 }
226
227 /// Gets a flag value as a boolean, returning a default if not present
228 ///
229 /// # Arguments
230 ///
231 /// * `name` - The name of the flag
232 /// * `default` - The default value to return if the flag is not set or cannot be parsed
233 ///
234 /// # Examples
235 ///
236 /// ```
237 /// use flag_rs::context::Context;
238 ///
239 /// let mut ctx = Context::new(vec![]);
240 /// ctx.set_flag("verbose".to_string(), "true".to_string());
241 ///
242 /// assert_eq!(ctx.flag_bool_or("verbose", false), true);
243 /// assert_eq!(ctx.flag_bool_or("missing", false), false);
244 /// ```
245 pub fn flag_bool_or(&self, name: &str, default: bool) -> bool {
246 self.flag_bool(name).unwrap_or(default)
247 }
248
249 /// Gets a flag value as an integer, returning a default if not present
250 ///
251 /// # Arguments
252 ///
253 /// * `name` - The name of the flag
254 /// * `default` - The default value to return if the flag is not set or cannot be parsed
255 ///
256 /// # Examples
257 ///
258 /// ```
259 /// use flag_rs::context::Context;
260 ///
261 /// let mut ctx = Context::new(vec![]);
262 /// ctx.set_flag("port".to_string(), "8080".to_string());
263 ///
264 /// assert_eq!(ctx.flag_int_or("port", 3000), 8080);
265 /// assert_eq!(ctx.flag_int_or("missing", 3000), 3000);
266 /// ```
267 pub fn flag_int_or(&self, name: &str, default: i64) -> i64 {
268 self.flag_int(name).unwrap_or(default)
269 }
270
271 /// Gets a flag value as a float, returning a default if not present
272 ///
273 /// # Arguments
274 ///
275 /// * `name` - The name of the flag
276 /// * `default` - The default value to return if the flag is not set or cannot be parsed
277 ///
278 /// # Examples
279 ///
280 /// ```
281 /// use flag_rs::context::Context;
282 ///
283 /// let mut ctx = Context::new(vec![]);
284 /// ctx.set_flag("ratio".to_string(), "0.75".to_string());
285 ///
286 /// assert_eq!(ctx.flag_float_or("ratio", 0.5), 0.75);
287 /// assert_eq!(ctx.flag_float_or("missing", 0.5), 0.5);
288 /// ```
289 pub fn flag_float_or(&self, name: &str, default: f64) -> f64 {
290 self.flag_float(name).unwrap_or(default)
291 }
292
293 /// Stores a typed value in the context
294 ///
295 /// Values are stored by their type, so only one value of each type
296 /// can be stored at a time. Storing a new value of the same type
297 /// will overwrite the previous value.
298 ///
299 /// # Type Parameters
300 ///
301 /// * `T` - The type of value to store (must be `Send + Sync`)
302 ///
303 /// # Examples
304 ///
305 /// ```
306 /// use flag_rs::context::Context;
307 ///
308 /// struct ApiClient {
309 /// endpoint: String,
310 /// }
311 ///
312 /// let mut ctx = Context::new(vec![]);
313 /// ctx.set(ApiClient { endpoint: "https://api.example.com".to_string() });
314 /// ```
315 pub fn set<T: Any + Send + Sync>(&mut self, value: T) {
316 self.values.insert(TypeId::of::<T>(), Box::new(value));
317 }
318
319 /// Retrieves a typed value from the context
320 ///
321 /// # Type Parameters
322 ///
323 /// * `T` - The type of value to retrieve
324 ///
325 /// # Returns
326 ///
327 /// Returns `Some(&T)` if a value of that type exists, `None` otherwise
328 pub fn get<T: Any + Send + Sync>(&self) -> Option<&T> {
329 self.values
330 .get(&TypeId::of::<T>())
331 .and_then(|v| (**v).downcast_ref())
332 }
333
334 /// Retrieves a mutable reference to a typed value from the context
335 ///
336 /// # Type Parameters
337 ///
338 /// * `T` - The type of value to retrieve
339 ///
340 /// # Returns
341 ///
342 /// Returns `Some(&mut T)` if a value of that type exists, `None` otherwise
343 pub fn get_mut<T: Any + Send + Sync>(&mut self) -> Option<&mut T> {
344 self.values
345 .get_mut(&TypeId::of::<T>())
346 .and_then(|v| (**v).downcast_mut())
347 }
348}
349
350#[cfg(test)]
351mod tests {
352 use super::*;
353
354 #[test]
355 fn test_context_args() {
356 let args = vec!["arg1".to_string(), "arg2".to_string()];
357 let mut ctx = Context::new(args.clone());
358 assert_eq!(ctx.args(), &args);
359
360 ctx.args_mut().push("arg3".to_string());
361 assert_eq!(ctx.args().len(), 3);
362 }
363
364 #[test]
365 fn test_context_flags() {
366 let mut ctx = Context::new(vec![]);
367
368 ctx.set_flag("verbose".to_string(), "true".to_string());
369 ctx.set_flag("output".to_string(), "json".to_string());
370
371 assert_eq!(ctx.flag("verbose"), Some(&"true".to_string()));
372 assert_eq!(ctx.flag("output"), Some(&"json".to_string()));
373 assert_eq!(ctx.flag("nonexistent"), None);
374 }
375
376 #[test]
377 fn test_context_values() {
378 #[derive(Debug, PartialEq)]
379 struct Config {
380 timeout: u32,
381 }
382
383 let mut ctx = Context::new(vec![]);
384 let config = Config { timeout: 30 };
385
386 ctx.set(config);
387
388 assert_eq!(ctx.get::<Config>(), Some(&Config { timeout: 30 }));
389 assert_eq!(ctx.get::<String>(), None);
390
391 if let Some(cfg) = ctx.get_mut::<Config>() {
392 cfg.timeout = 60;
393 }
394
395 assert_eq!(ctx.get::<Config>(), Some(&Config { timeout: 60 }));
396 }
397}