#
cargo/ops/
cargo_config.rs

    1//! Implementation of `cargo config` subcommand.
    2
    3use crate::util::context::{ConfigKey, ConfigValue as CV, Definition, GlobalContext};
    4use crate::util::errors::CargoResult;
    5use crate::{drop_eprintln, drop_println};
    6use anyhow::{bail, format_err, Error};
    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    /// For clap.
    20    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
    45/// Options for `cargo config get`.
    46pub 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
    91/// Checks for environment variables that might be used.
    92fn maybe_env<'gctx>(
    93    gctx: &'gctx GlobalContext,
    94    key: &ConfigKey,
    95    cv: &CV,
    96) -> Option<Vec<(&'gctx str, &'gctx str)>> {
    97    // Only fetching a table is unable to load env values. Leaf entries should
    98    // work properly.
    99    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() {
    109        None
    110    } else {
    111        Some(env)
    112    }
    113}
    114
    115fn print_toml(gctx: &GlobalContext, opts: &GetOptions<'_>, key: &ConfigKey, cv: &CV) {
    116    let origin = |def: &Definition| -> String {
    117        if !opts.show_origin {
    118            return "".to_string();
    119        }
    120        format!(" # {}", def)
    121    };
    122    match cv {
    123        CV::Boolean(val, def) => drop_println!(gctx, "{} = {}{}", key, val, origin(def)),
    124        CV::Integer(val, def) => drop_println!(gctx, "{} = {}{}", key, val, origin(def)),
    125        CV::String(val, def) => drop_println!(
    126            gctx,
    127            "{} = {}{}",
    128            key,
    129            toml_edit::Value::from(val),
    130            origin(def)
    131        ),
    132        CV::List(vals, _def) => {
    133            if opts.show_origin {
    134                drop_println!(gctx, "{} = [", key);
    135                for (val, def) in vals {
    136                    drop_println!(
    137                        gctx,
    138                        "    {}, # {}",
    139                        serde::Serialize::serialize(val, toml_edit::ser::ValueSerializer::new())
    140                            .unwrap(),
    141                        def
    142                    );
    143                }
    144                drop_println!(gctx, "]");
    145            } else {
    146                let vals: toml_edit::Array = vals.iter().map(|x| &x.0).collect();
    147                drop_println!(gctx, "{} = {}", key, vals);
    148            }
    149        }
    150        CV::Table(table, _def) => {
    151            let mut key_vals: Vec<_> = table.iter().collect();
    152            key_vals.sort_by(|a, b| a.0.cmp(b.0));
    153            for (table_key, val) in key_vals {
    154                let mut subkey = key.clone();
    155                // push or push_sensitive shouldn't matter here, since this is
    156                // not dealing with environment variables.
    157                subkey.push(table_key);
    158                print_toml(gctx, opts, &subkey, val);
    159            }
    160        }
    161    }
    162}
    163
    164fn print_toml_env(gctx: &GlobalContext, env: &[(&str, &str)]) {
    165    drop_println!(
    166        gctx,
    167        "# The following environment variables may affect the loaded values."
    168    );
    169    for (env_key, env_value) in env {
    170        let val = shell_escape::escape(Cow::Borrowed(env_value));
    171        drop_println!(gctx, "# {}={}", env_key, val);
    172    }
    173}
    174
    175fn print_json_env(gctx: &GlobalContext, env: &[(&str, &str)]) {
    176    drop_eprintln!(
    177        gctx,
    178        "note: The following environment variables may affect the loaded values."
    179    );
    180    for (env_key, env_value) in env {
    181        let val = shell_escape::escape(Cow::Borrowed(env_value));
    182        drop_eprintln!(gctx, "{}={}", env_key, val);
    183    }
    184}
    185
    186fn print_json(gctx: &GlobalContext, key: &ConfigKey, cv: &CV, include_key: bool) {
    187    let json_value = if key.is_root() || !include_key {
    188        cv_to_json(cv)
    189    } else {
    190        let mut parts: Vec<_> = key.parts().collect();
    191        let last_part = parts.pop().unwrap();
    192        let mut root_table = json!({});
    193        // Create a JSON object with nested keys up to the value being displayed.
    194        let mut table = &mut root_table;
    195        for part in parts {
    196            table[part] = json!({});
    197            table = table.get_mut(part).unwrap();
    198        }
    199        table[last_part] = cv_to_json(cv);
    200        root_table
    201    };
    202    drop_println!(gctx, "{}", serde_json::to_string(&json_value).unwrap());
    203
    204    // Helper for recursively converting a CV to JSON.
    205    fn cv_to_json(cv: &CV) -> serde_json::Value {
    206        match cv {
    207            CV::Boolean(val, _def) => json!(val),
    208            CV::Integer(val, _def) => json!(val),
    209            CV::String(val, _def) => json!(val),
    210            CV::List(vals, _def) => {
    211                let jvals: Vec<_> = vals.iter().map(|(val, _def)| json!(val)).collect();
    212                json!(jvals)
    213            }
    214            CV::Table(map, _def) => {
    215                let mut table = json!({});
    216                for (key, val) in map {
    217                    table[key] = cv_to_json(val);
    218                }
    219                table
    220            }
    221        }
    222    }
    223}
    224
    225fn print_toml_unmerged(
    226    gctx: &GlobalContext,
    227    opts: &GetOptions<'_>,
    228    key: &ConfigKey,
    229) -> CargoResult<()> {
    230    let print_table = |cv: &CV| {
    231        drop_println!(gctx, "# {}", cv.definition());
    232        print_toml(gctx, opts, &ConfigKey::new(), cv);
    233        drop_println!(gctx, "");
    234    };
    235    // This removes entries from the given CV so that all that remains is the
    236    // given key. Returns false if no entries were found.
    237    fn trim_cv(mut cv: &mut CV, key: &ConfigKey) -> CargoResult<bool> {
    238        for (i, part) in key.parts().enumerate() {
    239            match cv {
    240                CV::Table(map, _def) => {
    241                    map.retain(|key, _value| key == part);
    242                    match map.get_mut(part) {
    243                        Some(val) => cv = val,
    244                        None => return Ok(false),
    245                    }
    246                }
    247                _ => {
    248                    let mut key_so_far = ConfigKey::new();
    249                    for part in key.parts().take(i) {
    250                        key_so_far.push(part);
    251                    }
    252                    bail!(
    253                        "expected table for configuration key `{}`, \
    254                         but found {} in {}",
    255                        key_so_far,
    256                        cv.desc(),
    257                        cv.definition()
    258                    )
    259                }
    260            }
    261        }
    262        Ok(match cv {
    263            CV::Table(map, _def) => !map.is_empty(),
    264            _ => true,
    265        })
    266    }
    267
    268    let mut cli_args = gctx.cli_args_as_table()?;
    269    if trim_cv(&mut cli_args, key)? {
    270        print_table(&cli_args);
    271    }
    272
    273    // This slurps up some extra env vars that aren't technically part of the
    274    // "config" (or are special-cased). I'm personally fine with just keeping
    275    // them here, though it might be confusing. The vars I'm aware of:
    276    //
    277    // * CARGO
    278    // * CARGO_HOME
    279    // * CARGO_NAME
    280    // * CARGO_EMAIL
    281    // * CARGO_INCREMENTAL
    282    // * CARGO_TARGET_DIR
    283    // * CARGO_CACHE_RUSTC_INFO
    284    //
    285    // All of these except CARGO, CARGO_HOME, and CARGO_CACHE_RUSTC_INFO are
    286    // actually part of the config, but they are special-cased in the code.
    287    //
    288    // TODO: It might be a good idea to teach the Config loader to support
    289    // environment variable aliases so that these special cases are less
    290    // special, and will just naturally get loaded as part of the config.
    291    let mut env: Vec<_> = gctx
    292        .env()
    293        .filter(|(env_key, _val)| env_key.starts_with(key.as_env_key()))
    294        .collect();
    295    if !env.is_empty() {
    296        env.sort_by_key(|x| x.0);
    297        drop_println!(gctx, "# Environment variables");
    298        for (key, value) in env {
    299            // Displaying this in "shell" syntax instead of TOML, since that
    300            // somehow makes more sense to me.
    301            let val = shell_escape::escape(Cow::Borrowed(value));
    302            drop_println!(gctx, "# {}={}", key, val);
    303        }
    304        drop_println!(gctx, "");
    305    }
    306
    307    let unmerged = gctx.load_values_unmerged()?;
    308    for mut cv in unmerged {
    309        if trim_cv(&mut cv, key)? {
    310            print_table(&cv);
    311        }
    312    }
    313    Ok(())
    314}