diff --git a/src/symbols/hook.rs b/src/symbols/hook.rs
new file mode 100644
index 0000000..30a8ed8
--- /dev/null
+++ b/src/symbols/hook.rs
@@ -0,0 +1,103 @@
+use std::error::Error;
+use std::fmt;
+
+use symbols::Symbol;
+
+pub struct Hook where A: Symbol, B: Symbol {
+ a: A,
+ b: B
+}
+
+impl Hook where A: Symbol, B: Symbol {
+ pub fn new(a: A, b: B) -> Self {
+ Hook { a: a, b: b }
+ }
+}
+
+impl Symbol for Hook where A: Symbol, B: Symbol {
+ fn target_reached(&self) -> Result> {
+ self.a.target_reached().and_then(|reached| if reached { self.b.target_reached() } else { Ok(reached) })
+ }
+
+ fn execute(&self) -> Result<(), Box> {
+ try!(self.a.execute());
+ self.b.execute()
+ }
+}
+
+impl fmt::Display for Hook where A: Symbol, B: Symbol {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{
+ write!(f, "Hook {} and then {}", self.a, self.b)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use std::error::Error;
+ use std::fmt;
+
+ use symbols::Symbol;
+ use symbols::hook::Hook;
+
+ struct ErrSymbol(String);
+ impl Symbol for ErrSymbol {
+ fn target_reached(&self) -> Result> { Err(self.0.clone().into()) }
+ fn execute(&self) -> Result<(), Box> { Err(self.0.clone().into()) }
+ }
+ impl fmt::Display for ErrSymbol { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{ write!(f, "") } }
+
+ struct OkSymbol(bool);
+ impl Symbol for OkSymbol {
+ fn target_reached(&self) -> Result> { Ok(self.0) }
+ fn execute(&self) -> Result<(), Box> { Ok(()) }
+ }
+ impl fmt::Display for OkSymbol { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{ write!(f, "") } }
+
+ #[test]
+ fn first_target_reached_fails() {
+ let res = Hook::new(ErrSymbol("first".into()), ErrSymbol("second".into())).target_reached();
+ assert_eq!(res.unwrap_err().description(), "first");
+ }
+
+ #[test]
+ fn first_target_not_reached() {
+ let res = Hook::new(OkSymbol(false), ErrSymbol("second".into())).target_reached();
+ assert_eq!(res.unwrap(), false);
+ }
+
+ #[test]
+ fn second_target_reached_fails() {
+ let res = Hook::new(OkSymbol(true), ErrSymbol("second".into())).target_reached();
+ assert_eq!(res.unwrap_err().description(), "second");
+ }
+
+ #[test]
+ fn second_target_not_reached() {
+ let res = Hook::new(OkSymbol(true), OkSymbol(false)).target_reached();
+ assert_eq!(res.unwrap(), false);
+ }
+
+ #[test]
+ fn everything_reached() {
+ let res = Hook::new(OkSymbol(true), OkSymbol(true)).target_reached();
+ assert_eq!(res.unwrap(), true);
+ }
+
+ #[test]
+ fn first_execute_fails() {
+ let res = Hook::new(ErrSymbol("first".into()), ErrSymbol("second".into())).execute();
+ assert_eq!(res.unwrap_err().description(), "first");
+ }
+
+ #[test]
+ fn second_execute_fails() {
+ let res = Hook::new(OkSymbol(true), ErrSymbol("second".into())).execute();
+ assert_eq!(res.unwrap_err().description(), "second");
+ }
+
+ #[test]
+ fn everything_executes() {
+ let res = Hook::new(OkSymbol(true), OkSymbol(true)).execute();
+ assert_eq!(res.unwrap(), ());
+ }
+}
diff --git a/src/symbols/mod.rs b/src/symbols/mod.rs
index bc58be4..0f5f66b 100644
--- a/src/symbols/mod.rs
+++ b/src/symbols/mod.rs
@@ -18,3 +18,4 @@ pub mod npm;
pub mod user;
pub mod systemd;
pub mod nginx;
+pub mod hook;