jj_cli/commands/config/
list.rs1use clap_complete::ArgValueCandidates;
16use jj_lib::config::ConfigNamePathBuf;
17use jj_lib::config::ConfigSource;
18use jj_lib::settings::UserSettings;
19use tracing::instrument;
20
21use super::ConfigLevelArgs;
22use crate::cli_util::CommandHelper;
23use crate::command_error::CommandError;
24use crate::complete;
25use crate::config::AnnotatedValue;
26use crate::config::resolved_config_values;
27use crate::generic_templater;
28use crate::generic_templater::GenericTemplateLanguage;
29use crate::templater::TemplatePropertyExt as _;
30use crate::templater::TemplateRenderer;
31use crate::ui::Ui;
32
33#[derive(clap::Args, Clone, Debug)]
35#[command(mut_group("config_level", |g| g.required(false)))]
36pub struct ConfigListArgs {
37 #[arg(add = ArgValueCandidates::new(complete::config_keys))]
39 pub name: Option<ConfigNamePathBuf>,
40 #[arg(long, conflicts_with = "config_level")]
42 pub include_defaults: bool,
43 #[arg(long)]
45 pub include_overridden: bool,
46 #[command(flatten)]
47 pub level: ConfigLevelArgs,
48 #[arg(
67 long, short = 'T',
68 verbatim_doc_comment,
69 add = ArgValueCandidates::new(complete::template_aliases)
70 )]
71 template: Option<String>,
72}
73
74#[instrument(skip_all)]
75pub fn cmd_config_list(
76 ui: &mut Ui,
77 command: &CommandHelper,
78 args: &ConfigListArgs,
79) -> Result<(), CommandError> {
80 let template: TemplateRenderer<AnnotatedValue> = {
81 let language = config_template_language(command.settings());
82 let text = match &args.template {
83 Some(value) => value.to_owned(),
84 None => command.settings().get_string("templates.config_list")?,
85 };
86 command
87 .parse_template(ui, &language, &text)?
88 .labeled(["config_list"])
89 };
90
91 let name_path = args.name.clone().unwrap_or_else(ConfigNamePathBuf::root);
92 let mut annotated_values = resolved_config_values(command.settings().config(), &name_path);
93 if let Some(target_source) = args.level.get_source_kind() {
97 annotated_values.retain(|annotated| annotated.source == target_source);
98 } else if !args.include_defaults {
99 annotated_values.retain(|annotated| annotated.source != ConfigSource::Default);
100 }
101 if !args.include_overridden {
102 annotated_values.retain(|annotated| !annotated.is_overridden);
103 }
104
105 if !annotated_values.is_empty() {
106 ui.request_pager();
107 let mut formatter = ui.stdout_formatter();
108 for annotated in &annotated_values {
109 template.format(annotated, formatter.as_mut())?;
110 }
111 } else {
112 if let Some(name) = &args.name {
114 writeln!(ui.warning_default(), "No matching config key for {name}")?;
115 } else {
116 writeln!(ui.warning_default(), "No config to list")?;
117 }
118 }
119 Ok(())
120}
121
122type ConfigTemplateLanguage = GenericTemplateLanguage<'static, AnnotatedValue>;
123
124generic_templater::impl_self_property_wrapper!(AnnotatedValue);
125
126fn config_template_language(settings: &UserSettings) -> ConfigTemplateLanguage {
129 let mut language = ConfigTemplateLanguage::new(settings);
130 language.add_keyword("name", |self_property| {
131 let out_property = self_property.map(|annotated| annotated.name.to_string());
132 Ok(out_property.into_dyn_wrapped())
133 });
134 language.add_keyword("value", |self_property| {
135 let out_property = self_property.map(|annotated| annotated.value.decorated("", ""));
137 Ok(out_property.into_dyn_wrapped())
138 });
139 language.add_keyword("source", |self_property| {
140 let out_property = self_property.map(|annotated| annotated.source.to_string());
141 Ok(out_property.into_dyn_wrapped())
142 });
143 language.add_keyword("path", |self_property| {
144 let out_property = self_property.map(|annotated| {
145 annotated
147 .path
148 .as_ref()
149 .map_or_else(String::new, |path| path.to_string_lossy().into_owned())
150 });
151 Ok(out_property.into_dyn_wrapped())
152 });
153 language.add_keyword("overridden", |self_property| {
154 let out_property = self_property.map(|annotated| annotated.is_overridden);
155 Ok(out_property.into_dyn_wrapped())
156 });
157 language
158}