Skip to content

Commit

Permalink
fix: Handle plain strings in --sync-aliases
Browse files Browse the repository at this point in the history
It's valid and not that uncommon to have an alias specified by a single
string. So far, cargo-run-bin panics with a very unhelpful message when
it counters that. This commit fixes that. While I was at it, I also
turned other nearby unwrap()s into error messages to avoid similarly
bad UX with other unexpected (likely invalid) config.toml contents.
  • Loading branch information
hanna-kruppe committed Nov 21, 2024
1 parent c6bd9ec commit c63d362
Showing 1 changed file with 72 additions and 12 deletions.
84 changes: 72 additions & 12 deletions src/cargo_config.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use std::fs;

use anyhow::bail;
use anyhow::Context;
use anyhow::Result;
use toml_edit::table;
use toml_edit::value;
use toml_edit::Array;
use toml_edit::Document;
use toml_edit::Value;

use crate::metadata;

Expand All @@ -16,23 +19,56 @@ pub fn sync_aliases() -> Result<()> {
toml_str = fs::read_to_string(&config_path)?.parse()?;
}

let binary_packages = metadata::get_binary_packages()?;
let new_config = update_aliases_toml(&toml_str, binary_packages)
.context("failed to update aliases in .cargo/config.toml")?;

if !config_path.parent().unwrap().exists() {
fs::create_dir(config_path.parent().unwrap())?;
}

fs::write(config_path, new_config)?;

return Ok(());
}

fn update_aliases_toml(
toml_str: &str,
binary_packages: Vec<metadata::BinaryPackage>,
) -> Result<String> {
let mut doc = toml_str.parse::<Document>()?;
if doc.get("alias").is_none() {
doc["alias"] = table();
}

let aliases = doc["alias"].as_table_mut().unwrap();
// If the TOML structure is not as and we panic because of that, that makes for poor user
// experience, so try to report errors for everything that could go wrong.
let aliases = doc["alias"]
.as_table_mut()
.context("alias key should be a table")?;
let mut remove_keys: Vec<String> = vec![];
for (key, value) in aliases.get_values() {
if value.as_array().unwrap().get(0).unwrap().as_str().unwrap() == "bin" {
remove_keys.push(key[0].get().to_owned());
let [name] = key.as_slice() else {
bail!("unexpected nested table: {key:?}")
};
// The value can be either a single string (implicitly split on spaces) or an array of
// strings. We always create an array, but a user might use a single string for other
// aliases, so we have to at least not crash on such values.
if let Value::Array(parts) = value {
let first_part = parts
.get(0)
.with_context(|| format!("alias {name:?} is empty array"))?
.as_str()
.with_context(|| format!("alias {name:?} should be array of strings"))?;
if first_part == "bin" {
remove_keys.push(name.get().to_owned());
}
}
}
for key in remove_keys {
aliases.remove(&key);
}

let binary_packages = metadata::get_binary_packages()?;
for binary_package in binary_packages {
let mut bin = binary_package.package;
if let Some(bin_target) = binary_package.bin_target {
Expand All @@ -48,14 +84,7 @@ pub fn sync_aliases() -> Result<()> {
arr.push(bin.clone());
doc["alias"][bin.replace("cargo-", "")] = value(arr);
}

if !config_path.parent().unwrap().exists() {
fs::create_dir(config_path.parent().unwrap())?;
}

fs::write(config_path, doc.to_string())?;

return Ok(());
return Ok(doc.to_string());
}

/// Verifies in cargo-binstall is available in alias'.
Expand All @@ -74,3 +103,34 @@ pub fn binstall_alias_exists() -> Result<bool> {
let aliases = doc["alias"].as_table_mut().unwrap();
return Ok(aliases.contains_key("binstall"));
}

#[cfg(test)]
mod tests {
use super::update_aliases_toml;

#[test]
fn skips_bare_string_alias() {
let original_toml = r#"
[alias]
xtask = "run --package xtask --"
"#;
let new_toml = update_aliases_toml(original_toml, Vec::new()).unwrap();
assert_eq!(original_toml, new_toml);
}

#[test]
fn doesnt_panic_on_empty_array() {
let result = update_aliases_toml("alias.mistake = []", Vec::new());
// Currently an error, could be skipped instead, in that case the Ok(..) result should be
// tested a bit.
assert!(result.is_err());
}

#[test]
fn doesnt_panic_on_nested_keys() {
let result = update_aliases_toml("alias.alias.alias = 'test'", Vec::new());
// Currently an error, could be skipped instead, in that case the Ok(..) result should be
// tested a bit.
assert!(result.is_err());
}
}

0 comments on commit c63d362

Please sign in to comment.