Skip to main content

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 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/// List variables set in config files, along with their values.
36#[derive(clap::Args, Clone, Debug)]
37#[command(mut_group("config_level", |g| g.required(false)))]
38pub struct ConfigListArgs {
39    /// An optional name of a specific config option to look up.
40    #[arg(add = ArgValueCandidates::new(complete::config_keys))]
41    pub name: Option<ConfigNamePathBuf>,
42
43    /// Whether to explicitly include built-in default values in the list.
44    #[arg(long, conflicts_with = "config_level")]
45    pub include_defaults: bool,
46
47    /// Allow printing overridden values.
48    #[arg(long)]
49    pub include_overridden: bool,
50
51    #[command(flatten)]
52    pub level: ConfigLevelArgs,
53
54    /// Render each variable using the given template
55    ///
56    /// The following keywords are available in the template expression:
57    ///
58    /// * `name: String`: Config name, in [TOML's "dotted key" format].
59    /// * `value: ConfigValue`: Value to be formatted in TOML syntax.
60    /// * `overridden: Boolean`: True if the value is shadowed by other.
61    /// * `source: String`: Source of the value.
62    /// * `path: Option<FsPath>`: Path to the config file.
63    ///
64    /// Can be overridden by the `templates.config_list` setting. To
65    /// see a detailed config list, use the `builtin_config_list_detailed`
66    /// template.
67    ///
68    /// See [`jj help -k templates`] for more information.
69    ///
70    /// [TOML's "dotted key" format]: https://toml.io/en/v1.0.0#keys
71    ///
72    /// [`jj help -k templates`]:
73    ///     https://docs.jj-vcs.dev/latest/templates/
74    #[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    // The default layer could be excluded beforehand as layers[len..], but we
99    // can't do the same for "annotated.source == target_source" in order for
100    // resolved_config_values() to mark values overridden by the upper layers.
101    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        // Note to stderr explaining why output is empty.
118        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
131// AnnotatedValue will be cloned internally in the templater. If the cloning
132// cost matters, wrap it with Rc.
133fn 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        // .decorated("", "") to trim leading/trailing whitespace
141        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}