1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
use std::error;
use std::fs;
use std::fs::File;
use std::io::Read;

extern crate toml_edit;
use toml_edit::{value, Document};

extern crate reqwest;

extern crate serde_json;
use serde_json::Value;

pub fn update_toml_file(
    filename: &str,
    unsafe_file_updates: bool,
) -> Result<(), Box<error::Error>> {
    println!("Reading file: {}", filename);

    let mut contents = String::new();
    {
        let mut f = File::open(filename)?;
        f.read_to_string(&mut contents)
            .expect("Something went wrong reading the file.");
    }

    let new_contents = update_toml(&contents)?;
    if new_contents == contents {
        return Ok(());
    }

    if !unsafe_file_updates {
        let filename_old = filename.to_string() + ".old";
        let _ = fs::remove_file(&filename_old);
        fs::copy(filename, filename_old)?;
    }

    fs::write(filename, new_contents)?;
    Ok(())
}

#[test]
fn test_update_toml() {
    let toml = r#"
[package]
version = "0.1.0"

[dependencies]
structopt = "0.2"
toml_edit = "0.1.3"
    "#;

    let expected = r#"
[package]
version = "0.1.0"

[dependencies]
structopt = "0.2.15"
toml_edit = "0.1.3"
    "#;

    let result = update_toml(toml).unwrap();
    assert_eq!(result, expected);
}

fn update_toml(toml: &str) -> Result<String, Box<error::Error>> {
    let mut doc = toml.parse::<Document>()?;

    let mut updates: Vec<(String, String)> = Vec::new();
    {
        let section = &doc["dependencies"].as_table();
        for key in section {
            for (the_crate, local_version) in key.iter() {
                let local_version = local_version.as_value().unwrap();
                let local_version = match local_version.as_str() {
                    Some(v) => v.trim(),
                    None => {
                        println!("** Error: Can not parse {}", &local_version);
                        continue;
                    }
                };

                println!("\tFound: {}", the_crate);
                println!("\t\tLocal version:  {}", local_version);

                let online_version = lookup_latest_version(&the_crate)?;
                println!("\t\tOnline version: {}", &online_version);

                if local_version != online_version {
                    updates.push((the_crate.to_string(), online_version));
                }
            }
        }
    }

    for (the_crate, version) in updates {
        doc["dependencies"][&the_crate] = value(version);
    }
    Ok(doc.to_string())
}

fn lookup_latest_version(crate_name: &str) -> Result<String, Box<error::Error>> {
    let uri = "https://crates.io/api/v1/crates/".to_string() + crate_name;

    let mut http_res = reqwest::get(&uri)?;
    let mut http_body = String::new();
    http_res.read_to_string(&mut http_body)?;

    let json_doc: Value = serde_json::from_str(&http_body)?;
    let mut version: String = json_doc["versions"][0]["num"].to_string();

    if version.starts_with('"') {
        version = version.get(1..).unwrap().to_string();
    }
    if version.ends_with('"') {
        let end = version.len() - 1;
        version = version.get(..end).unwrap().to_string();
    }

    Ok(version)
}