use Error;
use std::path::{Path, PathBuf};
use std::io::prelude::*;
use std::{fs, io, fmt, str};
use toml;
use git2;
#[derive(Debug)]
pub struct Package
{
pub name: String,
pub path: PathBuf,
pub remote_path: PackagePath,
pub version: String,
pub revision: String,
}
#[derive(Debug)]
pub enum PackageError
{
AlreadyExists,
NotInstalled,
NoMetadata,
}
impl Package
{
pub fn open(remote_path: PackagePath, path: &Path) -> Result<Self, Error> {
let metadata_file = path.join("package.toml");
if !metadata_file.exists() {
return Err(Error::Package(PackageError::NoMetadata));
}
let file = fs::File::open(metadata_file)?;
let metadata_bytes: Result<Vec<u8>, _> = file.bytes().collect();
let metadata_text = String::from_utf8(metadata_bytes?).unwrap();
let metadata: toml::Value = metadata_text.parse().unwrap();
let package_name = metadata.lookup("package.name").unwrap().as_str().unwrap();
let package_version = metadata.lookup("package.version").unwrap().as_str().unwrap();
let repository = git2::Repository::open(path).unwrap();
let revision = format!("{}", repository.head().unwrap().target().unwrap());
Ok(Package {
name: package_name.to_owned(),
path: path.to_owned(),
remote_path: remote_path,
version: package_version.to_owned(),
revision: revision,
})
}
pub fn fetch(&mut self) -> Result<bool, git2::Error> {
let repository = self.open_repository()?;
let previous_ref = repository.find_reference("refs/remotes/origin/master").unwrap();
let remote_name = repository.remotes().unwrap().iter().next().unwrap().unwrap().to_owned();
let mut remote = repository.find_remote(&remote_name)?;
remote.connect(git2::Direction::Fetch)?;
remote.download(&[], None)?;
remote.disconnect();
remote.update_tips(None, true,
git2::AutotagOption::Unspecified, None)?;
let new_ref = repository.find_reference("refs/remotes/origin/master").unwrap();
Ok(previous_ref != new_ref)
}
pub fn update(&mut self) -> Result<bool, git2::Error> {
if self.fetch()? {
let repository = self.open_repository()?;
repository.set_head("refs/remotes/origin/master")?;
repository.checkout_head(Some(git2::build::CheckoutBuilder::new().force()))?;
self.revision = format!("{}", repository.head().unwrap().target().unwrap());
Ok(true)
} else {
Ok(false)
}
}
pub fn open_repository(&self) -> Result<git2::Repository, git2::Error> {
git2::Repository::open(&self.path)
}
pub fn destroy(self) -> Result<(), io::Error> {
fs::remove_dir_all(self.path)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum PackagePath
{
GitHub {
organisation: String,
repository: String,
},
}
impl PackagePath
{
pub fn url(&self) -> String {
match *self {
PackagePath::GitHub { ref organisation, ref repository } => {
format!("https://github.com/{}/{}.git", organisation, repository)
}
}
}
pub fn directory(&self) -> String {
match *self {
PackagePath::GitHub { ref organisation, ref repository } => {
format!("{}/{}", organisation, repository)
},
}
}
}
impl fmt::Display for PackageError
{
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self {
PackageError::AlreadyExists => write!(fmt, "the package is already installed"),
PackageError::NoMetadata => write!(fmt, "the package does not contain a package.toml file"),
PackageError::NotInstalled => write!(fmt, "the package is not installed"),
}
}
}
impl fmt::Display for PackagePath
{
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", self.directory())
}
}
impl str::FromStr for PackagePath
{
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<_> = s.split("/").collect();
if parts.len() == 2 {
Ok(PackagePath::GitHub {
organisation: parts[0].to_owned(),
repository: parts[1].to_owned(),
})
} else {
Err(())
}
}
}