]> git.proxmox.com Git - cargo.git/blob - src/cargo/ops/cargo_config.rs
Auto merge of #9467 - ehuss:install-metadata, r=alexcrichton
[cargo.git] / src / cargo / ops / cargo_config.rs
1 //! Implementation of `cargo config` subcommand.
2
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};
7 use serde_json::json;
8 use std::borrow::Cow;
9 use std::fmt;
10 use std::str::FromStr;
11
12 pub enum ConfigFormat {
13 Toml,
14 Json,
15 JsonValue,
16 }
17
18 impl ConfigFormat {
19 /// For clap.
20 pub const POSSIBLE_VALUES: &'static [&'static str] = &["toml", "json", "json-value"];
21 }
22
23 impl FromStr for ConfigFormat {
24 type Err = Error;
25 fn from_str(s: &str) -> CargoResult<Self> {
26 match s {
27 "toml" => Ok(ConfigFormat::Toml),
28 "json" => Ok(ConfigFormat::Json),
29 "json-value" => Ok(ConfigFormat::JsonValue),
30 f => bail!("unknown config format `{}`", f),
31 }
32 }
33 }
34
35 impl fmt::Display for ConfigFormat {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 match *self {
38 ConfigFormat::Toml => write!(f, "toml"),
39 ConfigFormat::Json => write!(f, "json"),
40 ConfigFormat::JsonValue => write!(f, "json-value"),
41 }
42 }
43 }
44
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,
50 pub merged: bool,
51 }
52
53 pub fn get(config: &Config, opts: &GetOptions<'_>) -> CargoResult<()> {
54 if opts.show_origin {
55 if !matches!(opts.format, ConfigFormat::Toml) {
56 bail!(
57 "the `{}` format does not support --show-origin, try the `toml` format instead",
58 opts.format
59 );
60 }
61 }
62 let key = match opts.key {
63 Some(key) => ConfigKey::from_str(key),
64 None => ConfigKey::new(),
65 };
66 if opts.merged {
67 let cv = config
68 .get_cv_with_env(&key)?
69 .ok_or_else(|| format_err!("config value `{}` is not set", key))?;
70 match opts.format {
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),
74 }
75 if let Some(env) = maybe_env(config, &key, &cv) {
76 match opts.format {
77 ConfigFormat::Toml => print_toml_env(config, &env),
78 ConfigFormat::Json | ConfigFormat::JsonValue => print_json_env(config, &env),
79 }
80 }
81 } else {
82 match &opts.format {
83 ConfigFormat::Toml => print_toml_unmerged(config, opts, &key)?,
84 format => bail!(
85 "the `{}` format does not support --merged=no, try the `toml` format instead",
86 format
87 ),
88 }
89 }
90 Ok(())
91 }
92
93 /// Checks for environment variables that might be used.
94 fn maybe_env<'config>(
95 config: &'config Config,
96 key: &ConfigKey,
97 cv: &CV,
98 ) -> Option<Vec<(&'config String, &'config String)>> {
99 // Only fetching a table is unable to load env values. Leaf entries should
100 // work properly.
101 match cv {
102 CV::Table(_map, _def) => {}
103 _ => return None,
104 }
105 let mut env: Vec<_> = config
106 .env()
107 .iter()
108 .filter(|(env_key, _val)| env_key.starts_with(&format!("{}_", key.as_env_key())))
109 .collect();
110 env.sort_by_key(|x| x.0);
111 if env.is_empty() {
112 None
113 } else {
114 Some(env)
115 }
116 }
117
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();
122 }
123 format!(" # {}", def)
124 };
125 match cv {
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!(
129 config,
130 "{} = {}{}",
131 key,
132 toml::to_string(&val).unwrap(),
133 origin(def)
134 ),
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);
140 }
141 drop_println!(config, "]");
142 } else {
143 let vals: Vec<&String> = vals.iter().map(|x| &x.0).collect();
144 drop_println!(config, "{} = {}", key, toml::to_string(&vals).unwrap());
145 }
146 }
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);
156 }
157 }
158 }
159 }
160
161 fn print_toml_env(config: &Config, env: &[(&String, &String)]) {
162 drop_println!(
163 config,
164 "# The following environment variables may affect the loaded values."
165 );
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);
169 }
170 }
171
172 fn print_json_env(config: &Config, env: &[(&String, &String)]) {
173 drop_eprintln!(
174 config,
175 "note: The following environment variables may affect the loaded values."
176 );
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);
180 }
181 }
182
183 fn print_json(config: &Config, key: &ConfigKey, cv: &CV, include_key: bool) {
184 let json_value = if key.is_root() || !include_key {
185 cv_to_json(cv)
186 } else {
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;
192 for part in parts {
193 table[part] = json!({});
194 table = table.get_mut(part).unwrap();
195 }
196 table[last_part] = cv_to_json(cv);
197 root_table
198 };
199 drop_println!(config, "{}", serde_json::to_string(&json_value).unwrap());
200
201 // Helper for recursively converting a CV to JSON.
202 fn cv_to_json(cv: &CV) -> serde_json::Value {
203 match cv {
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();
209 json!(jvals)
210 }
211 CV::Table(map, _def) => {
212 let mut table = json!({});
213 for (key, val) in map {
214 table[key] = cv_to_json(val);
215 }
216 table
217 }
218 }
219 }
220 }
221
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, "");
227 };
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() {
232 match cv {
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),
238 }
239 }
240 _ => {
241 let mut key_so_far = ConfigKey::new();
242 for part in key.parts().take(i) {
243 key_so_far.push(part);
244 }
245 bail!(
246 "expected table for configuration key `{}`, \
247 but found {} in {}",
248 key_so_far,
249 cv.desc(),
250 cv.definition()
251 )
252 }
253 }
254 }
255 Ok(match cv {
256 CV::Table(map, _def) => !map.is_empty(),
257 _ => true,
258 })
259 }
260
261 let mut cli_args = config.cli_args_as_table()?;
262 if trim_cv(&mut cli_args, key)? {
263 print_table(&cli_args);
264 }
265
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:
269 //
270 // * CARGO
271 // * CARGO_HOME
272 // * CARGO_NAME
273 // * CARGO_EMAIL
274 // * CARGO_INCREMENTAL
275 // * CARGO_TARGET_DIR
276 // * CARGO_CACHE_RUSTC_INFO
277 //
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.
280 //
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
285 .env()
286 .iter()
287 .filter(|(env_key, _val)| env_key.starts_with(key.as_env_key()))
288 .collect();
289 if !env.is_empty() {
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);
297 }
298 drop_println!(config, "");
299 }
300
301 let unmerged = config.load_values_unmerged()?;
302 for mut cv in unmerged {
303 if trim_cv(&mut cv, key)? {
304 print_table(&cv);
305 }
306 }
307 Ok(())
308 }