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
37thread_local! {
39 static CONTEXT_STACK: RefCell<Vec<Arc<Context>>> = const { RefCell::new(Vec::new()) };
40}
41
42pub fn push_context(ctx: Arc<Context>) {
61 CONTEXT_STACK.with(|stack| {
62 stack.borrow_mut().push(ctx);
63 });
64}
65
66pub fn pop_context() -> Option<Arc<Context>> {
84 CONTEXT_STACK.with(|stack| stack.borrow_mut().pop())
85}
86
87pub fn get_current_context() -> Option<Arc<Context>> {
109 CONTEXT_STACK.with(|stack| stack.borrow().last().cloned())
110}
111
112pub type BoxedValue = Arc<dyn Any + Send + Sync>;
115
116pub struct Context {
151 parent: Option<Arc<Context>>,
153
154 info_name: Option<String>,
158
159 params: HashMap<String, BoxedValue>,
161
162 args: Vec<String>,
164
165 obj: Option<BoxedValue>,
167
168 meta: HashMap<String, BoxedValue>,
171
172 default_map: Option<HashMap<String, BoxedValue>>,
174
175 invoked_subcommand: Option<String>,
179
180 terminal_width: Option<usize>,
182
183 max_content_width: Option<usize>,
186
187 allow_extra_args: bool,
189
190 allow_interspersed_args: bool,
192
193 ignore_unknown_options: bool,
195
196 help_option_names: Vec<String>,
198
199 resilient_parsing: bool,
202
203 auto_envvar_prefix: Option<String>,
206
207 color: Option<bool>,
209
210 show_default: Option<bool>,
212
213 parameter_source: HashMap<String, ParameterSource>,
215
216 close_callbacks: RefCell<Vec<Box<dyn FnOnce() + Send>>>,
219}
220
221impl std::fmt::Debug for Context {
223 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224 f.debug_struct("Context")
225 .field(
226 "parent",
227 &self.parent.as_ref().map(|p| p.info_name.as_deref()),
228 )
229 .field("info_name", &self.info_name)
230 .field("params", &format!("<{} params>", self.params.len()))
231 .field("args", &self.args)
232 .field("obj", &self.obj.as_ref().map(|_| "<obj>"))
233 .field("meta", &format!("<{} entries>", self.meta.len()))
234 .field(
235 "default_map",
236 &self
237 .default_map
238 .as_ref()
239 .map(|m| format!("<{} entries>", m.len())),
240 )
241 .field("invoked_subcommand", &self.invoked_subcommand)
242 .field("terminal_width", &self.terminal_width)
243 .field("max_content_width", &self.max_content_width)
244 .field("allow_extra_args", &self.allow_extra_args)
245 .field("allow_interspersed_args", &self.allow_interspersed_args)
246 .field("ignore_unknown_options", &self.ignore_unknown_options)
247 .field("help_option_names", &self.help_option_names)
248 .field("resilient_parsing", &self.resilient_parsing)
249 .field("auto_envvar_prefix", &self.auto_envvar_prefix)
250 .field("color", &self.color)
251 .field("show_default", &self.show_default)
252 .field("parameter_source", &self.parameter_source)
253 .field(
254 "close_callbacks",
255 &format!("<{} callbacks>", self.close_callbacks.borrow().len()),
256 )
257 .finish()
258 }
259}
260
261impl Default for Context {
263 fn default() -> Self {
264 Self {
265 parent: None,
266 info_name: None,
267 params: HashMap::new(),
268 args: Vec::new(),
269 obj: None,
270 meta: HashMap::new(),
271 default_map: None,
272 invoked_subcommand: None,
273 terminal_width: None,
274 max_content_width: None,
275 allow_extra_args: false,
276 allow_interspersed_args: true,
277 ignore_unknown_options: false,
278 help_option_names: vec!["--help".to_string()],
279 resilient_parsing: false,
280 auto_envvar_prefix: None,
281 color: None,
282 show_default: None,
283 parameter_source: HashMap::new(),
284 close_callbacks: RefCell::new(Vec::new()),
285 }
286 }
287}
288
289impl Context {
290 #[inline]
294 pub fn new() -> Self {
295 Self::default()
296 }
297
298 #[inline]
300 pub fn parent(&self) -> Option<&Arc<Context>> {
301 self.parent.as_ref()
302 }
303
304 #[inline]
306 pub fn info_name(&self) -> Option<&str> {
307 self.info_name.as_deref()
308 }
309
310 #[inline]
312 pub fn params(&self) -> &HashMap<String, BoxedValue> {
313 &self.params
314 }
315
316 #[inline]
318 pub fn params_mut(&mut self) -> &mut HashMap<String, BoxedValue> {
319 &mut self.params
320 }
321
322 pub fn get_param<T: 'static>(&self, name: &str) -> Option<&T> {
339 self.params.get(name).and_then(|v| v.downcast_ref::<T>())
340 }
341
342 #[inline]
344 pub fn args(&self) -> &[String] {
345 &self.args
346 }
347
348 #[inline]
350 pub fn args_mut(&mut self) -> &mut Vec<String> {
351 &mut self.args
352 }
353
354 pub fn obj<T: 'static>(&self) -> Option<&T> {
372 self.obj.as_ref().and_then(|v| v.downcast_ref::<T>())
373 }
374
375 pub fn find_obj<T: 'static>(&self) -> Option<&T> {
380 let mut current: Option<&Context> = Some(self);
381 while let Some(ctx) = current {
382 if let Some(obj) = ctx.obj::<T>() {
383 return Some(obj);
384 }
385 current = ctx.parent.as_ref().map(|p| p.as_ref());
386 }
387 None
388 }
389
390 pub fn set_obj<T: Any + Send + Sync + 'static>(&mut self, obj: T) {
392 self.obj = Some(Arc::new(obj));
393 }
394
395 #[inline]
397 pub fn meta(&self) -> &HashMap<String, BoxedValue> {
398 &self.meta
399 }
400
401 #[inline]
403 pub fn meta_mut(&mut self) -> &mut HashMap<String, BoxedValue> {
404 &mut self.meta
405 }
406
407 pub fn get_meta<T: 'static>(&self, key: &str) -> Option<&T> {
409 self.meta.get(key).and_then(|v| v.downcast_ref::<T>())
410 }
411
412 #[inline]
414 pub fn invoked_subcommand(&self) -> Option<&str> {
415 self.invoked_subcommand.as_deref()
416 }
417
418 #[inline]
420 pub fn set_invoked_subcommand(&mut self, name: Option<String>) {
421 self.invoked_subcommand = name;
422 }
423
424 #[inline]
426 pub fn terminal_width(&self) -> Option<usize> {
427 self.terminal_width
428 }
429
430 #[inline]
432 pub fn max_content_width(&self) -> Option<usize> {
433 self.max_content_width
434 }
435
436 #[inline]
438 pub fn allow_extra_args(&self) -> bool {
439 self.allow_extra_args
440 }
441
442 #[inline]
444 pub fn allow_interspersed_args(&self) -> bool {
445 self.allow_interspersed_args
446 }
447
448 #[inline]
450 pub fn ignore_unknown_options(&self) -> bool {
451 self.ignore_unknown_options
452 }
453
454 #[inline]
456 pub fn help_option_names(&self) -> &[String] {
457 &self.help_option_names
458 }
459
460 #[inline]
462 pub fn resilient_parsing(&self) -> bool {
463 self.resilient_parsing
464 }
465
466 #[inline]
468 pub fn auto_envvar_prefix(&self) -> Option<&str> {
469 self.auto_envvar_prefix.as_deref()
470 }
471
472 #[inline]
474 pub fn color(&self) -> Option<bool> {
475 self.color
476 }
477
478 #[inline]
480 pub fn show_default(&self) -> Option<bool> {
481 self.show_default
482 }
483
484 pub fn command_path(&self) -> String {
505 let mut parts = Vec::new();
506
507 let mut current: Option<&Context> = Some(self);
509 while let Some(ctx) = current {
510 if let Some(name) = &ctx.info_name {
511 parts.push(name.as_str());
512 }
513 current = ctx.parent.as_ref().map(|p| p.as_ref());
514 }
515
516 parts.reverse();
518 parts.join(" ")
519 }
520
521 pub fn find_root(&self) -> &Context {
525 let mut current: &Context = self;
526 while let Some(parent) = ¤t.parent {
527 current = parent.as_ref();
528 }
529 current
530 }
531
532 #[inline]
549 pub fn get_parameter_source(&self, name: &str) -> Option<ParameterSource> {
550 self.parameter_source.get(name).copied()
551 }
552
553 #[inline]
557 pub fn set_parameter_source(&mut self, name: &str, source: ParameterSource) {
558 self.parameter_source.insert(name.to_string(), source);
559 }
560
561 pub fn fail(&self, message: &str) -> ClickError {
577 ClickError::usage(message)
578 }
579
580 #[inline]
596 pub fn abort(&self) -> ClickError {
597 ClickError::abort()
598 }
599
600 #[inline]
616 pub fn exit(&self, code: i32) -> ClickError {
617 ClickError::exit(code)
618 }
619
620 pub fn call_on_close(&self, f: impl FnOnce() + Send + 'static) {
644 self.close_callbacks.borrow_mut().push(Box::new(f));
645 }
646
647 pub fn close(&self) {
652 let callbacks: Vec<_> = self.close_callbacks.borrow_mut().drain(..).collect();
653 for callback in callbacks.into_iter().rev() {
655 callback();
656 }
657 }
658
659 pub fn lookup_default(&self, name: &str) -> Option<&dyn Any> {
682 self.default_map
683 .as_ref()
684 .and_then(|map| map.get(name))
685 .map(|v| v.as_ref() as &dyn Any)
686 }
687
688 pub fn lookup_default_value(&self, name: &str) -> Option<BoxedValue> {
690 self.default_map
691 .as_ref()
692 .and_then(|map| map.get(name))
693 .cloned()
694 }
695
696 pub fn invoke(
733 self: &Arc<Self>,
734 cmd: &dyn crate::group::CommandLike,
735 args: &[String],
736 ) -> Result<(), ClickError> {
737 let cmd_name = cmd.name().unwrap_or("invoked");
739
740 let child_ctx = cmd.make_context(cmd_name, args.to_vec(), Some(Arc::clone(self)))?;
742 let child_ctx = Arc::new(child_ctx);
743
744 push_context(Arc::clone(&child_ctx));
746
747 let result = cmd.invoke(&child_ctx);
749
750 pop_context();
752
753 child_ctx.close();
755
756 result
757 }
758
759 pub fn forward(
798 self: &Arc<Self>,
799 cmd: &dyn crate::group::CommandLike,
800 ) -> Result<(), ClickError> {
801 let cmd_name = cmd.name().unwrap_or("forwarded");
803
804 let child_builder = ContextBuilder::new()
806 .info_name(cmd_name)
807 .parent(Arc::clone(self));
808
809 let mut child_ctx = child_builder.build();
812
813 for (key, value) in self.params.iter() {
815 child_ctx.params.insert(key.clone(), Arc::clone(value));
816 }
817
818 for (key, source) in self.parameter_source.iter() {
820 child_ctx.parameter_source.insert(key.clone(), *source);
821 }
822
823 let child_ctx = Arc::new(child_ctx);
824
825 push_context(Arc::clone(&child_ctx));
827
828 let result = cmd.invoke(&child_ctx);
830
831 pop_context();
833
834 child_ctx.close();
836
837 result
838 }
839
840 pub fn with_resource<T, F, C, R>(&self, resource: T, f: F, cleanup: C) -> R
894 where
895 T: Send + 'static,
896 F: FnOnce(&T) -> R,
897 C: FnOnce() + Send + 'static,
898 {
899 self.call_on_close(cleanup);
901
902 f(&resource)
904 }
905}
906
907#[derive(Default)]
927pub struct ContextBuilder {
928 parent: Option<Arc<Context>>,
929 info_name: Option<String>,
930 obj: Option<BoxedValue>,
931 auto_envvar_prefix: Option<String>,
932 default_map: Option<HashMap<String, BoxedValue>>,
933 terminal_width: Option<usize>,
934 max_content_width: Option<usize>,
935 resilient_parsing: bool,
936 allow_extra_args: Option<bool>,
937 allow_interspersed_args: Option<bool>,
938 ignore_unknown_options: Option<bool>,
939 help_option_names: Option<Vec<String>>,
940 color: Option<bool>,
941 show_default: Option<bool>,
942}
943
944impl ContextBuilder {
945 #[inline]
947 pub fn new() -> Self {
948 Self::default()
949 }
950
951 pub fn parent(mut self, parent: Arc<Context>) -> Self {
956 self.parent = Some(parent);
957 self
958 }
959
960 pub fn info_name(mut self, name: impl Into<String>) -> Self {
964 self.info_name = Some(name.into());
965 self
966 }
967
968 pub fn obj<T: Any + Send + Sync + 'static>(mut self, obj: T) -> Self {
972 self.obj = Some(Arc::new(obj));
973 self
974 }
975
976 pub fn auto_envvar_prefix(mut self, prefix: impl Into<String>) -> Self {
981 self.auto_envvar_prefix = Some(prefix.into());
982 self
983 }
984
985 pub fn default_map(mut self, map: HashMap<String, BoxedValue>) -> Self {
990 self.default_map = Some(map);
991 self
992 }
993
994 pub fn terminal_width(mut self, width: usize) -> Self {
996 self.terminal_width = Some(width);
997 self
998 }
999
1000 pub fn max_content_width(mut self, width: usize) -> Self {
1002 self.max_content_width = Some(width);
1003 self
1004 }
1005
1006 pub fn resilient_parsing(mut self, enabled: bool) -> Self {
1011 self.resilient_parsing = enabled;
1012 self
1013 }
1014
1015 pub fn allow_extra_args(mut self, allow: bool) -> Self {
1017 self.allow_extra_args = Some(allow);
1018 self
1019 }
1020
1021 pub fn allow_interspersed_args(mut self, allow: bool) -> Self {
1023 self.allow_interspersed_args = Some(allow);
1024 self
1025 }
1026
1027 pub fn ignore_unknown_options(mut self, ignore: bool) -> Self {
1029 self.ignore_unknown_options = Some(ignore);
1030 self
1031 }
1032
1033 pub fn help_option_names(mut self, names: Vec<String>) -> Self {
1037 self.help_option_names = Some(names);
1038 self
1039 }
1040
1041 pub fn color(mut self, color: bool) -> Self {
1043 self.color = Some(color);
1044 self
1045 }
1046
1047 pub fn show_default(mut self, show: bool) -> Self {
1049 self.show_default = Some(show);
1050 self
1051 }
1052
1053 pub fn build(self) -> Context {
1055 let (terminal_width, max_content_width, color, show_default, help_option_names, obj, meta) =
1057 if let Some(ref parent) = self.parent {
1058 (
1059 self.terminal_width.or(parent.terminal_width),
1060 self.max_content_width.or(parent.max_content_width),
1061 self.color.or(parent.color),
1062 self.show_default.or(parent.show_default),
1063 self.help_option_names
1064 .unwrap_or_else(|| parent.help_option_names.clone()),
1065 self.obj.or_else(|| parent.obj.clone()),
1067 parent.meta.clone(),
1069 )
1070 } else {
1071 (
1072 self.terminal_width,
1073 self.max_content_width,
1074 self.color,
1075 self.show_default,
1076 self.help_option_names
1077 .unwrap_or_else(|| vec!["--help".to_string()]),
1078 self.obj,
1079 HashMap::new(),
1080 )
1081 };
1082
1083 let auto_envvar_prefix = if let Some(prefix) = self.auto_envvar_prefix {
1085 Some(prefix.to_uppercase().replace('-', "_"))
1087 } else if let (Some(ref parent), Some(ref info_name)) = (&self.parent, &self.info_name) {
1088 parent.auto_envvar_prefix.as_ref().map(|parent_prefix| {
1090 format!(
1091 "{}_{}",
1092 parent_prefix,
1093 info_name.to_uppercase().replace('-', "_")
1094 )
1095 })
1096 } else {
1097 None
1098 };
1099
1100 let parent_default_map = self.parent.as_ref().and_then(|parent| parent.default_map.clone());
1102 let default_map = match (parent_default_map, self.default_map) {
1103 (Some(mut inherited), Some(child)) => {
1104 for (key, value) in child {
1105 inherited.insert(key, value);
1106 }
1107 Some(inherited)
1108 }
1109 (Some(inherited), None) => Some(inherited),
1110 (None, Some(child)) => Some(child),
1111 (None, None) => None,
1112 };
1113
1114 Context {
1115 parent: self.parent,
1116 info_name: self.info_name,
1117 params: HashMap::new(),
1118 args: Vec::new(),
1119 obj,
1120 meta,
1121 default_map,
1122 invoked_subcommand: None,
1123 terminal_width,
1124 max_content_width,
1125 allow_extra_args: self.allow_extra_args.unwrap_or(false),
1126 allow_interspersed_args: self.allow_interspersed_args.unwrap_or(true),
1127 ignore_unknown_options: self.ignore_unknown_options.unwrap_or(false),
1128 help_option_names,
1129 resilient_parsing: self.resilient_parsing,
1130 auto_envvar_prefix,
1131 color,
1132 show_default,
1133 parameter_source: HashMap::new(),
1134 close_callbacks: RefCell::new(Vec::new()),
1135 }
1136 }
1137}
1138
1139#[cfg(test)]
1140mod tests {
1141 use super::*;
1142 use std::sync::atomic::{AtomicUsize, Ordering};
1143
1144 #[test]
1145 fn test_context_default_values() {
1146 let ctx = Context::new();
1147
1148 assert!(ctx.parent().is_none());
1149 assert!(ctx.info_name().is_none());
1150 assert!(ctx.params().is_empty());
1151 assert!(ctx.args().is_empty());
1152 assert!(!ctx.allow_extra_args());
1153 assert!(ctx.allow_interspersed_args());
1154 assert!(!ctx.ignore_unknown_options());
1155 assert_eq!(ctx.help_option_names(), &["--help".to_string()]);
1156 assert!(!ctx.resilient_parsing());
1157 assert!(ctx.auto_envvar_prefix().is_none());
1158 assert!(ctx.color().is_none());
1159 assert!(ctx.show_default().is_none());
1160 }
1161
1162 #[test]
1163 fn test_context_builder() {
1164 let ctx = ContextBuilder::new()
1165 .info_name("myapp")
1166 .allow_extra_args(true)
1167 .allow_interspersed_args(false)
1168 .ignore_unknown_options(true)
1169 .terminal_width(120)
1170 .max_content_width(100)
1171 .resilient_parsing(true)
1172 .auto_envvar_prefix("MYAPP")
1173 .color(true)
1174 .show_default(false)
1175 .help_option_names(vec!["--help".to_string(), "-h".to_string()])
1176 .build();
1177
1178 assert_eq!(ctx.info_name(), Some("myapp"));
1179 assert!(ctx.allow_extra_args());
1180 assert!(!ctx.allow_interspersed_args());
1181 assert!(ctx.ignore_unknown_options());
1182 assert_eq!(ctx.terminal_width(), Some(120));
1183 assert_eq!(ctx.max_content_width(), Some(100));
1184 assert!(ctx.resilient_parsing());
1185 assert_eq!(ctx.auto_envvar_prefix(), Some("MYAPP"));
1186 assert_eq!(ctx.color(), Some(true));
1187 assert_eq!(ctx.show_default(), Some(false));
1188 assert_eq!(
1189 ctx.help_option_names(),
1190 &["--help".to_string(), "-h".to_string()]
1191 );
1192 }
1193
1194 #[test]
1195 fn test_parent_child_context_chain() {
1196 let parent = Arc::new(
1197 ContextBuilder::new()
1198 .info_name("cli")
1199 .terminal_width(100)
1200 .color(true)
1201 .build(),
1202 );
1203
1204 let child = ContextBuilder::new()
1205 .info_name("subcommand")
1206 .parent(Arc::clone(&parent))
1207 .build();
1208
1209 assert_eq!(child.terminal_width(), Some(100));
1211 assert_eq!(child.color(), Some(true));
1212
1213 assert!(child.parent().is_some());
1215 assert_eq!(child.parent().unwrap().info_name(), Some("cli"));
1216 }
1217
1218 #[test]
1219 fn test_command_path() {
1220 let root = Arc::new(ContextBuilder::new().info_name("cli").build());
1221 let sub = Arc::new(
1222 ContextBuilder::new()
1223 .info_name("group")
1224 .parent(Arc::clone(&root))
1225 .build(),
1226 );
1227 let leaf = ContextBuilder::new()
1228 .info_name("command")
1229 .parent(sub)
1230 .build();
1231
1232 assert_eq!(root.command_path(), "cli");
1233 assert_eq!(leaf.command_path(), "cli group command");
1234 }
1235
1236 #[test]
1237 fn test_find_root() {
1238 let root = Arc::new(ContextBuilder::new().info_name("cli").build());
1239 let child = Arc::new(
1240 ContextBuilder::new()
1241 .info_name("sub")
1242 .parent(Arc::clone(&root))
1243 .build(),
1244 );
1245 let grandchild = ContextBuilder::new()
1246 .info_name("leaf")
1247 .parent(child)
1248 .build();
1249
1250 assert_eq!(grandchild.find_root().info_name(), Some("cli"));
1251 }
1252
1253 #[test]
1254 fn test_thread_local_stack() {
1255 assert!(get_current_context().is_none());
1257
1258 let ctx1 = Arc::new(ContextBuilder::new().info_name("ctx1").build());
1259 let ctx2 = Arc::new(ContextBuilder::new().info_name("ctx2").build());
1260
1261 push_context(Arc::clone(&ctx1));
1263 assert_eq!(get_current_context().unwrap().info_name(), Some("ctx1"));
1264
1265 push_context(Arc::clone(&ctx2));
1267 assert_eq!(get_current_context().unwrap().info_name(), Some("ctx2"));
1268
1269 let popped = pop_context();
1271 assert_eq!(popped.unwrap().info_name(), Some("ctx2"));
1272 assert_eq!(get_current_context().unwrap().info_name(), Some("ctx1"));
1273
1274 pop_context();
1276 assert!(get_current_context().is_none());
1277 }
1278
1279 #[test]
1280 fn test_parameter_source_tracking() {
1281 let mut ctx = Context::new();
1282
1283 assert!(ctx.get_parameter_source("name").is_none());
1285
1286 ctx.set_parameter_source("name", ParameterSource::CommandLine);
1288 assert_eq!(
1289 ctx.get_parameter_source("name"),
1290 Some(ParameterSource::CommandLine)
1291 );
1292
1293 ctx.set_parameter_source("name", ParameterSource::Environment);
1295 assert_eq!(
1296 ctx.get_parameter_source("name"),
1297 Some(ParameterSource::Environment)
1298 );
1299 }
1300
1301 #[test]
1302 fn test_close_callbacks() {
1303 let counter = Arc::new(AtomicUsize::new(0));
1304 let c1 = Arc::clone(&counter);
1305 let c2 = Arc::clone(&counter);
1306 let c3 = Arc::clone(&counter);
1307
1308 let ctx = Context::new();
1309
1310 ctx.call_on_close(move || {
1312 c1.fetch_add(1, Ordering::SeqCst);
1313 });
1314 ctx.call_on_close(move || {
1315 c2.fetch_add(10, Ordering::SeqCst);
1316 });
1317 ctx.call_on_close(move || {
1318 c3.fetch_add(100, Ordering::SeqCst);
1319 });
1320
1321 assert_eq!(counter.load(Ordering::SeqCst), 0);
1322
1323 ctx.close();
1324
1325 assert_eq!(counter.load(Ordering::SeqCst), 111);
1327
1328 ctx.close();
1330 assert_eq!(counter.load(Ordering::SeqCst), 111);
1331 }
1332
1333 #[test]
1334 fn test_error_creation() {
1335 let ctx = ContextBuilder::new().info_name("myapp").build();
1336
1337 let usage_err = ctx.fail("something went wrong");
1338 assert!(matches!(usage_err, ClickError::UsageError { .. }));
1339 assert_eq!(usage_err.exit_code(), 2);
1340
1341 let abort_err = ctx.abort();
1342 assert!(matches!(abort_err, ClickError::Abort));
1343 assert_eq!(abort_err.exit_code(), 1);
1344
1345 let exit_err = ctx.exit(42);
1346 assert!(matches!(exit_err, ClickError::Exit { code: 42 }));
1347 assert_eq!(exit_err.exit_code(), 42);
1348 }
1349
1350 #[test]
1351 fn test_params_access() {
1352 let mut ctx = Context::new();
1353
1354 ctx.params_mut()
1355 .insert("count".to_string(), Arc::new(42i32));
1356 ctx.params_mut()
1357 .insert("name".to_string(), Arc::new("Alice".to_string()));
1358
1359 assert_eq!(ctx.get_param::<i32>("count"), Some(&42));
1360 assert_eq!(ctx.get_param::<String>("name"), Some(&"Alice".to_string()));
1361 assert!(ctx.get_param::<i32>("name").is_none()); assert!(ctx.get_param::<i32>("missing").is_none()); }
1364
1365 #[test]
1366 fn test_obj_access() {
1367 #[derive(Debug, PartialEq)]
1368 struct AppState {
1369 value: i32,
1370 }
1371
1372 let ctx = ContextBuilder::new().obj(AppState { value: 123 }).build();
1373
1374 let state = ctx.obj::<AppState>().unwrap();
1375 assert_eq!(state.value, 123);
1376
1377 assert!(ctx.obj::<String>().is_none());
1379 }
1380
1381 #[test]
1382 fn test_lookup_default() {
1383 let mut defaults: HashMap<String, BoxedValue> = HashMap::new();
1384 defaults.insert("count".to_string(), Arc::new(42i32));
1385 defaults.insert("name".to_string(), Arc::new("default".to_string()));
1386
1387 let ctx = ContextBuilder::new().default_map(defaults).build();
1388
1389 let count_default = ctx.lookup_default("count").unwrap();
1390 assert_eq!(count_default.downcast_ref::<i32>(), Some(&42));
1391
1392 let name_default = ctx.lookup_default("name").unwrap();
1393 assert_eq!(
1394 name_default.downcast_ref::<String>(),
1395 Some(&"default".to_string())
1396 );
1397
1398 assert!(ctx.lookup_default("missing").is_none());
1399 }
1400
1401 #[test]
1402 fn test_default_map_inheritance() {
1403 let mut parent_defaults: HashMap<String, BoxedValue> = HashMap::new();
1404 parent_defaults.insert("count".to_string(), Arc::new(5i32));
1405
1406 let parent = Arc::new(ContextBuilder::new().default_map(parent_defaults).build());
1407
1408 let mut child_defaults: HashMap<String, BoxedValue> = HashMap::new();
1409 child_defaults.insert("count".to_string(), Arc::new(10i32));
1410 child_defaults.insert("name".to_string(), Arc::new("Bob".to_string()));
1411
1412 let child = ContextBuilder::new()
1413 .parent(Arc::clone(&parent))
1414 .default_map(child_defaults)
1415 .build();
1416
1417 let count_default = child.lookup_default("count").unwrap();
1418 assert_eq!(count_default.downcast_ref::<i32>(), Some(&10));
1419
1420 let name_default = child.lookup_default("name").unwrap();
1421 assert_eq!(
1422 name_default.downcast_ref::<String>(),
1423 Some(&"Bob".to_string())
1424 );
1425 }
1426
1427 #[test]
1428 fn test_auto_envvar_prefix_normalization() {
1429 let ctx = ContextBuilder::new().auto_envvar_prefix("my-app").build();
1431 assert_eq!(ctx.auto_envvar_prefix(), Some("MY_APP"));
1432
1433 let parent = Arc::new(ContextBuilder::new().auto_envvar_prefix("MY_APP").build());
1435 let child = ContextBuilder::new()
1436 .info_name("sub-cmd")
1437 .parent(parent)
1438 .build();
1439 assert_eq!(child.auto_envvar_prefix(), Some("MY_APP_SUB_CMD"));
1440 }
1441
1442 #[test]
1443 fn test_args_access() {
1444 let mut ctx = Context::new();
1445
1446 assert!(ctx.args().is_empty());
1447
1448 ctx.args_mut().push("extra1".to_string());
1449 ctx.args_mut().push("extra2".to_string());
1450
1451 assert_eq!(ctx.args(), &["extra1", "extra2"]);
1452 }
1453
1454 #[test]
1455 fn test_meta_access() {
1456 let mut ctx = Context::new();
1457
1458 ctx.meta_mut()
1459 .insert("mymodule.key".to_string(), Arc::new(42i32));
1460
1461 assert_eq!(ctx.get_meta::<i32>("mymodule.key"), Some(&42));
1462 assert!(ctx.get_meta::<String>("mymodule.key").is_none()); assert!(ctx.get_meta::<i32>("other.key").is_none()); }
1465
1466 #[test]
1467 fn test_invoked_subcommand() {
1468 let mut ctx = Context::new();
1469
1470 assert!(ctx.invoked_subcommand().is_none());
1471
1472 ctx.set_invoked_subcommand(Some("subcommand".to_string()));
1473 assert_eq!(ctx.invoked_subcommand(), Some("subcommand"));
1474
1475 ctx.set_invoked_subcommand(Some("*".to_string()));
1476 assert_eq!(ctx.invoked_subcommand(), Some("*"));
1477
1478 ctx.set_invoked_subcommand(None);
1479 assert!(ctx.invoked_subcommand().is_none());
1480 }
1481
1482 #[test]
1487 fn test_context_invoke_command() {
1488 use crate::command::Command;
1489 use std::sync::atomic::{AtomicBool, Ordering};
1490
1491 let invoked = Arc::new(AtomicBool::new(false));
1492 let invoked_clone = Arc::clone(&invoked);
1493
1494 let other_cmd = Command::new("other")
1495 .callback(move |_ctx| {
1496 invoked_clone.store(true, Ordering::SeqCst);
1497 Ok(())
1498 })
1499 .build();
1500
1501 let ctx = Arc::new(ContextBuilder::new().info_name("main").build());
1502 let result = ctx.invoke(&other_cmd, &[]);
1503
1504 assert!(result.is_ok());
1505 assert!(invoked.load(Ordering::SeqCst));
1506 }
1507
1508 #[test]
1509 fn test_context_invoke_with_args() {
1510 use crate::command::Command;
1511 use crate::argument::Argument;
1512 use std::sync::Mutex;
1513
1514 let captured_name = Arc::new(Mutex::new(String::new()));
1515 let captured_clone = Arc::clone(&captured_name);
1516
1517 let other_cmd = Command::new("greet")
1518 .argument(Argument::new("name").build())
1519 .callback(move |ctx| {
1520 if let Some(name) = ctx.get_param::<String>("name") {
1521 let mut lock = captured_clone.lock().unwrap();
1522 *lock = name.clone();
1523 }
1524 Ok(())
1525 })
1526 .build();
1527
1528 let ctx = Arc::new(ContextBuilder::new().info_name("main").build());
1529 let result = ctx.invoke(&other_cmd, &["Alice".to_string()]);
1530
1531 assert!(result.is_ok());
1532 let name = captured_name.lock().unwrap();
1533 assert_eq!(*name, "Alice");
1534 }
1535
1536 #[test]
1537 fn test_context_invoke_creates_child_context() {
1538 use crate::command::Command;
1539 use std::sync::Mutex;
1540
1541 let parent_name = Arc::new(Mutex::new(None::<String>));
1542 let parent_clone = Arc::clone(&parent_name);
1543
1544 let other_cmd = Command::new("child")
1545 .callback(move |ctx| {
1546 if let Some(parent) = ctx.parent() {
1547 let mut lock = parent_clone.lock().unwrap();
1548 *lock = parent.info_name().map(|s| s.to_string());
1549 }
1550 Ok(())
1551 })
1552 .build();
1553
1554 let ctx = Arc::new(ContextBuilder::new().info_name("main").build());
1555 let result = ctx.invoke(&other_cmd, &[]);
1556
1557 assert!(result.is_ok());
1558 let captured = parent_name.lock().unwrap();
1559 assert_eq!(*captured, Some("main".to_string()));
1560 }
1561
1562 #[test]
1563 fn test_context_forward_copies_params() {
1564 use crate::command::Command;
1565 use std::sync::Mutex;
1566
1567 let forwarded_name = Arc::new(Mutex::new(None::<String>));
1568 let forwarded_clone = Arc::clone(&forwarded_name);
1569
1570 let other_cmd = Command::new("receiver")
1571 .callback(move |ctx| {
1572 let mut lock = forwarded_clone.lock().unwrap();
1573 *lock = ctx.get_param::<String>("name").cloned();
1574 Ok(())
1575 })
1576 .build();
1577
1578 let mut ctx = ContextBuilder::new().info_name("sender").build();
1579 ctx.params_mut().insert("name".to_string(), Arc::new("Forwarded".to_string()));
1580 let ctx = Arc::new(ctx);
1581
1582 let result = ctx.forward(&other_cmd);
1583
1584 assert!(result.is_ok());
1585 let captured = forwarded_name.lock().unwrap();
1586 assert_eq!(*captured, Some("Forwarded".to_string()));
1587 }
1588
1589 #[test]
1590 fn test_context_forward_copies_multiple_params() {
1591 use crate::command::Command;
1592 use std::sync::Mutex;
1593
1594 let forwarded_params = Arc::new(Mutex::new((None::<String>, None::<i32>)));
1595 let params_clone = Arc::clone(&forwarded_params);
1596
1597 let other_cmd = Command::new("receiver")
1598 .callback(move |ctx| {
1599 let mut lock = params_clone.lock().unwrap();
1600 lock.0 = ctx.get_param::<String>("name").cloned();
1601 lock.1 = ctx.get_param::<i32>("count").copied();
1602 Ok(())
1603 })
1604 .build();
1605
1606 let mut ctx = ContextBuilder::new().info_name("sender").build();
1607 ctx.params_mut().insert("name".to_string(), Arc::new("Test".to_string()));
1608 ctx.params_mut().insert("count".to_string(), Arc::new(42i32));
1609 let ctx = Arc::new(ctx);
1610
1611 let result = ctx.forward(&other_cmd);
1612
1613 assert!(result.is_ok());
1614 let captured = forwarded_params.lock().unwrap();
1615 assert_eq!(captured.0, Some("Test".to_string()));
1616 assert_eq!(captured.1, Some(42));
1617 }
1618
1619 #[test]
1620 fn test_context_forward_copies_parameter_sources() {
1621 use crate::command::Command;
1622 use std::sync::Mutex;
1623
1624 let forwarded_source = Arc::new(Mutex::new(None::<ParameterSource>));
1625 let source_clone = Arc::clone(&forwarded_source);
1626
1627 let other_cmd = Command::new("receiver")
1628 .callback(move |ctx| {
1629 let mut lock = source_clone.lock().unwrap();
1630 *lock = ctx.get_parameter_source("name");
1631 Ok(())
1632 })
1633 .build();
1634
1635 let mut ctx = ContextBuilder::new().info_name("sender").build();
1636 ctx.params_mut().insert("name".to_string(), Arc::new("Test".to_string()));
1637 ctx.set_parameter_source("name", ParameterSource::CommandLine);
1638 let ctx = Arc::new(ctx);
1639
1640 let result = ctx.forward(&other_cmd);
1641
1642 assert!(result.is_ok());
1643 let captured = forwarded_source.lock().unwrap();
1644 assert_eq!(*captured, Some(ParameterSource::CommandLine));
1645 }
1646
1647 #[test]
1648 fn test_context_forward_creates_child_context() {
1649 use crate::command::Command;
1650 use std::sync::Mutex;
1651
1652 let parent_name = Arc::new(Mutex::new(None::<String>));
1653 let parent_clone = Arc::clone(&parent_name);
1654
1655 let other_cmd = Command::new("receiver")
1656 .callback(move |ctx| {
1657 if let Some(parent) = ctx.parent() {
1658 let mut lock = parent_clone.lock().unwrap();
1659 *lock = parent.info_name().map(|s| s.to_string());
1660 }
1661 Ok(())
1662 })
1663 .build();
1664
1665 let ctx = Arc::new(ContextBuilder::new().info_name("sender").build());
1666 let result = ctx.forward(&other_cmd);
1667
1668 assert!(result.is_ok());
1669 let captured = parent_name.lock().unwrap();
1670 assert_eq!(*captured, Some("sender".to_string()));
1671 }
1672
1673 #[test]
1674 fn test_with_resource_basic() {
1675 use std::sync::atomic::{AtomicBool, Ordering};
1676
1677 struct Resource {
1678 value: i32,
1679 }
1680
1681 let cleaned_up = Arc::new(AtomicBool::new(false));
1682 let cleaned_up_clone = Arc::clone(&cleaned_up);
1683
1684 let ctx = ContextBuilder::new().build();
1685
1686 let result = ctx.with_resource(
1687 Resource { value: 42 },
1688 |res| res.value * 2,
1689 move || {
1690 cleaned_up_clone.store(true, Ordering::SeqCst);
1691 },
1692 );
1693
1694 assert_eq!(result, 84);
1695 assert!(!cleaned_up.load(Ordering::SeqCst)); ctx.close();
1698 assert!(cleaned_up.load(Ordering::SeqCst)); }
1700
1701 #[test]
1702 fn test_with_resource_multiple() {
1703 use std::sync::atomic::{AtomicUsize, Ordering};
1704
1705 let cleanup_count = Arc::new(AtomicUsize::new(0));
1706 let count1 = Arc::clone(&cleanup_count);
1707 let count2 = Arc::clone(&cleanup_count);
1708
1709 let ctx = ContextBuilder::new().build();
1710
1711 let result1 = ctx.with_resource(
1712 10,
1713 |res| *res + 5,
1714 move || {
1715 count1.fetch_add(1, Ordering::SeqCst);
1716 },
1717 );
1718
1719 let result2 = ctx.with_resource(
1720 20,
1721 |res| *res * 2,
1722 move || {
1723 count2.fetch_add(1, Ordering::SeqCst);
1724 },
1725 );
1726
1727 assert_eq!(result1, 15);
1728 assert_eq!(result2, 40);
1729 assert_eq!(cleanup_count.load(Ordering::SeqCst), 0);
1730
1731 ctx.close();
1732 assert_eq!(cleanup_count.load(Ordering::SeqCst), 2);
1733 }
1734
1735 #[test]
1736 fn test_with_resource_string_resource() {
1737 use std::sync::atomic::{AtomicBool, Ordering};
1738
1739 let cleaned_up = Arc::new(AtomicBool::new(false));
1740 let cleaned_up_clone = Arc::clone(&cleaned_up);
1741
1742 let ctx = ContextBuilder::new().build();
1743
1744 let result = ctx.with_resource(
1745 String::from("hello"),
1746 |s| s.len(),
1747 move || {
1748 cleaned_up_clone.store(true, Ordering::SeqCst);
1749 },
1750 );
1751
1752 assert_eq!(result, 5);
1753
1754 ctx.close();
1755 assert!(cleaned_up.load(Ordering::SeqCst));
1756 }
1757
1758 #[test]
1759 fn test_invoke_error_propagation() {
1760 use crate::command::Command;
1761 use crate::error::ClickError;
1762
1763 let other_cmd = Command::new("failing")
1764 .callback(|_ctx| {
1765 Err(ClickError::usage("intentional failure"))
1766 })
1767 .build();
1768
1769 let ctx = Arc::new(ContextBuilder::new().info_name("main").build());
1770 let result = ctx.invoke(&other_cmd, &[]);
1771
1772 assert!(result.is_err());
1773 let err = result.unwrap_err();
1774 assert!(matches!(err, ClickError::UsageError { .. }));
1775 }
1776
1777 #[test]
1778 fn test_forward_error_propagation() {
1779 use crate::command::Command;
1780 use crate::error::ClickError;
1781
1782 let other_cmd = Command::new("failing")
1783 .callback(|_ctx| {
1784 Err(ClickError::usage("intentional failure"))
1785 })
1786 .build();
1787
1788 let ctx = Arc::new(ContextBuilder::new().info_name("main").build());
1789 let result = ctx.forward(&other_cmd);
1790
1791 assert!(result.is_err());
1792 let err = result.unwrap_err();
1793 assert!(matches!(err, ClickError::UsageError { .. }));
1794 }
1795
1796 #[test]
1797 fn test_invoke_runs_child_close_callbacks() {
1798 use crate::command::Command;
1799 use std::sync::atomic::{AtomicBool, Ordering};
1800
1801 let child_closed = Arc::new(AtomicBool::new(false));
1802 let closed_clone = Arc::clone(&child_closed);
1803
1804 let other_cmd = Command::new("child")
1805 .callback(move |ctx| {
1806 let closed_clone = Arc::clone(&closed_clone);
1807 ctx.call_on_close(move || {
1808 closed_clone.store(true, Ordering::SeqCst);
1809 });
1810 Ok(())
1811 })
1812 .build();
1813
1814 let ctx = Arc::new(ContextBuilder::new().info_name("main").build());
1815 let result = ctx.invoke(&other_cmd, &[]);
1816
1817 assert!(result.is_ok());
1818 assert!(child_closed.load(Ordering::SeqCst));
1820 }
1821
1822 #[test]
1823 fn test_forward_runs_child_close_callbacks() {
1824 use crate::command::Command;
1825 use std::sync::atomic::{AtomicBool, Ordering};
1826
1827 let child_closed = Arc::new(AtomicBool::new(false));
1828 let closed_clone = Arc::clone(&child_closed);
1829
1830 let other_cmd = Command::new("child")
1831 .callback(move |ctx| {
1832 let closed_clone = Arc::clone(&closed_clone);
1833 ctx.call_on_close(move || {
1834 closed_clone.store(true, Ordering::SeqCst);
1835 });
1836 Ok(())
1837 })
1838 .build();
1839
1840 let ctx = Arc::new(ContextBuilder::new().info_name("main").build());
1841 let result = ctx.forward(&other_cmd);
1842
1843 assert!(result.is_ok());
1844 assert!(child_closed.load(Ordering::SeqCst));
1846 }
1847}