1 //! Implementation of `cargo config` subcommand.
3 use crate::util
::config
::{Config, ConfigKey, ConfigValue as CV, Definition}
;
4 use crate::util
::errors
::CargoResult
;
5 use crate::{drop_eprintln, drop_println}
;
6 use anyhow
::{bail, format_err, Error}
;
10 use std
::str::FromStr
;
12 pub enum ConfigFormat
{
20 pub const POSSIBLE_VALUES
: &'
static [&'
static str] = &["toml", "json", "json-value"];
23 impl FromStr
for ConfigFormat
{
25 fn from_str(s
: &str) -> CargoResult
<Self> {
27 "toml" => Ok(ConfigFormat
::Toml
),
28 "json" => Ok(ConfigFormat
::Json
),
29 "json-value" => Ok(ConfigFormat
::JsonValue
),
30 f
=> bail
!("unknown config format `{}`", f
),
35 impl fmt
::Display
for ConfigFormat
{
36 fn fmt(&self, f
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
38 ConfigFormat
::Toml
=> write
!(f
, "toml"),
39 ConfigFormat
::Json
=> write
!(f
, "json"),
40 ConfigFormat
::JsonValue
=> write
!(f
, "json-value"),
45 /// Options for `cargo config get`.
46 pub struct GetOptions
<'a
> {
47 pub key
: Option
<&'a
str>,
48 pub format
: ConfigFormat
,
49 pub show_origin
: bool
,
53 pub fn get(config
: &Config
, opts
: &GetOptions
<'_
>) -> CargoResult
<()> {
55 if !matches
!(opts
.format
, ConfigFormat
::Toml
) {
57 "the `{}` format does not support --show-origin, try the `toml` format instead",
62 let key
= match opts
.key
{
63 Some(key
) => ConfigKey
::from_str(key
),
64 None
=> ConfigKey
::new(),
68 .get_cv_with_env(&key
)?
69 .ok_or_else(|| format_err
!("config value `{}` is not set", key
))?
;
71 ConfigFormat
::Toml
=> print_toml(config
, opts
, &key
, &cv
),
72 ConfigFormat
::Json
=> print_json(config
, &key
, &cv
, true),
73 ConfigFormat
::JsonValue
=> print_json(config
, &key
, &cv
, false),
75 if let Some(env
) = maybe_env(config
, &key
, &cv
) {
77 ConfigFormat
::Toml
=> print_toml_env(config
, &env
),
78 ConfigFormat
::Json
| ConfigFormat
::JsonValue
=> print_json_env(config
, &env
),
83 ConfigFormat
::Toml
=> print_toml_unmerged(config
, opts
, &key
)?
,
85 "the `{}` format does not support --merged=no, try the `toml` format instead",
93 /// Checks for environment variables that might be used.
94 fn maybe_env
<'config
>(
95 config
: &'config Config
,
98 ) -> Option
<Vec
<(&'config String
, &'config String
)>> {
99 // Only fetching a table is unable to load env values. Leaf entries should
102 CV
::Table(_map
, _def
) => {}
105 let mut env
: Vec
<_
> = config
108 .filter(|(env_key
, _val
)| env_key
.starts_with(&format
!("{}_", key
.as_env_key())))
110 env
.sort_by_key(|x
| x
.0);
118 fn print_toml(config
: &Config
, opts
: &GetOptions
<'_
>, key
: &ConfigKey
, cv
: &CV
) {
119 let origin
= |def
: &Definition
| -> String
{
120 if !opts
.show_origin
{
121 return "".to_string();
123 format
!(" # {}", def
)
126 CV
::Boolean(val
, def
) => drop_println
!(config
, "{} = {}{}", key
, val
, origin(def
)),
127 CV
::Integer(val
, def
) => drop_println
!(config
, "{} = {}{}", key
, val
, origin(def
)),
128 CV
::String(val
, def
) => drop_println
!(
132 toml
::to_string(&val
).unwrap(),
135 CV
::List(vals
, _def
) => {
136 if opts
.show_origin
{
137 drop_println
!(config
, "{} = [", key
);
138 for (val
, def
) in vals
{
139 drop_println
!(config
, " {}, # {}", toml
::to_string(&val
).unwrap(), def
);
141 drop_println
!(config
, "]");
143 let vals
: Vec
<&String
> = vals
.iter().map(|x
| &x
.0).collect();
144 drop_println
!(config
, "{} = {}", key
, toml
::to_string(&vals
).unwrap());
147 CV
::Table(table
, _def
) => {
148 let mut key_vals
: Vec
<_
> = table
.iter().collect();
149 key_vals
.sort_by(|a
, b
| a
.0.cmp(b
.0));
150 for (table_key
, val
) in key_vals
{
151 let mut subkey
= key
.clone();
152 // push or push_sensitive shouldn't matter here, since this is
153 // not dealing with environment variables.
154 subkey
.push(table_key
);
155 print_toml(config
, opts
, &subkey
, val
);
161 fn print_toml_env(config
: &Config
, env
: &[(&String
, &String
)]) {
164 "# The following environment variables may affect the loaded values."
166 for (env_key
, env_value
) in env
{
167 let val
= shell_escape
::escape(Cow
::Borrowed(env_value
));
168 drop_println
!(config
, "# {}={}", env_key
, val
);
172 fn print_json_env(config
: &Config
, env
: &[(&String
, &String
)]) {
175 "note: The following environment variables may affect the loaded values."
177 for (env_key
, env_value
) in env
{
178 let val
= shell_escape
::escape(Cow
::Borrowed(env_value
));
179 drop_eprintln
!(config
, "{}={}", env_key
, val
);
183 fn print_json(config
: &Config
, key
: &ConfigKey
, cv
: &CV
, include_key
: bool
) {
184 let json_value
= if key
.is_root() || !include_key
{
187 let mut parts
: Vec
<_
> = key
.parts().collect();
188 let last_part
= parts
.pop().unwrap();
189 let mut root_table
= json
!({}
);
190 // Create a JSON object with nested keys up to the value being displayed.
191 let mut table
= &mut root_table
;
193 table
[part
] = json
!({}
);
194 table
= table
.get_mut(part
).unwrap();
196 table
[last_part
] = cv_to_json(cv
);
199 drop_println
!(config
, "{}", serde_json
::to_string(&json_value
).unwrap());
201 // Helper for recursively converting a CV to JSON.
202 fn cv_to_json(cv
: &CV
) -> serde_json
::Value
{
204 CV
::Boolean(val
, _def
) => json
!(val
),
205 CV
::Integer(val
, _def
) => json
!(val
),
206 CV
::String(val
, _def
) => json
!(val
),
207 CV
::List(vals
, _def
) => {
208 let jvals
: Vec
<_
> = vals
.iter().map(|(val
, _def
)| json
!(val
)).collect();
211 CV
::Table(map
, _def
) => {
212 let mut table
= json
!({}
);
213 for (key
, val
) in map
{
214 table
[key
] = cv_to_json(val
);
222 fn print_toml_unmerged(config
: &Config
, opts
: &GetOptions
<'_
>, key
: &ConfigKey
) -> CargoResult
<()> {
223 let print_table
= |cv
: &CV
| {
224 drop_println
!(config
, "# {}", cv
.definition());
225 print_toml(config
, opts
, &ConfigKey
::new(), cv
);
226 drop_println
!(config
, "");
228 // This removes entries from the given CV so that all that remains is the
229 // given key. Returns false if no entries were found.
230 fn trim_cv(mut cv
: &mut CV
, key
: &ConfigKey
) -> CargoResult
<bool
> {
231 for (i
, part
) in key
.parts().enumerate() {
233 CV
::Table(map
, _def
) => {
234 map
.retain(|key
, _value
| key
== part
);
235 match map
.get_mut(part
) {
236 Some(val
) => cv
= val
,
237 None
=> return Ok(false),
241 let mut key_so_far
= ConfigKey
::new();
242 for part
in key
.parts().take(i
) {
243 key_so_far
.push(part
);
246 "expected table for configuration key `{}`, \
256 CV
::Table(map
, _def
) => !map
.is_empty(),
261 let mut cli_args
= config
.cli_args_as_table()?
;
262 if trim_cv(&mut cli_args
, key
)?
{
263 print_table(&cli_args
);
266 // This slurps up some extra env vars that aren't technically part of the
267 // "config" (or are special-cased). I'm personally fine with just keeping
268 // them here, though it might be confusing. The vars I'm aware of:
274 // * CARGO_INCREMENTAL
275 // * CARGO_TARGET_DIR
276 // * CARGO_CACHE_RUSTC_INFO
278 // All of these except CARGO, CARGO_HOME, and CARGO_CACHE_RUSTC_INFO are
279 // actually part of the config, but they are special-cased in the code.
281 // TODO: It might be a good idea to teach the Config loader to support
282 // environment variable aliases so that these special cases are less
283 // special, and will just naturally get loaded as part of the config.
284 let mut env
: Vec
<_
> = config
287 .filter(|(env_key
, _val
)| env_key
.starts_with(key
.as_env_key()))
290 env
.sort_by_key(|x
| x
.0);
291 drop_println
!(config
, "# Environment variables");
292 for (key
, value
) in env
{
293 // Displaying this in "shell" syntax instead of TOML, since that
294 // somehow makes more sense to me.
295 let val
= shell_escape
::escape(Cow
::Borrowed(value
));
296 drop_println
!(config
, "# {}={}", key
, val
);
298 drop_println
!(config
, "");
301 let unmerged
= config
.load_values_unmerged()?
;
302 for mut cv
in unmerged
{
303 if trim_cv(&mut cv
, key
)?
{