rye_uv/
lib.rs

1use anyhow::anyhow;
2use regex::Regex;
3use semver::{Version, VersionReq};
4use std::process::Command;
5use toml_edit::{value, DocumentMut, Item, Table};
6
7fn is_compatible(actual_version: &Version, version_requirement: &str) -> Option<bool> {
8    VersionReq::parse(version_requirement)
9        .and_then(|x| Ok(x.matches(&actual_version)))
10        .ok()
11}
12
13fn parse_semver(tool_name: &str, stdout: &str) -> Option<String> {
14    let re = Regex::new(&format!(r"{} (\d+\.\d+\.\d+)", tool_name)).unwrap();
15    re.captures(stdout).and_then(|x| Some(x[1].to_string()))
16}
17
18pub fn get_tool_version(tool_name: &str) -> anyhow::Result<Version> {
19    let mut command = Command::new(tool_name);
20    let uv_v = command.arg("-V");
21    let out = uv_v
22        .output()
23        .expect(&format!("Cannot run '{} -V' command", tool_name));
24    let out_string =
25        String::from_utf8(out.stdout).expect(&format!("Cannot fetch '{} -V' output", tool_name));
26    let out_semver =
27        parse_semver(tool_name, &out_string).ok_or(anyhow!("Cannot parse version number"))?;
28    Version::parse(&out_semver).map_err(anyhow::Error::from)
29}
30
31fn nest(item: &mut Item, old_key: &str, new_key: &str) {
32    if let Some((key, universal)) = item
33        .as_table_mut()
34        .expect("Error while parsing the item as as table")
35        .remove_entry(old_key)
36    {
37        let mut sub_table = item
38            .get_mut(new_key)
39            .and_then(|x| x.as_table_mut())
40            .unwrap_or(&mut Table::new())
41            .clone();
42
43        sub_table.insert(&key, universal);
44        item[new_key] = Item::Table(sub_table);
45    }
46}
47
48fn rename(table: &mut Table, key: &str, new_key: &str) {
49    if let Some(value) = table.remove(key) {
50        table[new_key] = value;
51    }
52}
53
54fn rename_and_invert(table: &mut Item, key: &str, new_key: &str) -> anyhow::Result<()> {
55    if let Some(key_val) = table.as_table_mut().and_then(|x| x.remove(key)) {
56        let new_val = key_val.as_bool();
57        table[new_key] = value(!new_val.ok_or(anyhow!("Value of {} is not a valid bool", key))?)
58    }
59    Ok(())
60}
61
62pub fn convert(document: &mut DocumentMut, uv: Version) -> anyhow::Result<()> {
63    let uv_ge_4 = is_compatible(&uv, ">=0.4.0");
64
65    if let Some(tool) = document.get_mut("tool") {
66        if let Some(tool) = tool.as_table_mut() {
67            rename(tool, "rye", "uv");
68
69            nest(&mut tool["uv"], "universal", "pip");
70            nest(&mut tool["uv"], "generate-hashes", "pip");
71
72            rename_and_invert(&mut tool["uv"], "lock-with-sources", "no-sources")?;
73
74            if let Some(uv_4_compatible) = uv_ge_4 {
75                if uv_4_compatible {
76                    rename_and_invert(&mut tool["uv"], "virtual", "package")?;
77                } else {
78                    tool["uv"].as_table_mut().and_then(|x| x.remove("virtual"));
79                }
80            }
81        }
82    }
83
84    Ok(())
85}
86
87#[cfg(test)]
88mod tests {
89    use pretty_assertions::assert_eq;
90    use std::str::FromStr;
91
92    use toml_edit::Value;
93
94    use super::*;
95
96    #[test]
97    fn test_rename() -> anyhow::Result<()> {
98        let mut table = Table::new();
99        let val = Item::Value(Value::from_str(&"'foo'")?);
100        table["test_foo"] = val.clone();
101        rename(&mut table, "test_foo", "test_bar");
102        assert!(table.contains_key("test_bar"));
103        assert!(!table.contains_key("test_foo"));
104        assert_eq!(table["test_bar"].to_string(), val.to_string());
105        Ok(())
106    }
107
108    #[test]
109    fn test_rename_and_invert() -> anyhow::Result<()> {
110        let mut table = Table::new();
111        let mut inner_table = Table::new();
112        let val = Item::Value(Value::from(true));
113        inner_table["test_foo"] = val.clone();
114        table["bool_val"] = Item::Table(inner_table);
115
116        rename_and_invert(&mut table["bool_val"], "test_foo", "test_bar")?;
117        assert_eq!(table["bool_val"]["test_bar"].as_bool().unwrap(), false);
118        Ok(())
119    }
120}