jj_cli/commands/config/
list.rs

1// Copyright 2020 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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/// List variables set in config files, along with their values.
34#[derive(clap::Args, Clone, Debug)]
35#[command(mut_group("config_level", |g| g.required(false)))]
36pub struct ConfigListArgs {
37    /// An optional name of a specific config option to look up.
38    #[arg(add = ArgValueCandidates::new(complete::config_keys))]
39    pub name: Option<ConfigNamePathBuf>,
40
41    /// Whether to explicitly include built-in default values in the list.
42    #[arg(long, conflicts_with = "config_level")]
43    pub include_defaults: bool,
44
45    /// Allow printing overridden values.
46    #[arg(long)]
47    pub include_overridden: bool,
48
49    #[command(flatten)]
50    pub level: ConfigLevelArgs,
51
52    /// Render each variable using the given template
53    ///
54    /// The following keywords are available in the template expression:
55    ///
56    /// * `name: String`: Config name, in [TOML's "dotted key" format].
57    /// * `value: ConfigValue`: Value to be formatted in TOML syntax.
58    /// * `overridden: Boolean`: True if the value is shadowed by other.
59    /// * `source: String`: Source of the value.
60    /// * `path: String`: Path to the config file.
61    ///
62    /// Can be overridden by the `templates.config_list` setting. To
63    /// see a detailed config list, use the `builtin_config_list_detailed`
64    /// template.
65    ///
66    /// See [`jj help -k templates`] for more information.
67    ///
68    /// [TOML's "dotted key" format]: https://toml.io/en/v1.0.0#keys
69    ///
70    /// [`jj help -k templates`]:
71    ///     https://docs.jj-vcs.dev/latest/templates/
72    #[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    // The default layer could be excluded beforehand as layers[len..], but we
97    // can't do the same for "annotated.source == target_source" in order for
98    // resolved_config_values() to mark values overridden by the upper layers.
99    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        // Note to stderr explaining why output is empty.
116        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
129// AnnotatedValue will be cloned internally in the templater. If the cloning
130// cost matters, wrap it with Rc.
131fn 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        // .decorated("", "") to trim leading/trailing whitespace
139        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            // TODO: maybe add FilePath(PathBuf) template type?
149            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}