1use crate::util::context::{ConfigKey, ConfigValue as CV, Definition, GlobalContext};
4use crate::util::errors::CargoResult;
5use crate::{drop_eprintln, drop_println};
6use anyhow::{Error, bail, format_err};
7use serde_json::json;
8use std::borrow::Cow;
9use std::fmt;
10use std::str::FromStr;
11
12pub enum ConfigFormat {
13 Toml,
14 Json,
15 JsonValue,
16}
17
18impl ConfigFormat {
19 pub const POSSIBLE_VALUES: [&'static str; 3] = ["toml", "json", "json-value"];
21}
22
23impl 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
35impl 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
45pub struct GetOptions<'a> {
47 pub key: Option<&'a str>,
48 pub format: ConfigFormat,
49 pub show_origin: bool,
50 pub merged: bool,
51}
52
53pub fn get(gctx: &GlobalContext, opts: &GetOptions<'_>) -> CargoResult<()> {
54 if opts.show_origin && !matches!(opts.format, ConfigFormat::Toml) {
55 bail!(
56 "the `{}` format does not support --show-origin, try the `toml` format instead",
57 opts.format
58 );
59 }
60 let key = match opts.key {
61 Some(key) => ConfigKey::from_str(key),
62 None => ConfigKey::new(),
63 };
64 if opts.merged {
65 let cv = gctx
66 .get_cv_with_env(&key)?
67 .ok_or_else(|| format_err!("config value `{}` is not set", key))?;
68 match opts.format {
69 ConfigFormat::Toml => print_toml(gctx, opts, &key, &cv),
70 ConfigFormat::Json => print_json(gctx, &key, &cv, true),
71 ConfigFormat::JsonValue => print_json(gctx, &key, &cv, false),
72 }
73 if let Some(env) = maybe_env(gctx, &key, &cv) {
74 match opts.format {
75 ConfigFormat::Toml => print_toml_env(gctx, &env),
76 ConfigFormat::Json | ConfigFormat::JsonValue => print_json_env(gctx, &env),
77 }
78 }
79 } else {
80 match &opts.format {
81 ConfigFormat::Toml => print_toml_unmerged(gctx, opts, &key)?,
82 format => bail!(
83 "the `{}` format does not support --merged=no, try the `toml` format instead",
84 format
85 ),
86 }
87 }
88 Ok(())
89}
90
91fn maybe_env<'gctx>(
93 gctx: &'gctx GlobalContext,
94 key: &ConfigKey,
95 cv: &CV,
96) -> Option<Vec<(&'gctx str, &'gctx str)>> {
97 match cv {
100 CV::Table(_map, _def) => {}
101 _ => return None,
102 }
103 let mut env: Vec<_> = gctx
104 .env()
105 .filter(|(env_key, _val)| env_key.starts_with(&format!("{}_", key.as_env_key())))
106 .collect();
107 env.sort_by_key(|x| x.0);
108 if env.is_empty() { None } else { Some(env) }
109}
110
111fn print_toml(gctx: &GlobalContext, opts: &GetOptions<'_>, key: &ConfigKey, cv: &CV) {
112 let origin = |def: &Definition| -> String {
113 if !opts.show_origin {
114 return "".to_string();
115 }
116 format!(" # {}", def)
117 };
118
119 fn cv_to_toml(cv: &CV) -> toml_edit::Value {
120 match cv {
121 CV::String(s, _) => toml_edit::Value::from(s.as_str()),
122 CV::Integer(i, _) => toml_edit::Value::from(*i),
123 CV::Boolean(b, _) => toml_edit::Value::from(*b),
124 CV::List(l, _) => toml_edit::Value::from_iter(l.iter().map(cv_to_toml)),
125 CV::Table(t, _) => toml_edit::Value::from_iter({
126 let mut t: Vec<_> = t.iter().collect();
127 t.sort_by_key(|t| t.0);
128 t.into_iter().map(|(k, v)| (k, cv_to_toml(v)))
129 }),
130 }
131 }
132
133 match cv {
134 CV::Boolean(val, def) => drop_println!(gctx, "{} = {}{}", key, val, origin(def)),
135 CV::Integer(val, def) => drop_println!(gctx, "{} = {}{}", key, val, origin(def)),
136 CV::String(val, def) => drop_println!(
137 gctx,
138 "{} = {}{}",
139 key,
140 toml_edit::Value::from(val),
141 origin(def)
142 ),
143 CV::List(vals, _def) => {
144 if opts.show_origin {
145 drop_println!(gctx, "{} = [", key);
146 for cv in vals {
147 let val = cv_to_toml(cv);
148 let def = cv.definition();
149 drop_println!(gctx, " {val}, # {def}");
150 }
151 drop_println!(gctx, "]");
152 } else {
153 let vals: toml_edit::Array = vals.iter().map(cv_to_toml).collect();
154 drop_println!(gctx, "{} = {}", key, vals);
155 }
156 }
157 CV::Table(table, _def) => {
158 let mut key_vals: Vec<_> = table.iter().collect();
159 key_vals.sort_by(|a, b| a.0.cmp(b.0));
160 for (table_key, val) in key_vals {
161 let mut subkey = key.clone();
162 subkey.push(table_key);
165 print_toml(gctx, opts, &subkey, val);
166 }
167 }
168 }
169}
170
171fn print_toml_env(gctx: &GlobalContext, env: &[(&str, &str)]) {
172 drop_println!(
173 gctx,
174 "# The following environment variables may affect the loaded values."
175 );
176 for (env_key, env_value) in env {
177 let val = shell_escape::escape(Cow::Borrowed(env_value));
178 drop_println!(gctx, "# {}={}", env_key, val);
179 }
180}
181
182fn print_json_env(gctx: &GlobalContext, env: &[(&str, &str)]) {
183 drop_eprintln!(
184 gctx,
185 "note: The following environment variables may affect the loaded values."
186 );
187 for (env_key, env_value) in env {
188 let val = shell_escape::escape(Cow::Borrowed(env_value));
189 drop_eprintln!(gctx, "{}={}", env_key, val);
190 }
191}
192
193fn print_json(gctx: &GlobalContext, key: &ConfigKey, cv: &CV, include_key: bool) {
194 let json_value = if key.is_root() || !include_key {
195 cv_to_json(cv)
196 } else {
197 let mut parts: Vec<_> = key.parts().collect();
198 let last_part = parts.pop().unwrap();
199 let mut root_table = json!({});
200 let mut table = &mut root_table;
202 for part in parts {
203 table[part] = json!({});
204 table = table.get_mut(part).unwrap();
205 }
206 table[last_part] = cv_to_json(cv);
207 root_table
208 };
209 drop_println!(gctx, "{}", serde_json::to_string(&json_value).unwrap());
210
211 fn cv_to_json(cv: &CV) -> serde_json::Value {
213 match cv {
214 CV::Boolean(val, _def) => json!(val),
215 CV::Integer(val, _def) => json!(val),
216 CV::String(val, _def) => json!(val),
217 CV::List(vals, _def) => {
218 let jvals: Vec<_> = vals.iter().map(cv_to_json).collect();
219 json!(jvals)
220 }
221 CV::Table(map, _def) => {
222 let mut table = json!({});
223 for (key, val) in map {
224 table[key] = cv_to_json(val);
225 }
226 table
227 }
228 }
229 }
230}
231
232fn print_toml_unmerged(
233 gctx: &GlobalContext,
234 opts: &GetOptions<'_>,
235 key: &ConfigKey,
236) -> CargoResult<()> {
237 let print_table = |cv: &CV| {
238 drop_println!(gctx, "# {}", cv.definition());
239 print_toml(gctx, opts, &ConfigKey::new(), cv);
240 drop_println!(gctx, "");
241 };
242 fn trim_cv(mut cv: &mut CV, key: &ConfigKey) -> CargoResult<bool> {
245 for (i, part) in key.parts().enumerate() {
246 match cv {
247 CV::Table(map, _def) => {
248 map.retain(|key, _value| key == part);
249 match map.get_mut(part) {
250 Some(val) => cv = val,
251 None => return Ok(false),
252 }
253 }
254 _ => {
255 let mut key_so_far = ConfigKey::new();
256 for part in key.parts().take(i) {
257 key_so_far.push(part);
258 }
259 bail!(
260 "expected table for configuration key `{}`, \
261 but found {} in {}",
262 key_so_far,
263 cv.desc(),
264 cv.definition()
265 )
266 }
267 }
268 }
269 Ok(match cv {
270 CV::Table(map, _def) => !map.is_empty(),
271 _ => true,
272 })
273 }
274
275 let mut cli_args = gctx.cli_args_as_table()?;
276 if trim_cv(&mut cli_args, key)? {
277 print_table(&cli_args);
278 }
279
280 let mut env: Vec<_> = gctx
299 .env()
300 .filter(|(env_key, _val)| env_key.starts_with(key.as_env_key()))
301 .collect();
302 if !env.is_empty() {
303 env.sort_by_key(|x| x.0);
304 drop_println!(gctx, "# Environment variables");
305 for (key, value) in env {
306 let val = shell_escape::escape(Cow::Borrowed(value));
309 drop_println!(gctx, "# {}={}", key, val);
310 }
311 drop_println!(gctx, "");
312 }
313
314 let unmerged = gctx.load_values_unmerged()?;
315 for mut cv in unmerged {
316 if trim_cv(&mut cv, key)? {
317 print_table(&cv);
318 }
319 }
320 Ok(())
321}