scaffold 0.5.1

Quickly add dependencies to your Rust project.
Documentation
use crate::error::Error;
use crate::version_getter::VersionGetter;
use serde::Deserialize;
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
use std::str::FromStr;
use toml::Value;

#[derive(Debug, Deserialize)]
struct RawDependencyGroups {
    #[serde(flatten)]
    groups: HashMap<String, RawDependencyGroup>,
}

#[derive(Debug, Deserialize)]
struct RawDependencyGroup {
    #[serde(flatten)]
    raw_dependencies: HashMap<String, Value>,
}

#[derive(Debug)]
struct DependencyGroups {
    groups: HashMap<String, Vec<Dependency>>,
}

#[derive(Debug)]
pub enum Version {
    Latest,
    Specific(String),
}

impl FromStr for Version {
    type Err = ();

    fn from_str(version: &str) -> Result<Self, Self::Err> {
        match version {
            "" | "*" => Ok(Version::Latest),
            specific => Ok(Version::Specific(specific.to_string())),
        }
    }
}

#[derive(Debug)]
pub struct Dependency {
    pub name: String,
    pub version: Version,
    pub other: Option<Value>,
}

impl Dependency {
    pub fn new<T: Into<String>>(name: T, version: Version, other: &str) -> Result<Self, Error> {
        Ok(Self {
            name: name.into(),
            version,
            other: Some(Value::from_str(other).map_err(|error| Error::TomlDe(error))?),
        })
    }

    pub fn new_latest<T: Into<String>>(name: T) -> Self {
        Self {
            name: name.into(),
            version: Version::Latest,
            other: None,
        }
    }

    fn from_value(name: &str, value: Value) -> Option<Self> {
        match value {
            Value::String(version) => {
                let name = name.to_string();
                let version = Version::from_str(&version).unwrap();
                let other = None;

                Some(Self {
                    name,
                    version,
                    other,
                })
            }
            Value::Table(mut table) => {
                let name = name.to_string();

                let version = match table.get("version").and_then(|it| it.as_str()) {
                    None => Version::Latest,
                    Some(version) => Version::from_str(version).unwrap(),
                };

                table.remove("version");

                Some(Self {
                    name,
                    version,
                    other: Some(Value::Table(table)),
                })
            }
            _ => None,
        }
    }
}

impl Dependency {
    pub fn get_version_as_string(
        &self,
        version_getter: &mut VersionGetter,
        use_tilde_version: bool,
    ) -> String {
        match &self.version {
            Version::Latest => {
                match version_getter.get_crate_version(&self.name, use_tilde_version) {
                    None => {
                        eprintln!("Could not find version for crate {:?}.", &self.name);
                        "*".to_string()
                    }
                    Some(version) => version,
                }
            }
            Version::Specific(version) => version.to_string(),
        }
    }

    pub fn get_pretty_string(
        &self,
        version_getter: &mut VersionGetter,
        use_tilde_version: bool,
    ) -> String {
        let version = self.get_version_as_string(version_getter, use_tilde_version);

        match &self.other {
            None => format!("{} = {:?}", &self.name, version),
            Some(other) => {
                if let Value::Table(mut other) = other.clone() {
                    other.insert("version".to_string(), Value::String(version));
                    let other = format!("{}", Value::Table(other))
                        .lines()
                        .collect::<Vec<_>>()
                        .join(", ");

                    format!("{} = {{ {} }}", &self.name, other)
                } else {
                    panic!("Expected Value::Table, got something else.")
                }
            }
        }
    }
}

pub fn get_groups(path: &PathBuf) -> Result<HashMap<String, Vec<Dependency>>, Error> {
    let raw_dependency_groups = read_toml_file(path)?;

    let mut groups = HashMap::new();

    for (group_name, raw_group) in raw_dependency_groups.groups {
        for (dependency_name, raw_dependency) in raw_group.raw_dependencies {
            if let Some(dependency) = Dependency::from_value(&dependency_name, raw_dependency) {
                groups
                    .entry(group_name.to_string())
                    .or_insert_with(Vec::new)
                    .push(dependency);
            }
        }
    }

    for group in groups.values_mut() {
        group.sort_by_key(|it| it.name.to_string());
    }

    Ok(groups)
}

fn read_toml_file(path: &PathBuf) -> Result<RawDependencyGroups, Error> {
    match File::open(path) {
        Ok(mut handle) => {
            let mut buffer = String::new();
            match handle.read_to_string(&mut buffer) {
                Ok(_) => match toml::from_str::<RawDependencyGroups>(&buffer) {
                    Ok(data) => Ok(data),
                    Err(error) => Err(Error::TomlDe(error)),
                },
                Err(error) => Err(Error::Io(error)),
            }
        }
        Err(error) => Err(Error::Io(error)),
    }
}