use std::fmt::Arguments;
use std::path::{Path, PathBuf};
use glob::Pattern;
use walkdir::{DirEntry, WalkDir, WalkDirIterator};
use cache::Cache;
use config::Config;
use printer::Printer;
use query::Query;
use remote::Remote;
use repository::Repository;
use vcs::{self, Vcs};
pub struct Workspace {
cache: Cache,
config: Config,
printer: Printer,
}
impl Workspace {
pub fn new() -> ::Result<Self> {
let config = Config::new(None)?;
let cache = Cache::new(None)?;
Ok(Workspace {
cache: cache,
config: config,
printer: Printer::default(),
})
}
pub fn set_root_dir<P: Into<PathBuf>>(&mut self, root: P) {
self.config.root_dir = root.into();
}
pub fn verbose_output(mut self, verbose: bool) -> Self {
self.printer.verbose = verbose;
self
}
pub fn print(&self, args: Arguments) {
self.printer.print(args)
}
pub fn repositories(&self) -> Option<&[Repository]> {
self.cache
.get_opt()
.map(|cache| cache.repositories.as_slice())
}
pub fn config(&self) -> &Config {
&self.config
}
pub fn import_repositories<P: AsRef<Path>>(&mut self, root: P, depth: Option<usize>) -> ::Result<()> {
for path in collect_repositories(root, depth, &self.config.exclude_patterns) {
if let Some(repo) = self.new_repository_from_path(&path)? {
self.add_repository(repo);
}
}
Ok(())
}
pub fn add_repository(&mut self, repo: Repository) {
let ref mut repos = self.cache.get_mut().repositories;
if let Some(r) = repos.iter_mut().find(|r| r.is_same_local(&repo)) {
self.printer.print(format_args!(
"Overwrite existed entry: {}\n",
repo.path_string()
));
*r = repo;
return;
}
self.printer
.print(format_args!("Add new entry: {}\n", repo.path_string()));
repos.push(repo);
}
pub fn add_repository_if_exists(&mut self, path: &Path) -> ::Result<()> {
let repo = match self.new_repository_from_path(path)? {
Some(repo) => repo,
None => {
self.printer.print(format_args!(
"Ignored: {} is not a repository\n",
path.display()
));
return Ok(());
}
};
self.add_repository(repo);
Ok(())
}
pub fn drop_invalid_repositories(&mut self) {
let mut new_repo = Vec::new();
for repo in &self.cache.get_mut().repositories {
let repo = match repo.clone().refresh() {
Some(r) => r,
None => continue,
};
if self.config
.exclude_patterns
.iter()
.all(|ex| !ex.matches(&repo.path_string()))
{
new_repo.push(repo.clone());
} else {
self.printer
.print(format_args!("Dropped: {}\n", repo.path_string()));
}
}
self.cache.get_mut().repositories = new_repo;
}
pub fn sort_repositories(&mut self) {
self.cache
.get_mut()
.repositories
.sort_by(|a, b| a.name().cmp(b.name()));
}
pub fn save_cache(&mut self) -> ::Result<()> {
self.cache.dump()?;
Ok(())
}
pub fn resolve_query(&self, query: &Query) -> ::Result<PathBuf> {
let root = &self.config.root_dir;
let host = query.host().unwrap_or_else(|| &self.config.host);
let path = root.join(host).join(query.path());
Ok(path)
}
pub fn default_host(&self) -> &str {
&self.config.host
}
pub fn for_each_repo<F: Fn(&Repository) -> ::Result<()>>(&self, f: F) -> ::Result<()> {
let repos = self.repositories()
.ok_or("The cache has not initialized yet")?;
for repo in repos {
f(&repo)?;
}
Ok(())
}
fn new_repository_from_path(&self, path: &Path) -> ::Result<Option<Repository>> {
let vcs = match vcs::detect_from_path(&path) {
Some(vcs) => vcs,
None => return Ok(None),
};
let remote = match vcs.get_remote_url(&path)? {
Some(remote) => remote,
None => return Ok(None),
};
Repository::new(path, vcs, Remote::new(remote)).map(Some)
}
pub fn create_empty_repository(&mut self, path: &Path, vcs: Vcs) -> ::Result<()> {
self.printer.print(format_args!(
"Creating an empty repository at \"{}\" (VCS: {:?})\n",
path.display(),
vcs
));
if vcs::detect_from_path(path).is_some() {
self.printer.print(format_args!(
"[info] The repository {} has already existed.\n",
path.display()
));
return Ok(());
}
vcs.do_init(path)?;
let repo = Repository::new(path, vcs, None)?;
self.add_repository(repo);
Ok(())
}
pub fn clone_repository(&mut self, remote: Remote, dest: &Path, vcs: Vcs) -> ::Result<()> {
self.printer.print(format_args!(
"[info] Clone from {} into {} by using {:?}\n",
remote.url(),
dest.display(),
vcs,
));
if vcs::detect_from_path(&dest).is_some() {
self.printer.print(format_args!(
"The repository {} has already existed.\n",
dest.display()
));
return Ok(());
}
vcs.do_clone(&dest, &remote.url(), &[] as &[String])?;
let repo = Repository::new(dest, vcs, remote)?;
self.add_repository(repo);
Ok(())
}
}
fn collect_repositories<P>(root: P, depth: Option<usize>, excludes: &[Pattern]) -> Vec<PathBuf>
where
P: AsRef<Path>,
{
let filter = {
let root = root.as_ref();
move |entry: &DirEntry| {
if entry.path() == root {
return true;
}
!entry
.path()
.parent()
.map(|path| vcs::detect_from_path(&path).is_some())
.unwrap_or(false)
&& entry
.path()
.canonicalize()
.ok()
.map(|path| {
let path = path.to_str().unwrap().trim_left_matches(r"\\?\");
excludes.iter().all(|ex| !ex.matches(path))
})
.unwrap_or(false)
}
};
let mut walkdir = WalkDir::new(root.as_ref()).follow_links(true);
if let Some(depth) = depth {
walkdir = walkdir.max_depth(depth);
}
walkdir
.into_iter()
.filter_entry(filter)
.filter_map(Result::ok)
.filter(|entry| vcs::detect_from_path(entry.path()).is_some())
.map(|entry| entry.path().into())
.collect()
}