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
41 #[arg(long, conflicts_with = "config_level")]
43 pub include_defaults: bool,
44
45 #[arg(long)]
47 pub include_overridden: bool,
48
49 #[command(flatten)]
50 pub level: ConfigLevelArgs,
51
52 #[arg(long, short = 'T', verbatim_doc_comment)]
73 #[arg(add = ArgValueCandidates::new(complete::template_aliases))]
74 template: Option<String>,
75}
76
77#[instrument(skip_all)]
78pub fn cmd_config_list(
79 ui: &mut Ui,
80 command: &CommandHelper,
81 args: &ConfigListArgs,
82) -> Result<(), CommandError> {
83 let template: TemplateRenderer<AnnotatedValue> = {
84 let language = config_template_language(command.settings());
85 let text = match &args.template {
86 Some(value) => value.to_owned(),
87 None => command.settings().get_string("templates.config_list")?,
88 };
89 command
90 .parse_template(ui, &language, &text)?
91 .labeled(["config_list"])
92 };
93
94 let name_path = args.name.clone().unwrap_or_else(ConfigNamePathBuf::root);
95 let mut annotated_values = resolved_config_values(command.settings().config(), &name_path);
96 if let Some(target_source) = args.level.get_source_kind() {
100 annotated_values.retain(|annotated| annotated.source == target_source);
101 } else if !args.include_defaults {
102 annotated_values.retain(|annotated| annotated.source != ConfigSource::Default);
103 }
104 if !args.include_overridden {
105 annotated_values.retain(|annotated| !annotated.is_overridden);
106 }
107
108 if !annotated_values.is_empty() {
109 ui.request_pager();
110 let mut formatter = ui.stdout_formatter();
111 for annotated in &annotated_values {
112 template.format(annotated, formatter.as_mut())?;
113 }
114 } else {
115 if let Some(name) = &args.name {
117 writeln!(ui.warning_default(), "No matching config key for {name}")?;
118 } else {
119 writeln!(ui.warning_default(), "No config to list")?;
120 }
121 }
122 Ok(())
123}
124
125type ConfigTemplateLanguage = GenericTemplateLanguage<'static, AnnotatedValue>;
126
127generic_templater::impl_self_property_wrapper!(AnnotatedValue);
128
129fn config_template_language(settings: &UserSettings) -> ConfigTemplateLanguage {
132 let mut language = ConfigTemplateLanguage::new(settings);
133 language.add_keyword("name", |self_property| {
134 let out_property = self_property.map(|annotated| annotated.name.to_string());
135 Ok(out_property.into_dyn_wrapped())
136 });
137 language.add_keyword("value", |self_property| {
138 let out_property = self_property.map(|annotated| annotated.value.decorated("", ""));
140 Ok(out_property.into_dyn_wrapped())
141 });
142 language.add_keyword("source", |self_property| {
143 let out_property = self_property.map(|annotated| annotated.source.to_string());
144 Ok(out_property.into_dyn_wrapped())
145 });
146 language.add_keyword("path", |self_property| {
147 let out_property = self_property.map(|annotated| {
148 annotated
150 .path
151 .as_ref()
152 .map_or_else(String::new, |path| path.to_string_lossy().into_owned())
153 });
154 Ok(out_property.into_dyn_wrapped())
155 });
156 language.add_keyword("overridden", |self_property| {
157 let out_property = self_property.map(|annotated| annotated.is_overridden);
158 Ok(out_property.into_dyn_wrapped())
159 });
160 language
161}