use std::fs::{canonicalize, read_dir, ReadDir};
use std::path::{Path, PathBuf};
use std::collections::VecDeque;
use std::os::unix::fs::PermissionsExt;
use std::sync::Arc;
use common::prelude::*;
use common::state::State;
use scripts::Script;
pub(in scripts) struct Collector {
dirs: VecDeque<ReadDir>,
state: Arc<State>,
base: PathBuf,
recursive: bool,
}
impl Collector {
pub(in scripts) fn new<P: AsRef<Path>>(
base: P,
state: Arc<State>,
recursive: bool,
) -> Result<Self> {
let mut dirs = VecDeque::new();
dirs.push_front(read_dir(&base)?);
Ok(Collector {
dirs: dirs,
state: state,
base: base.as_ref().to_path_buf(),
recursive: recursive,
})
}
fn collect_file(&mut self, e: PathBuf) -> Result<Option<Arc<Script>>> {
if e.is_dir() {
if self.recursive {
self.dirs.push_back(read_dir(&e)?);
}
return Ok(None);
}
let mode = e.metadata()?.permissions().mode();
if !((mode & 0o111) != 0 && (mode & 0o444) != 0) {
return Ok(None);
}
let name = match e.strip_prefix(&self.base) {
Ok(stripped) => stripped,
Err(_) => &e,
}.to_str()
.unwrap()
.to_string();
let exec = canonicalize(&e)?.to_str().unwrap().into();
Ok(Some(Arc::new(Script::load(name, exec, &self.state)?)))
}
}
impl Iterator for Collector {
type Item = Result<Arc<Script>>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let entry = if let Some(iter) = self.dirs.get_mut(0) {
iter.next()
} else {
return None;
};
match entry {
Some(Ok(entry)) => {
match self.collect_file(entry.path()) {
Ok(result) => {
if let Some(script) = result {
return Some(Ok(script));
}
}
Err(err) => {
return Some(Err(err));
}
}
}
Some(Err(err)) => {
return Some(Err(err.into()));
}
None => {
let _ = self.dirs.pop_front();
}
}
}
}
}
#[cfg(test)]
mod tests {
use std::os::unix::fs::OpenOptionsExt;
use std::fs;
use common::prelude::*;
use scripts::test_utils::*;
use super::Collector;
fn assert_collected(
env: &TestEnv,
recurse: bool,
expected: &[&str],
) -> Result<()> {
let mut found = 0;
let c = Collector::new(&env.scripts_dir(), env.state(), recurse)?;
for script in c {
found += 1;
let script = script?;
if !expected.contains(&script.name()) {
panic!("Unexpected script collected: {}", script.name());
}
}
assert_eq!(found, expected.len());
Ok(())
}
#[test]
fn test_scripts_collection_collects_all_the_valid_scripts() {
test_wrapper(|env| {
env.create_script("first.sh", &[])?;
env.create_script("second.sh", &[])?;
fs::OpenOptions::new()
.create(true)
.write(true)
.mode(0o644)
.open(env.scripts_dir().join("third.sh"))?;
let dir = env.scripts_dir().join("subdir");
fs::create_dir(&dir)?;
env.create_script_into(&dir, "fourth.sh", &[])?;
assert_collected(&env, false, &["first.sh", "second.sh"])?;
assert_collected(
&env,
true,
&["first.sh", "second.sh", "subdir/fourth.sh"],
)?;
Ok(())
});
}
#[test]
fn test_scripts_collection_with_invalid_scripts_fails() {
test_wrapper(|env| {
env.create_script(
"valid.sh",
&[
r#"#!/bin/bash"#,
r#"## Fisher-Testing: {}"#,
r#"echo "I'm valid!""#,
],
)?;
assert_collected(&env, false, &["valid.sh"])?;
env.create_script(
"invalid.sh",
&[
r#"#!/bin/bash"#,
r#"## Fisher-InvalidProviderDoNotReallyCreateThis: {}"#,
r#"echo "I'm not valid :(""#,
],
)?;
assert_collected(&env, false, &["valid.sh", "invalid.sh"])
.err()
.expect("The collection should return an error");
Ok(())
})
}
}