use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use toml;
use app_result::{AppResult, app_err_msg};
use types::{
TagsRoot,
TagsRoots,
SourceKind
};
pub fn read_dependencies(cargo_toml_dir: &Path) -> AppResult<TagsRoots> {
let toml_table = {
let mut cargo_toml = cargo_toml_dir.to_path_buf();
cargo_toml.push("Cargo.toml");
try!(parse_toml(&cargo_toml))
};
let mut tags_roots: TagsRoots = Vec::new();
let deps = try!(collect_dependencies(&toml_table));
if deps.is_empty() {
tags_roots.push(TagsRoot::Proj { root_dir: cargo_toml_dir.to_path_buf(), dependencies: Vec::new() });
return Ok(tags_roots);
}
let lock_table = {
let mut cargo_lock = cargo_toml_dir.to_path_buf();
cargo_lock.push("Cargo.lock");
try!(parse_toml(&cargo_lock))
};
let packages: Vec<&toml::Table> = try!(
lock_table.get("package")
.and_then(toml::Value::as_slice)
.map(|s| {
s.iter()
.filter_map(toml::Value::as_table)
.collect()
})
.ok_or(app_err_msg(format!("Couldn't get Array of Tables entry for 'package'!")))
);
for package in packages.iter() {
let lib_name = try!(
package.get("name")
.and_then(toml::Value::as_str)
.ok_or_else(|| app_err_msg(format!("Couldn't find name string in package: '{:?}'!", package)))
);
let dep_names = try!(get_dependencies(*package));
let mut dep_src_kinds: Vec<SourceKind> = Vec::new();
for dep_name in dep_names.iter() {
let dep_package = try!(find_package(&packages, *dep_name));
if let Ok(src_kind) = get_source_kind(dep_package, *dep_name) {
dep_src_kinds.push(src_kind);
}
}
if let Ok(src_kind) = get_source_kind(*package, lib_name) {
tags_roots.push(TagsRoot::Lib { src_kind: src_kind, dependencies: dep_src_kinds });
}
}
let mut lib_src_kinds: Vec<SourceKind> = Vec::new();
for (lib_name, value) in deps {
match *value {
toml::Value::String(_) => {
let pkg = try!(find_package(&packages, lib_name));
lib_src_kinds.push(try!(get_source_kind(pkg, lib_name)));
}
toml::Value::Table(ref table) => {
if let Some(path) = table.get("path") {
let mut path = try!(
path.as_str().ok_or_else(|| {
app_err_msg(format!("Expected a String for 'path' entry in '{}'", value))
})
.map(PathBuf::from)
);
if path.is_relative() {
let mut abs_path = cargo_toml_dir.to_path_buf();
abs_path.push(path);
path = abs_path;
}
lib_src_kinds.push(SourceKind::Path { lib_name: lib_name.clone(), path: path });
} else if table.get("version").is_some() || table.get("git").is_some() {
let pkg = try!(find_package(&packages, lib_name));
lib_src_kinds.push(try!(get_source_kind(pkg, lib_name)));
} else {
return Err(app_err_msg(format!("Couldn't find a 'path', 'version' or 'git' attribute for '{}' in '{}'",
lib_name, value)));
}
}
_ => {
return Err(app_err_msg(format!("Expected a String or a Table for the dependency with the name '{}', but got: '{}'",
lib_name, value)));
}
}
}
tags_roots.push(TagsRoot::Proj { root_dir: cargo_toml_dir.to_path_buf(), dependencies: lib_src_kinds });
Ok(tags_roots)
}
fn get_source_kind(lib_package: &toml::Table, lib_name: &str) -> AppResult<SourceKind> {
let source = try!(
lib_package.get("source")
.and_then(toml::Value::as_str)
.ok_or_else(|| app_err_msg(format!("Couldn't find source string in package: '{:?}'!", lib_package)))
);
let src_type = try!(
source.split('+')
.nth(0)
.ok_or_else(|| app_err_msg(format!("Couldn't find source type in: '{}'!", source)))
);
let version = try!(
lib_package.get("version")
.and_then(toml::Value::as_str)
.ok_or_else(|| app_err_msg(format!("Couldn't find version string in package: '{:?}'!", lib_package)))
);
match src_type {
"git" => {
let commit_hash = try!(
source.split('#')
.last()
.ok_or_else(|| app_err_msg(format!("Couldn't find commit hash in source entry: '{}'!", source)))
);
Ok(SourceKind::Git { lib_name: lib_name.to_string(), commit_hash: commit_hash.to_string() })
},
"registry" => {
Ok(SourceKind::CratesIo { lib_name: lib_name.to_string(), version: version.to_string() })
}
_ => {
Err(app_err_msg(format!("Unexpected source type '{}' in package: '{:?}'!", src_type, lib_package)))
}
}
}
type DepName = String;
fn collect_dependencies(cargo_toml: &toml::Table) -> AppResult<Vec<(&DepName, &toml::Value)>> {
let mut deps = Vec::with_capacity(50);
for dep_type in &["dependencies", "build-dependencies", "dev-dependencies"] {
if let Some(deps_value) = cargo_toml.get(*dep_type) {
let deps_table = try!(
deps_value.as_table()
.ok_or(app_err_msg(format!("Couldn't get toml::Table entry for '{}'! Got a '{}'!", dep_type, deps_value)))
);
for dep in deps_table {
deps.push(dep);
}
}
}
Ok(deps)
}
fn get_dependencies(lib_package: &toml::Table) -> AppResult<Vec<&str>> {
let mut dep_names: Vec<&str> = Vec::new();
if ! lib_package.contains_key("dependencies") {
return Ok(dep_names);
}
let dep_strs: Vec<&str> = try!(
lib_package.get("dependencies")
.and_then(toml::Value::as_slice)
.map(|s| {
s.iter()
.filter_map(toml::Value::as_str)
.collect()
})
.ok_or_else(|| app_err_msg(format!("Couldn't get Array of Strings for 'dependencies' entry: '{:?}'!", lib_package)))
);
for dep_str in dep_strs.iter() {
let dep_name = try!(
dep_str.split(' ')
.nth(0)
.ok_or_else(|| app_err_msg(format!("Couldn't get name from dependency: '{}'!", dep_str)))
);
dep_names.push(dep_name);
}
Ok(dep_names)
}
fn find_package<'a>(packages: &'a Vec<&toml::Table>, lib_name: &str) -> AppResult<&'a toml::Table> {
let package = try!(
packages.iter()
.find(|p| p.get("name").and_then(toml::Value::as_str) == Some(lib_name))
.ok_or_else(|| app_err_msg(format!("Couldn't find package with name = '{}'!", lib_name)))
);
Ok(*package)
}
fn parse_toml(path: &Path) -> AppResult<toml::Table> {
let mut file = try!(File::open(path));
let mut string = String::new();
try!(file.read_to_string(&mut string));
let mut parser = toml::Parser::new(&string);
parser.parse().ok_or_else(|| app_err_msg(format!("Couldn't parse '{}': {:?}", path.display(), parser.errors)))
}