use std::path::{Path, PathBuf};
use std::fs::{self, File};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::process::Command;
use std::ops::{Drop, Deref};
use std::fmt;
use semver::Version;
use rt_result::RtResult;
use dirs::{rusty_tags_cache_dir, rusty_tags_locks_dir};
use config::Config;
use tempfile::NamedTempFile;
#[derive(Debug)]
pub struct DepTree {
roots: Vec<SourceId>,
sources: Vec<Option<Source>>,
dependencies: Vec<Option<Vec<SourceId>>>,
parents: Vec<Option<Vec<SourceId>>>
}
impl DepTree {
pub fn new() -> DepTree {
DepTree {
roots: Vec::with_capacity(10),
sources: Vec::new(),
dependencies: Vec::new(),
parents: Vec::new()
}
}
pub fn reserve_num_sources(&mut self, num: usize) {
self.sources.reserve(num);
self.dependencies.reserve(num);
self.parents.reserve(num);
}
pub fn roots(&self) -> Sources {
Sources::new(&self.sources, Some(&self.roots))
}
pub fn dependencies(&self, source: &Source) -> Sources {
Sources::new(&self.sources, self.dependencies_slice(source))
}
pub fn all_sources<'a>(&'a self) -> Box<Iterator<Item=&Source> + 'a> {
Box::new(self.sources
.iter()
.filter_map(|s| s.as_ref()))
}
pub fn ancestors<'a>(&'a self, sources: &[&Source]) -> Vec<&'a Source> {
let mut ancestor_srcs = Vec::with_capacity(50000);
let mut dep_graph = Vec::with_capacity(100);
for src in sources {
self.ancestors_internal(src, &mut ancestor_srcs, &mut dep_graph);
}
unique_sources(&mut ancestor_srcs);
ancestor_srcs
}
pub fn new_source(&mut self) -> SourceId {
let id = self.sources.len();
self.sources.push(None);
self.dependencies.push(None);
self.parents.push(None);
SourceId { id }
}
pub fn set_roots(&mut self, ids: Vec<SourceId>) {
self.roots = ids;
}
pub fn set_source(&mut self, src: Source, dependencies: Vec<SourceId>) {
let src_id = src.id;
self.sources[*src_id] = Some(src);
if dependencies.is_empty() {
return;
}
for dep in &dependencies {
let dep_id: usize = **dep;
if self.parents[dep_id].is_none() {
self.parents[dep_id] = Some(Vec::with_capacity(10));
}
if let Some(ref mut parents) = self.parents[dep_id] {
parents.push(src_id);
}
}
self.dependencies[*src_id] = Some(dependencies);
}
fn dependencies_slice(&self, source: &Source) -> Option<&[SourceId]> {
self.dependencies[*source.id].as_ref().map(Vec::as_slice)
}
fn ancestors_internal<'a>(&'a self, source: &Source,
ancestor_srcs: &mut Vec<&'a Source>,
dep_graph: &mut Vec<SourceId>) {
dep_graph.push(source.id);
if let Some(ref parents) = self.parents[*source.id] {
for p_id in parents {
if dep_graph.iter().find(|id| *id == p_id) != None {
continue;
}
if let Some(ref p) = self.sources[**p_id] {
ancestor_srcs.push(p);
self.ancestors_internal(p, ancestor_srcs, dep_graph);
}
}
}
dep_graph.pop();
}
}
#[derive(Clone)]
pub struct Sources<'a> {
sources: &'a [Option<Source>],
source_ids: Option<&'a [SourceId]>,
idx: usize
}
impl<'a> Sources<'a> {
fn new(sources: &'a [Option<Source>], source_ids: Option<&'a [SourceId]>) -> Sources<'a> {
Sources { sources, source_ids, idx: 0 }
}
}
impl<'a> Iterator for Sources<'a> {
type Item = &'a Source;
fn next(&mut self) -> Option<Self::Item> {
if let Some(source_ids) = self.source_ids {
if self.idx >= source_ids.len() {
None
} else {
let id = source_ids[self.idx];
let src = self.sources[*id].as_ref();
self.idx += 1;
src
}
} else {
None
}
}
}
pub enum SourceLock {
Locked {
path: PathBuf,
file: File
},
AlreadyLocked {
path: PathBuf
}
}
impl SourceLock {
fn new(source: &Source, tags_spec: &TagsSpec) -> RtResult<SourceLock> {
let file_name = format!("{}-{}.{}", source.name, source.hash, tags_spec.file_extension());
let lock_file = rusty_tags_locks_dir()?.join(file_name);
if lock_file.is_file() {
Ok(SourceLock::AlreadyLocked { path: lock_file })
} else {
Ok(SourceLock::Locked {
file: File::create(&lock_file)?,
path: lock_file
})
}
}
}
impl Drop for SourceLock {
fn drop(&mut self) {
match *self {
SourceLock::Locked { ref path, .. } => {
if path.is_file() {
let _ = fs::remove_file(&path);
}
}
SourceLock::AlreadyLocked { .. } => {}
}
}
}
#[derive(Debug)]
pub struct Source {
pub id: SourceId,
pub name: String,
pub version: Version,
pub dir: PathBuf,
pub hash: String,
pub is_root: bool,
pub tags_file: PathBuf,
pub cached_tags_file: PathBuf,
}
impl Source {
pub fn new(id: SourceId, source_version: &SourceVersion, dir: &Path, is_root: bool, config: &Config) -> RtResult<Source> {
let tags_dir = find_dir_upwards_containing("Cargo.toml", dir).unwrap_or(dir.to_path_buf());
let tags_file = tags_dir.join(config.tags_spec.file_name());
let hash = source_hash(dir);
let cached_tags_file = {
let cache_dir = rusty_tags_cache_dir()?;
let file_name = format!("{}-{}.{}", source_version.name, hash, config.tags_spec.file_extension());
cache_dir.join(&file_name)
};
Ok(Source {
id: id,
name: source_version.name.to_owned(),
version: source_version.version.clone(),
dir: dir.to_owned(),
hash: hash,
is_root: is_root,
tags_file: tags_file,
cached_tags_file: cached_tags_file
})
}
pub fn needs_tags_update(&self, config: &Config) -> bool {
if config.force_recreate {
return true;
}
if self.is_root {
return true;
}
! self.cached_tags_file.is_file() || ! self.tags_file.is_file()
}
pub fn recreate_status(&self, config: &Config) -> String {
if config.force_recreate {
format!("Forced recreating of tags for {}", self.source_version())
} else if self.is_root {
format!("Recreating tags for cargo project root {}", self.source_version())
} else if ! self.cached_tags_file.is_file() {
format!("Recreating tags for {}, because of missing cache file at '{:?}'",
self.source_version(), self.cached_tags_file)
} else if ! self.tags_file.is_file() {
format!("Recreating tags for {}, because of missing tags file at '{:?}'",
self.source_version(), self.tags_file)
} else {
format!("Recreating tags for {}, because one of its dependencies was updated",
self.source_version())
}
}
pub fn lock(&self, tags_spec: &TagsSpec) -> RtResult<SourceLock> {
SourceLock::new(self, tags_spec)
}
fn source_version(&self) -> String {
format!("({}, {})", self.name, self.version)
}
}
pub struct SourceWithTmpTags<'a> {
pub source: &'a Source,
pub tags_file: NamedTempFile,
}
impl<'a> SourceWithTmpTags<'a> {
pub fn new(source: &'a Source) -> RtResult<SourceWithTmpTags<'a>> {
Ok(SourceWithTmpTags {
source,
tags_file: NamedTempFile::new()?
})
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Debug)]
pub struct SourceId {
id: usize
}
impl Deref for SourceId {
type Target = usize;
fn deref(&self) -> &Self::Target {
&self.id
}
}
#[derive(PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
pub struct SourceVersion<'a> {
pub name: &'a str,
pub version: Version
}
impl<'a> SourceVersion<'a> {
pub fn new(name: &'a str, version: Version) -> SourceVersion<'a> {
SourceVersion { name, version }
}
pub fn parse_from_id(id: &'a str) -> RtResult<SourceVersion<'a>> {
let mut split = id.split(' ');
let name = split.next();
if name == None {
return Err(format!("Couldn't extract name from id: '{}'", id).into());
}
let name = name.unwrap();
let version = split.next();
if version == None {
return Err(format!("Couldn't extract version from id: '{}'", id).into());
}
let version = version.unwrap();
Ok(SourceVersion::new(name, Version::parse(version)?))
}
}
impl<'a> fmt::Debug for SourceVersion<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.name, self.version)
}
}
impl<'a> fmt::Display for SourceVersion<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.name, self.version)
}
}
fn source_hash(source_dir: &Path) -> String {
let mut hasher = DefaultHasher::new();
source_dir.hash(&mut hasher);
hasher.finish().to_string()
}
arg_enum! {
#[derive(Eq, PartialEq, Debug)]
pub enum TagsKind {
Vi,
Emacs
}
}
type ExeName = String;
#[derive(Debug)]
pub enum TagsExe {
ExuberantCtags(ExeName),
UniversalCtags(ExeName)
}
pub struct TagsSpec {
pub kind: TagsKind,
exe: TagsExe,
vi_tags: String,
emacs_tags: String,
ctags_options: String
}
impl TagsSpec {
pub fn new(kind: TagsKind, exe: TagsExe, vi_tags: String, emacs_tags: String, ctags_options: String) -> RtResult<TagsSpec> {
if vi_tags == emacs_tags {
return Err(format!("It's not supported to use the same tags name '{}' for vi and emacs!", vi_tags).into());
}
Ok(TagsSpec {
kind: kind,
exe: exe,
vi_tags: vi_tags,
emacs_tags: emacs_tags,
ctags_options: ctags_options
})
}
pub fn file_extension(&self) -> &'static str {
match self.kind {
TagsKind::Vi => "vi",
TagsKind::Emacs => "emacs"
}
}
pub fn file_name(&self) -> &str {
match self.kind {
TagsKind::Vi => &self.vi_tags,
TagsKind::Emacs => &self.emacs_tags
}
}
pub fn ctags_command(&self) -> Command {
match self.exe {
TagsExe::ExuberantCtags(ref exe_name) => {
let mut cmd = Command::new(&exe_name);
self.generic_ctags_options(&mut cmd);
cmd.arg("--languages=Rust")
.arg("--langdef=Rust")
.arg("--langmap=Rust:.rs")
.arg("--regex-Rust=/^[ \\t]*(#\\[[^\\]]\\][ \\t]*)*(pub[ \\t]+)?(extern[ \\t]+)?(\"[^\"]+\"[ \\t]+)?(unsafe[ \\t]+)?fn[ \\t]+([a-zA-Z0-9_]+)/\\6/f,functions,function definitions/")
.arg("--regex-Rust=/^[ \\t]*(pub[ \\t]+)?type[ \\t]+([a-zA-Z0-9_]+)/\\2/T,types,type definitions/")
.arg("--regex-Rust=/^[ \\t]*(pub[ \\t]+)?enum[ \\t]+([a-zA-Z0-9_]+)/\\2/g,enum,enumeration names/")
.arg("--regex-Rust=/^[ \\t]*(pub[ \\t]+)?struct[ \\t]+([a-zA-Z0-9_]+)/\\2/s,structure names/")
.arg("--regex-Rust=/^[ \\t]*(pub[ \\t]+)?mod[ \\t]+([a-zA-Z0-9_]+)\\s*\\{/\\2/m,modules,module names/")
.arg("--regex-Rust=/^[ \\t]*(pub[ \\t]+)?(static|const)[ \\t]+([a-zA-Z0-9_]+)/\\3/c,consts,static constants/")
.arg("--regex-Rust=/^[ \\t]*(pub[ \\t]+)?(unsafe[ \\t]+)?trait[ \\t]+([a-zA-Z0-9_]+)/\\3/t,traits,traits/")
.arg("--regex-Rust=/^[ \\t]*macro_rules![ \\t]+([a-zA-Z0-9_]+)/\\1/d,macros,macro definitions/");
cmd
}
TagsExe::UniversalCtags(ref exe_name) => {
let mut cmd = Command::new(&exe_name);
self.generic_ctags_options(&mut cmd);
cmd.arg("--languages=Rust");
cmd
}
}
}
fn generic_ctags_options(&self, cmd: &mut Command) {
match self.kind {
TagsKind::Vi => {}
TagsKind::Emacs => { cmd.arg("-e"); }
}
cmd.arg("--recurse");
if ! self.ctags_options.is_empty() {
cmd.arg(&self.ctags_options);
}
}
}
pub fn unique_sources(sources: &mut Vec<&Source>) {
sources.sort_unstable_by(|a, b| a.id.cmp(&b.id));
sources.dedup_by_key(|s| &s.id);
}
fn find_dir_upwards_containing(file_name: &str, start_dir: &Path) -> RtResult<PathBuf> {
let mut dir = start_dir.to_path_buf();
loop {
if let Ok(files) = fs::read_dir(&dir) {
for path in files.map(|r| r.map(|d| d.path())) {
match path {
Ok(ref path) if path.is_file() =>
match path.file_name() {
Some(name) if name.to_str() == Some(file_name) => return Ok(dir),
_ => continue
},
_ => continue
}
}
}
if ! dir.pop() {
return Err(format!("Couldn't find '{}' starting at directory '{}'!", file_name, start_dir.display()).into());
}
}
}