diff --git a/Classes/CommandArgumentFilter.php b/Classes/CommandArgumentFilter.php
new file mode 100644
index 0000000..1ffa45e
--- /dev/null
+++ b/Classes/CommandArgumentFilter.php
@@ -0,0 +1,343 @@
+<?php
+/**
+ * Filters an array and extracts and validates command line arguments
+ *
+ * @author Patrick Forget <patforg at webtrendi.com>
+ */
+
+namespace Clapp;
+
+/**
+ * Filters an array and extracts and validates command line arguments
+ *
+ * @author Patrick Forget <patforg at webtrendi.com>
+ */
+class CommandArgumentFilter
+{
+    /**
+     * Command line arguments
+     * @var array
+     */
+    private $arguments = array();
+    
+    /**
+     * Definition of allowed parameters
+     * @var \Clapp\CommandLineArgumentDefinition
+     */
+    private $definitions = null;
+
+    /**
+     * Flag if arguments have been parsed in to params
+     * @var boolean
+     */
+    private $parsed = false;
+
+    /**
+     * Parsed params
+     * @var array
+     */
+    private $params = array();
+
+    /**
+     * Trailing values
+     * @var string
+     */
+    private $trailingValues = "";
+    
+    /**
+     * program name
+     * @var string
+     */
+    private $programName = "";
+
+    /**
+     * class constructor
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param \Clapp\CommandLineDefinition $definitions contains list of allowed parameters
+     * @param array $args list of arguments to filter.
+     */
+    public function __construct(\Clapp\CommandLineArgumentDefinition $definitions, $args)
+    {
+        if (is_array($args)) {
+            $this->arguments = $args;
+        } //if
+
+        $this->definitions = $definitions;
+    } // __construct()
+
+    /**
+     * returns parameter matching provided name
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string name of the paramter to retreive
+     *
+     * @return mixed if param the param appears only once the method will
+     *     return 1 if the parameter doesn't take a value. The specified value
+     *     for that param will returned if it does take value.
+     *
+     *     If many occurence of the param appear the number of occurences will
+     *     be returned for params that do not take values. An array of values
+     *     will be returned for the parameters that do take values.
+     *
+     *     If the parameter is not present null if it takes a value and false if
+     *     it's not present and doesn't allow values
+     */
+    public function getParam($name)
+    {
+        if (!$this->parsed) {
+            $this->parseParams();
+        } //if
+
+        $longName = strlen($name) === 1 ? $this->definitions->getLongName($name) : $name;
+        if (isset($this->params[$longName])) {
+            return $this->params[$longName];
+        } else {
+            if ($this->definitions->allowsValue($longName)) {
+                return null;
+            } else {
+                return false;
+            } //if
+        } //if
+
+    } // getParam()
+
+    /**
+     * retreive the program name
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    public function getProgramName()
+    {
+        if (!$this->parsed) {
+            $this->parseParams();
+        } //if
+
+        return $this->programName;
+    } // getProgramName()
+    
+    /**
+     * retreive the trailing values
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    public function getTrailingValues()
+    {
+        if (!$this->parsed) {
+            $this->parseParams();
+        } //if
+
+        return $this->trailingValues;
+    } // getTrailingValues()
+
+    /**
+     * extracts params from arguments
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    protected function parseParams()
+    {
+
+        $argumentStack = $this->arguments;
+
+        $expectingValue = false;
+        $currentLongName = null;
+        $currentValue = null;
+        $trailingValues = "";
+        $endOfDashedArguments = false;
+        $addParam = false;
+        $argumentsLeft = sizeof($argumentStack);
+        $multiShortParams = array();
+
+        $this->programName = array_shift($argumentStack); // remove first argument which is the program name
+
+        while ($currentArgument = array_shift($argumentStack)) {
+            $argumentsLeft--;
+            $currentArgumentLength = strlen($currentArgument);
+
+            // arguments that don't start with a dash
+            if (substr($currentArgument, 0, 1) !== '-') {
+                if ($expectingValue) {
+                    $currentValue = $currentArgument;
+                    $addParam = true;
+                } else {
+                    $trailingValues .= " ". $currentArgument;
+                    $endOfDashedArguments = true;
+                } //if
+
+            // double dash detected
+            } elseif (substr($currentArgument, 1, 1)  === '-') {
+                if ($expectingValue) {
+                    throw new \UnexpectedValueException("Parameter {$currentLongName} expects a values");
+                } //if
+
+                /* stop parsing arguments if double dash
+                   only param is encountered  */
+                if ($currentArgumentLength == 2) {
+                    if ($trailingValues !== "") {
+                        throw new \UnexpectedValueException("Trailing values must appear after double dash");
+                    } //if
+
+                    $trailingValues = " ". implode(" ", $argumentStack);
+                    $argumentStack = array();
+                    $endOfDashedArguments = true;
+                    break;
+                } //if
+
+                $longNameParts = explode("=", substr($currentArgument, 2), 2);
+
+                $currentLongName = $longNameParts[0];
+
+                if (sizeof($longNameParts) > 1) {
+                    $currentValue = $longNameParts[1];
+                    $addParam = true;
+                } elseif ($this->definitions->allowsValue($currentLongName)) {
+                    $expectingValue = true;
+                } else {
+                    $addParam = true;
+                } //if
+
+            // single dash
+            } else {
+                if ($expectingValue) {
+                    throw new \UnexpectedValueException("Parameter {$currentLongName} expects a values");
+                } //if
+
+                $shortNameParts = explode("=", substr($currentArgument, 1), 2);
+
+                $shortName = $shortNameParts[0];
+
+                if (strlen($shortName) <= 1) {
+                    $currentLongName = $this->definitions->getLongName($shortName);
+
+                    if ($currentLongName === null) {
+                        throw new \InvalidArgumentException("Unable to find name with ".
+                            "provided parameter ({$shortName})");
+                    } //if
+
+                    if (sizeof($shortNameParts) > 1) {
+                        $currentValue = $shortNameParts[1];
+                        $addParam = true;
+                    } elseif ($this->definitions->allowsValue($currentLongName)) {
+                        $expectingValue = true;
+                    } else {
+                        $addParam = true;
+                    } //if
+
+                } else {
+                    $multiShortParams = str_split($shortName);
+
+                    /* process the last one (which is the only one that can have a value) */
+                    $lastParam = array_pop($multiShortParams);
+                    $currentLongName = $this->definitions->getLongName($lastParam);
+                    if (sizeof($shortNameParts) > 1) {
+                        $currentValue = $shortNameParts[1];
+                        $addParam = true;
+                    } elseif ($this->definitions->allowsValue($lastParam)) {
+                        $expectingValue = true;
+                    } else {
+                        $addParam = true;
+                    } //if
+
+                } //if
+                
+            } //if
+
+            if ($addParam) {
+                if ($endOfDashedArguments) {
+                    throw new \UnexpectedValueException("Unexpected argument after undashed values");
+                } //if
+
+                /* Not sure how this could happen */
+                // @codeCoverageIgnoreStart
+                if ($currentLongName === false || $currentLongName === null) {
+                    throw new \UnexpectedValueException("Missing argument name");
+                } //if
+                // @codeCoverageIgnoreEnd
+
+                if (!$this->definitions->paramExists($currentLongName)) {
+                    throw new \InvalidArgumentException("Invalid argument name");
+                } //if
+
+                $allowsMultiple = $this->definitions->allowsMultiple($currentLongName);
+                $allowsValue = $this->definitions->allowsValue($currentLongName);
+
+                if (isset($this->params[$currentLongName]) && !$allowsMultiple) {
+                    throw new \UnexpectedValueException("Multiple instace of parameter {$currentLongName} not allowed");
+                } //if
+
+                if ($allowsValue) {
+                    /* Missing values should always be detected before addParam is true */
+                    // @codeCoverageIgnoreStart
+                    if ($currentValue === null) {
+                        throw new \UnexpectedValueException("Parameter {$currentLongName} expects a values");
+                    } //if
+                    // @codeCoverageIgnoreEnd
+
+                } elseif ($currentValue !== null) {
+                    throw new \UnexpectedValueException("Parameter {$currentLongName} does not accept values");
+
+                } else {
+                    $currentValue = true;
+                } //if
+
+                if ($allowsMultiple) {
+                    if ($allowsValue) {
+                        if (!isset($this->params[$currentLongName])) {
+                            $this->params[$currentLongName] = array();
+                        } //if
+
+                        $this->params[$currentLongName][] = $currentValue;
+
+                    } else {
+                        if (!isset($this->params[$currentLongName])) {
+                            $this->params[$currentLongName] = 0;
+                        } //if
+
+                        $this->params[$currentLongName]++;
+
+                    } //if
+
+                } else {
+                    $this->params[$currentLongName] = $currentValue;
+                } //if
+
+                foreach ($multiShortParams as $shortName) {
+                    $argumentStack[] = "-{$shortName}";
+                    $argumentsLeft++;
+                } //foreach
+
+                /* reset stuff for next param */
+                $expectingValue = false;
+                $currentLongName = null;
+                $currentValue = null;
+                $addParam = false;
+                $multiShortParams = array();
+
+            } //if
+
+        } //while
+
+        if ($expectingValue !== false) {
+            throw new \UnexpectedValueException("Parameter {$currentLongName} expects a values");
+        } //if
+
+        /* Not sure how this could happen */
+        // @codeCoverageIgnoreStart
+        if ($currentLongName !== null ||
+            $addParam !== false ||
+            $currentValue !== null ||
+            sizeof($multiShortParams) !== 0) {
+            throw new \UnexpectedValueException("Unable to process some parameters");
+        } //if
+        // @codeCoverageIgnoreEnd
+
+        if ($trailingValues !== "") {
+            $this->trailingValues = substr($trailingValues, 1); // remove extra space at the begging
+        } //if
+
+        $this->parsed = true;
+    } // parseParams()
+}

diff --git a/Classes/CommandArgumentFilter.php b/Classes/CommandArgumentFilter.php
new file mode 100644
index 0000000..1ffa45e
--- /dev/null
+++ b/Classes/CommandArgumentFilter.php
@@ -0,0 +1,343 @@
+<?php
+/**
+ * Filters an array and extracts and validates command line arguments
+ *
+ * @author Patrick Forget <patforg at webtrendi.com>
+ */
+
+namespace Clapp;
+
+/**
+ * Filters an array and extracts and validates command line arguments
+ *
+ * @author Patrick Forget <patforg at webtrendi.com>
+ */
+class CommandArgumentFilter
+{
+    /**
+     * Command line arguments
+     * @var array
+     */
+    private $arguments = array();
+    
+    /**
+     * Definition of allowed parameters
+     * @var \Clapp\CommandLineArgumentDefinition
+     */
+    private $definitions = null;
+
+    /**
+     * Flag if arguments have been parsed in to params
+     * @var boolean
+     */
+    private $parsed = false;
+
+    /**
+     * Parsed params
+     * @var array
+     */
+    private $params = array();
+
+    /**
+     * Trailing values
+     * @var string
+     */
+    private $trailingValues = "";
+    
+    /**
+     * program name
+     * @var string
+     */
+    private $programName = "";
+
+    /**
+     * class constructor
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param \Clapp\CommandLineDefinition $definitions contains list of allowed parameters
+     * @param array $args list of arguments to filter.
+     */
+    public function __construct(\Clapp\CommandLineArgumentDefinition $definitions, $args)
+    {
+        if (is_array($args)) {
+            $this->arguments = $args;
+        } //if
+
+        $this->definitions = $definitions;
+    } // __construct()
+
+    /**
+     * returns parameter matching provided name
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string name of the paramter to retreive
+     *
+     * @return mixed if param the param appears only once the method will
+     *     return 1 if the parameter doesn't take a value. The specified value
+     *     for that param will returned if it does take value.
+     *
+     *     If many occurence of the param appear the number of occurences will
+     *     be returned for params that do not take values. An array of values
+     *     will be returned for the parameters that do take values.
+     *
+     *     If the parameter is not present null if it takes a value and false if
+     *     it's not present and doesn't allow values
+     */
+    public function getParam($name)
+    {
+        if (!$this->parsed) {
+            $this->parseParams();
+        } //if
+
+        $longName = strlen($name) === 1 ? $this->definitions->getLongName($name) : $name;
+        if (isset($this->params[$longName])) {
+            return $this->params[$longName];
+        } else {
+            if ($this->definitions->allowsValue($longName)) {
+                return null;
+            } else {
+                return false;
+            } //if
+        } //if
+
+    } // getParam()
+
+    /**
+     * retreive the program name
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    public function getProgramName()
+    {
+        if (!$this->parsed) {
+            $this->parseParams();
+        } //if
+
+        return $this->programName;
+    } // getProgramName()
+    
+    /**
+     * retreive the trailing values
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    public function getTrailingValues()
+    {
+        if (!$this->parsed) {
+            $this->parseParams();
+        } //if
+
+        return $this->trailingValues;
+    } // getTrailingValues()
+
+    /**
+     * extracts params from arguments
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    protected function parseParams()
+    {
+
+        $argumentStack = $this->arguments;
+
+        $expectingValue = false;
+        $currentLongName = null;
+        $currentValue = null;
+        $trailingValues = "";
+        $endOfDashedArguments = false;
+        $addParam = false;
+        $argumentsLeft = sizeof($argumentStack);
+        $multiShortParams = array();
+
+        $this->programName = array_shift($argumentStack); // remove first argument which is the program name
+
+        while ($currentArgument = array_shift($argumentStack)) {
+            $argumentsLeft--;
+            $currentArgumentLength = strlen($currentArgument);
+
+            // arguments that don't start with a dash
+            if (substr($currentArgument, 0, 1) !== '-') {
+                if ($expectingValue) {
+                    $currentValue = $currentArgument;
+                    $addParam = true;
+                } else {
+                    $trailingValues .= " ". $currentArgument;
+                    $endOfDashedArguments = true;
+                } //if
+
+            // double dash detected
+            } elseif (substr($currentArgument, 1, 1)  === '-') {
+                if ($expectingValue) {
+                    throw new \UnexpectedValueException("Parameter {$currentLongName} expects a values");
+                } //if
+
+                /* stop parsing arguments if double dash
+                   only param is encountered  */
+                if ($currentArgumentLength == 2) {
+                    if ($trailingValues !== "") {
+                        throw new \UnexpectedValueException("Trailing values must appear after double dash");
+                    } //if
+
+                    $trailingValues = " ". implode(" ", $argumentStack);
+                    $argumentStack = array();
+                    $endOfDashedArguments = true;
+                    break;
+                } //if
+
+                $longNameParts = explode("=", substr($currentArgument, 2), 2);
+
+                $currentLongName = $longNameParts[0];
+
+                if (sizeof($longNameParts) > 1) {
+                    $currentValue = $longNameParts[1];
+                    $addParam = true;
+                } elseif ($this->definitions->allowsValue($currentLongName)) {
+                    $expectingValue = true;
+                } else {
+                    $addParam = true;
+                } //if
+
+            // single dash
+            } else {
+                if ($expectingValue) {
+                    throw new \UnexpectedValueException("Parameter {$currentLongName} expects a values");
+                } //if
+
+                $shortNameParts = explode("=", substr($currentArgument, 1), 2);
+
+                $shortName = $shortNameParts[0];
+
+                if (strlen($shortName) <= 1) {
+                    $currentLongName = $this->definitions->getLongName($shortName);
+
+                    if ($currentLongName === null) {
+                        throw new \InvalidArgumentException("Unable to find name with ".
+                            "provided parameter ({$shortName})");
+                    } //if
+
+                    if (sizeof($shortNameParts) > 1) {
+                        $currentValue = $shortNameParts[1];
+                        $addParam = true;
+                    } elseif ($this->definitions->allowsValue($currentLongName)) {
+                        $expectingValue = true;
+                    } else {
+                        $addParam = true;
+                    } //if
+
+                } else {
+                    $multiShortParams = str_split($shortName);
+
+                    /* process the last one (which is the only one that can have a value) */
+                    $lastParam = array_pop($multiShortParams);
+                    $currentLongName = $this->definitions->getLongName($lastParam);
+                    if (sizeof($shortNameParts) > 1) {
+                        $currentValue = $shortNameParts[1];
+                        $addParam = true;
+                    } elseif ($this->definitions->allowsValue($lastParam)) {
+                        $expectingValue = true;
+                    } else {
+                        $addParam = true;
+                    } //if
+
+                } //if
+                
+            } //if
+
+            if ($addParam) {
+                if ($endOfDashedArguments) {
+                    throw new \UnexpectedValueException("Unexpected argument after undashed values");
+                } //if
+
+                /* Not sure how this could happen */
+                // @codeCoverageIgnoreStart
+                if ($currentLongName === false || $currentLongName === null) {
+                    throw new \UnexpectedValueException("Missing argument name");
+                } //if
+                // @codeCoverageIgnoreEnd
+
+                if (!$this->definitions->paramExists($currentLongName)) {
+                    throw new \InvalidArgumentException("Invalid argument name");
+                } //if
+
+                $allowsMultiple = $this->definitions->allowsMultiple($currentLongName);
+                $allowsValue = $this->definitions->allowsValue($currentLongName);
+
+                if (isset($this->params[$currentLongName]) && !$allowsMultiple) {
+                    throw new \UnexpectedValueException("Multiple instace of parameter {$currentLongName} not allowed");
+                } //if
+
+                if ($allowsValue) {
+                    /* Missing values should always be detected before addParam is true */
+                    // @codeCoverageIgnoreStart
+                    if ($currentValue === null) {
+                        throw new \UnexpectedValueException("Parameter {$currentLongName} expects a values");
+                    } //if
+                    // @codeCoverageIgnoreEnd
+
+                } elseif ($currentValue !== null) {
+                    throw new \UnexpectedValueException("Parameter {$currentLongName} does not accept values");
+
+                } else {
+                    $currentValue = true;
+                } //if
+
+                if ($allowsMultiple) {
+                    if ($allowsValue) {
+                        if (!isset($this->params[$currentLongName])) {
+                            $this->params[$currentLongName] = array();
+                        } //if
+
+                        $this->params[$currentLongName][] = $currentValue;
+
+                    } else {
+                        if (!isset($this->params[$currentLongName])) {
+                            $this->params[$currentLongName] = 0;
+                        } //if
+
+                        $this->params[$currentLongName]++;
+
+                    } //if
+
+                } else {
+                    $this->params[$currentLongName] = $currentValue;
+                } //if
+
+                foreach ($multiShortParams as $shortName) {
+                    $argumentStack[] = "-{$shortName}";
+                    $argumentsLeft++;
+                } //foreach
+
+                /* reset stuff for next param */
+                $expectingValue = false;
+                $currentLongName = null;
+                $currentValue = null;
+                $addParam = false;
+                $multiShortParams = array();
+
+            } //if
+
+        } //while
+
+        if ($expectingValue !== false) {
+            throw new \UnexpectedValueException("Parameter {$currentLongName} expects a values");
+        } //if
+
+        /* Not sure how this could happen */
+        // @codeCoverageIgnoreStart
+        if ($currentLongName !== null ||
+            $addParam !== false ||
+            $currentValue !== null ||
+            sizeof($multiShortParams) !== 0) {
+            throw new \UnexpectedValueException("Unable to process some parameters");
+        } //if
+        // @codeCoverageIgnoreEnd
+
+        if ($trailingValues !== "") {
+            $this->trailingValues = substr($trailingValues, 1); // remove extra space at the begging
+        } //if
+
+        $this->parsed = true;
+    } // parseParams()
+}
diff --git a/Classes/CommandLineArgumentDefinition.php b/Classes/CommandLineArgumentDefinition.php
new file mode 100644
index 0000000..2b2e8fa
--- /dev/null
+++ b/Classes/CommandLineArgumentDefinition.php
@@ -0,0 +1,389 @@
+<?php
+/**
+ * Defines list and formats of command line arguments
+ *
+ * @author Patrick Forget <patforg at webtrendi.com>
+ */
+
+namespace Clapp;
+
+/**
+ * Defines list and formats of command line arguments
+ *
+ * @author Patrick Forget <patforg at webtrendi.com>
+ */
+class CommandLineArgumentDefinition
+{
+
+    /**
+     * @var array
+     */
+    private $definitions = array();
+
+    /**
+     * long names as keys and array of properties as values
+     *
+     * properties are as follows
+     * * string "shortName" one letter char to the corresponding short name
+     * * boolean "isMultipleAllowed" true if mutliple instances of the param are allowed
+     * * mixed "parameterType" false if paramters are not alloweda value,
+     *       otherwise a string with the value "integer" or "string"
+     * * string "description" description of the parameter
+     * @var array
+     */
+    private $longNames = array();
+    
+    /**
+     * list of short names as keys and their long name equivalent as values
+     * @var array
+     */
+    private $shortNames = array();
+
+    /**
+     * Flag if arguments have been parsed in to params
+     * @var boolean
+     */
+    private $isParsed = false;
+
+    /**
+     * class constructor
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param array $definitions contains list of allowed parameters
+     *     the key is the long name of the parameter followed by a pipe (|)
+     *     then a single character specifying the short name.
+     *
+     *     If the parameter allows for arguments then an equal sign (=)
+     *     follows and then the type of paramter.
+     *
+     *     Allowed types are either i, int or integer for integer  types
+     *     and s, str or string for string types.
+     *
+     *     If a parameter can appear more than once the last character of
+     *     the key should be a plus character (+).
+     *
+     *     The value of the entry is the definition of what the paramter
+     *     does.
+     */
+    public function __construct($definitions)
+    {
+        if (is_array($definitions)) {
+            $this->definitions = $definitions;
+        } //if
+    } // __construct()
+
+    /**
+     * checks if parameter is allowed
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string $name either short or long name of the parameter to check
+     *
+     * @return boolean true if definition exisits, false otherwise
+     */
+    public function paramExists($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        if (strlen($name) == 1) {
+            return isset($this->shortNames[$name]);
+        } else {
+            return isset($this->longNames[$name]);
+        } //if
+    } // paramExists($name)
+    
+
+    /**
+     * checks if parameter allows a value if so what type
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string $name either short or long name of the parameter to check
+     *
+     * @return boolean|string false doesn't allow value, The value "string" or "integer" depending which type it allows
+     */
+    public function allowsValue($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        $longName = (strlen($name) == 1 ? ( isset($this->shortNames[$name]) ? $this->shortNames[$name] : '') : $name);
+
+        if (isset($this->longNames[$longName])) {
+            return $this->longNames[$longName]['parameterType'] !== false ? true : false;
+        } else {
+            return false;
+        } //if
+    } // allowsValue()
+    
+    /**
+     * returns the type of value allowed
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    public function getValueType($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        $longName = (strlen($name) == 1 ? ( isset($this->shortNames[$name]) ? $this->shortNames[$name] : '') : $name);
+
+        if (isset($this->longNames[$longName]['parameterType'])
+            && $this->longNames[$longName]['parameterType'] !== false) {
+            return $this->longNames[$longName]['parameterType'];
+        } else {
+            return '';
+        } //if
+    } // getValueType()
+    
+
+    /**
+     * checks if pamultiple instance of parameter are allowed
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string $name either short or long name of the parameter to check
+     *
+     * @return boolean false if parameter doesn't allow multiple values, true if it does
+     */
+    public function allowsMultiple($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        $longName = (strlen($name) == 1 ? ( isset($this->shortNames[$name]) ? $this->shortNames[$name] : '') : $name);
+
+        if (isset($this->longNames[$longName])) {
+            return $this->longNames[$longName]['isMultipleAllowed'];
+        } else {
+            return false;
+        } //if
+    } // allowsMultiple()
+
+    /**
+     * retreive short name of a parameter using its long name
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string $name long name of the parameter to check
+     *
+     * @return string character of the short name or null if it doesn't exist
+     */
+    public function getShortName($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        if (isset($this->longNames[$name])) {
+            return $this->longNames[$name]['shortName'];
+        } else {
+            return null;
+        } //if
+    } // getShortName($name)
+    
+    /**
+     * retreive long name of a parameter using its short name
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string $name short name of the parameter to check
+     *
+     * @return string long name or null if it doesn't exist
+     */
+    public function getLongName($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        if (isset($this->shortNames[$name])) {
+            return $this->shortNames[$name];
+        } else {
+            return null;
+        } //if
+    } // getLongName($name)
+
+    /**
+     * retreive description of a paramter
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string $name either short or long name of the parameter to check
+     *
+     * @return string description or null if it doesn't exist
+     */
+    public function getDescription($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        $longName = (strlen($name) == 1 ? ( isset($this->shortNames[$name]) ? $this->shortNames[$name] : '') : $name);
+
+        if (isset($this->longNames[$longName])) {
+            return $this->longNames[$longName]['description'];
+        } else {
+            return null;
+        } //if
+    } // getDescription()
+    
+    /**
+     * builds a usage definition based on definition of params
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    public function getUsage()
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        /* build list of argument names and calculate
+           the first column width so we can pad to 
+           align definitions */
+        $firstCol = array();
+        $longestDef = 0;
+        foreach (array_keys($this->longNames) as $longName) {
+            ob_start();
+            echo "--{$longName}|-{$this->getShortName($longName)}";
+
+            if ($this->allowsValue($longName)) {
+                echo "={$this->getValueType($longName)}";
+            } //if
+
+            if ($this->allowsMultiple($longName)) {
+                echo "+";
+            } //if
+
+            $defLength = ob_get_length();
+
+            $longestDef = max($longestDef, $defLength);
+
+            $firstCol[$longName] = ob_get_contents();
+            ob_end_clean();
+
+        } //foreach
+
+        $firstColMaxWidth = $longestDef + 4;
+
+        ob_start();
+
+        foreach ($firstCol as $longName => $def) {
+            $currentDefLength = strlen($def);
+
+            $padding = str_repeat(" ", $firstColMaxWidth - $currentDefLength);
+
+            echo "{$def}{$padding}{$this->getDescription($longName)}", PHP_EOL;
+        } //foreach
+
+        echo PHP_EOL;
+
+        $usage = ob_get_contents();
+        ob_end_clean();
+        
+        return $usage;
+
+    } // getUsage()
+    
+
+    /**
+     * parses the definitions
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    protected function parseDefinitions()
+    {
+        foreach ($this->definitions as $nameDef => $description) {
+            $nameParts = explode("|", $nameDef);
+
+            if (sizeof($nameParts) !== 2) {
+                throw new \UnexpectedValueException("Unexpected argument name definition expecting \"longName|char\"");
+            } //if
+
+            $longName = $nameParts[0];
+            $isMulti = false;
+            $parameterType = false;
+
+            $shortNameLength = strlen($nameParts[1]);
+
+            if ($shortNameLength == 1) {
+                $shortName = $nameParts[1];
+            } else {
+                $secondChar = substr($nameParts[1], 1, 1);
+
+                switch ($secondChar) {
+                    case '=':
+                        $shortNameParts = explode("=", $nameParts[1]);
+
+                        $shortName = $shortNameParts[0];
+                        $parameterTypeString = $shortNameParts[1];
+
+                        if (substr($parameterTypeString, -1) === '+') {
+                            $isMulti = true;
+                            $parameterTypeString = substr($parameterTypeString, 0, -1); // remove trailing +
+                        } //if
+
+                        switch ($parameterTypeString) {
+                            case 'i':
+                            case 'int':
+                            case 'integer':
+                                $parameterType = 'integer';
+                                break;
+                            case 's':
+                            case 'str':
+                            case 'string':
+                                $parameterType = 'string';
+                                break;
+                            default:
+                                throw new \UnexpectedValueException("Expecting parameter type".
+                                   " to be either integer or string");
+                                break;
+                        } //switch
+
+                        break;
+                    case '+':
+                        if ($shortNameLength > 2) {
+                            throw new \UnexpectedValueException("Multiple flag charachter (+)".
+                               " should be last character in definition");
+                        } //if
+
+                        $shortName = substr($nameParts[1], 0, 1);
+                        $isMulti = true;
+
+                        break;
+                    default:
+                        throw new \UnexpectedValueException("Expecting short name definition to be a single char");
+                        break;
+                } // switch
+
+            } //if
+
+            if (isset($this->longNames[$longName])) {
+                throw new \UnexpectedValueException("Cannot redefine long name {$longName}");
+            } //if
+
+            if (isset($this->shortNames[$shortName])) {
+                throw new \UnexpectedValueException("Cannot redefine short name {$shortName}");
+            } //if
+
+            $this->longNames[$longName] = array(
+                'shortName' => $shortName,
+                'isMultipleAllowed' => $isMulti,
+                'parameterType' => $parameterType,
+                'description' => $description
+            );
+
+            $this->shortNames[$shortName] = $longName;
+
+        } //foreach
+
+        $this->isParsed = true;
+    } // parseDefinitions()
+}

diff --git a/Classes/CommandArgumentFilter.php b/Classes/CommandArgumentFilter.php
new file mode 100644
index 0000000..1ffa45e
--- /dev/null
+++ b/Classes/CommandArgumentFilter.php
@@ -0,0 +1,343 @@
+<?php
+/**
+ * Filters an array and extracts and validates command line arguments
+ *
+ * @author Patrick Forget <patforg at webtrendi.com>
+ */
+
+namespace Clapp;
+
+/**
+ * Filters an array and extracts and validates command line arguments
+ *
+ * @author Patrick Forget <patforg at webtrendi.com>
+ */
+class CommandArgumentFilter
+{
+    /**
+     * Command line arguments
+     * @var array
+     */
+    private $arguments = array();
+    
+    /**
+     * Definition of allowed parameters
+     * @var \Clapp\CommandLineArgumentDefinition
+     */
+    private $definitions = null;
+
+    /**
+     * Flag if arguments have been parsed in to params
+     * @var boolean
+     */
+    private $parsed = false;
+
+    /**
+     * Parsed params
+     * @var array
+     */
+    private $params = array();
+
+    /**
+     * Trailing values
+     * @var string
+     */
+    private $trailingValues = "";
+    
+    /**
+     * program name
+     * @var string
+     */
+    private $programName = "";
+
+    /**
+     * class constructor
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param \Clapp\CommandLineDefinition $definitions contains list of allowed parameters
+     * @param array $args list of arguments to filter.
+     */
+    public function __construct(\Clapp\CommandLineArgumentDefinition $definitions, $args)
+    {
+        if (is_array($args)) {
+            $this->arguments = $args;
+        } //if
+
+        $this->definitions = $definitions;
+    } // __construct()
+
+    /**
+     * returns parameter matching provided name
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string name of the paramter to retreive
+     *
+     * @return mixed if param the param appears only once the method will
+     *     return 1 if the parameter doesn't take a value. The specified value
+     *     for that param will returned if it does take value.
+     *
+     *     If many occurence of the param appear the number of occurences will
+     *     be returned for params that do not take values. An array of values
+     *     will be returned for the parameters that do take values.
+     *
+     *     If the parameter is not present null if it takes a value and false if
+     *     it's not present and doesn't allow values
+     */
+    public function getParam($name)
+    {
+        if (!$this->parsed) {
+            $this->parseParams();
+        } //if
+
+        $longName = strlen($name) === 1 ? $this->definitions->getLongName($name) : $name;
+        if (isset($this->params[$longName])) {
+            return $this->params[$longName];
+        } else {
+            if ($this->definitions->allowsValue($longName)) {
+                return null;
+            } else {
+                return false;
+            } //if
+        } //if
+
+    } // getParam()
+
+    /**
+     * retreive the program name
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    public function getProgramName()
+    {
+        if (!$this->parsed) {
+            $this->parseParams();
+        } //if
+
+        return $this->programName;
+    } // getProgramName()
+    
+    /**
+     * retreive the trailing values
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    public function getTrailingValues()
+    {
+        if (!$this->parsed) {
+            $this->parseParams();
+        } //if
+
+        return $this->trailingValues;
+    } // getTrailingValues()
+
+    /**
+     * extracts params from arguments
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    protected function parseParams()
+    {
+
+        $argumentStack = $this->arguments;
+
+        $expectingValue = false;
+        $currentLongName = null;
+        $currentValue = null;
+        $trailingValues = "";
+        $endOfDashedArguments = false;
+        $addParam = false;
+        $argumentsLeft = sizeof($argumentStack);
+        $multiShortParams = array();
+
+        $this->programName = array_shift($argumentStack); // remove first argument which is the program name
+
+        while ($currentArgument = array_shift($argumentStack)) {
+            $argumentsLeft--;
+            $currentArgumentLength = strlen($currentArgument);
+
+            // arguments that don't start with a dash
+            if (substr($currentArgument, 0, 1) !== '-') {
+                if ($expectingValue) {
+                    $currentValue = $currentArgument;
+                    $addParam = true;
+                } else {
+                    $trailingValues .= " ". $currentArgument;
+                    $endOfDashedArguments = true;
+                } //if
+
+            // double dash detected
+            } elseif (substr($currentArgument, 1, 1)  === '-') {
+                if ($expectingValue) {
+                    throw new \UnexpectedValueException("Parameter {$currentLongName} expects a values");
+                } //if
+
+                /* stop parsing arguments if double dash
+                   only param is encountered  */
+                if ($currentArgumentLength == 2) {
+                    if ($trailingValues !== "") {
+                        throw new \UnexpectedValueException("Trailing values must appear after double dash");
+                    } //if
+
+                    $trailingValues = " ". implode(" ", $argumentStack);
+                    $argumentStack = array();
+                    $endOfDashedArguments = true;
+                    break;
+                } //if
+
+                $longNameParts = explode("=", substr($currentArgument, 2), 2);
+
+                $currentLongName = $longNameParts[0];
+
+                if (sizeof($longNameParts) > 1) {
+                    $currentValue = $longNameParts[1];
+                    $addParam = true;
+                } elseif ($this->definitions->allowsValue($currentLongName)) {
+                    $expectingValue = true;
+                } else {
+                    $addParam = true;
+                } //if
+
+            // single dash
+            } else {
+                if ($expectingValue) {
+                    throw new \UnexpectedValueException("Parameter {$currentLongName} expects a values");
+                } //if
+
+                $shortNameParts = explode("=", substr($currentArgument, 1), 2);
+
+                $shortName = $shortNameParts[0];
+
+                if (strlen($shortName) <= 1) {
+                    $currentLongName = $this->definitions->getLongName($shortName);
+
+                    if ($currentLongName === null) {
+                        throw new \InvalidArgumentException("Unable to find name with ".
+                            "provided parameter ({$shortName})");
+                    } //if
+
+                    if (sizeof($shortNameParts) > 1) {
+                        $currentValue = $shortNameParts[1];
+                        $addParam = true;
+                    } elseif ($this->definitions->allowsValue($currentLongName)) {
+                        $expectingValue = true;
+                    } else {
+                        $addParam = true;
+                    } //if
+
+                } else {
+                    $multiShortParams = str_split($shortName);
+
+                    /* process the last one (which is the only one that can have a value) */
+                    $lastParam = array_pop($multiShortParams);
+                    $currentLongName = $this->definitions->getLongName($lastParam);
+                    if (sizeof($shortNameParts) > 1) {
+                        $currentValue = $shortNameParts[1];
+                        $addParam = true;
+                    } elseif ($this->definitions->allowsValue($lastParam)) {
+                        $expectingValue = true;
+                    } else {
+                        $addParam = true;
+                    } //if
+
+                } //if
+                
+            } //if
+
+            if ($addParam) {
+                if ($endOfDashedArguments) {
+                    throw new \UnexpectedValueException("Unexpected argument after undashed values");
+                } //if
+
+                /* Not sure how this could happen */
+                // @codeCoverageIgnoreStart
+                if ($currentLongName === false || $currentLongName === null) {
+                    throw new \UnexpectedValueException("Missing argument name");
+                } //if
+                // @codeCoverageIgnoreEnd
+
+                if (!$this->definitions->paramExists($currentLongName)) {
+                    throw new \InvalidArgumentException("Invalid argument name");
+                } //if
+
+                $allowsMultiple = $this->definitions->allowsMultiple($currentLongName);
+                $allowsValue = $this->definitions->allowsValue($currentLongName);
+
+                if (isset($this->params[$currentLongName]) && !$allowsMultiple) {
+                    throw new \UnexpectedValueException("Multiple instace of parameter {$currentLongName} not allowed");
+                } //if
+
+                if ($allowsValue) {
+                    /* Missing values should always be detected before addParam is true */
+                    // @codeCoverageIgnoreStart
+                    if ($currentValue === null) {
+                        throw new \UnexpectedValueException("Parameter {$currentLongName} expects a values");
+                    } //if
+                    // @codeCoverageIgnoreEnd
+
+                } elseif ($currentValue !== null) {
+                    throw new \UnexpectedValueException("Parameter {$currentLongName} does not accept values");
+
+                } else {
+                    $currentValue = true;
+                } //if
+
+                if ($allowsMultiple) {
+                    if ($allowsValue) {
+                        if (!isset($this->params[$currentLongName])) {
+                            $this->params[$currentLongName] = array();
+                        } //if
+
+                        $this->params[$currentLongName][] = $currentValue;
+
+                    } else {
+                        if (!isset($this->params[$currentLongName])) {
+                            $this->params[$currentLongName] = 0;
+                        } //if
+
+                        $this->params[$currentLongName]++;
+
+                    } //if
+
+                } else {
+                    $this->params[$currentLongName] = $currentValue;
+                } //if
+
+                foreach ($multiShortParams as $shortName) {
+                    $argumentStack[] = "-{$shortName}";
+                    $argumentsLeft++;
+                } //foreach
+
+                /* reset stuff for next param */
+                $expectingValue = false;
+                $currentLongName = null;
+                $currentValue = null;
+                $addParam = false;
+                $multiShortParams = array();
+
+            } //if
+
+        } //while
+
+        if ($expectingValue !== false) {
+            throw new \UnexpectedValueException("Parameter {$currentLongName} expects a values");
+        } //if
+
+        /* Not sure how this could happen */
+        // @codeCoverageIgnoreStart
+        if ($currentLongName !== null ||
+            $addParam !== false ||
+            $currentValue !== null ||
+            sizeof($multiShortParams) !== 0) {
+            throw new \UnexpectedValueException("Unable to process some parameters");
+        } //if
+        // @codeCoverageIgnoreEnd
+
+        if ($trailingValues !== "") {
+            $this->trailingValues = substr($trailingValues, 1); // remove extra space at the begging
+        } //if
+
+        $this->parsed = true;
+    } // parseParams()
+}
diff --git a/Classes/CommandLineArgumentDefinition.php b/Classes/CommandLineArgumentDefinition.php
new file mode 100644
index 0000000..2b2e8fa
--- /dev/null
+++ b/Classes/CommandLineArgumentDefinition.php
@@ -0,0 +1,389 @@
+<?php
+/**
+ * Defines list and formats of command line arguments
+ *
+ * @author Patrick Forget <patforg at webtrendi.com>
+ */
+
+namespace Clapp;
+
+/**
+ * Defines list and formats of command line arguments
+ *
+ * @author Patrick Forget <patforg at webtrendi.com>
+ */
+class CommandLineArgumentDefinition
+{
+
+    /**
+     * @var array
+     */
+    private $definitions = array();
+
+    /**
+     * long names as keys and array of properties as values
+     *
+     * properties are as follows
+     * * string "shortName" one letter char to the corresponding short name
+     * * boolean "isMultipleAllowed" true if mutliple instances of the param are allowed
+     * * mixed "parameterType" false if paramters are not alloweda value,
+     *       otherwise a string with the value "integer" or "string"
+     * * string "description" description of the parameter
+     * @var array
+     */
+    private $longNames = array();
+    
+    /**
+     * list of short names as keys and their long name equivalent as values
+     * @var array
+     */
+    private $shortNames = array();
+
+    /**
+     * Flag if arguments have been parsed in to params
+     * @var boolean
+     */
+    private $isParsed = false;
+
+    /**
+     * class constructor
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param array $definitions contains list of allowed parameters
+     *     the key is the long name of the parameter followed by a pipe (|)
+     *     then a single character specifying the short name.
+     *
+     *     If the parameter allows for arguments then an equal sign (=)
+     *     follows and then the type of paramter.
+     *
+     *     Allowed types are either i, int or integer for integer  types
+     *     and s, str or string for string types.
+     *
+     *     If a parameter can appear more than once the last character of
+     *     the key should be a plus character (+).
+     *
+     *     The value of the entry is the definition of what the paramter
+     *     does.
+     */
+    public function __construct($definitions)
+    {
+        if (is_array($definitions)) {
+            $this->definitions = $definitions;
+        } //if
+    } // __construct()
+
+    /**
+     * checks if parameter is allowed
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string $name either short or long name of the parameter to check
+     *
+     * @return boolean true if definition exisits, false otherwise
+     */
+    public function paramExists($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        if (strlen($name) == 1) {
+            return isset($this->shortNames[$name]);
+        } else {
+            return isset($this->longNames[$name]);
+        } //if
+    } // paramExists($name)
+    
+
+    /**
+     * checks if parameter allows a value if so what type
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string $name either short or long name of the parameter to check
+     *
+     * @return boolean|string false doesn't allow value, The value "string" or "integer" depending which type it allows
+     */
+    public function allowsValue($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        $longName = (strlen($name) == 1 ? ( isset($this->shortNames[$name]) ? $this->shortNames[$name] : '') : $name);
+
+        if (isset($this->longNames[$longName])) {
+            return $this->longNames[$longName]['parameterType'] !== false ? true : false;
+        } else {
+            return false;
+        } //if
+    } // allowsValue()
+    
+    /**
+     * returns the type of value allowed
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    public function getValueType($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        $longName = (strlen($name) == 1 ? ( isset($this->shortNames[$name]) ? $this->shortNames[$name] : '') : $name);
+
+        if (isset($this->longNames[$longName]['parameterType'])
+            && $this->longNames[$longName]['parameterType'] !== false) {
+            return $this->longNames[$longName]['parameterType'];
+        } else {
+            return '';
+        } //if
+    } // getValueType()
+    
+
+    /**
+     * checks if pamultiple instance of parameter are allowed
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string $name either short or long name of the parameter to check
+     *
+     * @return boolean false if parameter doesn't allow multiple values, true if it does
+     */
+    public function allowsMultiple($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        $longName = (strlen($name) == 1 ? ( isset($this->shortNames[$name]) ? $this->shortNames[$name] : '') : $name);
+
+        if (isset($this->longNames[$longName])) {
+            return $this->longNames[$longName]['isMultipleAllowed'];
+        } else {
+            return false;
+        } //if
+    } // allowsMultiple()
+
+    /**
+     * retreive short name of a parameter using its long name
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string $name long name of the parameter to check
+     *
+     * @return string character of the short name or null if it doesn't exist
+     */
+    public function getShortName($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        if (isset($this->longNames[$name])) {
+            return $this->longNames[$name]['shortName'];
+        } else {
+            return null;
+        } //if
+    } // getShortName($name)
+    
+    /**
+     * retreive long name of a parameter using its short name
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string $name short name of the parameter to check
+     *
+     * @return string long name or null if it doesn't exist
+     */
+    public function getLongName($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        if (isset($this->shortNames[$name])) {
+            return $this->shortNames[$name];
+        } else {
+            return null;
+        } //if
+    } // getLongName($name)
+
+    /**
+     * retreive description of a paramter
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string $name either short or long name of the parameter to check
+     *
+     * @return string description or null if it doesn't exist
+     */
+    public function getDescription($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        $longName = (strlen($name) == 1 ? ( isset($this->shortNames[$name]) ? $this->shortNames[$name] : '') : $name);
+
+        if (isset($this->longNames[$longName])) {
+            return $this->longNames[$longName]['description'];
+        } else {
+            return null;
+        } //if
+    } // getDescription()
+    
+    /**
+     * builds a usage definition based on definition of params
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    public function getUsage()
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        /* build list of argument names and calculate
+           the first column width so we can pad to 
+           align definitions */
+        $firstCol = array();
+        $longestDef = 0;
+        foreach (array_keys($this->longNames) as $longName) {
+            ob_start();
+            echo "--{$longName}|-{$this->getShortName($longName)}";
+
+            if ($this->allowsValue($longName)) {
+                echo "={$this->getValueType($longName)}";
+            } //if
+
+            if ($this->allowsMultiple($longName)) {
+                echo "+";
+            } //if
+
+            $defLength = ob_get_length();
+
+            $longestDef = max($longestDef, $defLength);
+
+            $firstCol[$longName] = ob_get_contents();
+            ob_end_clean();
+
+        } //foreach
+
+        $firstColMaxWidth = $longestDef + 4;
+
+        ob_start();
+
+        foreach ($firstCol as $longName => $def) {
+            $currentDefLength = strlen($def);
+
+            $padding = str_repeat(" ", $firstColMaxWidth - $currentDefLength);
+
+            echo "{$def}{$padding}{$this->getDescription($longName)}", PHP_EOL;
+        } //foreach
+
+        echo PHP_EOL;
+
+        $usage = ob_get_contents();
+        ob_end_clean();
+        
+        return $usage;
+
+    } // getUsage()
+    
+
+    /**
+     * parses the definitions
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    protected function parseDefinitions()
+    {
+        foreach ($this->definitions as $nameDef => $description) {
+            $nameParts = explode("|", $nameDef);
+
+            if (sizeof($nameParts) !== 2) {
+                throw new \UnexpectedValueException("Unexpected argument name definition expecting \"longName|char\"");
+            } //if
+
+            $longName = $nameParts[0];
+            $isMulti = false;
+            $parameterType = false;
+
+            $shortNameLength = strlen($nameParts[1]);
+
+            if ($shortNameLength == 1) {
+                $shortName = $nameParts[1];
+            } else {
+                $secondChar = substr($nameParts[1], 1, 1);
+
+                switch ($secondChar) {
+                    case '=':
+                        $shortNameParts = explode("=", $nameParts[1]);
+
+                        $shortName = $shortNameParts[0];
+                        $parameterTypeString = $shortNameParts[1];
+
+                        if (substr($parameterTypeString, -1) === '+') {
+                            $isMulti = true;
+                            $parameterTypeString = substr($parameterTypeString, 0, -1); // remove trailing +
+                        } //if
+
+                        switch ($parameterTypeString) {
+                            case 'i':
+                            case 'int':
+                            case 'integer':
+                                $parameterType = 'integer';
+                                break;
+                            case 's':
+                            case 'str':
+                            case 'string':
+                                $parameterType = 'string';
+                                break;
+                            default:
+                                throw new \UnexpectedValueException("Expecting parameter type".
+                                   " to be either integer or string");
+                                break;
+                        } //switch
+
+                        break;
+                    case '+':
+                        if ($shortNameLength > 2) {
+                            throw new \UnexpectedValueException("Multiple flag charachter (+)".
+                               " should be last character in definition");
+                        } //if
+
+                        $shortName = substr($nameParts[1], 0, 1);
+                        $isMulti = true;
+
+                        break;
+                    default:
+                        throw new \UnexpectedValueException("Expecting short name definition to be a single char");
+                        break;
+                } // switch
+
+            } //if
+
+            if (isset($this->longNames[$longName])) {
+                throw new \UnexpectedValueException("Cannot redefine long name {$longName}");
+            } //if
+
+            if (isset($this->shortNames[$shortName])) {
+                throw new \UnexpectedValueException("Cannot redefine short name {$shortName}");
+            } //if
+
+            $this->longNames[$longName] = array(
+                'shortName' => $shortName,
+                'isMultipleAllowed' => $isMulti,
+                'parameterType' => $parameterType,
+                'description' => $description
+            );
+
+            $this->shortNames[$shortName] = $longName;
+
+        } //foreach
+
+        $this->isParsed = true;
+    } // parseDefinitions()
+}
diff --git a/Classes/Curl.php b/Classes/Curl.php
new file mode 100644
index 0000000..45a70c7
--- /dev/null
+++ b/Classes/Curl.php
@@ -0,0 +1,23 @@
+<?php
+class curl {
+     
+      public function curlQuery() {
+      $ch = curl_init();
+
+      curl_setopt($ch, CURLOPT_URL, $this->url);
+      curl_setopt($ch, CURLOPT_HEADER, 0);
+      curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.6) Gecko/20070725 Firefox/2.0.0.6");
+      curl_setopt($ch, CURLOPT_RETURNTRANSFER, True);
+      curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
+      curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+      //curl_setopt($ch, CURLOPT_VERBOSE, true); // verbose mode for debugging
+
+      $json = curl_exec($ch);
+
+      curl_close($ch);
+
+      $array = json_decode($json, true);
+      return $array;
+      }
+}
+?>
\ No newline at end of file

diff --git a/Classes/CommandArgumentFilter.php b/Classes/CommandArgumentFilter.php
new file mode 100644
index 0000000..1ffa45e
--- /dev/null
+++ b/Classes/CommandArgumentFilter.php
@@ -0,0 +1,343 @@
+<?php
+/**
+ * Filters an array and extracts and validates command line arguments
+ *
+ * @author Patrick Forget <patforg at webtrendi.com>
+ */
+
+namespace Clapp;
+
+/**
+ * Filters an array and extracts and validates command line arguments
+ *
+ * @author Patrick Forget <patforg at webtrendi.com>
+ */
+class CommandArgumentFilter
+{
+    /**
+     * Command line arguments
+     * @var array
+     */
+    private $arguments = array();
+    
+    /**
+     * Definition of allowed parameters
+     * @var \Clapp\CommandLineArgumentDefinition
+     */
+    private $definitions = null;
+
+    /**
+     * Flag if arguments have been parsed in to params
+     * @var boolean
+     */
+    private $parsed = false;
+
+    /**
+     * Parsed params
+     * @var array
+     */
+    private $params = array();
+
+    /**
+     * Trailing values
+     * @var string
+     */
+    private $trailingValues = "";
+    
+    /**
+     * program name
+     * @var string
+     */
+    private $programName = "";
+
+    /**
+     * class constructor
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param \Clapp\CommandLineDefinition $definitions contains list of allowed parameters
+     * @param array $args list of arguments to filter.
+     */
+    public function __construct(\Clapp\CommandLineArgumentDefinition $definitions, $args)
+    {
+        if (is_array($args)) {
+            $this->arguments = $args;
+        } //if
+
+        $this->definitions = $definitions;
+    } // __construct()
+
+    /**
+     * returns parameter matching provided name
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string name of the paramter to retreive
+     *
+     * @return mixed if param the param appears only once the method will
+     *     return 1 if the parameter doesn't take a value. The specified value
+     *     for that param will returned if it does take value.
+     *
+     *     If many occurence of the param appear the number of occurences will
+     *     be returned for params that do not take values. An array of values
+     *     will be returned for the parameters that do take values.
+     *
+     *     If the parameter is not present null if it takes a value and false if
+     *     it's not present and doesn't allow values
+     */
+    public function getParam($name)
+    {
+        if (!$this->parsed) {
+            $this->parseParams();
+        } //if
+
+        $longName = strlen($name) === 1 ? $this->definitions->getLongName($name) : $name;
+        if (isset($this->params[$longName])) {
+            return $this->params[$longName];
+        } else {
+            if ($this->definitions->allowsValue($longName)) {
+                return null;
+            } else {
+                return false;
+            } //if
+        } //if
+
+    } // getParam()
+
+    /**
+     * retreive the program name
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    public function getProgramName()
+    {
+        if (!$this->parsed) {
+            $this->parseParams();
+        } //if
+
+        return $this->programName;
+    } // getProgramName()
+    
+    /**
+     * retreive the trailing values
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    public function getTrailingValues()
+    {
+        if (!$this->parsed) {
+            $this->parseParams();
+        } //if
+
+        return $this->trailingValues;
+    } // getTrailingValues()
+
+    /**
+     * extracts params from arguments
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    protected function parseParams()
+    {
+
+        $argumentStack = $this->arguments;
+
+        $expectingValue = false;
+        $currentLongName = null;
+        $currentValue = null;
+        $trailingValues = "";
+        $endOfDashedArguments = false;
+        $addParam = false;
+        $argumentsLeft = sizeof($argumentStack);
+        $multiShortParams = array();
+
+        $this->programName = array_shift($argumentStack); // remove first argument which is the program name
+
+        while ($currentArgument = array_shift($argumentStack)) {
+            $argumentsLeft--;
+            $currentArgumentLength = strlen($currentArgument);
+
+            // arguments that don't start with a dash
+            if (substr($currentArgument, 0, 1) !== '-') {
+                if ($expectingValue) {
+                    $currentValue = $currentArgument;
+                    $addParam = true;
+                } else {
+                    $trailingValues .= " ". $currentArgument;
+                    $endOfDashedArguments = true;
+                } //if
+
+            // double dash detected
+            } elseif (substr($currentArgument, 1, 1)  === '-') {
+                if ($expectingValue) {
+                    throw new \UnexpectedValueException("Parameter {$currentLongName} expects a values");
+                } //if
+
+                /* stop parsing arguments if double dash
+                   only param is encountered  */
+                if ($currentArgumentLength == 2) {
+                    if ($trailingValues !== "") {
+                        throw new \UnexpectedValueException("Trailing values must appear after double dash");
+                    } //if
+
+                    $trailingValues = " ". implode(" ", $argumentStack);
+                    $argumentStack = array();
+                    $endOfDashedArguments = true;
+                    break;
+                } //if
+
+                $longNameParts = explode("=", substr($currentArgument, 2), 2);
+
+                $currentLongName = $longNameParts[0];
+
+                if (sizeof($longNameParts) > 1) {
+                    $currentValue = $longNameParts[1];
+                    $addParam = true;
+                } elseif ($this->definitions->allowsValue($currentLongName)) {
+                    $expectingValue = true;
+                } else {
+                    $addParam = true;
+                } //if
+
+            // single dash
+            } else {
+                if ($expectingValue) {
+                    throw new \UnexpectedValueException("Parameter {$currentLongName} expects a values");
+                } //if
+
+                $shortNameParts = explode("=", substr($currentArgument, 1), 2);
+
+                $shortName = $shortNameParts[0];
+
+                if (strlen($shortName) <= 1) {
+                    $currentLongName = $this->definitions->getLongName($shortName);
+
+                    if ($currentLongName === null) {
+                        throw new \InvalidArgumentException("Unable to find name with ".
+                            "provided parameter ({$shortName})");
+                    } //if
+
+                    if (sizeof($shortNameParts) > 1) {
+                        $currentValue = $shortNameParts[1];
+                        $addParam = true;
+                    } elseif ($this->definitions->allowsValue($currentLongName)) {
+                        $expectingValue = true;
+                    } else {
+                        $addParam = true;
+                    } //if
+
+                } else {
+                    $multiShortParams = str_split($shortName);
+
+                    /* process the last one (which is the only one that can have a value) */
+                    $lastParam = array_pop($multiShortParams);
+                    $currentLongName = $this->definitions->getLongName($lastParam);
+                    if (sizeof($shortNameParts) > 1) {
+                        $currentValue = $shortNameParts[1];
+                        $addParam = true;
+                    } elseif ($this->definitions->allowsValue($lastParam)) {
+                        $expectingValue = true;
+                    } else {
+                        $addParam = true;
+                    } //if
+
+                } //if
+                
+            } //if
+
+            if ($addParam) {
+                if ($endOfDashedArguments) {
+                    throw new \UnexpectedValueException("Unexpected argument after undashed values");
+                } //if
+
+                /* Not sure how this could happen */
+                // @codeCoverageIgnoreStart
+                if ($currentLongName === false || $currentLongName === null) {
+                    throw new \UnexpectedValueException("Missing argument name");
+                } //if
+                // @codeCoverageIgnoreEnd
+
+                if (!$this->definitions->paramExists($currentLongName)) {
+                    throw new \InvalidArgumentException("Invalid argument name");
+                } //if
+
+                $allowsMultiple = $this->definitions->allowsMultiple($currentLongName);
+                $allowsValue = $this->definitions->allowsValue($currentLongName);
+
+                if (isset($this->params[$currentLongName]) && !$allowsMultiple) {
+                    throw new \UnexpectedValueException("Multiple instace of parameter {$currentLongName} not allowed");
+                } //if
+
+                if ($allowsValue) {
+                    /* Missing values should always be detected before addParam is true */
+                    // @codeCoverageIgnoreStart
+                    if ($currentValue === null) {
+                        throw new \UnexpectedValueException("Parameter {$currentLongName} expects a values");
+                    } //if
+                    // @codeCoverageIgnoreEnd
+
+                } elseif ($currentValue !== null) {
+                    throw new \UnexpectedValueException("Parameter {$currentLongName} does not accept values");
+
+                } else {
+                    $currentValue = true;
+                } //if
+
+                if ($allowsMultiple) {
+                    if ($allowsValue) {
+                        if (!isset($this->params[$currentLongName])) {
+                            $this->params[$currentLongName] = array();
+                        } //if
+
+                        $this->params[$currentLongName][] = $currentValue;
+
+                    } else {
+                        if (!isset($this->params[$currentLongName])) {
+                            $this->params[$currentLongName] = 0;
+                        } //if
+
+                        $this->params[$currentLongName]++;
+
+                    } //if
+
+                } else {
+                    $this->params[$currentLongName] = $currentValue;
+                } //if
+
+                foreach ($multiShortParams as $shortName) {
+                    $argumentStack[] = "-{$shortName}";
+                    $argumentsLeft++;
+                } //foreach
+
+                /* reset stuff for next param */
+                $expectingValue = false;
+                $currentLongName = null;
+                $currentValue = null;
+                $addParam = false;
+                $multiShortParams = array();
+
+            } //if
+
+        } //while
+
+        if ($expectingValue !== false) {
+            throw new \UnexpectedValueException("Parameter {$currentLongName} expects a values");
+        } //if
+
+        /* Not sure how this could happen */
+        // @codeCoverageIgnoreStart
+        if ($currentLongName !== null ||
+            $addParam !== false ||
+            $currentValue !== null ||
+            sizeof($multiShortParams) !== 0) {
+            throw new \UnexpectedValueException("Unable to process some parameters");
+        } //if
+        // @codeCoverageIgnoreEnd
+
+        if ($trailingValues !== "") {
+            $this->trailingValues = substr($trailingValues, 1); // remove extra space at the begging
+        } //if
+
+        $this->parsed = true;
+    } // parseParams()
+}
diff --git a/Classes/CommandLineArgumentDefinition.php b/Classes/CommandLineArgumentDefinition.php
new file mode 100644
index 0000000..2b2e8fa
--- /dev/null
+++ b/Classes/CommandLineArgumentDefinition.php
@@ -0,0 +1,389 @@
+<?php
+/**
+ * Defines list and formats of command line arguments
+ *
+ * @author Patrick Forget <patforg at webtrendi.com>
+ */
+
+namespace Clapp;
+
+/**
+ * Defines list and formats of command line arguments
+ *
+ * @author Patrick Forget <patforg at webtrendi.com>
+ */
+class CommandLineArgumentDefinition
+{
+
+    /**
+     * @var array
+     */
+    private $definitions = array();
+
+    /**
+     * long names as keys and array of properties as values
+     *
+     * properties are as follows
+     * * string "shortName" one letter char to the corresponding short name
+     * * boolean "isMultipleAllowed" true if mutliple instances of the param are allowed
+     * * mixed "parameterType" false if paramters are not alloweda value,
+     *       otherwise a string with the value "integer" or "string"
+     * * string "description" description of the parameter
+     * @var array
+     */
+    private $longNames = array();
+    
+    /**
+     * list of short names as keys and their long name equivalent as values
+     * @var array
+     */
+    private $shortNames = array();
+
+    /**
+     * Flag if arguments have been parsed in to params
+     * @var boolean
+     */
+    private $isParsed = false;
+
+    /**
+     * class constructor
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param array $definitions contains list of allowed parameters
+     *     the key is the long name of the parameter followed by a pipe (|)
+     *     then a single character specifying the short name.
+     *
+     *     If the parameter allows for arguments then an equal sign (=)
+     *     follows and then the type of paramter.
+     *
+     *     Allowed types are either i, int or integer for integer  types
+     *     and s, str or string for string types.
+     *
+     *     If a parameter can appear more than once the last character of
+     *     the key should be a plus character (+).
+     *
+     *     The value of the entry is the definition of what the paramter
+     *     does.
+     */
+    public function __construct($definitions)
+    {
+        if (is_array($definitions)) {
+            $this->definitions = $definitions;
+        } //if
+    } // __construct()
+
+    /**
+     * checks if parameter is allowed
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string $name either short or long name of the parameter to check
+     *
+     * @return boolean true if definition exisits, false otherwise
+     */
+    public function paramExists($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        if (strlen($name) == 1) {
+            return isset($this->shortNames[$name]);
+        } else {
+            return isset($this->longNames[$name]);
+        } //if
+    } // paramExists($name)
+    
+
+    /**
+     * checks if parameter allows a value if so what type
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string $name either short or long name of the parameter to check
+     *
+     * @return boolean|string false doesn't allow value, The value "string" or "integer" depending which type it allows
+     */
+    public function allowsValue($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        $longName = (strlen($name) == 1 ? ( isset($this->shortNames[$name]) ? $this->shortNames[$name] : '') : $name);
+
+        if (isset($this->longNames[$longName])) {
+            return $this->longNames[$longName]['parameterType'] !== false ? true : false;
+        } else {
+            return false;
+        } //if
+    } // allowsValue()
+    
+    /**
+     * returns the type of value allowed
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    public function getValueType($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        $longName = (strlen($name) == 1 ? ( isset($this->shortNames[$name]) ? $this->shortNames[$name] : '') : $name);
+
+        if (isset($this->longNames[$longName]['parameterType'])
+            && $this->longNames[$longName]['parameterType'] !== false) {
+            return $this->longNames[$longName]['parameterType'];
+        } else {
+            return '';
+        } //if
+    } // getValueType()
+    
+
+    /**
+     * checks if pamultiple instance of parameter are allowed
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string $name either short or long name of the parameter to check
+     *
+     * @return boolean false if parameter doesn't allow multiple values, true if it does
+     */
+    public function allowsMultiple($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        $longName = (strlen($name) == 1 ? ( isset($this->shortNames[$name]) ? $this->shortNames[$name] : '') : $name);
+
+        if (isset($this->longNames[$longName])) {
+            return $this->longNames[$longName]['isMultipleAllowed'];
+        } else {
+            return false;
+        } //if
+    } // allowsMultiple()
+
+    /**
+     * retreive short name of a parameter using its long name
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string $name long name of the parameter to check
+     *
+     * @return string character of the short name or null if it doesn't exist
+     */
+    public function getShortName($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        if (isset($this->longNames[$name])) {
+            return $this->longNames[$name]['shortName'];
+        } else {
+            return null;
+        } //if
+    } // getShortName($name)
+    
+    /**
+     * retreive long name of a parameter using its short name
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string $name short name of the parameter to check
+     *
+     * @return string long name or null if it doesn't exist
+     */
+    public function getLongName($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        if (isset($this->shortNames[$name])) {
+            return $this->shortNames[$name];
+        } else {
+            return null;
+        } //if
+    } // getLongName($name)
+
+    /**
+     * retreive description of a paramter
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string $name either short or long name of the parameter to check
+     *
+     * @return string description or null if it doesn't exist
+     */
+    public function getDescription($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        $longName = (strlen($name) == 1 ? ( isset($this->shortNames[$name]) ? $this->shortNames[$name] : '') : $name);
+
+        if (isset($this->longNames[$longName])) {
+            return $this->longNames[$longName]['description'];
+        } else {
+            return null;
+        } //if
+    } // getDescription()
+    
+    /**
+     * builds a usage definition based on definition of params
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    public function getUsage()
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        /* build list of argument names and calculate
+           the first column width so we can pad to 
+           align definitions */
+        $firstCol = array();
+        $longestDef = 0;
+        foreach (array_keys($this->longNames) as $longName) {
+            ob_start();
+            echo "--{$longName}|-{$this->getShortName($longName)}";
+
+            if ($this->allowsValue($longName)) {
+                echo "={$this->getValueType($longName)}";
+            } //if
+
+            if ($this->allowsMultiple($longName)) {
+                echo "+";
+            } //if
+
+            $defLength = ob_get_length();
+
+            $longestDef = max($longestDef, $defLength);
+
+            $firstCol[$longName] = ob_get_contents();
+            ob_end_clean();
+
+        } //foreach
+
+        $firstColMaxWidth = $longestDef + 4;
+
+        ob_start();
+
+        foreach ($firstCol as $longName => $def) {
+            $currentDefLength = strlen($def);
+
+            $padding = str_repeat(" ", $firstColMaxWidth - $currentDefLength);
+
+            echo "{$def}{$padding}{$this->getDescription($longName)}", PHP_EOL;
+        } //foreach
+
+        echo PHP_EOL;
+
+        $usage = ob_get_contents();
+        ob_end_clean();
+        
+        return $usage;
+
+    } // getUsage()
+    
+
+    /**
+     * parses the definitions
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    protected function parseDefinitions()
+    {
+        foreach ($this->definitions as $nameDef => $description) {
+            $nameParts = explode("|", $nameDef);
+
+            if (sizeof($nameParts) !== 2) {
+                throw new \UnexpectedValueException("Unexpected argument name definition expecting \"longName|char\"");
+            } //if
+
+            $longName = $nameParts[0];
+            $isMulti = false;
+            $parameterType = false;
+
+            $shortNameLength = strlen($nameParts[1]);
+
+            if ($shortNameLength == 1) {
+                $shortName = $nameParts[1];
+            } else {
+                $secondChar = substr($nameParts[1], 1, 1);
+
+                switch ($secondChar) {
+                    case '=':
+                        $shortNameParts = explode("=", $nameParts[1]);
+
+                        $shortName = $shortNameParts[0];
+                        $parameterTypeString = $shortNameParts[1];
+
+                        if (substr($parameterTypeString, -1) === '+') {
+                            $isMulti = true;
+                            $parameterTypeString = substr($parameterTypeString, 0, -1); // remove trailing +
+                        } //if
+
+                        switch ($parameterTypeString) {
+                            case 'i':
+                            case 'int':
+                            case 'integer':
+                                $parameterType = 'integer';
+                                break;
+                            case 's':
+                            case 'str':
+                            case 'string':
+                                $parameterType = 'string';
+                                break;
+                            default:
+                                throw new \UnexpectedValueException("Expecting parameter type".
+                                   " to be either integer or string");
+                                break;
+                        } //switch
+
+                        break;
+                    case '+':
+                        if ($shortNameLength > 2) {
+                            throw new \UnexpectedValueException("Multiple flag charachter (+)".
+                               " should be last character in definition");
+                        } //if
+
+                        $shortName = substr($nameParts[1], 0, 1);
+                        $isMulti = true;
+
+                        break;
+                    default:
+                        throw new \UnexpectedValueException("Expecting short name definition to be a single char");
+                        break;
+                } // switch
+
+            } //if
+
+            if (isset($this->longNames[$longName])) {
+                throw new \UnexpectedValueException("Cannot redefine long name {$longName}");
+            } //if
+
+            if (isset($this->shortNames[$shortName])) {
+                throw new \UnexpectedValueException("Cannot redefine short name {$shortName}");
+            } //if
+
+            $this->longNames[$longName] = array(
+                'shortName' => $shortName,
+                'isMultipleAllowed' => $isMulti,
+                'parameterType' => $parameterType,
+                'description' => $description
+            );
+
+            $this->shortNames[$shortName] = $longName;
+
+        } //foreach
+
+        $this->isParsed = true;
+    } // parseDefinitions()
+}
diff --git a/Classes/Curl.php b/Classes/Curl.php
new file mode 100644
index 0000000..45a70c7
--- /dev/null
+++ b/Classes/Curl.php
@@ -0,0 +1,23 @@
+<?php
+class curl {
+     
+      public function curlQuery() {
+      $ch = curl_init();
+
+      curl_setopt($ch, CURLOPT_URL, $this->url);
+      curl_setopt($ch, CURLOPT_HEADER, 0);
+      curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.6) Gecko/20070725 Firefox/2.0.0.6");
+      curl_setopt($ch, CURLOPT_RETURNTRANSFER, True);
+      curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
+      curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+      //curl_setopt($ch, CURLOPT_VERBOSE, true); // verbose mode for debugging
+
+      $json = curl_exec($ch);
+
+      curl_close($ch);
+
+      $array = json_decode($json, true);
+      return $array;
+      }
+}
+?>
\ No newline at end of file
diff --git a/GoStats.php b/GoStats.php
new file mode 100755
index 0000000..c1ba2c6
--- /dev/null
+++ b/GoStats.php
@@ -0,0 +1,401 @@
+#!/usr/bin/php
+<?php
+error_reporting(0);
+
+/***
+ * Configuration Settings - CHANGE THESE
+ */
+$url = ""; // URL of GoPhish listner e.g. http://www.site.com:8080/
+$key = ""; // GoPhish API key
+$pwd = "/opt/Pwdlyser/"; // /directory/containing/pwdlyser
+$geoip = true; // use freegeoip.net on IP addresses? set to false to disable this.
+
+/***
+ * Main program - Don't edit below
+ */
+echo "╔═╗┌─┐╔═╗┌┬┐┌─┐┌┬┐┌─┐\n║ ╦│ │╚═╗ │ ├─┤ │ └─┐ v1.0\n╚═╝└─┘╚═╝ ┴ ┴ ┴ ┴ └─┘\n";
+
+foreach (glob("Classes/*.php") as $filename)
+    include $filename;
+
+$definitions = new \Clapp\CommandLineArgumentDefinition(
+    array(
+        "help|h"            => "Shows help message",
+        "list|l"            => "List campaigns and their ID's",
+        "campaign|c=i"      => "Get campaign by id",
+        "dump|d=s"          => "Dump user:pass list to </path/to/file.txt>",
+        "training|t=s"      => "Dump list of users requiring training </path/to/file.txt>",
+        "all|a"             => "All of the below options",
+        "ips|i"             => "Top 10 IP's",
+        "useragent|u"       => "Top 10 user agents",
+        "attempts|m"        => "Top 10 attempts to log in",
+        "active|o"          => "Active times",
+        "speed|e"           => "Clickthrough speed",
+        "stats|s"           => "Victim statistics",
+        "pass|p"            => "Password analysis with pwdlyser",
+    )
+);
+
+$filter = new \Clapp\CommandArgumentFilter($definitions, $argv);
+
+if ($filter->getParam('h') === true || $argc < 2) {
+    fwrite(STDERR, $definitions->getUsage());
+    exit(0);
+} 
+
+/* Get list of campaigns */
+if ($filter->getParam("list") !== false) {
+	echo "[+] Getting data from server\n";
+	$curl = new curl();
+    $curl->url = "$url/api/campaigns/?api_key=$key";
+    $list = $curl->curlQuery();
+
+    if(isset($list->message) && $list->message == "Invalid API Key"){
+    	echo "[!] Invalid API key\n";
+    	exit(0);
+    }else{
+    	echo "[id] -campaign name-\n";
+    	foreach($list as $id)
+    		echo "[".$id['id']."] ".$id['name']."\n";
+    }
+    exit(0);
+} 
+
+/* Get campaign data */
+$campid = $filter->getParam('c');
+if ($campid == null || !is_numeric($campid)) {
+    echo "[!] Campaign ID not set\nn";
+    exit(0);
+}else{
+	echo "[+] Getting data from server\n";
+	$curl = new curl();
+    $curl->url = "$url/api/campaigns/$campid?api_key=$key";
+    $list = $curl->curlQuery();
+    if(isset($list->message) && $list->message == "Invalid API Key"){
+    	echo "[!] Invalid API key\n";
+    	exit(0);
+    }else{
+    	/* all data got correctly time to do stuff! */
+    	echo "[$campid] ".$list['name']."\n";
+    	echo "\n--- Notable times ---\n";
+
+        if(isset($list['launch_date']) && $list['launch_date'] <> ""){
+            $time = date('d-m-Y H:i', $datetime = strtotime(substr($list['launch_date'], 0, 10) . ' ' . substr($list['launch_date'], 11, 8 )));
+            echo "Campaign launched: $time\n";
+        }
+
+    	foreach($list['timeline'] as $record){
+    		if($record['message'] == "Email Sent"){
+    			$time = date('d-m-Y H:i', $datetime = strtotime(substr($record['time'], 0, 10) . ' ' . substr($record['time'], 11, 8 )));
+    			echo "First email sent: $time\n";
+    			break;
+    		}
+    	}
+        foreach($list['timeline'] as $record){
+            if($record['message'] == "Email Sent"){
+                $time = date('d-m-Y H:i', $datetime = strtotime(substr($record['time'], 0, 10) . ' ' . substr($record['time'], 11, 8 ))); 
+            }
+        }
+        echo "Last email sent:: $time\n";
+    	foreach($list['timeline'] as $record){
+    		if($record['message'] == "Clicked Link"){
+    			$time = date('d-m-Y H:i', $datetime = strtotime(substr($record['time'], 0, 10) . ' ' . substr($record['time'], 11, 8 )));
+    			echo "First email opened: $time\n";
+    			break;
+    		}
+    	}
+    	foreach($list['timeline'] as $record){
+    		if($record['message'] == "Clicked Link"){
+    			$time = date('d-m-Y H:i', $datetime = strtotime(substr($record['time'], 0, 10) . ' ' . substr($record['time'], 11, 8 )));
+    			echo "First page view: $time\n";
+    			break;
+    		}
+    	}
+    	foreach($list['timeline'] as $record){
+    		if($record['message'] == "Submitted Data"){
+    			$time = date('d-m-Y H:i', $datetime = strtotime(substr($record['time'], 0, 10) . ' ' . substr($record['time'], 11, 8 )));
+    			echo "First credentials submitted: $time\n";
+    			break;
+    		}
+    	}
+        if(isset($list['completed_date']) && $list['completed_date'] <> ""){
+            $time = date('d-m-Y H:i', $datetime = strtotime(substr($list['completed_date'], 0, 10) . ' ' . substr($list['completed_date'], 11, 8 )));
+            echo "Campaign finished: $time\n";
+        }
+    }
+} 
+
+/* Top 10 IP's */
+if ($filter->getParam("ips") !== false || $filter->getParam("all") !== false) {
+    $ips = array();
+    foreach($list['timeline'] as $item){
+    	if($item['details'] <> ""){
+    		$details = json_decode($item['details'], true);
+    		if($details['browser']['address'] !== "unknown")
+    			$ips[] = (string)$details['browser']['address'];
+    	}
+    }
+    $ips = array_count_values($ips);
+    arsort($ips);
+    $ips = array_slice($ips,0,10,true);
+    echo "\n--- Top 10 IP's ---\n";
+    foreach($ips as $ip=>$no){
+        $geoip_details = "";
+        if($geoip == true){
+            $geojson = file_get_contents("http://freegeoip.net/json/$ip");
+            $geodetails = json_decode($geojson, true);
+            $geoip_details = "- ".$geodetails['country_name'].", ".$geodetails['city'];
+        }
+    	echo "[$no] $ip $geoip_details\n";
+    }
+}
+
+/* Top 10 user agent's */
+if ($filter->getParam("useragent") !== false || $filter->getParam("all") !== false) {
+    $agents = array();
+    foreach($list['timeline'] as $item){
+    	if($item['details'] <> "" && $item['message'] == "Clicked Link"){ // only people who visited site, not email user agent
+    		$details = json_decode($item['details'], true);
+    		if($details['browser']['user-agent'] !== "unknown" && $details['browser']['user-agent'] !== "")
+    			$agents[] = (string)$details['browser']['user-agent'];
+    	}
+    }
+    $agents = array_count_values($agents);
+    arsort($agents);
+    $agents = array_slice($agents,0,10,true);
+    echo "\n--- Top 10 User Agents ---\n";
+    foreach($agents as $ua=>$no){
+    	echo "[$no] $ua\n";
+    }
+} 
+
+/* Top 10 attempts to log in */
+if($filter->getParam("attempts") !== false || $filter->getParam("all") !== false) {
+	$userids  = array();
+	foreach($list['results'] as $item){
+		$userids[$item['id']] = $item['email']; 
+    }
+    
+	$attemptrids = array();
+	foreach($list['timeline'] as $item){
+		if($item['details'] <> ""){
+    		$details = json_decode($item['details'], true);
+    		if(isset($details['payload']['password'][0]) && $details['payload']['password'][0] <> ""){
+    			$attemptrids[$details['payload']['rid'][0]] += 1;
+    		}
+    	}
+    }
+    arsort($attemptrids); 
+    $attemptrids = array_slice($attemptrids,0,10,true);
+    echo "\n--- Top 10 Login Attempts ---\n";
+    foreach($attemptrids as $id=>$amount){
+	    $newemail= preg_replace('/(?:^|.@).\K|.\.[^@]*$(*SKIP)(*F)|.(?=.*?\.)/', '*', $userids[$id]);
+	    echo "[$amount] $newemail\n";
+	}
+}
+
+/* Active times */
+if($filter->getParam("active") !== false || $filter->getParam("all") !== false) {
+    $active_count = array();
+    $active_percent = array();
+    $total = 0;
+    echo "\n--- Active times (hour, actions & percent) ---\n";
+    foreach($list['timeline'] as $item){
+        if($item['message'] != "Campaign Created" && $item['message'] != "Email Sent" ){
+            $hour = (int)substr($item['time'], 11, 2);
+            $active_count[$hour]++;
+            $total++;
+        }
+
+    }
+    foreach($active_count as $id => $count) // populate percentages
+        $active_percent[$id] = ($count / $total) * 100;
+
+    for($i = 0; $i <= 12; $i++){
+        $iDsp = str_pad($i, 2, " ", STR_PAD_LEFT);
+        $j = $i+12;
+        $user1 = str_pad($active_count[$i], 4, " ", STR_PAD_LEFT);
+        $user2 = str_pad($active_count[$j], 4, " ", STR_PAD_LEFT);
+        $percent1 = number_format($active_percent[$i], 2, '.', '');
+        $percent1 = str_pad($percent1, 5, " ", STR_PAD_LEFT);
+        $percent2 = number_format($active_percent[$j], 2, '.', '');
+        $percent2 = str_pad($percent2, 5, " ", STR_PAD_LEFT);
+        
+        echo "$iDsp - $user1 = $percent1%  |  $j - $user2 = $percent2% \n";
+    }
+}
+
+/* Clickthrough speed */
+if ($filter->getParam("speed") !== false || $filter->getParam("all") !== false) {
+    $speed_opened = array();
+    $speed_visited = array();
+    $speed_offset = array();
+    echo "\n--- Clickthrough Speed ---\n";
+    foreach($list['timeline'] as $item){
+        if($item['message'] == "Email Opened"){
+            $details = json_decode($item['details'], true);
+            $check_rid = $details['payload']['rid'][0];
+
+            $current_time = strtotime(substr($item['time'], 0, 10) . ' ' . substr($item['time'], 11, 8 ));
+            $existing_time = strtotime(substr($speed_opened[$check_rid], 0, 10) . ' ' . substr($speed_opened[$check_rid], 11, 8 ));
+
+            if(!isset($speed_opened[$check_rid]) || $existing_time > $current_time)
+                $speed_opened[$check_rid] = $item['time'];       
+        }
+        if($item['message'] == "Clicked Link"){
+            $details = json_decode($item['details'], true);
+            $check_rid = $details['payload']['rid'][0];
+            if(!isset($speed_visited[$check_rid]))
+                $speed_visited[$check_rid] = $item['time'];
+        }
+    }
+    foreach($speed_opened as $id=>$val){ // remove all the ones that didn't visit site
+        if(!isset($speed_visited[$id]))
+            unset($speed_opened[$id]);
+    }
+    foreach($speed_visited as $id=>$val){ // remove all the ones that didn't load email tracking image
+        if(!isset($speed_opened[$id]))
+            unset($speed_visited[$id]);
+    }
+    foreach($speed_opened as $id=>$val){ //calculate speed between reading email and clicking link
+        $date_opened = substr($val, 0, 10);
+        $time_opened = substr($val, 11, 8 );
+        $time_opened_stamp = strtotime($date_opened." ".$time_opened);
+
+        $date_visited = substr($speed_visited[$id], 0, 10);
+        $time_visited = substr($speed_visited[$id], 11, 8 );
+        $time_visited_stamp = strtotime($date_visited." ".$time_visited);
+
+        $diff = $time_visited_stamp - $time_opened_stamp;
+        if($diff > 0)
+            $speed_offset[$id] = $diff;
+    }
+
+    unset($speed_opened); // check me out doing memory management and cleaning up! :D
+    unset($speed_visited);
+
+    $quickest = min($speed_offset);
+    echo "Quickest click: $quickest sec\n";
+    $longest = max($speed_offset);
+    $longest = floor(($longest / 60) % 60);
+    echo "Longest click: $longest min\n";
+    $sec_5 = array_reduce($speed_offset, function ($a, $b){
+        return ($b <= 5) ? ++$a : $a; });
+    echo "Users clicked < 5 sec: $sec_5 \n";
+    $sec_30 = array_reduce($speed_offset, function ($a, $b){
+        return ($b <= 30) ? ++$a : $a; });
+    echo "Users clicked < 30 sec: $sec_30 \n";
+    $sec_60 = array_reduce($speed_offset, function ($a, $b){
+        return ($b <= 60) ? ++$a : $a; });
+    echo "Users clicked < 1 min: $sec_60 \n";
+}
+
+/* Victim statistics */
+if ($filter->getParam("stats") !== false || $filter->getParam("all") !== false) {
+	$status = array();
+	foreach($list['results'] as $item){
+		if($item['status'] <> ""){
+			$status[] = $item['status'];
+		}
+	}
+	echo "\n--- Victim Statistics ---\n";
+	$statusall = count($status);
+	$counts = array_count_values($status);
+	echo "Targets: ".$statusall."\n";
+	$openedpercent = ($counts['Email Opened'] / $statusall) * 100;
+	echo "Email opened: ".$counts['Email Opened']." (".round($openedpercent, 2)."%)\n";
+	$linkpercent = ($counts['Clicked Link'] / $statusall) * 100;
+	echo "Visited link: ".$counts['Clicked Link']." (".round($linkpercent, 2)."%)\n";
+	$subpercent = ($counts['Submitted Data'] / $statusall) * 100;
+	echo "Submitted data: ".$counts['Submitted Data']." (".round($subpercent, 2)."%)\n";
+	foreach($list['timeline'] as $item){
+		if($item['details'] <> ""){
+			$details = json_decode($item['details'], true);
+    		if($details['payload']['password'][0] <> "")
+    			$totalLoginAttempts++;
+		}
+	}
+    echo "Total login attempts: $totalLoginAttempts\n";
+}
+
+/* Pwdlyzer */
+if ($filter->getParam("pass") !== false || $filter->getParam("all") !== false) {
+	$username = array();
+	$password = array();
+	echo "\n--- Password Statistics ---\n";
+	foreach($list['timeline'] as $item){
+		if($item['details'] <> ""){
+    		$details = json_decode($item['details'], true);
+    		if($details['payload']['password'][0] <> ""){
+    			$username[] = $details['payload']['username'][0];
+    			$password[] =  $details['payload']['password'][0];
+    		}
+    	}
+	}
+	$tmpfname = tempnam("/tmp", "GoStats-");
+	$pwdfname = tempnam("/tmp", "GoStats-");
+	$handle = fopen($tmpfname, "w");
+	foreach($username as $id=>$user){
+		fwrite($handle, "$user:".$password[$id]."\n");
+	}
+	fclose($handle);
+	echo "[+] Launching pwdlyzer\n";
+	exec("cd $pwd && ./pwdlyser.py -p $tmpfname --all > $pwdfname");
+	unlink($tmpfname);
+	echo "[+] pwdlyzer results at: $pwdfname\n";
+}
+
+/* dump username:password list to file */
+$dumpfile = $filter->getParam('dump');
+if(file_exists($dumpfile)){
+	echo "[!] File already exists ($dumpfile)\n";
+    exit(0);
+}
+if(!file_exists($dumpfile) && isset($dumpfile)){
+	$username = array();
+	$password = array();
+	echo "\n--- Dumping username:password to file ---\n";
+	foreach($list['timeline'] as $item){
+		if($item['details'] <> ""){
+    		$details = json_decode($item['details'], true);
+    		if($details['payload']['password'][0] <> ""){
+    			$username[] = $details['payload']['username'][0];
+    			$password[] =  $details['payload']['password'][0];
+    		}
+    	}
+	}
+	$handle = fopen($dumpfile, "w");
+	foreach($username as $id=>$user){
+		fwrite($handle, "$user:".$password[$id]."\n");
+	}
+	fclose($handle);
+	echo "[+] File created: $dumpfile\n";
+}
+
+/* dump list of users requiring training */
+$dumpfile2 = $filter->getParam('training');
+if(file_exists($dumpfile2)){
+    echo "[!] File already exists ($dumpfile)\n";
+    exit(0);
+}
+if(!file_exists($dumpfile2) && isset($dumpfile2)){
+    $tusername = array();
+    $temail = array();
+    $tstatus = array();
+    echo "\n--- Dumping list of users requiring training ---\n";
+    foreach($list['results'] as $item){
+        if($item['status'] == "Submitted Data" || $item['status'] == "Clicked Link"){
+                $tusername[] = $item['first_name']." ".$item['last_name'];
+                $temail[] = $item['email'];
+                $tstatus[] = $item['status'];
+        }
+    }
+    $handle = fopen($dumpfile2, "w");
+    foreach($tusername as $id=>$user){
+        fwrite($handle, "$user, ".$temail[$id].", ".$tstatus[$id]."\n");
+    }
+    fclose($handle);
+    echo "[+] File created: $dumpfile2\n";
+}
+
+?>

diff --git a/Classes/CommandArgumentFilter.php b/Classes/CommandArgumentFilter.php
new file mode 100644
index 0000000..1ffa45e
--- /dev/null
+++ b/Classes/CommandArgumentFilter.php
@@ -0,0 +1,343 @@
+<?php
+/**
+ * Filters an array and extracts and validates command line arguments
+ *
+ * @author Patrick Forget <patforg at webtrendi.com>
+ */
+
+namespace Clapp;
+
+/**
+ * Filters an array and extracts and validates command line arguments
+ *
+ * @author Patrick Forget <patforg at webtrendi.com>
+ */
+class CommandArgumentFilter
+{
+    /**
+     * Command line arguments
+     * @var array
+     */
+    private $arguments = array();
+    
+    /**
+     * Definition of allowed parameters
+     * @var \Clapp\CommandLineArgumentDefinition
+     */
+    private $definitions = null;
+
+    /**
+     * Flag if arguments have been parsed in to params
+     * @var boolean
+     */
+    private $parsed = false;
+
+    /**
+     * Parsed params
+     * @var array
+     */
+    private $params = array();
+
+    /**
+     * Trailing values
+     * @var string
+     */
+    private $trailingValues = "";
+    
+    /**
+     * program name
+     * @var string
+     */
+    private $programName = "";
+
+    /**
+     * class constructor
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param \Clapp\CommandLineDefinition $definitions contains list of allowed parameters
+     * @param array $args list of arguments to filter.
+     */
+    public function __construct(\Clapp\CommandLineArgumentDefinition $definitions, $args)
+    {
+        if (is_array($args)) {
+            $this->arguments = $args;
+        } //if
+
+        $this->definitions = $definitions;
+    } // __construct()
+
+    /**
+     * returns parameter matching provided name
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string name of the paramter to retreive
+     *
+     * @return mixed if param the param appears only once the method will
+     *     return 1 if the parameter doesn't take a value. The specified value
+     *     for that param will returned if it does take value.
+     *
+     *     If many occurence of the param appear the number of occurences will
+     *     be returned for params that do not take values. An array of values
+     *     will be returned for the parameters that do take values.
+     *
+     *     If the parameter is not present null if it takes a value and false if
+     *     it's not present and doesn't allow values
+     */
+    public function getParam($name)
+    {
+        if (!$this->parsed) {
+            $this->parseParams();
+        } //if
+
+        $longName = strlen($name) === 1 ? $this->definitions->getLongName($name) : $name;
+        if (isset($this->params[$longName])) {
+            return $this->params[$longName];
+        } else {
+            if ($this->definitions->allowsValue($longName)) {
+                return null;
+            } else {
+                return false;
+            } //if
+        } //if
+
+    } // getParam()
+
+    /**
+     * retreive the program name
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    public function getProgramName()
+    {
+        if (!$this->parsed) {
+            $this->parseParams();
+        } //if
+
+        return $this->programName;
+    } // getProgramName()
+    
+    /**
+     * retreive the trailing values
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    public function getTrailingValues()
+    {
+        if (!$this->parsed) {
+            $this->parseParams();
+        } //if
+
+        return $this->trailingValues;
+    } // getTrailingValues()
+
+    /**
+     * extracts params from arguments
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    protected function parseParams()
+    {
+
+        $argumentStack = $this->arguments;
+
+        $expectingValue = false;
+        $currentLongName = null;
+        $currentValue = null;
+        $trailingValues = "";
+        $endOfDashedArguments = false;
+        $addParam = false;
+        $argumentsLeft = sizeof($argumentStack);
+        $multiShortParams = array();
+
+        $this->programName = array_shift($argumentStack); // remove first argument which is the program name
+
+        while ($currentArgument = array_shift($argumentStack)) {
+            $argumentsLeft--;
+            $currentArgumentLength = strlen($currentArgument);
+
+            // arguments that don't start with a dash
+            if (substr($currentArgument, 0, 1) !== '-') {
+                if ($expectingValue) {
+                    $currentValue = $currentArgument;
+                    $addParam = true;
+                } else {
+                    $trailingValues .= " ". $currentArgument;
+                    $endOfDashedArguments = true;
+                } //if
+
+            // double dash detected
+            } elseif (substr($currentArgument, 1, 1)  === '-') {
+                if ($expectingValue) {
+                    throw new \UnexpectedValueException("Parameter {$currentLongName} expects a values");
+                } //if
+
+                /* stop parsing arguments if double dash
+                   only param is encountered  */
+                if ($currentArgumentLength == 2) {
+                    if ($trailingValues !== "") {
+                        throw new \UnexpectedValueException("Trailing values must appear after double dash");
+                    } //if
+
+                    $trailingValues = " ". implode(" ", $argumentStack);
+                    $argumentStack = array();
+                    $endOfDashedArguments = true;
+                    break;
+                } //if
+
+                $longNameParts = explode("=", substr($currentArgument, 2), 2);
+
+                $currentLongName = $longNameParts[0];
+
+                if (sizeof($longNameParts) > 1) {
+                    $currentValue = $longNameParts[1];
+                    $addParam = true;
+                } elseif ($this->definitions->allowsValue($currentLongName)) {
+                    $expectingValue = true;
+                } else {
+                    $addParam = true;
+                } //if
+
+            // single dash
+            } else {
+                if ($expectingValue) {
+                    throw new \UnexpectedValueException("Parameter {$currentLongName} expects a values");
+                } //if
+
+                $shortNameParts = explode("=", substr($currentArgument, 1), 2);
+
+                $shortName = $shortNameParts[0];
+
+                if (strlen($shortName) <= 1) {
+                    $currentLongName = $this->definitions->getLongName($shortName);
+
+                    if ($currentLongName === null) {
+                        throw new \InvalidArgumentException("Unable to find name with ".
+                            "provided parameter ({$shortName})");
+                    } //if
+
+                    if (sizeof($shortNameParts) > 1) {
+                        $currentValue = $shortNameParts[1];
+                        $addParam = true;
+                    } elseif ($this->definitions->allowsValue($currentLongName)) {
+                        $expectingValue = true;
+                    } else {
+                        $addParam = true;
+                    } //if
+
+                } else {
+                    $multiShortParams = str_split($shortName);
+
+                    /* process the last one (which is the only one that can have a value) */
+                    $lastParam = array_pop($multiShortParams);
+                    $currentLongName = $this->definitions->getLongName($lastParam);
+                    if (sizeof($shortNameParts) > 1) {
+                        $currentValue = $shortNameParts[1];
+                        $addParam = true;
+                    } elseif ($this->definitions->allowsValue($lastParam)) {
+                        $expectingValue = true;
+                    } else {
+                        $addParam = true;
+                    } //if
+
+                } //if
+                
+            } //if
+
+            if ($addParam) {
+                if ($endOfDashedArguments) {
+                    throw new \UnexpectedValueException("Unexpected argument after undashed values");
+                } //if
+
+                /* Not sure how this could happen */
+                // @codeCoverageIgnoreStart
+                if ($currentLongName === false || $currentLongName === null) {
+                    throw new \UnexpectedValueException("Missing argument name");
+                } //if
+                // @codeCoverageIgnoreEnd
+
+                if (!$this->definitions->paramExists($currentLongName)) {
+                    throw new \InvalidArgumentException("Invalid argument name");
+                } //if
+
+                $allowsMultiple = $this->definitions->allowsMultiple($currentLongName);
+                $allowsValue = $this->definitions->allowsValue($currentLongName);
+
+                if (isset($this->params[$currentLongName]) && !$allowsMultiple) {
+                    throw new \UnexpectedValueException("Multiple instace of parameter {$currentLongName} not allowed");
+                } //if
+
+                if ($allowsValue) {
+                    /* Missing values should always be detected before addParam is true */
+                    // @codeCoverageIgnoreStart
+                    if ($currentValue === null) {
+                        throw new \UnexpectedValueException("Parameter {$currentLongName} expects a values");
+                    } //if
+                    // @codeCoverageIgnoreEnd
+
+                } elseif ($currentValue !== null) {
+                    throw new \UnexpectedValueException("Parameter {$currentLongName} does not accept values");
+
+                } else {
+                    $currentValue = true;
+                } //if
+
+                if ($allowsMultiple) {
+                    if ($allowsValue) {
+                        if (!isset($this->params[$currentLongName])) {
+                            $this->params[$currentLongName] = array();
+                        } //if
+
+                        $this->params[$currentLongName][] = $currentValue;
+
+                    } else {
+                        if (!isset($this->params[$currentLongName])) {
+                            $this->params[$currentLongName] = 0;
+                        } //if
+
+                        $this->params[$currentLongName]++;
+
+                    } //if
+
+                } else {
+                    $this->params[$currentLongName] = $currentValue;
+                } //if
+
+                foreach ($multiShortParams as $shortName) {
+                    $argumentStack[] = "-{$shortName}";
+                    $argumentsLeft++;
+                } //foreach
+
+                /* reset stuff for next param */
+                $expectingValue = false;
+                $currentLongName = null;
+                $currentValue = null;
+                $addParam = false;
+                $multiShortParams = array();
+
+            } //if
+
+        } //while
+
+        if ($expectingValue !== false) {
+            throw new \UnexpectedValueException("Parameter {$currentLongName} expects a values");
+        } //if
+
+        /* Not sure how this could happen */
+        // @codeCoverageIgnoreStart
+        if ($currentLongName !== null ||
+            $addParam !== false ||
+            $currentValue !== null ||
+            sizeof($multiShortParams) !== 0) {
+            throw new \UnexpectedValueException("Unable to process some parameters");
+        } //if
+        // @codeCoverageIgnoreEnd
+
+        if ($trailingValues !== "") {
+            $this->trailingValues = substr($trailingValues, 1); // remove extra space at the begging
+        } //if
+
+        $this->parsed = true;
+    } // parseParams()
+}
diff --git a/Classes/CommandLineArgumentDefinition.php b/Classes/CommandLineArgumentDefinition.php
new file mode 100644
index 0000000..2b2e8fa
--- /dev/null
+++ b/Classes/CommandLineArgumentDefinition.php
@@ -0,0 +1,389 @@
+<?php
+/**
+ * Defines list and formats of command line arguments
+ *
+ * @author Patrick Forget <patforg at webtrendi.com>
+ */
+
+namespace Clapp;
+
+/**
+ * Defines list and formats of command line arguments
+ *
+ * @author Patrick Forget <patforg at webtrendi.com>
+ */
+class CommandLineArgumentDefinition
+{
+
+    /**
+     * @var array
+     */
+    private $definitions = array();
+
+    /**
+     * long names as keys and array of properties as values
+     *
+     * properties are as follows
+     * * string "shortName" one letter char to the corresponding short name
+     * * boolean "isMultipleAllowed" true if mutliple instances of the param are allowed
+     * * mixed "parameterType" false if paramters are not alloweda value,
+     *       otherwise a string with the value "integer" or "string"
+     * * string "description" description of the parameter
+     * @var array
+     */
+    private $longNames = array();
+    
+    /**
+     * list of short names as keys and their long name equivalent as values
+     * @var array
+     */
+    private $shortNames = array();
+
+    /**
+     * Flag if arguments have been parsed in to params
+     * @var boolean
+     */
+    private $isParsed = false;
+
+    /**
+     * class constructor
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param array $definitions contains list of allowed parameters
+     *     the key is the long name of the parameter followed by a pipe (|)
+     *     then a single character specifying the short name.
+     *
+     *     If the parameter allows for arguments then an equal sign (=)
+     *     follows and then the type of paramter.
+     *
+     *     Allowed types are either i, int or integer for integer  types
+     *     and s, str or string for string types.
+     *
+     *     If a parameter can appear more than once the last character of
+     *     the key should be a plus character (+).
+     *
+     *     The value of the entry is the definition of what the paramter
+     *     does.
+     */
+    public function __construct($definitions)
+    {
+        if (is_array($definitions)) {
+            $this->definitions = $definitions;
+        } //if
+    } // __construct()
+
+    /**
+     * checks if parameter is allowed
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string $name either short or long name of the parameter to check
+     *
+     * @return boolean true if definition exisits, false otherwise
+     */
+    public function paramExists($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        if (strlen($name) == 1) {
+            return isset($this->shortNames[$name]);
+        } else {
+            return isset($this->longNames[$name]);
+        } //if
+    } // paramExists($name)
+    
+
+    /**
+     * checks if parameter allows a value if so what type
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string $name either short or long name of the parameter to check
+     *
+     * @return boolean|string false doesn't allow value, The value "string" or "integer" depending which type it allows
+     */
+    public function allowsValue($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        $longName = (strlen($name) == 1 ? ( isset($this->shortNames[$name]) ? $this->shortNames[$name] : '') : $name);
+
+        if (isset($this->longNames[$longName])) {
+            return $this->longNames[$longName]['parameterType'] !== false ? true : false;
+        } else {
+            return false;
+        } //if
+    } // allowsValue()
+    
+    /**
+     * returns the type of value allowed
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    public function getValueType($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        $longName = (strlen($name) == 1 ? ( isset($this->shortNames[$name]) ? $this->shortNames[$name] : '') : $name);
+
+        if (isset($this->longNames[$longName]['parameterType'])
+            && $this->longNames[$longName]['parameterType'] !== false) {
+            return $this->longNames[$longName]['parameterType'];
+        } else {
+            return '';
+        } //if
+    } // getValueType()
+    
+
+    /**
+     * checks if pamultiple instance of parameter are allowed
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string $name either short or long name of the parameter to check
+     *
+     * @return boolean false if parameter doesn't allow multiple values, true if it does
+     */
+    public function allowsMultiple($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        $longName = (strlen($name) == 1 ? ( isset($this->shortNames[$name]) ? $this->shortNames[$name] : '') : $name);
+
+        if (isset($this->longNames[$longName])) {
+            return $this->longNames[$longName]['isMultipleAllowed'];
+        } else {
+            return false;
+        } //if
+    } // allowsMultiple()
+
+    /**
+     * retreive short name of a parameter using its long name
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string $name long name of the parameter to check
+     *
+     * @return string character of the short name or null if it doesn't exist
+     */
+    public function getShortName($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        if (isset($this->longNames[$name])) {
+            return $this->longNames[$name]['shortName'];
+        } else {
+            return null;
+        } //if
+    } // getShortName($name)
+    
+    /**
+     * retreive long name of a parameter using its short name
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string $name short name of the parameter to check
+     *
+     * @return string long name or null if it doesn't exist
+     */
+    public function getLongName($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        if (isset($this->shortNames[$name])) {
+            return $this->shortNames[$name];
+        } else {
+            return null;
+        } //if
+    } // getLongName($name)
+
+    /**
+     * retreive description of a paramter
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     *
+     * @param string $name either short or long name of the parameter to check
+     *
+     * @return string description or null if it doesn't exist
+     */
+    public function getDescription($name)
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        $longName = (strlen($name) == 1 ? ( isset($this->shortNames[$name]) ? $this->shortNames[$name] : '') : $name);
+
+        if (isset($this->longNames[$longName])) {
+            return $this->longNames[$longName]['description'];
+        } else {
+            return null;
+        } //if
+    } // getDescription()
+    
+    /**
+     * builds a usage definition based on definition of params
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    public function getUsage()
+    {
+        if (!$this->isParsed) {
+            $this->parseDefinitions();
+        } //if
+
+        /* build list of argument names and calculate
+           the first column width so we can pad to 
+           align definitions */
+        $firstCol = array();
+        $longestDef = 0;
+        foreach (array_keys($this->longNames) as $longName) {
+            ob_start();
+            echo "--{$longName}|-{$this->getShortName($longName)}";
+
+            if ($this->allowsValue($longName)) {
+                echo "={$this->getValueType($longName)}";
+            } //if
+
+            if ($this->allowsMultiple($longName)) {
+                echo "+";
+            } //if
+
+            $defLength = ob_get_length();
+
+            $longestDef = max($longestDef, $defLength);
+
+            $firstCol[$longName] = ob_get_contents();
+            ob_end_clean();
+
+        } //foreach
+
+        $firstColMaxWidth = $longestDef + 4;
+
+        ob_start();
+
+        foreach ($firstCol as $longName => $def) {
+            $currentDefLength = strlen($def);
+
+            $padding = str_repeat(" ", $firstColMaxWidth - $currentDefLength);
+
+            echo "{$def}{$padding}{$this->getDescription($longName)}", PHP_EOL;
+        } //foreach
+
+        echo PHP_EOL;
+
+        $usage = ob_get_contents();
+        ob_end_clean();
+        
+        return $usage;
+
+    } // getUsage()
+    
+
+    /**
+     * parses the definitions
+     *
+     * @author Patrick Forget <patforg at webtrendi.com>
+     */
+    protected function parseDefinitions()
+    {
+        foreach ($this->definitions as $nameDef => $description) {
+            $nameParts = explode("|", $nameDef);
+
+            if (sizeof($nameParts) !== 2) {
+                throw new \UnexpectedValueException("Unexpected argument name definition expecting \"longName|char\"");
+            } //if
+
+            $longName = $nameParts[0];
+            $isMulti = false;
+            $parameterType = false;
+
+            $shortNameLength = strlen($nameParts[1]);
+
+            if ($shortNameLength == 1) {
+                $shortName = $nameParts[1];
+            } else {
+                $secondChar = substr($nameParts[1], 1, 1);
+
+                switch ($secondChar) {
+                    case '=':
+                        $shortNameParts = explode("=", $nameParts[1]);
+
+                        $shortName = $shortNameParts[0];
+                        $parameterTypeString = $shortNameParts[1];
+
+                        if (substr($parameterTypeString, -1) === '+') {
+                            $isMulti = true;
+                            $parameterTypeString = substr($parameterTypeString, 0, -1); // remove trailing +
+                        } //if
+
+                        switch ($parameterTypeString) {
+                            case 'i':
+                            case 'int':
+                            case 'integer':
+                                $parameterType = 'integer';
+                                break;
+                            case 's':
+                            case 'str':
+                            case 'string':
+                                $parameterType = 'string';
+                                break;
+                            default:
+                                throw new \UnexpectedValueException("Expecting parameter type".
+                                   " to be either integer or string");
+                                break;
+                        } //switch
+
+                        break;
+                    case '+':
+                        if ($shortNameLength > 2) {
+                            throw new \UnexpectedValueException("Multiple flag charachter (+)".
+                               " should be last character in definition");
+                        } //if
+
+                        $shortName = substr($nameParts[1], 0, 1);
+                        $isMulti = true;
+
+                        break;
+                    default:
+                        throw new \UnexpectedValueException("Expecting short name definition to be a single char");
+                        break;
+                } // switch
+
+            } //if
+
+            if (isset($this->longNames[$longName])) {
+                throw new \UnexpectedValueException("Cannot redefine long name {$longName}");
+            } //if
+
+            if (isset($this->shortNames[$shortName])) {
+                throw new \UnexpectedValueException("Cannot redefine short name {$shortName}");
+            } //if
+
+            $this->longNames[$longName] = array(
+                'shortName' => $shortName,
+                'isMultipleAllowed' => $isMulti,
+                'parameterType' => $parameterType,
+                'description' => $description
+            );
+
+            $this->shortNames[$shortName] = $longName;
+
+        } //foreach
+
+        $this->isParsed = true;
+    } // parseDefinitions()
+}
diff --git a/Classes/Curl.php b/Classes/Curl.php
new file mode 100644
index 0000000..45a70c7
--- /dev/null
+++ b/Classes/Curl.php
@@ -0,0 +1,23 @@
+<?php
+class curl {
+     
+      public function curlQuery() {
+      $ch = curl_init();
+
+      curl_setopt($ch, CURLOPT_URL, $this->url);
+      curl_setopt($ch, CURLOPT_HEADER, 0);
+      curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.6) Gecko/20070725 Firefox/2.0.0.6");
+      curl_setopt($ch, CURLOPT_RETURNTRANSFER, True);
+      curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
+      curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+      //curl_setopt($ch, CURLOPT_VERBOSE, true); // verbose mode for debugging
+
+      $json = curl_exec($ch);
+
+      curl_close($ch);
+
+      $array = json_decode($json, true);
+      return $array;
+      }
+}
+?>
\ No newline at end of file
diff --git a/GoStats.php b/GoStats.php
new file mode 100755
index 0000000..c1ba2c6
--- /dev/null
+++ b/GoStats.php
@@ -0,0 +1,401 @@
+#!/usr/bin/php
+<?php
+error_reporting(0);
+
+/***
+ * Configuration Settings - CHANGE THESE
+ */
+$url = ""; // URL of GoPhish listner e.g. http://www.site.com:8080/
+$key = ""; // GoPhish API key
+$pwd = "/opt/Pwdlyser/"; // /directory/containing/pwdlyser
+$geoip = true; // use freegeoip.net on IP addresses? set to false to disable this.
+
+/***
+ * Main program - Don't edit below
+ */
+echo "╔═╗┌─┐╔═╗┌┬┐┌─┐┌┬┐┌─┐\n║ ╦│ │╚═╗ │ ├─┤ │ └─┐ v1.0\n╚═╝└─┘╚═╝ ┴ ┴ ┴ ┴ └─┘\n";
+
+foreach (glob("Classes/*.php") as $filename)
+    include $filename;
+
+$definitions = new \Clapp\CommandLineArgumentDefinition(
+    array(
+        "help|h"            => "Shows help message",
+        "list|l"            => "List campaigns and their ID's",
+        "campaign|c=i"      => "Get campaign by id",
+        "dump|d=s"          => "Dump user:pass list to </path/to/file.txt>",
+        "training|t=s"      => "Dump list of users requiring training </path/to/file.txt>",
+        "all|a"             => "All of the below options",
+        "ips|i"             => "Top 10 IP's",
+        "useragent|u"       => "Top 10 user agents",
+        "attempts|m"        => "Top 10 attempts to log in",
+        "active|o"          => "Active times",
+        "speed|e"           => "Clickthrough speed",
+        "stats|s"           => "Victim statistics",
+        "pass|p"            => "Password analysis with pwdlyser",
+    )
+);
+
+$filter = new \Clapp\CommandArgumentFilter($definitions, $argv);
+
+if ($filter->getParam('h') === true || $argc < 2) {
+    fwrite(STDERR, $definitions->getUsage());
+    exit(0);
+} 
+
+/* Get list of campaigns */
+if ($filter->getParam("list") !== false) {
+	echo "[+] Getting data from server\n";
+	$curl = new curl();
+    $curl->url = "$url/api/campaigns/?api_key=$key";
+    $list = $curl->curlQuery();
+
+    if(isset($list->message) && $list->message == "Invalid API Key"){
+    	echo "[!] Invalid API key\n";
+    	exit(0);
+    }else{
+    	echo "[id] -campaign name-\n";
+    	foreach($list as $id)
+    		echo "[".$id['id']."] ".$id['name']."\n";
+    }
+    exit(0);
+} 
+
+/* Get campaign data */
+$campid = $filter->getParam('c');
+if ($campid == null || !is_numeric($campid)) {
+    echo "[!] Campaign ID not set\nn";
+    exit(0);
+}else{
+	echo "[+] Getting data from server\n";
+	$curl = new curl();
+    $curl->url = "$url/api/campaigns/$campid?api_key=$key";
+    $list = $curl->curlQuery();
+    if(isset($list->message) && $list->message == "Invalid API Key"){
+    	echo "[!] Invalid API key\n";
+    	exit(0);
+    }else{
+    	/* all data got correctly time to do stuff! */
+    	echo "[$campid] ".$list['name']."\n";
+    	echo "\n--- Notable times ---\n";
+
+        if(isset($list['launch_date']) && $list['launch_date'] <> ""){
+            $time = date('d-m-Y H:i', $datetime = strtotime(substr($list['launch_date'], 0, 10) . ' ' . substr($list['launch_date'], 11, 8 )));
+            echo "Campaign launched: $time\n";
+        }
+
+    	foreach($list['timeline'] as $record){
+    		if($record['message'] == "Email Sent"){
+    			$time = date('d-m-Y H:i', $datetime = strtotime(substr($record['time'], 0, 10) . ' ' . substr($record['time'], 11, 8 )));
+    			echo "First email sent: $time\n";
+    			break;
+    		}
+    	}
+        foreach($list['timeline'] as $record){
+            if($record['message'] == "Email Sent"){
+                $time = date('d-m-Y H:i', $datetime = strtotime(substr($record['time'], 0, 10) . ' ' . substr($record['time'], 11, 8 ))); 
+            }
+        }
+        echo "Last email sent:: $time\n";
+    	foreach($list['timeline'] as $record){
+    		if($record['message'] == "Clicked Link"){
+    			$time = date('d-m-Y H:i', $datetime = strtotime(substr($record['time'], 0, 10) . ' ' . substr($record['time'], 11, 8 )));
+    			echo "First email opened: $time\n";
+    			break;
+    		}
+    	}
+    	foreach($list['timeline'] as $record){
+    		if($record['message'] == "Clicked Link"){
+    			$time = date('d-m-Y H:i', $datetime = strtotime(substr($record['time'], 0, 10) . ' ' . substr($record['time'], 11, 8 )));
+    			echo "First page view: $time\n";
+    			break;
+    		}
+    	}
+    	foreach($list['timeline'] as $record){
+    		if($record['message'] == "Submitted Data"){
+    			$time = date('d-m-Y H:i', $datetime = strtotime(substr($record['time'], 0, 10) . ' ' . substr($record['time'], 11, 8 )));
+    			echo "First credentials submitted: $time\n";
+    			break;
+    		}
+    	}
+        if(isset($list['completed_date']) && $list['completed_date'] <> ""){
+            $time = date('d-m-Y H:i', $datetime = strtotime(substr($list['completed_date'], 0, 10) . ' ' . substr($list['completed_date'], 11, 8 )));
+            echo "Campaign finished: $time\n";
+        }
+    }
+} 
+
+/* Top 10 IP's */
+if ($filter->getParam("ips") !== false || $filter->getParam("all") !== false) {
+    $ips = array();
+    foreach($list['timeline'] as $item){
+    	if($item['details'] <> ""){
+    		$details = json_decode($item['details'], true);
+    		if($details['browser']['address'] !== "unknown")
+    			$ips[] = (string)$details['browser']['address'];
+    	}
+    }
+    $ips = array_count_values($ips);
+    arsort($ips);
+    $ips = array_slice($ips,0,10,true);
+    echo "\n--- Top 10 IP's ---\n";
+    foreach($ips as $ip=>$no){
+        $geoip_details = "";
+        if($geoip == true){
+            $geojson = file_get_contents("http://freegeoip.net/json/$ip");
+            $geodetails = json_decode($geojson, true);
+            $geoip_details = "- ".$geodetails['country_name'].", ".$geodetails['city'];
+        }
+    	echo "[$no] $ip $geoip_details\n";
+    }
+}
+
+/* Top 10 user agent's */
+if ($filter->getParam("useragent") !== false || $filter->getParam("all") !== false) {
+    $agents = array();
+    foreach($list['timeline'] as $item){
+    	if($item['details'] <> "" && $item['message'] == "Clicked Link"){ // only people who visited site, not email user agent
+    		$details = json_decode($item['details'], true);
+    		if($details['browser']['user-agent'] !== "unknown" && $details['browser']['user-agent'] !== "")
+    			$agents[] = (string)$details['browser']['user-agent'];
+    	}
+    }
+    $agents = array_count_values($agents);
+    arsort($agents);
+    $agents = array_slice($agents,0,10,true);
+    echo "\n--- Top 10 User Agents ---\n";
+    foreach($agents as $ua=>$no){
+    	echo "[$no] $ua\n";
+    }
+} 
+
+/* Top 10 attempts to log in */
+if($filter->getParam("attempts") !== false || $filter->getParam("all") !== false) {
+	$userids  = array();
+	foreach($list['results'] as $item){
+		$userids[$item['id']] = $item['email']; 
+    }
+    
+	$attemptrids = array();
+	foreach($list['timeline'] as $item){
+		if($item['details'] <> ""){
+    		$details = json_decode($item['details'], true);
+    		if(isset($details['payload']['password'][0]) && $details['payload']['password'][0] <> ""){
+    			$attemptrids[$details['payload']['rid'][0]] += 1;
+    		}
+    	}
+    }
+    arsort($attemptrids); 
+    $attemptrids = array_slice($attemptrids,0,10,true);
+    echo "\n--- Top 10 Login Attempts ---\n";
+    foreach($attemptrids as $id=>$amount){
+	    $newemail= preg_replace('/(?:^|.@).\K|.\.[^@]*$(*SKIP)(*F)|.(?=.*?\.)/', '*', $userids[$id]);
+	    echo "[$amount] $newemail\n";
+	}
+}
+
+/* Active times */
+if($filter->getParam("active") !== false || $filter->getParam("all") !== false) {
+    $active_count = array();
+    $active_percent = array();
+    $total = 0;
+    echo "\n--- Active times (hour, actions & percent) ---\n";
+    foreach($list['timeline'] as $item){
+        if($item['message'] != "Campaign Created" && $item['message'] != "Email Sent" ){
+            $hour = (int)substr($item['time'], 11, 2);
+            $active_count[$hour]++;
+            $total++;
+        }
+
+    }
+    foreach($active_count as $id => $count) // populate percentages
+        $active_percent[$id] = ($count / $total) * 100;
+
+    for($i = 0; $i <= 12; $i++){
+        $iDsp = str_pad($i, 2, " ", STR_PAD_LEFT);
+        $j = $i+12;
+        $user1 = str_pad($active_count[$i], 4, " ", STR_PAD_LEFT);
+        $user2 = str_pad($active_count[$j], 4, " ", STR_PAD_LEFT);
+        $percent1 = number_format($active_percent[$i], 2, '.', '');
+        $percent1 = str_pad($percent1, 5, " ", STR_PAD_LEFT);
+        $percent2 = number_format($active_percent[$j], 2, '.', '');
+        $percent2 = str_pad($percent2, 5, " ", STR_PAD_LEFT);
+        
+        echo "$iDsp - $user1 = $percent1%  |  $j - $user2 = $percent2% \n";
+    }
+}
+
+/* Clickthrough speed */
+if ($filter->getParam("speed") !== false || $filter->getParam("all") !== false) {
+    $speed_opened = array();
+    $speed_visited = array();
+    $speed_offset = array();
+    echo "\n--- Clickthrough Speed ---\n";
+    foreach($list['timeline'] as $item){
+        if($item['message'] == "Email Opened"){
+            $details = json_decode($item['details'], true);
+            $check_rid = $details['payload']['rid'][0];
+
+            $current_time = strtotime(substr($item['time'], 0, 10) . ' ' . substr($item['time'], 11, 8 ));
+            $existing_time = strtotime(substr($speed_opened[$check_rid], 0, 10) . ' ' . substr($speed_opened[$check_rid], 11, 8 ));
+
+            if(!isset($speed_opened[$check_rid]) || $existing_time > $current_time)
+                $speed_opened[$check_rid] = $item['time'];       
+        }
+        if($item['message'] == "Clicked Link"){
+            $details = json_decode($item['details'], true);
+            $check_rid = $details['payload']['rid'][0];
+            if(!isset($speed_visited[$check_rid]))
+                $speed_visited[$check_rid] = $item['time'];
+        }
+    }
+    foreach($speed_opened as $id=>$val){ // remove all the ones that didn't visit site
+        if(!isset($speed_visited[$id]))
+            unset($speed_opened[$id]);
+    }
+    foreach($speed_visited as $id=>$val){ // remove all the ones that didn't load email tracking image
+        if(!isset($speed_opened[$id]))
+            unset($speed_visited[$id]);
+    }
+    foreach($speed_opened as $id=>$val){ //calculate speed between reading email and clicking link
+        $date_opened = substr($val, 0, 10);
+        $time_opened = substr($val, 11, 8 );
+        $time_opened_stamp = strtotime($date_opened." ".$time_opened);
+
+        $date_visited = substr($speed_visited[$id], 0, 10);
+        $time_visited = substr($speed_visited[$id], 11, 8 );
+        $time_visited_stamp = strtotime($date_visited." ".$time_visited);
+
+        $diff = $time_visited_stamp - $time_opened_stamp;
+        if($diff > 0)
+            $speed_offset[$id] = $diff;
+    }
+
+    unset($speed_opened); // check me out doing memory management and cleaning up! :D
+    unset($speed_visited);
+
+    $quickest = min($speed_offset);
+    echo "Quickest click: $quickest sec\n";
+    $longest = max($speed_offset);
+    $longest = floor(($longest / 60) % 60);
+    echo "Longest click: $longest min\n";
+    $sec_5 = array_reduce($speed_offset, function ($a, $b){
+        return ($b <= 5) ? ++$a : $a; });
+    echo "Users clicked < 5 sec: $sec_5 \n";
+    $sec_30 = array_reduce($speed_offset, function ($a, $b){
+        return ($b <= 30) ? ++$a : $a; });
+    echo "Users clicked < 30 sec: $sec_30 \n";
+    $sec_60 = array_reduce($speed_offset, function ($a, $b){
+        return ($b <= 60) ? ++$a : $a; });
+    echo "Users clicked < 1 min: $sec_60 \n";
+}
+
+/* Victim statistics */
+if ($filter->getParam("stats") !== false || $filter->getParam("all") !== false) {
+	$status = array();
+	foreach($list['results'] as $item){
+		if($item['status'] <> ""){
+			$status[] = $item['status'];
+		}
+	}
+	echo "\n--- Victim Statistics ---\n";
+	$statusall = count($status);
+	$counts = array_count_values($status);
+	echo "Targets: ".$statusall."\n";
+	$openedpercent = ($counts['Email Opened'] / $statusall) * 100;
+	echo "Email opened: ".$counts['Email Opened']." (".round($openedpercent, 2)."%)\n";
+	$linkpercent = ($counts['Clicked Link'] / $statusall) * 100;
+	echo "Visited link: ".$counts['Clicked Link']." (".round($linkpercent, 2)."%)\n";
+	$subpercent = ($counts['Submitted Data'] / $statusall) * 100;
+	echo "Submitted data: ".$counts['Submitted Data']." (".round($subpercent, 2)."%)\n";
+	foreach($list['timeline'] as $item){
+		if($item['details'] <> ""){
+			$details = json_decode($item['details'], true);
+    		if($details['payload']['password'][0] <> "")
+    			$totalLoginAttempts++;
+		}
+	}
+    echo "Total login attempts: $totalLoginAttempts\n";
+}
+
+/* Pwdlyzer */
+if ($filter->getParam("pass") !== false || $filter->getParam("all") !== false) {
+	$username = array();
+	$password = array();
+	echo "\n--- Password Statistics ---\n";
+	foreach($list['timeline'] as $item){
+		if($item['details'] <> ""){
+    		$details = json_decode($item['details'], true);
+    		if($details['payload']['password'][0] <> ""){
+    			$username[] = $details['payload']['username'][0];
+    			$password[] =  $details['payload']['password'][0];
+    		}
+    	}
+	}
+	$tmpfname = tempnam("/tmp", "GoStats-");
+	$pwdfname = tempnam("/tmp", "GoStats-");
+	$handle = fopen($tmpfname, "w");
+	foreach($username as $id=>$user){
+		fwrite($handle, "$user:".$password[$id]."\n");
+	}
+	fclose($handle);
+	echo "[+] Launching pwdlyzer\n";
+	exec("cd $pwd && ./pwdlyser.py -p $tmpfname --all > $pwdfname");
+	unlink($tmpfname);
+	echo "[+] pwdlyzer results at: $pwdfname\n";
+}
+
+/* dump username:password list to file */
+$dumpfile = $filter->getParam('dump');
+if(file_exists($dumpfile)){
+	echo "[!] File already exists ($dumpfile)\n";
+    exit(0);
+}
+if(!file_exists($dumpfile) && isset($dumpfile)){
+	$username = array();
+	$password = array();
+	echo "\n--- Dumping username:password to file ---\n";
+	foreach($list['timeline'] as $item){
+		if($item['details'] <> ""){
+    		$details = json_decode($item['details'], true);
+    		if($details['payload']['password'][0] <> ""){
+    			$username[] = $details['payload']['username'][0];
+    			$password[] =  $details['payload']['password'][0];
+    		}
+    	}
+	}
+	$handle = fopen($dumpfile, "w");
+	foreach($username as $id=>$user){
+		fwrite($handle, "$user:".$password[$id]."\n");
+	}
+	fclose($handle);
+	echo "[+] File created: $dumpfile\n";
+}
+
+/* dump list of users requiring training */
+$dumpfile2 = $filter->getParam('training');
+if(file_exists($dumpfile2)){
+    echo "[!] File already exists ($dumpfile)\n";
+    exit(0);
+}
+if(!file_exists($dumpfile2) && isset($dumpfile2)){
+    $tusername = array();
+    $temail = array();
+    $tstatus = array();
+    echo "\n--- Dumping list of users requiring training ---\n";
+    foreach($list['results'] as $item){
+        if($item['status'] == "Submitted Data" || $item['status'] == "Clicked Link"){
+                $tusername[] = $item['first_name']." ".$item['last_name'];
+                $temail[] = $item['email'];
+                $tstatus[] = $item['status'];
+        }
+    }
+    $handle = fopen($dumpfile2, "w");
+    foreach($tusername as $id=>$user){
+        fwrite($handle, "$user, ".$temail[$id].", ".$tstatus[$id]."\n");
+    }
+    fclose($handle);
+    echo "[+] File created: $dumpfile2\n";
+}
+
+?>
diff --git a/README.md b/README.md
index 07e3b17..01f7b48 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,133 @@
 GoStats
 ===============
 
-Get statistics from GoPhish campaign
\ No newline at end of file
+Get statistics from GoPhish campaigns
+
+Requirements
+===============
+Pwdlyser - [https://github.com/ins1gn1a/Pwdlyser](https://github.com/ins1gn1a/Pwdlyser)
+
+Installation and Usage
+===============
+git clone the repo
+
+    chmod +x ./GoStats.php
+
+Modify **GoStats.php** to contain your gophish URL, API key and path to pwdlyser
+
+    ./GoStats.php
+
+Example Output
+===============
+
+    root[/opt/GoStats]: ./GoStats.php
+    ╔═╗┌─┐╔═╗┌┬┐┌─┐┌┬┐┌─┐
+    ║ ╦│ │╚═╗ │ ├─┤ │ └─┐ v1.0
+    ╚═╝└─┘╚═╝ ┴ ┴ ┴ ┴ └─┘
+    --help|-h                Shows help message
+    --list|-l                List campaigns and their ID's
+    --campaign|-c=integer    Get campaign by id
+    --dump|-d=string         Dump user:pass list to </path/to/file.txt>
+    --training|-t=string     Dump list of users requiring training </path/to/file.txt>
+    --all|-a                 All of the below options
+    --ips|-i                 Top 10 IP's
+    --useragent|-u           Top 10 user agents
+    --attempts|-m            Top 10 attempts to log in
+    --active|-o              Active times
+    --speed|-e               Clickthrough speed
+    --stats|-s               Victim statistics
+    --pass|-p                Password analysis with pwdlyser
+
+    root[/opt/GoStats]: ./GoStats.php -l
+    ╔═╗┌─┐╔═╗┌┬┐┌─┐┌┬┐┌─┐
+    ║ ╦│ │╚═╗ │ ├─┤ │ └─┐ v1.0
+    ╚═╝└─┘╚═╝ ┴ ┴ ┴ ┴ └─┘
+    [+] Getting data from server
+    [id] -campaign name-
+    [33] Campaign_01
+    [60] Campaign_02
+
+    root[/opt/GoStats]: ./GoStats.php -c 60 -a
+    ╔═╗┌─┐╔═╗┌┬┐┌─┐┌┬┐┌─┐
+    ║ ╦│ │╚═╗ │ ├─┤ │ └─┐ v1.0
+    ╚═╝└─┘╚═╝ ┴ ┴ ┴ ┴ └─┘
+    [+] Getting data from server
+    [60] Campaign_02
+
+    [+] Notable times
+    Campaign launched: 16-10-2017 09:28
+    First email sent: 16-10-2017 10:20
+    Last email sent: 16-10-2017 10:25
+    First email opened: 16-10-2017 10:28
+    First page view: 16-10-2017 10:28
+    First credentials submitted: 16-10-2017 10:29
+    Campaign finished: 21-10-2017 10:09
+
+    [+] Top 10 IPs
+    [177] 130.***.**.50 - United Kingdom, London
+    [96] 212.***.**.69 - France, 
+    [41] 86.**.**.2 - United Kingdom, Edgware
+    [32] 193.***.**.190 - United Kingdom, 
+    [28] 205.***.**.189 - United States, Chesterfield
+    [19] 86.***.***.47 - United Kingdom, Gillingham
+    [15] 82.**.**.34 - United Kingdom, Bradford-on-Avon
+    [14] 24.**.***.62 - United States, New York
+    [9] 66.**.**.130 - United States, West Jordan
+    [7] 2.**.**.183 - United Kingdom, London
+
+    [+] Top 10 User Agents
+    [60] WebClient/1.0
+    [32] Mozilla/4.0 (redacted details)
+    [30] Mozilla/4.0 (redacted details)
+    [29] Mozilla/4.0 (redacted details)
+    [26] Mozilla/4.0 (redacted details)
+    [26] Mozilla/4.0 (redacted details)
+    [22] Mozilla/4.0 (redacted details)
+    [20] Mozilla/5.0 (redacted details) Chrome/61.0.3163.100 Safari/537.36
+    [18] Mozilla/4.0 (redacted details)
+    [16] Mozilla/4.0 (redacted details) AppleWebKit/603.3.8 
+
+    [+] Top 10 Login Attempts
+    [19] b**********s@w***n.com
+    [12] b***************o@w***n.com
+    [8] n*************e@g*********e.com
+    [7] c****a@w***n.com
+    [7] s***************d@g*********e.com
+    [7] e**********r@w***n.com
+    [6] c*********m@w***n.com
+    [5] d******j@w***n.com
+    [3] j********d@a********l.com
+    [3] c***********n@a********l.com
+
+    [+] Active times (hour, actions & percent)
+     0 -      =  0.00%  |  12 -   28 =  5.47% 
+     1 -      =  0.00%  |  13 -   22 =  4.30% 
+     2 -      =  0.00%  |  14 -   31 =  6.05% 
+     3 -      =  0.00%  |  15 -   25 =  4.88% 
+     4 -      =  0.00%  |  16 -   53 = 10.35% 
+     5 -    1 =  0.20%  |  17 -    2 =  0.39% 
+     6 -    5 =  0.98%  |  18 -   18 =  3.52% 
+     7 -    9 =  1.76%  |  19 -    8 =  1.56% 
+     8 -   21 =  4.10%  |  20 -      =  0.00% 
+     9 -  134 = 26.17%  |  21 -    4 =  0.78% 
+    10 -  125 = 24.41%  |  22 -    1 =  0.20% 
+    11 -   24 =  4.69%  |  23 -    1 =  0.20% 
+    12 -   28 =  5.47%  |  24 -      =  0.00%
+
+    [+] Clickthrough Speed
+    Quickest click: 2 sec
+    Longest click: 18 min
+    Users clicked < 5 sec: 7
+    Users clicked < 30 sec: 11
+    Users clicked < 1 min: 16
+
+    [+] Victim Statistics
+    Targets: 136
+    Email opened: 15 (11.03%)
+    Visited link: 20 (14.71%)
+    Submitted data: 20 (14.71%)
+    Total login attempts: 96
+
+    [+] Password Statistics
+    [+] Launching pwdlyzer
+    [!] pwdlyzer results at: /tmp/GoStats-VpGHth
\ No newline at end of file