use super::registry::Registry;
use crate::{
package::*,
remote::resolution::{DirectRes, IndexRes, Resolution},
util::{
errors::{ErrorKind, Res},
lock::DirLock,
},
};
use failure::{format_err, Error, ResultExt};
use indexmap::IndexMap;
use semver::Version;
use semver_constraints::Constraint;
use serde::{Deserialize, Serialize};
use serde_json;
use simsearch::{SearchOptions, SimSearch};
use std::{
fs,
io::{self, prelude::*, BufReader},
str::FromStr,
};
use toml;
use walkdir::WalkDir;
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct IndexConfig {
pub index: IndexConfInner,
}
impl FromStr for IndexConfig {
type Err = Error;
fn from_str(raw: &str) -> Result<Self, Self::Err> {
toml::from_str(raw)
.context(format_err!("invalid index config"))
.map_err(Error::from)
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct IndexConfInner {
pub secure: bool,
pub dependencies: IndexMap<String, IndexRes>,
pub registry: Option<Registry>,
}
impl Default for IndexConfInner {
fn default() -> Self {
IndexConfInner {
secure: false,
dependencies: IndexMap::new(),
registry: None,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct Dep<T> {
pub name: Name,
pub index: T,
pub req: Constraint,
}
pub type ResolvedDep = Dep<IndexRes>;
pub type TomlDep = Dep<Option<String>>;
#[derive(Debug, Default)]
pub struct Indices {
pub indices: IndexMap<IndexRes, Index>,
pub cache: IndexMap<PackageId, IndexMap<Version, ResolvedEntry>>,
}
impl Indices {
pub fn new(indices: Vec<Index>) -> Self {
let indices = indices.into_iter().map(|i| (i.id.clone(), i)).collect();
let cache = IndexMap::new();
Indices { indices, cache }
}
pub fn select_by_spec(&self, spec: &Spec) -> Res<Summary> {
let mut res = None;
for (ir, ix) in &self.indices {
if spec.resolution.is_none() || Some(&ir.clone().into()) == spec.resolution.as_ref() {
if let Ok(es) = ix.entries(&spec.name) {
if let Some(x) = es
.into_iter()
.filter(|x| {
!x.1.yanked
&& (spec.version.is_none()
|| Some(&x.1.version) == spec.version.as_ref())
})
.last()
{
if let Some(existing) = res {
return Err(format_err!(
"spec `{}` is ambiguous, and matches both {} and {}@{}|{}",
&spec,
existing,
&spec.name,
ir,
x.0
));
} else {
res = Some(Summary::new(
PackageId::new(spec.name.clone(), ir.clone().into()),
x.0,
));
}
}
}
}
}
Ok(res.ok_or_else(|| ErrorKind::PackageNotFound)?)
}
pub fn select(&mut self, pkg: &Summary) -> Res<&ResolvedEntry> {
let entry = self
.entries(pkg.id())?
.get(pkg.version())
.ok_or_else(|| ErrorKind::PackageNotFound)?;
Ok(entry)
}
pub fn count_versions(&self, pkg: &PackageId) -> usize {
self.cache.get(pkg).map(|m| m.len()).unwrap_or(0)
}
pub fn entries(&mut self, pkg: &PackageId) -> Res<&IndexMap<Version, ResolvedEntry>> {
if self.cache.contains_key(pkg) {
return Ok(&self.cache[pkg]);
}
let res = pkg.resolution();
if let Resolution::Index(ir) = res {
let ix = self.indices.get(ir);
if let Some(ix) = ix {
let mut v = ix.entries(pkg.name())?;
v.sort_keys();
self.cache.insert(pkg.clone(), v);
Ok(&self.cache[pkg])
} else {
Err(Error::from(ErrorKind::PackageNotFound))
}
} else {
Err(Error::from(ErrorKind::PackageNotFound))
}
}
pub fn search(&self, query: &str) -> Res<Vec<(Name, Version, &IndexRes)>> {
let mut engine: SimSearch<(&IndexRes, &str)> =
SimSearch::new_with(SearchOptions::new().stop_words(&["/", "\\"]));
let x = self
.indices
.iter()
.map(|x| x.1.packages().map(move |p| (x.0, p)))
.flatten()
.collect::<Vec<_>>();
for (ir, pkg) in &x {
engine.insert((ir, pkg), pkg);
}
let pkgs = engine.search(query);
pkgs.iter().map(|(ir, pkg)| {
let name = Name::from_str(pkg).unwrap();
let ix = &self.indices[*ir];
let ver: Version = ix.entries(&name)?.into_iter().map(|x| x.0).last().unwrap();
Ok((name, ver, *ir))
}).collect::<Res<Vec<_>>>()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct IndexEntry<T, L> {
pub name: Name,
pub version: Version,
pub dependencies: Vec<Dep<T>>,
pub yanked: bool,
pub location: L,
}
pub type ResolvedEntry = IndexEntry<IndexRes, DirectRes>;
pub type TomlEntry = IndexEntry<Option<String>, Option<DirectRes>>;
#[derive(Debug)]
pub struct Index {
pub id: IndexRes,
pub path: DirLock,
pub config: IndexConfig,
}
impl Index {
pub fn from_disk(res: DirectRes, path: DirLock) -> Res<Self> {
let id = IndexRes { res };
let pn = path.path().join("index.toml");
let file = fs::File::open(&pn)
.with_context(|e| format_err!("couldn't open index config {}: {}", pn.display(), e))?;
let mut file = BufReader::new(file);
let mut contents = String::new();
file.read_to_string(&mut contents)
.with_context(|e| format_err!("couldn't read index config {}: {}", pn.display(), e))?;
let config = IndexConfig::from_str(&contents)?;
Ok(Index { id, path, config })
}
pub fn entries(&self, name: &Name) -> Res<IndexMap<Version, ResolvedEntry>> {
let mut res = IndexMap::new();
let path = self.path.path().join(name.as_normalized());
let file = fs::File::open(path).context(ErrorKind::PackageNotFound)?;
let r = io::BufReader::new(&file);
for (lix, line) in r.lines().enumerate() {
let entry: TomlEntry = serde_json::from_str(&line?).context(format_err!(
"index entry {} for package {} is invalid",
lix + 1,
name
))?;
let dependencies = entry
.dependencies
.into_iter()
.map(|x| {
let index = x
.index
.and_then(|ix| self.config.index.dependencies.get(&ix))
.cloned()
.unwrap_or_else(|| self.id.clone());
Dep {
index,
name: x.name,
req: x.req,
}
})
.collect::<Vec<_>>();
let location = if let Some(url) = &self.config.index.registry {
if let Some(eloc) = entry.location {
Ok(eloc)
} else {
Ok(DirectRes::Registry {
registry: url.clone(),
name: name.clone(),
version: entry.version.clone(),
})
}
} else {
entry.location.ok_or_else(|| {
format_err!(
"no location for index entry {} of package {}",
lix + 1,
name
)
})
}?;
let entry: ResolvedEntry = IndexEntry {
name: entry.name,
version: entry.version,
dependencies,
yanked: entry.yanked,
location,
};
res.insert(entry.version.clone(), entry);
}
Ok(res)
}
pub fn packages(&self) -> impl Iterator<Item = String> {
let root_path = self.path.path().to_path_buf();
let git_path = root_path.join(".git");
WalkDir::new(self.path.path())
.min_depth(2)
.max_depth(3)
.into_iter()
.filter_entry(move |x| x.path().parent().unwrap() != git_path)
.filter_map(|x| x.ok())
.map(move |x| {
let stripped = x.path().strip_prefix(&root_path).unwrap();
stripped.to_string_lossy().replace("\\", "/").to_string()
})
}
pub fn depends(&self) -> impl Iterator<Item = &IndexRes> {
self.config.index.dependencies.iter().map(|x| x.1)
}
pub fn registry(&self) -> Option<&Registry> {
self.config.index.registry.as_ref()
}
}