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