1use std::any::Any;
30use std::cell::RefCell;
31use std::collections::HashMap;
32use std::sync::Arc;
33
34use crate::error::ClickError;
35use crate::source::ParameterSource;
36
37pub type HelpRenderer =
49 Arc<dyn Fn(&dyn crate::group::CommandLike, &Context) -> String + Send + Sync>;
50
51thread_local! {
53 static CONTEXT_STACK: RefCell<Vec<Arc<Context>>> = const { RefCell::new(Vec::new()) };
54}
55
56pub fn push_context(ctx: Arc<Context>) {
75 CONTEXT_STACK.with(|stack| {
76 stack.borrow_mut().push(ctx);
77 });
78}
79
80pub fn pop_context() -> Option<Arc<Context>> {
98 CONTEXT_STACK.with(|stack| stack.borrow_mut().pop())
99}
100
101pub fn get_current_context() -> Option<Arc<Context>> {
123 CONTEXT_STACK.with(|stack| stack.borrow().last().cloned())
124}
125
126pub type BoxedValue = Arc<dyn Any + Send + Sync>;
129
130pub struct Context {
165 parent: Option<Arc<Context>>,
167
168 info_name: Option<String>,
172
173 params: HashMap<String, BoxedValue>,
175
176 args: Vec<String>,
178
179 obj: Option<BoxedValue>,
181
182 meta: HashMap<String, BoxedValue>,
185
186 default_map: Option<HashMap<String, BoxedValue>>,
188
189 invoked_subcommand: Option<String>,
193
194 terminal_width: Option<usize>,
196
197 max_content_width: Option<usize>,
200
201 allow_extra_args: bool,
203
204 allow_interspersed_args: bool,
206
207 ignore_unknown_options: bool,
209
210 help_option_names: Vec<String>,
212
213 resilient_parsing: bool,
216
217 auto_envvar_prefix: Option<String>,
220
221 color: Option<bool>,
223
224 show_default: Option<bool>,
226
227 parameter_source: HashMap<String, ParameterSource>,
229
230 close_callbacks: RefCell<Vec<Box<dyn FnOnce() + Send>>>,
233
234 help_renderer: Option<HelpRenderer>,
240}
241
242impl std::fmt::Debug for Context {
244 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245 f.debug_struct("Context")
246 .field(
247 "parent",
248 &self.parent.as_ref().map(|p| p.info_name.as_deref()),
249 )
250 .field("info_name", &self.info_name)
251 .field("params", &format!("<{} params>", self.params.len()))
252 .field("args", &self.args)
253 .field("obj", &self.obj.as_ref().map(|_| "<obj>"))
254 .field("meta", &format!("<{} entries>", self.meta.len()))
255 .field(
256 "default_map",
257 &self
258 .default_map
259 .as_ref()
260 .map(|m| format!("<{} entries>", m.len())),
261 )
262 .field("invoked_subcommand", &self.invoked_subcommand)
263 .field("terminal_width", &self.terminal_width)
264 .field("max_content_width", &self.max_content_width)
265 .field("allow_extra_args", &self.allow_extra_args)
266 .field("allow_interspersed_args", &self.allow_interspersed_args)
267 .field("ignore_unknown_options", &self.ignore_unknown_options)
268 .field("help_option_names", &self.help_option_names)
269 .field("resilient_parsing", &self.resilient_parsing)
270 .field("auto_envvar_prefix", &self.auto_envvar_prefix)
271 .field("color", &self.color)
272 .field("show_default", &self.show_default)
273 .field("parameter_source", &self.parameter_source)
274 .field(
275 "close_callbacks",
276 &format!("<{} callbacks>", self.close_callbacks.borrow().len()),
277 )
278 .field(
279 "help_renderer",
280 &self.help_renderer.as_ref().map(|_| "<help_renderer>"),
281 )
282 .finish()
283 }
284}
285
286impl Default for Context {
288 fn default() -> Self {
289 Self {
290 parent: None,
291 info_name: None,
292 params: HashMap::new(),
293 args: Vec::new(),
294 obj: None,
295 meta: HashMap::new(),
296 default_map: None,
297 invoked_subcommand: None,
298 terminal_width: None,
299 max_content_width: None,
300 allow_extra_args: false,
301 allow_interspersed_args: true,
302 ignore_unknown_options: false,
303 help_option_names: vec!["--help".to_string()],
304 resilient_parsing: false,
305 auto_envvar_prefix: None,
306 color: None,
307 show_default: None,
308 parameter_source: HashMap::new(),
309 close_callbacks: RefCell::new(Vec::new()),
310 help_renderer: None,
311 }
312 }
313}
314
315impl Context {
316 #[inline]
320 pub fn new() -> Self {
321 Self::default()
322 }
323
324 #[inline]
326 pub fn parent(&self) -> Option<&Arc<Context>> {
327 self.parent.as_ref()
328 }
329
330 #[inline]
332 pub fn info_name(&self) -> Option<&str> {
333 self.info_name.as_deref()
334 }
335
336 #[inline]
338 pub fn params(&self) -> &HashMap<String, BoxedValue> {
339 &self.params
340 }
341
342 #[inline]
344 pub fn params_mut(&mut self) -> &mut HashMap<String, BoxedValue> {
345 &mut self.params
346 }
347
348 pub fn get_param<T: 'static>(&self, name: &str) -> Option<&T> {
365 self.params.get(name).and_then(|v| v.downcast_ref::<T>())
366 }
367
368 #[inline]
370 pub fn args(&self) -> &[String] {
371 &self.args
372 }
373
374 #[inline]
376 pub fn args_mut(&mut self) -> &mut Vec<String> {
377 &mut self.args
378 }
379
380 pub fn obj<T: 'static>(&self) -> Option<&T> {
398 self.obj.as_ref().and_then(|v| v.downcast_ref::<T>())
399 }
400
401 pub fn find_obj<T: 'static>(&self) -> Option<&T> {
406 let mut current: Option<&Context> = Some(self);
407 while let Some(ctx) = current {
408 if let Some(obj) = ctx.obj::<T>() {
409 return Some(obj);
410 }
411 current = ctx.parent.as_ref().map(|p| p.as_ref());
412 }
413 None
414 }
415
416 pub fn set_obj<T: Any + Send + Sync + 'static>(&mut self, obj: T) {
418 self.obj = Some(Arc::new(obj));
419 }
420
421 #[inline]
423 pub fn meta(&self) -> &HashMap<String, BoxedValue> {
424 &self.meta
425 }
426
427 #[inline]
429 pub fn meta_mut(&mut self) -> &mut HashMap<String, BoxedValue> {
430 &mut self.meta
431 }
432
433 pub fn get_meta<T: 'static>(&self, key: &str) -> Option<&T> {
435 self.meta.get(key).and_then(|v| v.downcast_ref::<T>())
436 }
437
438 #[inline]
440 pub fn invoked_subcommand(&self) -> Option<&str> {
441 self.invoked_subcommand.as_deref()
442 }
443
444 #[inline]
446 pub fn set_invoked_subcommand(&mut self, name: Option<String>) {
447 self.invoked_subcommand = name;
448 }
449
450 #[inline]
452 pub fn terminal_width(&self) -> Option<usize> {
453 self.terminal_width
454 }
455
456 #[inline]
458 pub fn max_content_width(&self) -> Option<usize> {
459 self.max_content_width
460 }
461
462 #[inline]
464 pub fn allow_extra_args(&self) -> bool {
465 self.allow_extra_args
466 }
467
468 #[inline]
470 pub fn allow_interspersed_args(&self) -> bool {
471 self.allow_interspersed_args
472 }
473
474 #[inline]
476 pub fn ignore_unknown_options(&self) -> bool {
477 self.ignore_unknown_options
478 }
479
480 #[inline]
482 pub fn help_option_names(&self) -> &[String] {
483 &self.help_option_names
484 }
485
486 #[inline]
488 pub fn resilient_parsing(&self) -> bool {
489 self.resilient_parsing
490 }
491
492 #[inline]
494 pub fn auto_envvar_prefix(&self) -> Option<&str> {
495 self.auto_envvar_prefix.as_deref()
496 }
497
498 #[inline]
500 pub fn color(&self) -> Option<bool> {
501 self.color
502 }
503
504 #[inline]
506 pub fn show_default(&self) -> Option<bool> {
507 self.show_default
508 }
509
510 pub fn help_renderer(&self) -> Option<&HelpRenderer> {
534 let mut current: Option<&Context> = Some(self);
535 while let Some(ctx) = current {
536 if ctx.help_renderer.is_some() {
537 return ctx.help_renderer.as_ref();
538 }
539 current = ctx.parent.as_ref().map(|p| p.as_ref());
540 }
541 None
542 }
543
544 pub fn command_path(&self) -> String {
565 let mut parts = Vec::new();
566
567 let mut current: Option<&Context> = Some(self);
569 while let Some(ctx) = current {
570 if let Some(name) = &ctx.info_name {
571 parts.push(name.as_str());
572 }
573 current = ctx.parent.as_ref().map(|p| p.as_ref());
574 }
575
576 parts.reverse();
578 parts.join(" ")
579 }
580
581 pub fn find_root(&self) -> &Context {
585 let mut current: &Context = self;
586 while let Some(parent) = ¤t.parent {
587 current = parent.as_ref();
588 }
589 current
590 }
591
592 #[inline]
609 pub fn get_parameter_source(&self, name: &str) -> Option<ParameterSource> {
610 self.parameter_source.get(name).copied()
611 }
612
613 #[inline]
617 pub fn set_parameter_source(&mut self, name: &str, source: ParameterSource) {
618 self.parameter_source.insert(name.to_string(), source);
619 }
620
621 pub fn fail(&self, message: &str) -> ClickError {
637 ClickError::usage(message)
638 }
639
640 #[inline]
656 pub fn abort(&self) -> ClickError {
657 ClickError::abort()
658 }
659
660 #[inline]
676 pub fn exit(&self, code: i32) -> ClickError {
677 ClickError::exit(code)
678 }
679
680 pub fn call_on_close(&self, f: impl FnOnce() + Send + 'static) {
704 self.close_callbacks.borrow_mut().push(Box::new(f));
705 }
706
707 pub fn close(&self) {
712 let callbacks: Vec<_> = self.close_callbacks.borrow_mut().drain(..).collect();
713 for callback in callbacks.into_iter().rev() {
715 callback();
716 }
717 }
718
719 pub fn lookup_default(&self, name: &str) -> Option<&dyn Any> {
742 self.default_map
743 .as_ref()
744 .and_then(|map| map.get(name))
745 .map(|v| v.as_ref() as &dyn Any)
746 }
747
748 pub fn lookup_default_value(&self, name: &str) -> Option<BoxedValue> {
750 self.default_map
751 .as_ref()
752 .and_then(|map| map.get(name))
753 .cloned()
754 }
755
756 pub fn invoke(
793 self: &Arc<Self>,
794 cmd: &dyn crate::group::CommandLike,
795 args: &[String],
796 ) -> Result<(), ClickError> {
797 let cmd_name = cmd.name().unwrap_or("invoked");
799
800 let child_ctx = cmd.make_context(cmd_name, args.to_vec(), Some(Arc::clone(self)))?;
802 let child_ctx = Arc::new(child_ctx);
803
804 push_context(Arc::clone(&child_ctx));
806
807 let result = cmd.invoke(&child_ctx);
809
810 pop_context();
812
813 child_ctx.close();
815
816 result
817 }
818
819 pub fn forward(
858 self: &Arc<Self>,
859 cmd: &dyn crate::group::CommandLike,
860 ) -> Result<(), ClickError> {
861 let cmd_name = cmd.name().unwrap_or("forwarded");
863
864 let child_builder = ContextBuilder::new()
866 .info_name(cmd_name)
867 .parent(Arc::clone(self));
868
869 let mut child_ctx = child_builder.build();
872
873 for (key, value) in self.params.iter() {
875 child_ctx.params.insert(key.clone(), Arc::clone(value));
876 }
877
878 for (key, source) in self.parameter_source.iter() {
880 child_ctx.parameter_source.insert(key.clone(), *source);
881 }
882
883 let child_ctx = Arc::new(child_ctx);
884
885 push_context(Arc::clone(&child_ctx));
887
888 let result = cmd.invoke(&child_ctx);
890
891 pop_context();
893
894 child_ctx.close();
896
897 result
898 }
899
900 pub fn with_resource<T, F, C, R>(&self, resource: T, f: F, cleanup: C) -> R
954 where
955 T: Send + 'static,
956 F: FnOnce(&T) -> R,
957 C: FnOnce() + Send + 'static,
958 {
959 self.call_on_close(cleanup);
961
962 f(&resource)
964 }
965}
966
967#[derive(Default)]
987pub struct ContextBuilder {
988 parent: Option<Arc<Context>>,
989 info_name: Option<String>,
990 obj: Option<BoxedValue>,
991 auto_envvar_prefix: Option<String>,
992 default_map: Option<HashMap<String, BoxedValue>>,
993 terminal_width: Option<usize>,
994 max_content_width: Option<usize>,
995 resilient_parsing: bool,
996 allow_extra_args: Option<bool>,
997 allow_interspersed_args: Option<bool>,
998 ignore_unknown_options: Option<bool>,
999 help_option_names: Option<Vec<String>>,
1000 color: Option<bool>,
1001 show_default: Option<bool>,
1002 help_renderer: Option<HelpRenderer>,
1003}
1004
1005impl ContextBuilder {
1006 #[inline]
1008 pub fn new() -> Self {
1009 Self::default()
1010 }
1011
1012 pub fn parent(mut self, parent: Arc<Context>) -> Self {
1017 self.parent = Some(parent);
1018 self
1019 }
1020
1021 pub fn info_name(mut self, name: impl Into<String>) -> Self {
1025 self.info_name = Some(name.into());
1026 self
1027 }
1028
1029 pub fn obj<T: Any + Send + Sync + 'static>(mut self, obj: T) -> Self {
1033 self.obj = Some(Arc::new(obj));
1034 self
1035 }
1036
1037 pub fn auto_envvar_prefix(mut self, prefix: impl Into<String>) -> Self {
1042 self.auto_envvar_prefix = Some(prefix.into());
1043 self
1044 }
1045
1046 pub fn default_map(mut self, map: HashMap<String, BoxedValue>) -> Self {
1051 self.default_map = Some(map);
1052 self
1053 }
1054
1055 pub fn terminal_width(mut self, width: usize) -> Self {
1057 self.terminal_width = Some(width);
1058 self
1059 }
1060
1061 pub fn max_content_width(mut self, width: usize) -> Self {
1063 self.max_content_width = Some(width);
1064 self
1065 }
1066
1067 pub fn resilient_parsing(mut self, enabled: bool) -> Self {
1072 self.resilient_parsing = enabled;
1073 self
1074 }
1075
1076 pub fn allow_extra_args(mut self, allow: bool) -> Self {
1078 self.allow_extra_args = Some(allow);
1079 self
1080 }
1081
1082 pub fn allow_interspersed_args(mut self, allow: bool) -> Self {
1084 self.allow_interspersed_args = Some(allow);
1085 self
1086 }
1087
1088 pub fn ignore_unknown_options(mut self, ignore: bool) -> Self {
1090 self.ignore_unknown_options = Some(ignore);
1091 self
1092 }
1093
1094 pub fn help_option_names(mut self, names: Vec<String>) -> Self {
1098 self.help_option_names = Some(names);
1099 self
1100 }
1101
1102 pub fn color(mut self, color: bool) -> Self {
1104 self.color = Some(color);
1105 self
1106 }
1107
1108 pub fn show_default(mut self, show: bool) -> Self {
1110 self.show_default = Some(show);
1111 self
1112 }
1113
1114 pub fn help_renderer(mut self, renderer: HelpRenderer) -> Self {
1141 self.help_renderer = Some(renderer);
1142 self
1143 }
1144
1145 pub fn build(self) -> Context {
1147 let (terminal_width, max_content_width, color, show_default, help_option_names, obj, meta) =
1149 if let Some(ref parent) = self.parent {
1150 (
1151 self.terminal_width.or(parent.terminal_width),
1152 self.max_content_width.or(parent.max_content_width),
1153 self.color.or(parent.color),
1154 self.show_default.or(parent.show_default),
1155 self.help_option_names
1156 .unwrap_or_else(|| parent.help_option_names.clone()),
1157 self.obj.or_else(|| parent.obj.clone()),
1159 parent.meta.clone(),
1161 )
1162 } else {
1163 (
1164 self.terminal_width,
1165 self.max_content_width,
1166 self.color,
1167 self.show_default,
1168 self.help_option_names
1169 .unwrap_or_else(|| vec!["--help".to_string()]),
1170 self.obj,
1171 HashMap::new(),
1172 )
1173 };
1174
1175 let auto_envvar_prefix = if let Some(prefix) = self.auto_envvar_prefix {
1177 Some(prefix.to_uppercase().replace('-', "_"))
1179 } else if let (Some(ref parent), Some(ref info_name)) = (&self.parent, &self.info_name) {
1180 parent.auto_envvar_prefix.as_ref().map(|parent_prefix| {
1182 format!(
1183 "{}_{}",
1184 parent_prefix,
1185 info_name.to_uppercase().replace('-', "_")
1186 )
1187 })
1188 } else {
1189 None
1190 };
1191
1192 let parent_default_map = self
1194 .parent
1195 .as_ref()
1196 .and_then(|parent| parent.default_map.clone());
1197 let default_map = match (parent_default_map, self.default_map) {
1198 (Some(mut inherited), Some(child)) => {
1199 for (key, value) in child {
1200 inherited.insert(key, value);
1201 }
1202 Some(inherited)
1203 }
1204 (Some(inherited), None) => Some(inherited),
1205 (None, Some(child)) => Some(child),
1206 (None, None) => None,
1207 };
1208
1209 Context {
1210 parent: self.parent,
1211 info_name: self.info_name,
1212 params: HashMap::new(),
1213 args: Vec::new(),
1214 obj,
1215 meta,
1216 default_map,
1217 invoked_subcommand: None,
1218 terminal_width,
1219 max_content_width,
1220 allow_extra_args: self.allow_extra_args.unwrap_or(false),
1221 allow_interspersed_args: self.allow_interspersed_args.unwrap_or(true),
1222 ignore_unknown_options: self.ignore_unknown_options.unwrap_or(false),
1223 help_option_names,
1224 resilient_parsing: self.resilient_parsing,
1225 auto_envvar_prefix,
1226 color,
1227 show_default,
1228 parameter_source: HashMap::new(),
1229 close_callbacks: RefCell::new(Vec::new()),
1230 help_renderer: self.help_renderer,
1231 }
1232 }
1233}
1234
1235#[cfg(test)]
1236mod tests {
1237 use super::*;
1238 use std::sync::atomic::{AtomicUsize, Ordering};
1239
1240 #[test]
1241 fn test_context_default_values() {
1242 let ctx = Context::new();
1243
1244 assert!(ctx.parent().is_none());
1245 assert!(ctx.info_name().is_none());
1246 assert!(ctx.params().is_empty());
1247 assert!(ctx.args().is_empty());
1248 assert!(!ctx.allow_extra_args());
1249 assert!(ctx.allow_interspersed_args());
1250 assert!(!ctx.ignore_unknown_options());
1251 assert_eq!(ctx.help_option_names(), &["--help".to_string()]);
1252 assert!(!ctx.resilient_parsing());
1253 assert!(ctx.auto_envvar_prefix().is_none());
1254 assert!(ctx.color().is_none());
1255 assert!(ctx.show_default().is_none());
1256 }
1257
1258 #[test]
1259 fn test_context_builder() {
1260 let ctx = ContextBuilder::new()
1261 .info_name("myapp")
1262 .allow_extra_args(true)
1263 .allow_interspersed_args(false)
1264 .ignore_unknown_options(true)
1265 .terminal_width(120)
1266 .max_content_width(100)
1267 .resilient_parsing(true)
1268 .auto_envvar_prefix("MYAPP")
1269 .color(true)
1270 .show_default(false)
1271 .help_option_names(vec!["--help".to_string(), "-h".to_string()])
1272 .build();
1273
1274 assert_eq!(ctx.info_name(), Some("myapp"));
1275 assert!(ctx.allow_extra_args());
1276 assert!(!ctx.allow_interspersed_args());
1277 assert!(ctx.ignore_unknown_options());
1278 assert_eq!(ctx.terminal_width(), Some(120));
1279 assert_eq!(ctx.max_content_width(), Some(100));
1280 assert!(ctx.resilient_parsing());
1281 assert_eq!(ctx.auto_envvar_prefix(), Some("MYAPP"));
1282 assert_eq!(ctx.color(), Some(true));
1283 assert_eq!(ctx.show_default(), Some(false));
1284 assert_eq!(
1285 ctx.help_option_names(),
1286 &["--help".to_string(), "-h".to_string()]
1287 );
1288 }
1289
1290 #[test]
1291 fn test_parent_child_context_chain() {
1292 let parent = Arc::new(
1293 ContextBuilder::new()
1294 .info_name("cli")
1295 .terminal_width(100)
1296 .color(true)
1297 .build(),
1298 );
1299
1300 let child = ContextBuilder::new()
1301 .info_name("subcommand")
1302 .parent(Arc::clone(&parent))
1303 .build();
1304
1305 assert_eq!(child.terminal_width(), Some(100));
1307 assert_eq!(child.color(), Some(true));
1308
1309 assert!(child.parent().is_some());
1311 assert_eq!(child.parent().unwrap().info_name(), Some("cli"));
1312 }
1313
1314 #[test]
1315 fn test_command_path() {
1316 let root = Arc::new(ContextBuilder::new().info_name("cli").build());
1317 let sub = Arc::new(
1318 ContextBuilder::new()
1319 .info_name("group")
1320 .parent(Arc::clone(&root))
1321 .build(),
1322 );
1323 let leaf = ContextBuilder::new()
1324 .info_name("command")
1325 .parent(sub)
1326 .build();
1327
1328 assert_eq!(root.command_path(), "cli");
1329 assert_eq!(leaf.command_path(), "cli group command");
1330 }
1331
1332 #[test]
1333 fn test_find_root() {
1334 let root = Arc::new(ContextBuilder::new().info_name("cli").build());
1335 let child = Arc::new(
1336 ContextBuilder::new()
1337 .info_name("sub")
1338 .parent(Arc::clone(&root))
1339 .build(),
1340 );
1341 let grandchild = ContextBuilder::new()
1342 .info_name("leaf")
1343 .parent(child)
1344 .build();
1345
1346 assert_eq!(grandchild.find_root().info_name(), Some("cli"));
1347 }
1348
1349 #[test]
1350 fn test_thread_local_stack() {
1351 assert!(get_current_context().is_none());
1353
1354 let ctx1 = Arc::new(ContextBuilder::new().info_name("ctx1").build());
1355 let ctx2 = Arc::new(ContextBuilder::new().info_name("ctx2").build());
1356
1357 push_context(Arc::clone(&ctx1));
1359 assert_eq!(get_current_context().unwrap().info_name(), Some("ctx1"));
1360
1361 push_context(Arc::clone(&ctx2));
1363 assert_eq!(get_current_context().unwrap().info_name(), Some("ctx2"));
1364
1365 let popped = pop_context();
1367 assert_eq!(popped.unwrap().info_name(), Some("ctx2"));
1368 assert_eq!(get_current_context().unwrap().info_name(), Some("ctx1"));
1369
1370 pop_context();
1372 assert!(get_current_context().is_none());
1373 }
1374
1375 #[test]
1376 fn test_parameter_source_tracking() {
1377 let mut ctx = Context::new();
1378
1379 assert!(ctx.get_parameter_source("name").is_none());
1381
1382 ctx.set_parameter_source("name", ParameterSource::CommandLine);
1384 assert_eq!(
1385 ctx.get_parameter_source("name"),
1386 Some(ParameterSource::CommandLine)
1387 );
1388
1389 ctx.set_parameter_source("name", ParameterSource::Environment);
1391 assert_eq!(
1392 ctx.get_parameter_source("name"),
1393 Some(ParameterSource::Environment)
1394 );
1395 }
1396
1397 #[test]
1398 fn test_close_callbacks() {
1399 let counter = Arc::new(AtomicUsize::new(0));
1400 let c1 = Arc::clone(&counter);
1401 let c2 = Arc::clone(&counter);
1402 let c3 = Arc::clone(&counter);
1403
1404 let ctx = Context::new();
1405
1406 ctx.call_on_close(move || {
1408 c1.fetch_add(1, Ordering::SeqCst);
1409 });
1410 ctx.call_on_close(move || {
1411 c2.fetch_add(10, Ordering::SeqCst);
1412 });
1413 ctx.call_on_close(move || {
1414 c3.fetch_add(100, Ordering::SeqCst);
1415 });
1416
1417 assert_eq!(counter.load(Ordering::SeqCst), 0);
1418
1419 ctx.close();
1420
1421 assert_eq!(counter.load(Ordering::SeqCst), 111);
1423
1424 ctx.close();
1426 assert_eq!(counter.load(Ordering::SeqCst), 111);
1427 }
1428
1429 #[test]
1430 fn test_error_creation() {
1431 let ctx = ContextBuilder::new().info_name("myapp").build();
1432
1433 let usage_err = ctx.fail("something went wrong");
1434 assert!(matches!(usage_err, ClickError::UsageError { .. }));
1435 assert_eq!(usage_err.exit_code(), 2);
1436
1437 let abort_err = ctx.abort();
1438 assert!(matches!(abort_err, ClickError::Abort));
1439 assert_eq!(abort_err.exit_code(), 1);
1440
1441 let exit_err = ctx.exit(42);
1442 assert!(matches!(exit_err, ClickError::Exit { code: 42 }));
1443 assert_eq!(exit_err.exit_code(), 42);
1444 }
1445
1446 #[test]
1447 fn test_params_access() {
1448 let mut ctx = Context::new();
1449
1450 ctx.params_mut()
1451 .insert("count".to_string(), Arc::new(42i32));
1452 ctx.params_mut()
1453 .insert("name".to_string(), Arc::new("Alice".to_string()));
1454
1455 assert_eq!(ctx.get_param::<i32>("count"), Some(&42));
1456 assert_eq!(ctx.get_param::<String>("name"), Some(&"Alice".to_string()));
1457 assert!(ctx.get_param::<i32>("name").is_none()); assert!(ctx.get_param::<i32>("missing").is_none()); }
1460
1461 #[test]
1462 fn test_obj_access() {
1463 #[derive(Debug, PartialEq)]
1464 struct AppState {
1465 value: i32,
1466 }
1467
1468 let ctx = ContextBuilder::new().obj(AppState { value: 123 }).build();
1469
1470 let state = ctx.obj::<AppState>().unwrap();
1471 assert_eq!(state.value, 123);
1472
1473 assert!(ctx.obj::<String>().is_none());
1475 }
1476
1477 #[test]
1478 fn test_lookup_default() {
1479 let mut defaults: HashMap<String, BoxedValue> = HashMap::new();
1480 defaults.insert("count".to_string(), Arc::new(42i32));
1481 defaults.insert("name".to_string(), Arc::new("default".to_string()));
1482
1483 let ctx = ContextBuilder::new().default_map(defaults).build();
1484
1485 let count_default = ctx.lookup_default("count").unwrap();
1486 assert_eq!(count_default.downcast_ref::<i32>(), Some(&42));
1487
1488 let name_default = ctx.lookup_default("name").unwrap();
1489 assert_eq!(
1490 name_default.downcast_ref::<String>(),
1491 Some(&"default".to_string())
1492 );
1493
1494 assert!(ctx.lookup_default("missing").is_none());
1495 }
1496
1497 #[test]
1498 fn test_default_map_inheritance() {
1499 let mut parent_defaults: HashMap<String, BoxedValue> = HashMap::new();
1500 parent_defaults.insert("count".to_string(), Arc::new(5i32));
1501
1502 let parent = Arc::new(ContextBuilder::new().default_map(parent_defaults).build());
1503
1504 let mut child_defaults: HashMap<String, BoxedValue> = HashMap::new();
1505 child_defaults.insert("count".to_string(), Arc::new(10i32));
1506 child_defaults.insert("name".to_string(), Arc::new("Bob".to_string()));
1507
1508 let child = ContextBuilder::new()
1509 .parent(Arc::clone(&parent))
1510 .default_map(child_defaults)
1511 .build();
1512
1513 let count_default = child.lookup_default("count").unwrap();
1514 assert_eq!(count_default.downcast_ref::<i32>(), Some(&10));
1515
1516 let name_default = child.lookup_default("name").unwrap();
1517 assert_eq!(
1518 name_default.downcast_ref::<String>(),
1519 Some(&"Bob".to_string())
1520 );
1521 }
1522
1523 #[test]
1524 fn test_auto_envvar_prefix_normalization() {
1525 let ctx = ContextBuilder::new().auto_envvar_prefix("my-app").build();
1527 assert_eq!(ctx.auto_envvar_prefix(), Some("MY_APP"));
1528
1529 let parent = Arc::new(ContextBuilder::new().auto_envvar_prefix("MY_APP").build());
1531 let child = ContextBuilder::new()
1532 .info_name("sub-cmd")
1533 .parent(parent)
1534 .build();
1535 assert_eq!(child.auto_envvar_prefix(), Some("MY_APP_SUB_CMD"));
1536 }
1537
1538 #[test]
1539 fn test_args_access() {
1540 let mut ctx = Context::new();
1541
1542 assert!(ctx.args().is_empty());
1543
1544 ctx.args_mut().push("extra1".to_string());
1545 ctx.args_mut().push("extra2".to_string());
1546
1547 assert_eq!(ctx.args(), &["extra1", "extra2"]);
1548 }
1549
1550 #[test]
1551 fn test_meta_access() {
1552 let mut ctx = Context::new();
1553
1554 ctx.meta_mut()
1555 .insert("mymodule.key".to_string(), Arc::new(42i32));
1556
1557 assert_eq!(ctx.get_meta::<i32>("mymodule.key"), Some(&42));
1558 assert!(ctx.get_meta::<String>("mymodule.key").is_none()); assert!(ctx.get_meta::<i32>("other.key").is_none()); }
1561
1562 #[test]
1563 fn test_invoked_subcommand() {
1564 let mut ctx = Context::new();
1565
1566 assert!(ctx.invoked_subcommand().is_none());
1567
1568 ctx.set_invoked_subcommand(Some("subcommand".to_string()));
1569 assert_eq!(ctx.invoked_subcommand(), Some("subcommand"));
1570
1571 ctx.set_invoked_subcommand(Some("*".to_string()));
1572 assert_eq!(ctx.invoked_subcommand(), Some("*"));
1573
1574 ctx.set_invoked_subcommand(None);
1575 assert!(ctx.invoked_subcommand().is_none());
1576 }
1577
1578 #[test]
1583 fn test_context_invoke_command() {
1584 use crate::command::Command;
1585 use std::sync::atomic::{AtomicBool, Ordering};
1586
1587 let invoked = Arc::new(AtomicBool::new(false));
1588 let invoked_clone = Arc::clone(&invoked);
1589
1590 let other_cmd = Command::new("other")
1591 .callback(move |_ctx| {
1592 invoked_clone.store(true, Ordering::SeqCst);
1593 Ok(())
1594 })
1595 .build();
1596
1597 let ctx = Arc::new(ContextBuilder::new().info_name("main").build());
1598 let result = ctx.invoke(&other_cmd, &[]);
1599
1600 assert!(result.is_ok());
1601 assert!(invoked.load(Ordering::SeqCst));
1602 }
1603
1604 #[test]
1605 fn test_context_invoke_with_args() {
1606 use crate::argument::Argument;
1607 use crate::command::Command;
1608 use std::sync::Mutex;
1609
1610 let captured_name = Arc::new(Mutex::new(String::new()));
1611 let captured_clone = Arc::clone(&captured_name);
1612
1613 let other_cmd = Command::new("greet")
1614 .argument(Argument::new("name").build())
1615 .callback(move |ctx| {
1616 if let Some(name) = ctx.get_param::<String>("name") {
1617 let mut lock = captured_clone.lock().unwrap();
1618 *lock = name.clone();
1619 }
1620 Ok(())
1621 })
1622 .build();
1623
1624 let ctx = Arc::new(ContextBuilder::new().info_name("main").build());
1625 let result = ctx.invoke(&other_cmd, &["Alice".to_string()]);
1626
1627 assert!(result.is_ok());
1628 let name = captured_name.lock().unwrap();
1629 assert_eq!(*name, "Alice");
1630 }
1631
1632 #[test]
1633 fn test_context_invoke_creates_child_context() {
1634 use crate::command::Command;
1635 use std::sync::Mutex;
1636
1637 let parent_name = Arc::new(Mutex::new(None::<String>));
1638 let parent_clone = Arc::clone(&parent_name);
1639
1640 let other_cmd = Command::new("child")
1641 .callback(move |ctx| {
1642 if let Some(parent) = ctx.parent() {
1643 let mut lock = parent_clone.lock().unwrap();
1644 *lock = parent.info_name().map(|s| s.to_string());
1645 }
1646 Ok(())
1647 })
1648 .build();
1649
1650 let ctx = Arc::new(ContextBuilder::new().info_name("main").build());
1651 let result = ctx.invoke(&other_cmd, &[]);
1652
1653 assert!(result.is_ok());
1654 let captured = parent_name.lock().unwrap();
1655 assert_eq!(*captured, Some("main".to_string()));
1656 }
1657
1658 #[test]
1659 fn test_context_forward_copies_params() {
1660 use crate::command::Command;
1661 use std::sync::Mutex;
1662
1663 let forwarded_name = Arc::new(Mutex::new(None::<String>));
1664 let forwarded_clone = Arc::clone(&forwarded_name);
1665
1666 let other_cmd = Command::new("receiver")
1667 .callback(move |ctx| {
1668 let mut lock = forwarded_clone.lock().unwrap();
1669 *lock = ctx.get_param::<String>("name").cloned();
1670 Ok(())
1671 })
1672 .build();
1673
1674 let mut ctx = ContextBuilder::new().info_name("sender").build();
1675 ctx.params_mut()
1676 .insert("name".to_string(), Arc::new("Forwarded".to_string()));
1677 let ctx = Arc::new(ctx);
1678
1679 let result = ctx.forward(&other_cmd);
1680
1681 assert!(result.is_ok());
1682 let captured = forwarded_name.lock().unwrap();
1683 assert_eq!(*captured, Some("Forwarded".to_string()));
1684 }
1685
1686 #[test]
1687 fn test_context_forward_copies_multiple_params() {
1688 use crate::command::Command;
1689 use std::sync::Mutex;
1690
1691 let forwarded_params = Arc::new(Mutex::new((None::<String>, None::<i32>)));
1692 let params_clone = Arc::clone(&forwarded_params);
1693
1694 let other_cmd = Command::new("receiver")
1695 .callback(move |ctx| {
1696 let mut lock = params_clone.lock().unwrap();
1697 lock.0 = ctx.get_param::<String>("name").cloned();
1698 lock.1 = ctx.get_param::<i32>("count").copied();
1699 Ok(())
1700 })
1701 .build();
1702
1703 let mut ctx = ContextBuilder::new().info_name("sender").build();
1704 ctx.params_mut()
1705 .insert("name".to_string(), Arc::new("Test".to_string()));
1706 ctx.params_mut()
1707 .insert("count".to_string(), Arc::new(42i32));
1708 let ctx = Arc::new(ctx);
1709
1710 let result = ctx.forward(&other_cmd);
1711
1712 assert!(result.is_ok());
1713 let captured = forwarded_params.lock().unwrap();
1714 assert_eq!(captured.0, Some("Test".to_string()));
1715 assert_eq!(captured.1, Some(42));
1716 }
1717
1718 #[test]
1719 fn test_context_forward_copies_parameter_sources() {
1720 use crate::command::Command;
1721 use std::sync::Mutex;
1722
1723 let forwarded_source = Arc::new(Mutex::new(None::<ParameterSource>));
1724 let source_clone = Arc::clone(&forwarded_source);
1725
1726 let other_cmd = Command::new("receiver")
1727 .callback(move |ctx| {
1728 let mut lock = source_clone.lock().unwrap();
1729 *lock = ctx.get_parameter_source("name");
1730 Ok(())
1731 })
1732 .build();
1733
1734 let mut ctx = ContextBuilder::new().info_name("sender").build();
1735 ctx.params_mut()
1736 .insert("name".to_string(), Arc::new("Test".to_string()));
1737 ctx.set_parameter_source("name", ParameterSource::CommandLine);
1738 let ctx = Arc::new(ctx);
1739
1740 let result = ctx.forward(&other_cmd);
1741
1742 assert!(result.is_ok());
1743 let captured = forwarded_source.lock().unwrap();
1744 assert_eq!(*captured, Some(ParameterSource::CommandLine));
1745 }
1746
1747 #[test]
1748 fn test_context_forward_creates_child_context() {
1749 use crate::command::Command;
1750 use std::sync::Mutex;
1751
1752 let parent_name = Arc::new(Mutex::new(None::<String>));
1753 let parent_clone = Arc::clone(&parent_name);
1754
1755 let other_cmd = Command::new("receiver")
1756 .callback(move |ctx| {
1757 if let Some(parent) = ctx.parent() {
1758 let mut lock = parent_clone.lock().unwrap();
1759 *lock = parent.info_name().map(|s| s.to_string());
1760 }
1761 Ok(())
1762 })
1763 .build();
1764
1765 let ctx = Arc::new(ContextBuilder::new().info_name("sender").build());
1766 let result = ctx.forward(&other_cmd);
1767
1768 assert!(result.is_ok());
1769 let captured = parent_name.lock().unwrap();
1770 assert_eq!(*captured, Some("sender".to_string()));
1771 }
1772
1773 #[test]
1774 fn test_with_resource_basic() {
1775 use std::sync::atomic::{AtomicBool, Ordering};
1776
1777 struct Resource {
1778 value: i32,
1779 }
1780
1781 let cleaned_up = Arc::new(AtomicBool::new(false));
1782 let cleaned_up_clone = Arc::clone(&cleaned_up);
1783
1784 let ctx = ContextBuilder::new().build();
1785
1786 let result = ctx.with_resource(
1787 Resource { value: 42 },
1788 |res| res.value * 2,
1789 move || {
1790 cleaned_up_clone.store(true, Ordering::SeqCst);
1791 },
1792 );
1793
1794 assert_eq!(result, 84);
1795 assert!(!cleaned_up.load(Ordering::SeqCst)); ctx.close();
1798 assert!(cleaned_up.load(Ordering::SeqCst)); }
1800
1801 #[test]
1802 fn test_with_resource_multiple() {
1803 use std::sync::atomic::{AtomicUsize, Ordering};
1804
1805 let cleanup_count = Arc::new(AtomicUsize::new(0));
1806 let count1 = Arc::clone(&cleanup_count);
1807 let count2 = Arc::clone(&cleanup_count);
1808
1809 let ctx = ContextBuilder::new().build();
1810
1811 let result1 = ctx.with_resource(
1812 10,
1813 |res| *res + 5,
1814 move || {
1815 count1.fetch_add(1, Ordering::SeqCst);
1816 },
1817 );
1818
1819 let result2 = ctx.with_resource(
1820 20,
1821 |res| *res * 2,
1822 move || {
1823 count2.fetch_add(1, Ordering::SeqCst);
1824 },
1825 );
1826
1827 assert_eq!(result1, 15);
1828 assert_eq!(result2, 40);
1829 assert_eq!(cleanup_count.load(Ordering::SeqCst), 0);
1830
1831 ctx.close();
1832 assert_eq!(cleanup_count.load(Ordering::SeqCst), 2);
1833 }
1834
1835 #[test]
1836 fn test_with_resource_string_resource() {
1837 use std::sync::atomic::{AtomicBool, Ordering};
1838
1839 let cleaned_up = Arc::new(AtomicBool::new(false));
1840 let cleaned_up_clone = Arc::clone(&cleaned_up);
1841
1842 let ctx = ContextBuilder::new().build();
1843
1844 let result = ctx.with_resource(
1845 String::from("hello"),
1846 |s| s.len(),
1847 move || {
1848 cleaned_up_clone.store(true, Ordering::SeqCst);
1849 },
1850 );
1851
1852 assert_eq!(result, 5);
1853
1854 ctx.close();
1855 assert!(cleaned_up.load(Ordering::SeqCst));
1856 }
1857
1858 #[test]
1859 fn test_invoke_error_propagation() {
1860 use crate::command::Command;
1861 use crate::error::ClickError;
1862
1863 let other_cmd = Command::new("failing")
1864 .callback(|_ctx| Err(ClickError::usage("intentional failure")))
1865 .build();
1866
1867 let ctx = Arc::new(ContextBuilder::new().info_name("main").build());
1868 let result = ctx.invoke(&other_cmd, &[]);
1869
1870 assert!(result.is_err());
1871 let err = result.unwrap_err();
1872 assert!(matches!(err, ClickError::UsageError { .. }));
1873 }
1874
1875 #[test]
1876 fn test_forward_error_propagation() {
1877 use crate::command::Command;
1878 use crate::error::ClickError;
1879
1880 let other_cmd = Command::new("failing")
1881 .callback(|_ctx| Err(ClickError::usage("intentional failure")))
1882 .build();
1883
1884 let ctx = Arc::new(ContextBuilder::new().info_name("main").build());
1885 let result = ctx.forward(&other_cmd);
1886
1887 assert!(result.is_err());
1888 let err = result.unwrap_err();
1889 assert!(matches!(err, ClickError::UsageError { .. }));
1890 }
1891
1892 #[test]
1893 fn test_invoke_runs_child_close_callbacks() {
1894 use crate::command::Command;
1895 use std::sync::atomic::{AtomicBool, Ordering};
1896
1897 let child_closed = Arc::new(AtomicBool::new(false));
1898 let closed_clone = Arc::clone(&child_closed);
1899
1900 let other_cmd = Command::new("child")
1901 .callback(move |ctx| {
1902 let closed_clone = Arc::clone(&closed_clone);
1903 ctx.call_on_close(move || {
1904 closed_clone.store(true, Ordering::SeqCst);
1905 });
1906 Ok(())
1907 })
1908 .build();
1909
1910 let ctx = Arc::new(ContextBuilder::new().info_name("main").build());
1911 let result = ctx.invoke(&other_cmd, &[]);
1912
1913 assert!(result.is_ok());
1914 assert!(child_closed.load(Ordering::SeqCst));
1916 }
1917
1918 #[test]
1919 fn test_forward_runs_child_close_callbacks() {
1920 use crate::command::Command;
1921 use std::sync::atomic::{AtomicBool, Ordering};
1922
1923 let child_closed = Arc::new(AtomicBool::new(false));
1924 let closed_clone = Arc::clone(&child_closed);
1925
1926 let other_cmd = Command::new("child")
1927 .callback(move |ctx| {
1928 let closed_clone = Arc::clone(&closed_clone);
1929 ctx.call_on_close(move || {
1930 closed_clone.store(true, Ordering::SeqCst);
1931 });
1932 Ok(())
1933 })
1934 .build();
1935
1936 let ctx = Arc::new(ContextBuilder::new().info_name("main").build());
1937 let result = ctx.forward(&other_cmd);
1938
1939 assert!(result.is_ok());
1940 assert!(child_closed.load(Ordering::SeqCst));
1942 }
1943
1944 #[test]
1949 fn test_help_renderer_set_and_get() {
1950 let renderer: HelpRenderer =
1951 Arc::new(|_cmd, ctx| format!("CUSTOM:{}", ctx.info_name().unwrap_or("")));
1952 let ctx = ContextBuilder::new()
1953 .info_name("myapp")
1954 .help_renderer(renderer)
1955 .build();
1956
1957 assert!(ctx.help_renderer().is_some());
1958 let out = ctx.help_renderer().unwrap()(&crate::command::Command::new("fake").build(), &ctx);
1959 assert_eq!(out, "CUSTOM:myapp");
1960 }
1961
1962 #[test]
1963 fn test_help_renderer_not_set_returns_none() {
1964 let ctx = ContextBuilder::new().info_name("myapp").build();
1965 assert!(ctx.help_renderer().is_none());
1966 }
1967
1968 #[test]
1969 fn test_help_renderer_inherited_through_parent() {
1970 let renderer: HelpRenderer =
1971 Arc::new(|_cmd, ctx| format!("INHERITED:{}", ctx.info_name().unwrap_or("")));
1972 let parent = Arc::new(
1973 ContextBuilder::new()
1974 .info_name("root")
1975 .help_renderer(renderer)
1976 .build(),
1977 );
1978 let child = ContextBuilder::new()
1979 .info_name("child")
1980 .parent(Arc::clone(&parent))
1981 .build();
1982 assert!(child.help_renderer.is_none()); assert!(child.help_renderer().is_some()); }
1986
1987 #[test]
1988 fn test_help_renderer_child_overrides_parent() {
1989 let parent_renderer: HelpRenderer = Arc::new(|_cmd, _ctx| "PARENT".to_string());
1990 let child_renderer: HelpRenderer = Arc::new(|_cmd, _ctx| "CHILD".to_string());
1991 let parent = Arc::new(
1992 ContextBuilder::new()
1993 .info_name("root")
1994 .help_renderer(parent_renderer)
1995 .build(),
1996 );
1997 let child = ContextBuilder::new()
1998 .info_name("sub")
1999 .parent(Arc::clone(&parent))
2000 .help_renderer(child_renderer)
2001 .build();
2002 let fake_cmd = crate::command::Command::new("fake").build();
2003 let fake_ctx = ContextBuilder::new().build();
2004 let out = child.help_renderer().unwrap()(&fake_cmd, &fake_ctx);
2005 assert_eq!(out, "CHILD");
2006 }
2007}