use std::sync::Arc;
use std::collections::HashMap;
use std::io::{self, Write};
use semver::{self, Version};
use crate::dependencies;
pub fn write<W: Write>(update_packages: &[Package], w: &mut W) -> Result<(), io::Error> {
use piston_meta::json;
writeln!(w, "{{")?;
let n0 = update_packages.len();
for (i0, update_package) in update_packages.iter().enumerate() {
write!(w, " ")?;
json::write_string(w, &update_package.name)?;
writeln!(w, ": {{")?;
writeln!(w, " \"order\": {},", update_package.order)?;
writeln!(w, " \"bump\": {{")?;
write!(w, " \"old\": ")?;
json::write_string(w, &format!("{}", update_package.bump.old))?;
writeln!(w, ",")?;
write!(w, " \"new\": ")?;
json::write_string(w, &format!("{}", update_package.bump.new))?;
writeln!(w, "")?;
writeln!(w, " }},")?;
writeln!(w, " \"dependencies\": {{")?;
let n1 = update_package.dependencies.len();
for (i1, dep) in update_package.dependencies.iter().enumerate() {
write!(w, " ")?;
json::write_string(w, &dep.name)?;
writeln!(w, ": {{")?;
writeln!(w, " \"bump\": {{")?;
write!(w, " \"old\": ")?;
json::write_string(w, &format!("{}", dep.bump.old))?;
writeln!(w, ",")?;
write!(w, " \"new\": ")?;
json::write_string(w, &format!("{}", dep.bump.new))?;
writeln!(w, "")?;
writeln!(w, " }}")?;
write!(w, " }}")?;
if i1 + 1 < n1 {
writeln!(w, ",")?;
} else {
writeln!(w, "")?;
}
}
writeln!(w, " }},")?;
writeln!(w, " \"dev-dependencies\": {{")?;
let n1 = update_package.dev_dependencies.len();
for (i1, dep) in update_package.dev_dependencies.iter().enumerate() {
write!(w, " ")?;
json::write_string(w, &dep.name)?;
writeln!(w, ": {{")?;
writeln!(w, " \"bump\": {{")?;
write!(w, " \"old\": ")?;
json::write_string(w, &format!("{}", dep.bump.old))?;
writeln!(w, ",")?;
write!(w, " \"new\": ")?;
json::write_string(w, &format!("{}", dep.bump.new))?;
writeln!(w, "")?;
writeln!(w, " }}")?;
write!(w, " }}")?;
if i1 + 1 < n1 {
writeln!(w, ",")?;
} else {
writeln!(w, "")?;
}
}
writeln!(w, " }}")?;
write!(w, " }}")?;
if i0 + 1 < n0 {
writeln!(w, ",")?;
} else {
writeln!(w, "")?;
}
}
writeln!(w, "}}")?;
Ok(())
}
pub struct Bump {
pub old: Version,
pub new: Version,
}
pub struct Dependency {
pub name: Arc<String>,
pub bump: Bump,
}
pub struct Package {
pub name: Arc<String>,
pub order: u32,
pub bump: Bump,
pub dependencies: Vec<Dependency>,
pub dev_dependencies: Vec<Dependency>,
}
pub fn parse_version(text: &str) -> Result<Version, semver::SemVerError> {
let text = if text == "1" { "1.0" } else { text };
let text = if text == "^1" { "1.0" } else { text };
let mut text = if text.starts_with(">=") {
&text[2..]
} else if text.starts_with("^") {
&text[1..]
} else if text.starts_with("=") {
&text[1..]
} else {
text
};
for ch in text.char_indices() {
if ch.1 == ',' {
text = &text[..ch.0];
}
}
let text = {
let n = text.len();
if text.ends_with(".*") {
&text[..n - 2]
} else {
text
}
};
match Version::parse(text) {
Err(_) => {
let append_zero = format!("{}.0", text);
Version::parse(&append_zero)
}
x => x,
}
}
pub fn generate_update_info_from(dependency_info: &str) -> Result<String, String> {
use piston_meta::*;
type PackageIndex = usize;
type Depth = u32;
fn depth_of(
package_index: PackageIndex,
package_indices: &HashMap<Arc<String>, PackageIndex>,
depths: &mut HashMap<PackageIndex, Depth>,
dependencies_data: &[dependencies::Package],
) -> Depth {
let package = &dependencies_data[package_index];
let d = depths.get(&package_index).map(|d| *d);
match d {
None => {
let new_depth: Depth = package
.dependencies
.iter()
.map(|dep| {
package_indices
.get(&dep.name)
.map(|&p| depth_of(p, package_indices, depths, dependencies_data))
.unwrap_or(0)
})
.max()
.unwrap_or(0) + 1;
depths.insert(package_index, new_depth);
new_depth
}
Some(x) => x,
}
}
fn ignore_version(text: &str) -> bool {
text == "*"
}
fn breaks(a: &Version, b: &Version) -> bool {
if a.major != b.major {
true
} else if a.major != 0 {
false
} else if a.minor != b.minor {
true
} else if a.minor != 0 {
false
} else if a.patch != b.patch {
true
} else {
false
}
}
fn increment_version(version: &mut Version) {
if version.major != 0 {
version.increment_major();
} else if version.minor != 0 {
version.increment_minor();
} else {
version.increment_patch();
}
}
let dependencies_meta_syntax = include_str!("../assets/dependencies/syntax.txt");
let dependencies_meta_rules =
stderr_unwrap(dependencies_meta_syntax, syntax(dependencies_meta_syntax));
let mut dependency_info_meta_data = vec![];
stderr_unwrap(
dependency_info,
parse(
&dependencies_meta_rules,
dependency_info,
&mut dependency_info_meta_data,
),
);
let mut ignored = vec![];
let dependencies_data = dependencies::convert(&dependency_info_meta_data, &mut ignored)
.map_err(|_| String::from("Could not convert dependency info"))?;
let package_indices: HashMap<Arc<String>, PackageIndex> = HashMap::from_iter(
dependencies_data
.iter()
.enumerate()
.map(|(i, p)| (p.name.clone(), i)),
);
let mut depths: HashMap<PackageIndex, Depth> = HashMap::new();
for i in 0..dependencies_data.len() {
let _depth = depth_of(i, &package_indices, &mut depths, &dependencies_data);
}
let mut new_versions: HashMap<Arc<String>, Version> = HashMap::new();
for package in &dependencies_data {
for dep in &package.dependencies {
if ignore_version(&dep.version) {
continue;
}
let version = parse_version(&dep.version).map_err(|_| {
format!(
"Could not parse version `{}` for dependency `{}` in `{}`",
&dep.version, &dep.name, &package.name
)
})?;
let v = new_versions.get(&dep.name).map(|v| v.clone());
match v {
None => {
new_versions.insert(dep.name.clone(), version);
}
Some(v) => {
if v < version {
new_versions.insert(dep.name.clone(), version);
}
}
}
}
for dep in &package.dev_dependencies {
if ignore_version(&dep.version) {
continue;
}
let version = parse_version(&dep.version).map_err(|_| {
format!(
"Could not parse version `{}` for dev dependency `{}` in `{}`",
&dep.version, &dep.name, &package.name
)
})?;
let v = new_versions.get(&dep.name).map(|v| v.clone());
match v {
None => {
new_versions.insert(dep.name.clone(), version);
}
Some(v) => {
if v < version {
new_versions.insert(dep.name.clone(), version);
}
}
}
}
}
for package in &dependencies_data {
let version = Version::parse(&package.version).map_err(|_| {
format!(
"Could not parse version `{}` for `{}`",
&package.version, &package.name
)
})?;
new_versions.insert(package.name.clone(), version);
}
let mut sorted_depths: Vec<_> = depths.iter().collect();
sorted_depths.sort_by(|&(_, da), &(_, db)| da.cmp(db));
let mut update_packages: Vec<Package> = vec![];
for (&package_index, &order) in sorted_depths {
let package = &dependencies_data[package_index];
let mut update_dependencies = vec![];
for dep in &package.dependencies {
if ignore_version(&dep.version) {
continue;
}
if dep.version.starts_with(">=") {
continue;
}
let old_version = parse_version(&dep.version).map_err(|_| {
format!(
"Could not parse version `{}` for dependency `{}` in `{}`",
&dep.version, &dep.name, &package.name
)
})?;
let new_version = new_versions.get(&dep.name).unwrap();
if breaks(new_version, &old_version) {
if let Some(ref ignore_version) = dep.ignore_version {
if &new_version.to_string() == &**ignore_version {
continue;
}
}
update_dependencies.push(Dependency {
name: dep.name.clone(),
bump: Bump {
old: old_version,
new: new_version.clone(),
},
});
}
}
if update_dependencies.len() > 0 {
let old_version = Version::parse(&package.version).map_err(|_| {
format!(
"Could not parse version `{}` for `{}`",
&package.version, &package.name
)
})?;
let new_version = new_versions.get_mut(&package.name).unwrap();
if *new_version == old_version {
increment_version(new_version);
}
update_packages.push(Package {
name: package.name.clone(),
order: order,
bump: Bump {
old: old_version,
new: new_version.clone(),
},
dependencies: update_dependencies,
dev_dependencies: vec![],
});
}
}
for update_package in &mut update_packages {
let package_index = *package_indices.get(&update_package.name).unwrap();
let package = &dependencies_data[package_index];
for dep in &package.dev_dependencies {
if ignore_version(&dep.version) {
continue;
}
if dep.version.starts_with(">=") {
continue;
}
let old_version = parse_version(&dep.version).map_err(|_| {
format!(
"Could not parse version `{}` for dev dependency `{}` in `{}`",
&dep.version, &dep.name, &package.name
)
})?;
let new_version = new_versions.get(&dep.name).unwrap();
if breaks(new_version, &old_version) {
if let Some(ref ignore_version) = dep.ignore_version {
if &new_version.to_string() == &**ignore_version {
continue;
}
}
update_package.dev_dependencies.push(Dependency {
name: dep.name.clone(),
bump: Bump {
old: old_version,
new: new_version.clone(),
},
});
}
}
}
let mut w: Vec<u8> = vec![];
write(&update_packages, &mut w).unwrap();
Ok(String::from_utf8(w).unwrap())
}