<?php

namespace Nur\Components\Builder\Providers;

use DateTime;
use Nur\Components\Builder\Html;
use Nur\Uri\Uri;

class FormProvider
{
    /**
     * The URI generator instance.
     *
     * @var \Nur\Uri\Uri;
     */
    protected $uri;

    /**
     * The HTML builder instance.
     *
     * @var \Nur\Components\HtmlBuilder
     */
    protected $html;

    /**
     * The CSRF token used by the form builder.
     *
     * @var string
     */
    protected $csrfToken;

    /**
     * An array of label names we've created.
     *
     * @var array
     */
    protected $labels = [];

    /**
     * The reserved form open attributes.
     *
     * @var array
     */
    protected $reserved = ['method', 'url', 'route', 'action', 'files'];

    /**
     * The form methods that should be spoofed, in uppercase.
     *
     * @var array
     */
    protected $spoofedMethods = ['DELETE', 'PATCH', 'PUT'];

    /**
     * The types of inputs to not fill values on by default.
     *
     * @var array
     */
    protected $skipValueTypes = ['file', 'password', 'checkbox', 'radio'];

    /**
     * Input Type.
     *
     * @var null
     */
    protected $type = null;

    /**
     * Create a new form builder instance.
     *
     * @param Uri    $uri
     * @param Html   $html
     * @param string $csrfToken
     *
     * @return void
     */
    public function __construct(Uri $uri, Html $html, $csrfToken)
    {
        $this->uri = $uri;
        $this->html = $html;
        $this->csrfToken = $csrfToken;
    }

    /**
     * Open up a new HTML form.
     *
     * @param  array $options
     *
     * @return string
     */
    public function open(array $options = [])
    {
        $method = array_get($options, 'method', 'post');

        // We need to extract the proper method from the attributes. If the method is
        // something other than GET or POST we'll use POST since we will spoof the
        // actual method since forms don't support the reserved methods in HTML.
        $attributes['method'] = $this->getMethod($method);

        $attributes['action'] = (isset($options['action']) ? $options['action'] : '');

        $attributes['accept-charset'] = (isset($options['accept-charset']) ? $options['accept-charset'] : 'UTF-8');

        // If the method is PUT, PATCH or DELETE we will need to add a spoofer hidden
        // field that will instruct the Symfony request to pretend the method is a
        // different method than it actually is, for convenience from the forms.
        $append = $this->getAppendage($method);

        if (isset($options['files']) && $options['files']) {
            $options['enctype'] = 'multipart/form-data';
        }

        // Finally we're ready to create the final form HTML field. We will attribute
        // format the array of attributes. We will also add on the appendage which
        // is used to spoof requests for this PUT, PATCH, etc. methods on forms.
        $attributes = array_merge(
            $attributes, array_except($options, $this->reserved)
        );

        // Finally, we will concatenate all of the attributes into a single string so
        // we can build out the final form open statement. We'll also append on an
        // extra value for the hidden _method field if it's needed for the form.
        $attributes = $this->html->attributes($attributes);

        return $this->toHtmlString('<form' . $attributes . '>' . $append);
    }

    /**
     * Close the current form.
     *
     * @return string
     */
    public function close()
    {
        $this->labels = [];

        return $this->toHtmlString('</form>');
    }

    /**
     * Generate a hidden field with the current CSRF token.
     *
     * @return string
     */
    public function token()
    {
        $token = ! empty($this->csrfToken) ? $this->csrfToken : csrfToken();

        return $this->hidden('_token', $token);
    }

    /**
     * Create a form label element.
     *
     * @param  string $name
     * @param  string $value
     * @param  array  $options
     * @param  bool   $escape_html
     *
     * @return string
     */
    public function label($name, $value = null, $options = [], $escape_html = true)
    {
        $this->labels[] = $name;

        $options = $this->html->attributes($options);

        $value = $this->formatLabel($name, $value);

        if ($escape_html) {
            $value = $this->html->entities($value);
        }

        return $this->toHtmlString('<label for="' . $name . '"' . $options . '>' . $value . '</label>');
    }

    /**
     * Create a form input field.
     *
     * @param  string $type
     * @param  string $name
     * @param  string $value
     * @param  array  $options
     *
     * @return string
     */
    public function input($type, $name, $value = null, $options = [])
    {
        $this->type = $type;

        if (! isset($options['name'])) {
            $options['name'] = $name;
        }

        // We will get the appropriate value for the given field. We will look for the
        // value in the session for the value in the old input data then we'll look
        // in the model instance if one is set. Otherwise we will just use empty.
        $id = $this->getIdAttribute($name, $options);

        if (! in_array($type, $this->skipValueTypes)) {
            $value = $this->getValueAttribute($name, $value);
        }

        // Once we have the type, value, and ID we can merge them into the rest of the
        // attributes array so we can convert them into their HTML attribute format
        // when creating the HTML element. Then, we will return the entire input.
        $merge = compact('type', 'value', 'id');

        $options = array_merge($options, $merge);

        return $this->toHtmlString('<input' . $this->html->attributes($options) . '>');
    }

    /**
     * Create a text input field.
     *
     * @param  string $name
     * @param  string $value
     * @param  array  $options
     *
     * @return string
     */
    public function text($name, $value = null, $options = [])
    {
        return $this->input('text', $name, $value, $options);
    }

    /**
     * Create a password input field.
     *
     * @param  string $name
     * @param  array  $options
     *
     * @return string
     */
    public function password($name, $options = [])
    {
        return $this->input('password', $name, '', $options);
    }

    /**
     * Create a hidden input field.
     *
     * @param  string $name
     * @param  string $value
     * @param  array  $options
     *
     * @return string
     */
    public function hidden($name, $value = null, $options = [])
    {
        return $this->input('hidden', $name, $value, $options);
    }

    /**
     * Create a search input field.
     *
     * @param  string $name
     * @param  string $value
     * @param  array  $options
     *
     * @return string
     */
    public function search($name, $value = null, $options = [])
    {
        return $this->input('search', $name, $value, $options);
    }

    /**
     * Create an e-mail input field.
     *
     * @param  string $name
     * @param  string $value
     * @param  array  $options
     *
     * @return string
     */
    public function email($name, $value = null, $options = [])
    {
        return $this->input('email', $name, $value, $options);
    }

    /**
     * Create a tel input field.
     *
     * @param  string $name
     * @param  string $value
     * @param  array  $options
     *
     * @return string
     */
    public function tel($name, $value = null, $options = [])
    {
        return $this->input('tel', $name, $value, $options);
    }

    /**
     * Create a number input field.
     *
     * @param  string $name
     * @param  string $value
     * @param  array  $options
     *
     * @return string
     */
    public function number($name, $value = null, $options = [])
    {
        return $this->input('number', $name, $value, $options);
    }

    /**
     * Create a date input field.
     *
     * @param  string $name
     * @param  string $value
     * @param  array  $options
     *
     * @return string
     */
    public function date($name, $value = null, $options = [])
    {
        if ($value instanceof DateTime) {
            $value = $value->format('Y-m-d');
        }

        return $this->input('date', $name, $value, $options);
    }

    /**
     * Create a datetime input field.
     *
     * @param  string $name
     * @param  string $value
     * @param  array  $options
     *
     * @return string
     */
    public function datetime($name, $value = null, $options = [])
    {
        if ($value instanceof DateTime) {
            $value = $value->format(DateTime::RFC3339);
        }

        return $this->input('datetime', $name, $value, $options);
    }

    /**
     * Create a datetime-local input field.
     *
     * @param  string $name
     * @param  string $value
     * @param  array  $options
     *
     * @return string
     */
    public function datetimeLocal($name, $value = null, $options = [])
    {
        if ($value instanceof DateTime) {
            $value = $value->format('Y-m-d\TH:i');
        }

        return $this->input('datetime-local', $name, $value, $options);
    }

    /**
     * Create a time input field.
     *
     * @param  string $name
     * @param  string $value
     * @param  array  $options
     *
     * @return string
     */
    public function time($name, $value = null, $options = [])
    {
        return $this->input('time', $name, $value, $options);
    }

    /**
     * Create a url input field.
     *
     * @param  string $name
     * @param  string $value
     * @param  array  $options
     *
     * @return string
     */
    public function url($name, $value = null, $options = [])
    {
        return $this->input('url', $name, $value, $options);
    }

    /**
     * Create a file input field.
     *
     * @param  string $name
     * @param  array  $options
     *
     * @return string
     */
    public function file($name, $options = [])
    {
        return $this->input('file', $name, null, $options);
    }

    /**
     * Create a textarea input field.
     *
     * @param  string $name
     * @param  string $value
     * @param  array  $options
     *
     * @return string
     */
    public function textarea($name, $value = null, $options = [])
    {
        $this->type = 'textarea';

        if (! isset($options['name'])) {
            $options['name'] = $name;
        }

        // Next we will look for the rows and cols attributes, as each of these are put
        // on the textarea element definition. If they are not present, we will just
        // assume some sane default values for these attributes for the developer.
        $options = $this->setTextAreaSize($options);

        $options['id'] = $this->getIdAttribute($name, $options);

        $value = (string)$this->getValueAttribute($name, $value);

        unset($options['size']);

        // Next we will convert the attributes into a string form. Also we have removed
        // the size attribute, as it was merely a short-cut for the rows and cols on
        // the element. Then we'll create the final textarea elements HTML for us.
        $options = $this->html->attributes($options);

        return $this->toHtmlString('<textarea' . $options . '>' . e($value) . '</textarea>');
    }

    /**
     * Create a select box field.
     *
     * @param  string $name
     * @param  array  $list
     * @param  string $selected
     * @param  array  $selectAttributes
     * @param  array  $optionsAttributes
     *
     * @return string
     */
    public function select(
        $name,
        $list = [],
        $selected = null,
        array $selectAttributes = [],
        array $optionsAttributes = []
    ) {
        $this->type = 'select';

        // When building a select box the "value" attribute is really the selected one
        // so we will use that when checking the model or session for a value which
        // should provide a convenient method of re-populating the forms on post.
        $selected = $this->getValueAttribute($name, $selected);

        $selectAttributes['id'] = $this->getIdAttribute($name, $selectAttributes);

        if (! isset($selectAttributes['name'])) {
            $selectAttributes['name'] = $name;
        }

        // We will simply loop through the options and build an HTML value for each of
        // them until we have an array of HTML declarations. Then we will join them
        // all together into one single HTML element that can be put on the form.
        $html = [];

        if (isset($selectAttributes['placeholder'])) {
            $html[] = $this->placeholderOption($selectAttributes['placeholder'], $selected);
            unset($selectAttributes['placeholder']);
        }

        foreach ($list as $value => $display) {
            $optionAttributes = isset($optionsAttributes[$value]) ? $optionsAttributes[$value] : [];
            $html[] = $this->getSelectOption($display, $value, $selected, $optionAttributes);
        }

        // Once we have all of this HTML, we can join this into a single element after
        // formatting the attributes into an HTML "attributes" string, then we will
        // build out a final select statement, which will contain all the values.
        $selectAttributes = $this->html->attributes($selectAttributes);

        $list = implode('', $html);

        return $this->toHtmlString("<select{$selectAttributes}>{$list}</select>");
    }

    /**
     * Create a select range field.
     *
     * @param  string $name
     * @param  string $begin
     * @param  string $end
     * @param  string $selected
     * @param  array  $options
     *
     * @return string
     */
    public function selectRange($name, $begin, $end, $selected = null, $options = [])
    {
        $range = array_combine($range = range($begin, $end), $range);

        return $this->select($name, $range, $selected, $options);
    }

    /**
     * Create a select year field.
     *
     * @return mixed
     */
    public function selectYear()
    {
        return call_user_func_array([$this, 'selectRange'], func_get_args());
    }

    /**
     * Create a select month field.
     *
     * @param  string $name
     * @param  string $selected
     * @param  array  $options
     * @param  string $format
     *
     * @return string
     */
    public function selectMonth($name, $selected = null, $options = [], $format = '%B')
    {
        $months = [];

        foreach (range(1, 12) as $month) {
            $months[$month] = strftime($format, mktime(0, 0, 0, $month, 1));
        }

        return $this->select($name, $months, $selected, $options);
    }

    /**
     * Get the select option for the given value.
     *
     * @param  string $display
     * @param  string $value
     * @param  string $selected
     * @param  array  $attributes
     *
     * @return string
     */
    public function getSelectOption($display, $value, $selected, array $attributes = [])
    {
        if (is_array($display)) {
            return $this->optionGroup($display, $value, $selected, $attributes);
        }

        return $this->option($display, $value, $selected, $attributes);
    }

    /**
     * Create a checkbox input field.
     *
     * @param  string $name
     * @param  mixed  $value
     * @param  bool   $checked
     * @param  array  $options
     *
     * @return string
     */
    public function checkbox($name, $value = 1, $checked = null, $options = [])
    {
        return $this->checkable('checkbox', $name, $value, $checked, $options);
    }

    /**
     * Create a radio button input field.
     *
     * @param  string $name
     * @param  mixed  $value
     * @param  bool   $checked
     * @param  array  $options
     *
     * @return string
     */
    public function radio($name, $value = null, $checked = null, $options = [])
    {
        if (is_null($value)) {
            $value = $name;
        }

        return $this->checkable('radio', $name, $value, $checked, $options);
    }

    /**
     * Create a HTML reset input element.
     *
     * @param  string $value
     * @param  array  $attributes
     *
     * @return string
     */
    public function reset($value, $attributes = [])
    {
        return $this->input('reset', null, $value, $attributes);
    }

    /**
     * Create a HTML image input element.
     *
     * @param  string $name
     * @param  string $file
     * @param  array  $attributes
     * @param  bool   $secure
     *
     * @return string
     */
    public function image($name, $file, $attributes = [], $secure = null)
    {
        $attributes['src'] = $this->uri->assets($file, $secure);

        return $this->input('image', $name, null, $attributes);
    }

    /**
     * Create a color input field.
     *
     * @param  string $name
     * @param  string $value
     * @param  array  $options
     *
     * @return string
     */
    public function color($name, $value = null, $options = [])
    {
        return $this->input('color', $name, $value, $options);
    }

    /**
     * Create a submit button element.
     *
     * @param  string $value
     * @param  array  $options
     *
     * @return string
     */
    public function submit($value = null, $options = [])
    {
        return $this->input('submit', null, $value, $options);
    }

    /**
     * Create a button element.
     *
     * @param  string $value
     * @param  array  $options
     *
     * @return string
     */
    public function button($value = null, $options = [])
    {
        if (! array_key_exists('type', $options)) {
            $options['type'] = 'button';
        }

        return $this->toHtmlString('<button' . $this->html->attributes($options) . '>' . $value . '</button>');
    }

    /**
     * Get the ID attribute for a field name.
     *
     * @param  string $name
     * @param  array  $attributes
     *
     * @return string
     */
    public function getIdAttribute($name, $attributes)
    {
        if (array_key_exists('id', $attributes)) {
            return $attributes['id'];
        }

        if (in_array($name, $this->labels)) {
            return $name;
        }
    }

    /**
     * Get the value that should be assigned to the field.
     *
     * @param  string $name
     * @param  string $value
     *
     * @return mixed
     */
    public function getValueAttribute($name, $value = null)
    {
        if (is_null($name)) {
            return $value;
        }

        if (! is_null($value)) {
            return $value;
        }
    }

    /**
     * Format the label value.
     *
     * @param  string      $name
     * @param  string|null $value
     *
     * @return string
     */
    protected function formatLabel($name, $value)
    {
        return $value ?: ucwords(str_replace('_', ' ', $name));
    }

    /**
     * Set the text area size on the attributes.
     *
     * @param  array $options
     *
     * @return array
     */
    protected function setTextAreaSize($options)
    {
        if (isset($options['size'])) {
            return $this->setQuickTextAreaSize($options);
        }

        // If the "size" attribute was not specified, we will just look for the regular
        // columns and rows attributes, using sane defaults if these do not exist on
        // the attributes array. We'll then return this entire options array back.
        $cols = array_get($options, 'cols', 50);

        $rows = array_get($options, 'rows', 10);

        return array_merge($options, compact('cols', 'rows'));
    }

    /**
     * Set the text area size using the quick "size" attribute.
     *
     * @param  array $options
     *
     * @return array
     */
    protected function setQuickTextAreaSize($options)
    {
        $segments = explode('x', $options['size']);

        return array_merge($options, ['cols' => $segments[0], 'rows' => $segments[1]]);
    }

    /**
     * Create an option group form element.
     *
     * @param  array  $list
     * @param  string $label
     * @param  string $selected
     * @param  array  $attributes
     *
     * @return string
     */
    protected function optionGroup($list, $label, $selected, array $attributes = [])
    {
        $html = [];

        foreach ($list as $value => $display) {
            $html[] = $this->option($display, $value, $selected, $attributes);
        }

        return $this->toHtmlString('<optgroup label="' . e($label) . '">' . implode('', $html) . '</optgroup>');
    }

    /**
     * Create a select element option.
     *
     * @param  string $display
     * @param  string $value
     * @param  string $selected
     * @param  array  $attributes
     *
     * @return string
     */
    protected function option($display, $value, $selected, array $attributes = [])
    {
        $selected = $this->getSelectedValue($value, $selected);

        $options = array_merge(['value' => $value, 'selected' => $selected], $attributes);

        return $this->toHtmlString('<option' . $this->html->attributes($options) . '>' . e($display) . '</option>');
    }

    /**
     * Create a placeholder select element option.
     *
     * @param $display
     * @param $selected
     *
     * @return string
     */
    protected function placeholderOption($display, $selected)
    {
        $selected = $this->getSelectedValue(null, $selected);

        $options = [
            'selected' => $selected,
            'value' => '',
        ];

        return $this->toHtmlString('<option' . $this->html->attributes($options) . '>' . e($display) . '</option>');
    }

    /**
     * Determine if the value is selected.
     *
     * @param  string $value
     * @param  string $selected
     *
     * @return null|string
     */
    protected function getSelectedValue($value, $selected)
    {
        if (is_array($selected)) {
            return in_array($value, $selected, true) ? 'selected' : null;
        }

        return ((string)$value == (string)$selected) ? 'selected' : null;
    }

    /**
     * Create a checkable input field.
     *
     * @param  string $type
     * @param  string $name
     * @param  mixed  $value
     * @param  bool   $checked
     * @param  array  $options
     *
     * @return string
     */
    protected function checkable($type, $name, $value, $checked, $options)
    {
        $this->type = $type;

        $checked = $this->getCheckedState($type, $name, $value, $checked);

        if ($checked) {
            $options['checked'] = 'checked';
        }

        return $this->input($type, $name, $value, $options);
    }

    /**
     * Get the check state for a checkable input.
     *
     * @param  string $type
     * @param  string $name
     * @param  mixed  $value
     * @param  bool   $checked
     *
     * @return bool
     */
    protected function getCheckedState($type, $name, $value, $checked)
    {
        switch ($type) {
            case 'checkbox':
                return $this->getCheckboxCheckedState($name, $value, $checked);

            case 'radio':
                return $this->getRadioCheckedState($name, $value, $checked);

            default:
                return $this->getValueAttribute($name) == $value;
        }
    }

    /**
     * Get the check state for a checkbox input.
     *
     * @param  string $name
     * @param  mixed  $value
     * @param  bool   $checked
     *
     * @return bool
     */
    protected function getCheckboxCheckedState($name, $value, $checked)
    {
        $posted = $this->getValueAttribute($name, $checked);

        if (is_array($posted)) {
            return in_array($value, $posted);
        } else {
            return (bool)$posted;
        }
    }

    /**
     * Get the check state for a radio input.
     *
     * @param  string $name
     * @param  mixed  $value
     * @param  bool   $checked
     *
     * @return bool
     */
    protected function getRadioCheckedState($name, $value, $checked)
    {
        return $this->getValueAttribute($name) == $value;
    }

    /**
     * Parse the form action method.
     *
     * @param  string $method
     *
     * @return string
     */
    protected function getMethod($method)
    {
        $method = strtoupper($method);

        return $method != 'GET' ? 'POST' : $method;
    }

    /**
     * Get the form appendage for the given method.
     *
     * @param  string $method
     *
     * @return string
     */
    protected function getAppendage($method)
    {
        list($method, $appendage) = [strtoupper($method), ''];

        // If the HTTP method is in this list of spoofed methods, we will attach the
        // method spoofer hidden input to the form. This allows us to use regular
        // form to initiate PUT and DELETE requests in addition to the typical.
        if (in_array($method, $this->spoofedMethods)) {
            $appendage .= $this->hidden('_method', $method);
        }

        // If the method is something other than GET we will go ahead and attach the
        // CSRF token to the form, as this can't hurt and is convenient to simply
        // always have available on every form the developers creates for them.
        if ($method != 'GET') {
            $appendage .= $this->token();
        }

        return $appendage;
    }

    /**
     * Transform the string to an Html serializable object
     *
     * @param $html
     *
     * @return string
     */
    protected function toHtmlString($html)
    {
        return ($html);
    }
}


<?php
namespace Gajus\Paggern;

/**
 * @link https://github.com/gajus/paggern for the canonical source repository
 * @license https://github.com/gajus/paggern/blob/master/LICENSE BSD 3-Clause
 */
class Lexer {
    const
        CLASS_UPPERCASE_UNAMBIGUOUS = 1;

    private
        /**
         * @var Psr\Log\LoggerInterface
         */
        $logger;

    /**
     * Tokeniser explodes input into components describing the properties expressed in the pattern.
     *
     * @param string $pattern
     * @param boolean $expand Augment token definition with the haystack of possible values.
     * @return array
     */
    public function tokenise ($pattern, $expand = false) {
        preg_match_all('
        /
                (?<class_U_explicit>\\\U)
                \{
                    (?<class_U_repetition>[0-9]+)
                \}
            |
                (?<class_U_implicit>\\\U)
            |
                \[
                    (?<range_token_explicit>[^]]+)
                \]
                \{
                    (?<range_repetition>[0-9]+)
                \}
            |
                \[
                    (?<range_token_implicit>[^]]+)
                \]
            |
                (?<literal_string>[^\\\[]+)

        /x
        ', $pattern, $matches, \PREG_SET_ORDER);

        $tokens = [];

        foreach ($matches as $match) {

            if (!empty($match['class_U_explicit'])) {
                $token = [
                    'type' => 'class',
                    'class' => static::CLASS_UPPERCASE_UNAMBIGUOUS,
                    'repetition' => (int) $match['class_U_repetition']
                ];

                if ($expand) {
                    $token['haystack'] = 'ABCDEFGHKMNOPRSTUVWXYZ23456789';
                }

                $tokens[] = $token;
            } else if (!empty($match['class_U_implicit'])) {
                $token = [
                    'type' => 'class',
                    'class' => static::CLASS_UPPERCASE_UNAMBIGUOUS,
                    'repetition' => 1
                ];

                if ($expand) {
                    $token['haystack'] = 'ABCDEFGHKMNOPRSTUVWXYZ23456789';
                }

                $tokens[] = $token;
            } else if (!empty($match['range_token_explicit'])) {
                $token = [
                    'type' => 'range',
                    'token' => $match['range_token_explicit'],
                    'repetition' => (int) $match['range_repetition']
                ];

                if ($expand) {
                    $token['haystack'] = static::expandRange($match['range_token_explicit']);
                }

                $tokens[] = $token;
            } else if (!empty($match['range_token_implicit'])) {
                $token = [
                    'type' => 'range',
                    'token' => $match['range_token_implicit'],
                    'repetition' => 1
                ];

                if ($expand) {
                    $token['haystack'] = static::expandRange($match['range_token_implicit']);
                }

                $tokens[] = $token;
            } else if (!empty($match['literal_string'])) {
                $tokens[] = [
                    'type' => 'literal',
                    'string' => $match['literal_string']
                ];
            }
        }

        return $tokens;
    }

    /**
     * @param string $range_definition Set of characters defined individually, using range or both.
     * @return string All characters that fit in the range.
     */
    static public function expandRange ($range_definition) {
        $haystack = preg_replace_callback('/(?<from>.)\-(?<to>.)/', function ($e) {
            if (is_numeric($e['from']) || is_numeric($e['to'])) {
                $from = $e['from'];
                $to = $e['to'];

                if ($from > $to) {
                    throw new Exception\LogicException('Invalid range definition. Start greater than end.');
                }

                $haystack = '';

                for ($from; $from <= $to; $from++) {
                    $haystack .= $from;
                }
            } else {
                $from = ord($e['from']);
                $to = ord($e['to']);

                if ($from > $to) {
                    throw new Exception\LogicException('Invalid range definition. Start greater than end.');
                }

                $haystack = '';

                for ($from; $from <= $to; $from++) {
                    $haystack .= chr($from);
                }
            }

            return $haystack;
        }, $range_definition);

        // Remove overlaping characters.
        $haystack = implode('', array_unique(str_split($haystack)));

        return $haystack;
    }
}


<?php
namespace Gajus\Paggern;

/**
 * @link https://github.com/gajus/paggern for the canonical source repository
 * @license https://github.com/gajus/paggern/blob/master/LICENSE BSD 3-Clause
 */
class Generator {
    private
        $generator;

    /**
     * @param RandomLib\Generator $generator
     */
    public function __construct (\RandomLib\Generator $generator = null) {
        if ($generator === null) {
            $factory = new \RandomLib\Factory;
            $this->generator = $factory->getGenerator(new \SecurityLib\Strength(\SecurityLib\Strength::MEDIUM));
        }
    }

    /**
     * Generate a set of random codes based on Paggern pattern.
     * Codes are guaranteed to be unique within the set.
     *
     * @param string $pattern Paggern pattern.
     * @param int $amount Number of codes to generate.
     * @param int $safeguard Number of additional codes generated in case there are duplicates that need to be replaced.
     * @return array
     */
    public function generateFromPattern ($pattern, $amount = 1, $safeguard = 100) {
        $lexer = new \Gajus\Paggern\Lexer();
        $tokens = $lexer->tokenise($pattern, true);

        $codes = array_fill(0, $amount + $safeguard, '');

        foreach ($tokens as &$token) {
            if ($token['type'] !== 'literal') {
                // Use RandomLib\Generator to populate token pool with random characters matching the pattern.
                // Pool is pre-generated for each token use. This is done to reduce the number of generator invocations.
                $token['pool'] = $this->generator->generateString($token['repetition'] * ($amount + $safeguard), $token['haystack']);
            }

            unset($token);
        }

        // Itterate through each code appending the value derived from the token.
        // In case of the range or class token, offset the value from the pre-generated pattern matching pool.
        foreach ($codes as $i => &$code) {
            foreach ($tokens as $token) {
                if ($token['type'] === 'literal') {
                    $code .= $token['string'];
                } else {
                    $code .= mb_substr($token['pool'], $token['repetition'] * $i, $token['repetition']);
                }
            }

            unset($code);
        }

        $codes = array_slice(array_unique($codes), 0, $amount);

        if (count($codes) < $amount) {
            throw new Exception\RuntimeException('Unique combination pool exhausted.');
        }

        return $codes;
    }
}


<?php

/*
 * This file is part of the Typhoon package.
 *
 * Copyright © 2013 Erin Millard
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Eloquent\Typhoon\CodeAnalysis\Issue;

use Eloquent\Typhax\Renderer\TypeRenderer;
use Eloquent\Typhoon\TypeCheck\TypeCheck;

class IssueRenderer implements IssueVisitorInterface
{
    /**
     * @param TypeRenderer|null $typeRenderer
     */
    public function __construct(TypeRenderer $typeRenderer = null)
    {
        $this->typeCheck = TypeCheck::get(__CLASS__, func_get_args());
        if (null === $typeRenderer) {
            $typeRenderer = new TypeRenderer;
        }

        $this->typeRenderer = $typeRenderer;
    }

    /**
     * @return TypeRenderer
     */
    public function typeRenderer()
    {
        $this->typeCheck->typeRenderer(func_get_args());

        return $this->typeRenderer;
    }

    /**
     * @param ClassIssue\MissingConstructorCall $issue
     *
     * @return string
     */
    public function visitMissingConstructorCall(ClassIssue\MissingConstructorCall $issue)
    {
        $this->typeCheck->visitMissingConstructorCall(func_get_args());

        return 'Incorrect or missing constructor initialization.';
    }

    /**
     * @param ClassIssue\MissingProperty $issue
     *
     * @return string
     */
    public function visitMissingProperty(ClassIssue\MissingProperty $issue)
    {
        $this->typeCheck->visitMissingProperty(func_get_args());

        return 'Incorrect or missing property definition.';
    }

    /**
     * @param MethodIssue\InadmissibleMethodCall $issue
     *
     * @return string
     */
    public function visitInadmissibleMethodCall(MethodIssue\InadmissibleMethodCall $issue)
    {
        $this->typeCheck->visitInadmissibleMethodCall(func_get_args());

        return 'Type check call should not be present.';
    }

    /**
     * @param MethodIssue\MissingMethodCall $issue
     *
     * @return string
     */
    public function visitMissingMethodCall(MethodIssue\MissingMethodCall $issue)
    {
        $this->typeCheck->visitMissingMethodCall(func_get_args());

        return 'Incorrect or missing type check call.';
    }

    /**
     * @param ParameterIssue\DefinedParameterVariableLength $issue
     *
     * @return string
     */
    public function visitDefinedParameterVariableLength(ParameterIssue\DefinedParameterVariableLength $issue)
    {
        $this->typeCheck->visitDefinedParameterVariableLength(func_get_args());

        return sprintf(
            'Variable-length parameter $%s should only be documented, not defined.',
            $issue->parameterName()
        );
    }

    /**
     * @param ParameterIssue\DocumentedParameterByReferenceMismatch $issue
     *
     * @return string
     */
    public function visitDocumentedParameterByReferenceMismatch(ParameterIssue\DocumentedParameterByReferenceMismatch $issue)
    {
        $this->typeCheck->visitDocumentedParameterByReferenceMismatch(func_get_args());

        if ($issue->isByReference()) {
            $nativeVariableType = 'by-reference';
            $documentedVariableType = 'by-value';
        } else {
            $nativeVariableType = 'by-value';
            $documentedVariableType = 'by-reference';
        }

        return sprintf(
            'Parameter $%s is defined as %s but documented as %s.',
            $issue->parameterName(),
            $nativeVariableType,
            $documentedVariableType
        );
    }

    /**
     * @param ParameterIssue\DocumentedParameterNameMismatch $issue
     *
     * @return string
     */
    public function visitDocumentedParameterNameMismatch(ParameterIssue\DocumentedParameterNameMismatch $issue)
    {
        $this->typeCheck->visitDocumentedParameterNameMismatch(func_get_args());

        return sprintf(
            'Documented parameter name $%s does not match defined parameter name $%s.',
            $issue->documentedParameterName(),
            $issue->parameterName()
        );
    }

    /**
     * @param ParameterIssue\DocumentedParameterTypeMismatch $issue
     *
     * @return string
     */
    public function visitDocumentedParameterTypeMismatch(ParameterIssue\DocumentedParameterTypeMismatch $issue)
    {
        $this->typeCheck->visitDocumentedParameterTypeMismatch(func_get_args());

        return sprintf(
            "Documented type '%s' is not correct for defined type '%s' of parameter $%s.",
            $issue->documentedType()->accept($this->typeRenderer()),
            $issue->type()->accept($this->typeRenderer()),
            $issue->parameterName()
        );
    }

    /**
     * @param ParameterIssue\DocumentedParameterUndefined $issue
     *
     * @return string
     */
    public function visitDocumentedParameterUndefined(ParameterIssue\DocumentedParameterUndefined $issue)
    {
        $this->typeCheck->visitDocumentedParameterUndefined(func_get_args());

        return sprintf(
            'Documented parameter $%s not defined.',
            $issue->parameterName()
        );
    }

    /**
     * @param ParameterIssue\UndocumentedParameter $issue
     *
     * @return string
     */
    public function visitUndocumentedParameter(ParameterIssue\UndocumentedParameter $issue)
    {
        $this->typeCheck->visitUndocumentedParameter(func_get_args());

        return sprintf(
            'Parameter $%s is not documented.',
            $issue->parameterName()
        );
    }

    private $typeRenderer;
    private $typeCheck;
}


<?php

/*
 * This file is part of the Typhoon package.
 *
 * Copyright © 2013 Erin Millard
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Eloquent\Typhoon\CodeAnalysis;

use Eloquent\Cosmos\ClassName;
use Eloquent\Typhax\Resolver\ObjectTypeClassNameResolver;
use Eloquent\Typhoon\ClassMapper\ClassDefinition;
use Eloquent\Typhoon\ClassMapper\ClassMapper;
use Eloquent\Typhoon\ClassMapper\MethodDefinition;
use Eloquent\Typhoon\Configuration\Configuration;
use Eloquent\Typhoon\Generator\ParameterListMerge\MergeTool;
use Eloquent\Typhoon\Parameter\ParameterList;
use Eloquent\Typhoon\Parser\ParameterListParser;
use Eloquent\Typhoon\Resolver\ParameterListClassNameResolver;
use Eloquent\Typhoon\TypeCheck\TypeCheck;
use Icecave\Pasta\AST\Type\AccessModifier;

class ProjectAnalyzer
{
    /**
     * @param ClassMapper|null         $classMapper
     * @param ParameterListParser|null $parameterListParser
     * @param MergeTool|null           $mergeTool
     */
    public function __construct(
        ClassMapper $classMapper = null,
        ParameterListParser $parameterListParser = null,
        MergeTool $mergeTool = null
    ) {
        $this->typeCheck = TypeCheck::get(__CLASS__, func_get_args());
        if (null === $classMapper) {
            $classMapper = new ClassMapper;
        }
        if (null === $parameterListParser) {
            $parameterListParser = new ParameterListParser;
        }
        if (null === $mergeTool) {
            $mergeTool = new MergeTool(false);
        }

        $this->classMapper = $classMapper;
        $this->parameterListParser = $parameterListParser;
        $this->mergeTool = $mergeTool;
    }

    /**
     * @return ClassMapper
     */
    public function classMapper()
    {
        $this->typeCheck->classMapper(func_get_args());

        return $this->classMapper;
    }

    /**
     * @return ParameterListParser
     */
    public function parameterListParser()
    {
        $this->typeCheck->parameterListParser(func_get_args());

        return $this->parameterListParser;
    }

    /**
     * @return MergeTool
     */
    public function mergeTool()
    {
        $this->typeCheck->mergeTool(func_get_args());

        return $this->mergeTool;
    }

    /**
     * @param Configuration $configuration
     *
     * @return AnalysisResult
     */
    public function analyze(Configuration $configuration)
    {
        $this->typeCheck->analyze(func_get_args());

        $facadeClassName = $configuration
            ->validatorNamespace()
            ->joinAtoms('TypeCheck')
        ;

        $sourcePaths = $configuration->sourcePaths();
        $issues = array();
        foreach ($this->classMapper()->classesByPaths($sourcePaths) as $classDefinition) {
            $this->analyzeClass(
                $configuration,
                $classDefinition,
                $facadeClassName,
                $issues
            );
        }

        return new AnalysisResult($issues);
    }

    /**
     * @param Configuration               $configuration
     * @param ClassDefinition             $classDefinition
     * @param ClassName                   $facadeClassName
     * @param array<Issue\IssueInterface> &$issues
     */
    protected function analyzeClass(
        Configuration $configuration,
        ClassDefinition $classDefinition,
        ClassName $facadeClassName,
        array &$issues
    ) {
        $this->typeCheck->analyzeClass(func_get_args());

        $expectedFacadeClassName = $classDefinition
            ->classNameResolver()
            ->shorten($facadeClassName)
        ;

        $constructorDefinition = null;
        $hasConstructorInit = false;
        $propertyName = 'typeCheck';
        if ($classDefinition->hasMethod('__construct')) {
            $constructorDefinition = $classDefinition->method('__construct');
            $hasConstructorInit = $this->methodHasInit(
                $constructorDefinition,
                $expectedFacadeClassName,
                $propertyName
            );
        }

        $hasNonStaticCalls = false;
        foreach ($classDefinition->methods() as $methodDefinition) {
            switch ($methodDefinition->name()) {
                case '__construct':
                case '__wakeup':
                    break;
                case '__destruct':
                case '__toString':
                    if (
                        $this->methodHasCall($methodDefinition, $propertyName) ||
                        $this->methodHasStaticCall($methodDefinition, $expectedFacadeClassName)
                    ) {
                        $issues[] = new Issue\MethodIssue\InadmissibleMethodCall(
                            $classDefinition,
                            $methodDefinition
                        );
                    }
                    break;
                case 'unserialize':
                    if ($this->classImplementsSerializable($classDefinition)) {
                        break;
                    }
                default:
                    if ($methodDefinition->isAbstract()) {
                        break;
                    }

                    $missingCall = false;
                    if ($methodDefinition->isStatic()) {
                        $missingCall = !$this->methodHasStaticCall($methodDefinition, $expectedFacadeClassName);
                    } else {
                        if ($this->methodHasCall($methodDefinition, $propertyName)) {
                            $hasNonStaticCalls = true;
                        } elseif (!$this->methodHasStaticCall($methodDefinition, $expectedFacadeClassName)) {
                            $missingCall = true;
                        }
                    }

                    if ($missingCall) {
                        $issues[] = new Issue\MethodIssue\MissingMethodCall(
                            $classDefinition,
                            $methodDefinition
                        );
                    }
            }

            if (!$methodDefinition->isAbstract()) {
                $methodReflector = $methodDefinition->createReflector();
                $blockComment = $methodReflector->getDocComment();
                if (false === $blockComment) {
                    $documentedParameterList = new ParameterList;
                } else {
                    $documentedParameterList = $this->parameterListParser()
                        ->parseBlockComment(
                            $classDefinition->className(),
                            $methodDefinition->name(),
                            $blockComment
                        )
                    ;
                }
                $documentedParameterList = $documentedParameterList->accept(
                    $this->createClassNameResolver($classDefinition)
                );
                $this->mergeTool()->clearIssues();
                $this->mergeTool()->merge(
                    $configuration,
                    $classDefinition,
                    $methodDefinition,
                    $documentedParameterList,
                    $this->parameterListParser()->parseReflector($methodReflector)
                );

                $issues = array_merge($issues, $this->mergeTool()->issues());
            }
        }

        if (
            ($hasNonStaticCalls && !$hasConstructorInit) ||
            (
                !$hasNonStaticCalls &&
                null !== $constructorDefinition &&
                !$this->methodHasConstructorStaticCall(
                    $constructorDefinition,
                    $expectedFacadeClassName
                )
            )
        ) {
            array_unshift(
                $issues,
                new Issue\ClassIssue\MissingConstructorCall(
                    $classDefinition
                )
            );
        }

        if ($hasConstructorInit) {
            if ($classDefinition->hasProperty($propertyName)) {
                $typhoonProperty = $classDefinition->property($propertyName);
                $missingProperty =
                    $typhoonProperty->isStatic() ||
                    AccessModifier::PRIVATE_() !== $typhoonProperty->accessModifier()
                ;
            } else {
                $missingProperty = true;
            }

            if ($missingProperty) {
                $issues[] = new Issue\ClassIssue\MissingProperty(
                    $classDefinition
                );
            }
        }
    }

    /**
     * @param MethodDefinition $methodDefinition
     * @param ClassName        $expectedFacadeClassName
     * @param string           &$propertyName
     *
     * @return boolean
     */
    protected function methodHasInit(
        MethodDefinition $methodDefinition,
        ClassName $expectedFacadeClassName,
        &$propertyName
    ) {
        $this->typeCheck->methodHasInit(func_get_args());

        $hasInit = false;
        $callPattern = sprintf(
            '/^\s*\$this\s*->\s*(%s)\s*=\s*%s\s*::\s*get\s*\(\s*__CLASS__\s*,\s*\\\\?func_get_args\s*\(\s*\)\s*\)\s*;$/',
            '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*', // PHP variable name
            preg_quote($expectedFacadeClassName->string(), '/')
        );

        $firstStatement = $this->parseFirstMethodStatement($methodDefinition->source());
        if (preg_match($callPattern, $firstStatement, $matches)) {
            $hasInit = true;
            $propertyName = $matches[1];
        }

        return $hasInit;
    }

    /**
     * @param MethodDefinition $methodDefinition
     * @param string           $propertyName
     *
     * @return boolean
     */
    protected function methodHasCall(
        MethodDefinition $methodDefinition,
        $propertyName
    ) {
        $this->typeCheck->methodHasCall(func_get_args());

        $hasCall = false;
        $expectedMethodName = $this->normalizeValidatorMethodName(
            $methodDefinition->name()
        );
        $callPattern = sprintf(
            '/^\s*\$this\s*->\s*%s\s*->\s*%s\s*\(\s*\\\\?func_get_args\s*\(\s*\)\s*\)\s*;$/',
            preg_quote($propertyName, '/'),
            preg_quote($expectedMethodName, '/')
        );

        $firstStatement = $this->parseFirstMethodStatement($methodDefinition->source());
        if (preg_match($callPattern, $firstStatement)) {
            $hasCall = true;
        }

        return $hasCall;
    }

    /**
     * @param MethodDefinition $methodDefinition
     * @param ClassName        $expectedFacadeClassName
     *
     * @return boolean
     */
    protected function methodHasStaticCall(
        MethodDefinition $methodDefinition,
        ClassName $expectedFacadeClassName
    ) {
        $this->typeCheck->methodHasStaticCall(func_get_args());

        $hasCall = false;
        $expectedMethodName = $this->normalizeValidatorMethodName(
            $methodDefinition->name()
        );
        $callPattern = sprintf(
            '/^\s*%s\s*::\s*get\s*\(\s*__CLASS__\s*\)\s*->\s*%s\s*\(\s*\\\\?func_get_args\s*\(\s*\)\s*\)\s*;$/',
            preg_quote($expectedFacadeClassName->string(), '/'),
            preg_quote($expectedMethodName, '/')
        );

        $firstStatement = $this->parseFirstMethodStatement($methodDefinition->source());
        if (preg_match($callPattern, $firstStatement)) {
            $hasCall = true;
        }

        return $hasCall;
    }

    /**
     * @param MethodDefinition $methodDefinition
     * @param ClassName        $expectedFacadeClassName
     *
     * @return boolean
     */
    protected function methodHasConstructorStaticCall(
        MethodDefinition $methodDefinition,
        ClassName $expectedFacadeClassName
    ) {
        $this->typeCheck->methodHasConstructorStaticCall(func_get_args());

        $hasCall = false;
        $callPattern = sprintf(
            '/^\s*%s\s*::\s*get\s*\(\s*__CLASS__\s*,\s*\\\\?func_get_args\s*\(\s*\)\s*\)\s*;$/',
            preg_quote($expectedFacadeClassName->string(), '/')
        );

        $firstStatement = $this->parseFirstMethodStatement($methodDefinition->source());
        if (preg_match($callPattern, $firstStatement)) {
            $hasCall = true;
        }

        return $hasCall;
    }

    /**
     * @param string $source
     *
     * @return string
     */
    protected function parseFirstMethodStatement($source)
    {
        $this->typeCheck->parseFirstMethodStatement(func_get_args());

        $tokens = token_get_all('<?php '.$source);
        array_shift($tokens);
        array_shift($tokens);

        $inModifiers = true;
        $inName = false;
        $inArguments = false;
        $parenthesisDepth = 0;
        $inBody = false;
        $inStatement = false;
        $statement = '';
        while ($token = next($tokens)) {
            $token = $this->normalizeToken($token);

            if ($inStatement) {
                $statement .= $token[1];
                if (';' === $token[0]) {
                    break;
                }
            } elseif ($inBody) {
                if (
                    T_WHITESPACE !== $token[0] &&
                    T_COMMENT !== $token[0] &&
                    T_DOC_COMMENT !== $token[0]
                ) {
                    $inStatement = true;
                    $statement .= $token[1];
                }
            } elseif ($inArguments) {
                if (')' === $token[0]) {
                    $parenthesisDepth --;
                } elseif ('(' === $token[0]) {
                    $parenthesisDepth ++;
                }

                if ('{' === $token[0] && 0 === $parenthesisDepth) {
                    $inBody = true;
                }
            } elseif ($inName) {
                if ('(' === $token[0]) {
                    $inArguments = true;
                    $parenthesisDepth ++;
                }
            } elseif ($inModifiers) {
                if (T_FUNCTION === $token[0]) {
                    $inName = true;
                }
            }
        }

        return $statement;
    }

    /**
     * @param string|tuple<integer,string,integer> $token
     *
     * @return tuple<integer|string,string,integer|null>
     */
    protected function normalizeToken($token)
    {
        $this->typeCheck->normalizeToken(func_get_args());

        if (is_array($token)) {
            return $token;
        }

        return array($token, $token, null);
    }

    /**
     * @param string $methodName
     *
     * @return string
     */
    protected function normalizeValidatorMethodName($methodName)
    {
        $this->typeCheck->normalizeValidatorMethodName(func_get_args());

        if ('__' === substr($methodName, 0, 2)) {
            $methodName = sprintf(
                'validate%s',
                ucfirst(substr($methodName, 2))
            );
        }

        return $methodName;
    }

    /**
     * @param ClassDefinition $classDefinition
     *
     * @return boolean
     */
    protected function classImplementsSerializable(ClassDefinition $classDefinition)
    {
        $this->typeCheck->classImplementsSerializable(func_get_args());

        return $classDefinition->createReflector()->implementsInterface('Serializable');
    }

    /**
     * @param ClassDefinition $classDefinition
     *
     * @return ParameterListClassNameResolver
     */
    protected function createClassNameResolver(ClassDefinition $classDefinition)
    {
        $this->typeCheck->createClassNameResolver(func_get_args());

        return new ParameterListClassNameResolver(
            new ObjectTypeClassNameResolver(
                $classDefinition->classNameResolver()
            )
        );
    }

    private $classMapper;
    private $parameterListParser;
    private $mergeTool;
    private $typeCheck;
}


<?php

namespace Harmony\Bundle\ThemeBundle\DependencyInjection;

use Exception;
use Harmony\Sdk\Theme\ThemeInterface;
use InvalidArgumentException;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\Yaml\Yaml;
use function array_keys;
use function dirname;
use function file_get_contents;

/**
 * Class HarmonyThemeExtension
 *
 * @package Harmony\Bundle\ThemeBundle\DependencyInjection
 */
class HarmonyThemeExtension extends Extension implements PrependExtensionInterface
{

    /**
     * Loads a specific configuration.
     *
     * @param array            $configs   An array of configuration values
     * @param ContainerBuilder $container A ContainerBuilder instance
     *
     * @throws InvalidArgumentException When provided tag is not defined in this extension
     * @throws Exception
     */
    public function load(array $configs, ContainerBuilder $container)
    {
        $loader = new Loader\YamlFileLoader($container, new FileLocator(dirname(__DIR__) . '/Resources/config'));
        $loader->load('services.yaml');

        $twigFilesystemLoaderDefinition = $container->getDefinition('twig.loader.filesystem');
        // register themes as Twig namespaces
        foreach ($container->getParameter('kernel.themes') as $namespace => $class) {
            /** @var ThemeInterface $themeClass */
            $themeClass = new $class();
            $twigFilesystemLoaderDefinition->addMethodCall('addPath', [$themeClass->getPath(), $namespace]);
        }
    }

    /**
     * Allow an extension to prepend the extension configurations.
     *
     * @param ContainerBuilder $container
     *
     * @throws Exception
     */
    public function prepend(ContainerBuilder $container)
    {
        // Generate a config array with the content of `liip_theme.yml` file
        $liipThemeConfig = Yaml::parse(file_get_contents(dirname(__DIR__) . '/Resources/config/liip_theme.yaml'));

        // Set available themes
        $liipThemeConfig['liip_theme']['themes'] = array_keys($container->getParameter('kernel.themes'));

        // Prepend the `liip_theme` settings
        $container->prependExtensionConfig('liip_theme', $liipThemeConfig['liip_theme']);

        // get all bundles
        $bundles = $container->getParameter('kernel.bundles');

        if (isset($bundles['HarmonySettingsManagerBundle'])) {
            // Generate a config array with the content of `settings_manager.yml` file
            $settings = Yaml::parse(file_get_contents(dirname(__DIR__) . '/Resources/config/settings_manager.yaml'));

            // Prepend the `harmony_settings_manager` settings
            $container->prependExtensionConfig('harmony_settings_manager', $settings['harmony_settings_manager']);
        }
    }
}


<?php declare(strict_types=1);

/**
 * @copyright (c) 2015-present brian ridley
 * @author brian ridley <ptlis@ptlis.net>
 * @license http://opensource.org/licenses/MIT MIT
 */

namespace ptlis\ShellCommand;

use ptlis\ShellCommand\Interfaces\CommandArgumentInterface;
use ptlis\ShellCommand\Interfaces\CommandBuilderInterface;
use ptlis\ShellCommand\Interfaces\CommandInterface;
use ptlis\ShellCommand\Interfaces\EnvironmentInterface;
use ptlis\ShellCommand\Interfaces\ProcessObserverInterface;
use ptlis\ShellCommand\Logger\AggregateLogger;
use ptlis\ShellCommand\Logger\NullProcessObserver;

/**
 * Immutable builder, used to create Commands.
 */
final class CommandBuilder implements CommandBuilderInterface
{
    /** @var EnvironmentInterface*/
    private $environment;

    /** @var string*/
    private $command;

    /** @var CommandArgumentInterface[] */
    private $argumentList = [];

    /** @var int */
    private $timeout = -1;

    /** @var int */
    private $pollTimeout = 1000;

    /** @var string */
    private $cwd = '';

    /** @var string|int[] */
    private $envVariableList = [];

    /** @var ProcessObserverInterface[] */
    private $observerList = [];

    public function __construct(
        EnvironmentInterface $environment = null
    ) {
        if (is_null($environment)) {
            $environment = $this->getEnvironment(PHP_OS);
        }

        $this->environment = $environment;
    }

    public function setCommand(string $command): CommandBuilderInterface
    {
        $newBuilder = clone $this;
        $newBuilder->command = $command;

        return $newBuilder;
    }

    public function addArgument(string $argument, bool $conditionalResult = true): CommandBuilderInterface
    {
        $newBuilder = clone $this;

        if ($conditionalResult) {
            $newBuilder->argumentList[] = new CommandArgumentEscaped($argument, $this->environment);
        }

        return $newBuilder;
    }

    public function addArguments(array $argumentList, bool $conditionalResult = true): CommandBuilderInterface
    {
        $newBuilder = clone $this;

        if ($conditionalResult) {
            foreach ($argumentList as $argument) {
                $newBuilder->argumentList[] = new CommandArgumentEscaped($argument, $this->environment);
            }
        }

        return $newBuilder;
    }

    public function addRawArgument(string $rawArgument, bool $conditionalResult = true): CommandBuilderInterface
    {
        $newBuilder = clone $this;

        if ($conditionalResult) {
            $newBuilder->argumentList[] = new CommandArgumentRaw($rawArgument);
        }

        return $newBuilder;
    }

    public function addRawArguments(array $rawArgumentList, bool $conditionalResult = true): CommandBuilderInterface
    {
        $newBuilder = clone $this;

        if ($conditionalResult) {
            foreach ($rawArgumentList as $rawArgument) {
                $newBuilder->argumentList[] = new CommandArgumentRaw($rawArgument);
            }
        }

        return $newBuilder;
    }

    public function setTimeout(int $timeout): CommandBuilderInterface
    {
        $newBuilder = clone $this;
        $newBuilder->timeout = $timeout;

        return $newBuilder;
    }

    public function setPollTimeout(int $pollTimeout): CommandBuilderInterface
    {
        $newBuilder = clone $this;
        $newBuilder->pollTimeout = $pollTimeout;

        return $newBuilder;
    }

    public function setCwd(string $cwd): CommandBuilderInterface
    {
        $newBuilder = clone $this;
        $newBuilder->cwd = $cwd;

        return $newBuilder;
    }

    public function addProcessObserver(ProcessObserverInterface $observer): CommandBuilderInterface
    {
        $observerList = $this->observerList;
        $observerList[] = $observer;

        $newBuilder = clone $this;
        $newBuilder->observerList = $observerList;

        return $newBuilder;
    }

    public function addEnvironmentVariable(
        string $key,
        string $value,
        bool $conditionalResult = true
    ): CommandBuilderInterface {
        $newBuilder = clone $this;

        if ($conditionalResult) {
            $envVariableList = $this->envVariableList;
            $envVariableList[$key] = $value;
            $newBuilder->envVariableList = $envVariableList;
        }

        return $newBuilder;
    }

    public function addEnvironmentVariables(array $envVars, bool $conditionalResult = true): CommandBuilderInterface
    {
        $newBuilder = clone $this;

        if ($conditionalResult) {
            $newBuilder->envVariableList = array_merge($newBuilder->envVariableList, $envVars);
        }

        return $newBuilder;
    }

    public function buildCommand(): CommandInterface
    {
        if (!$this->environment->validateCommand($this->command)) {
            throw new \RuntimeException('Invalid command "' . $this->command . '" provided to ' . __CLASS__ . '.');
        }

        $cwd = $this->cwd;
        if (!strlen($cwd)) {
            $cwd = getcwd();
        }

        return new Command(
            $this->environment,
            $this->getObserver(),
            $this->command,
            $this->argumentList,
            $cwd,
            $this->envVariableList,
            $this->timeout,
            $this->pollTimeout
        );
    }

    /**
     * If more than one observer is attached to the command then wrap them up in an Aggregate logger.
     *
     * This means that as far as calling code is called there's only one observer.
     */
    private function getObserver(): ProcessObserverInterface
    {
        if (1 === count($this->observerList)) {
            $observer = $this->observerList[0];
        } elseif (count($this->observerList)) {
            $observer = new AggregateLogger($this->observerList);
        } else {
            $observer = new NullProcessObserver();
        }

        return $observer;
    }

    /**
     * Build the correct Environment instance for the provided operating system.
     */
    public function getEnvironment(string $operatingSystem): EnvironmentInterface
    {
        $environmentList = [
            new UnixEnvironment()
        ];

        /** @var EnvironmentInterface $environment */
        $environment = null;
        foreach ($environmentList as $possibleEnvironment) {
            if (in_array($operatingSystem, $possibleEnvironment->getSupportedList())) {
                $environment = $possibleEnvironment;
            }
        }

        if (is_null($environment)) {
            throw new \RuntimeException(
                'Unable to find Environment for OS "' . $operatingSystem . '".'.
                'Try explicitly providing an Environment when instantiating the builder.'
            );
        }

        return $environment;
    }
}


<?php

namespace Harmony\Bundle\ThemeBundle\DependencyInjection;

use Harmony\Bundle\SettingsManagerBundle\Model\Type;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

/**
 * Class SettingsConfiguration
 *
 * @package Harmony\Bundle\ThemeBundle\DependencyInjection
 */
class SettingsConfiguration implements ConfigurationInterface
{

    /**
     * Generates the configuration tree builder.
     *
     * @see \Harmony\Bundle\SettingsManagerBundle\DependencyInjection\Configuration
     * @return TreeBuilder The tree builder
     * @throws \ReflectionException
     */
    public function getConfigTreeBuilder(): TreeBuilder
    {
        $treeBuilder = new TreeBuilder('settings');
        $rootNode = $treeBuilder->getRootNode();

        $rootNode
            ->arrayPrototype()
                ->children()
                    ->scalarNode('name')->isRequired()->end()
                    ->scalarNode('description')->end()
                    ->arrayNode('tags')
                        ->arrayPrototype()
                            ->beforeNormalization()
                            ->ifString()
                            ->then(function ($v) {
                                return ['name' => $v];
                            })
                            ->end()
                            ->children()
                                ->scalarNode('name')->end()
                            ->end()
                        ->end()
                    ->end()
                    ->enumNode('type')
                        ->values(array_values(Type::toArray()))
                        ->isRequired()
                    ->end()
                    ->arrayNode('type_options')
                       ->variablePrototype()->end()
                    ->end()
                    ->arrayNode('data')
                        ->beforeNormalization()
                        ->always()
                        ->then(function ($v) {
                            if (is_string($v) || is_int($v) || is_float($v)) {
                                return ['value' => $v];
                            }

                            if (is_array($v) && isset($v['value'])) {
                                return $v;
                            }

                            return ['value' => $v];
                        })
                        ->end()
                        ->children()
                            ->variableNode('value')
                            ->isRequired()
                            ->end()
                        ->end()
                    ->end()
                    ->arrayNode('choices')
                        ->variablePrototype()->end()
                    ->end()
                ->end()
            ->end()
        ;

        return $treeBuilder;
    }
}


<?php

/*
 * This file is part of the andreas-weber/php-runner library.
 *
 * (c) Andreas Weber <code@andreas-weber.me>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace AndreasWeber\Runner\Task;

use AndreasWeber\Runner\Exception\InvalidArgumentException;
use AndreasWeber\Runner\Exception\LogicException;
use AndreasWeber\Runner\Payload\PayloadInterface;
use AndreasWeber\Runner\Task\Exception\FailException;
use AndreasWeber\Runner\Task\Exception\RetryException;
use AndreasWeber\Runner\Task\Exception\SkipException;
use AndreasWeber\Runner\Task\Retries\Retries;

abstract class AbstractTask implements TaskInterface
{
    /**
     * @var PayloadInterface The payload
     */
    private $payload;

    /**
     * @var Retries Retries handler
     */
    private $maxRetries;

    /**
     * @var bool Was successfully executed
     */
    private $successfullyExecuted;

    /**
     * @var bool Fail task when a exit code other than 0 is given
     */
    private $failOnError;

    /**
     * __construct()
     */
    public function __construct()
    {
        $this->successfullyExecuted = false;
        $this->setFailOnError(false);
    }

    /**
     * Sets the payload.
     *
     * @param PayloadInterface $payload
     *
     * @return $this
     */
    public function setPayload(PayloadInterface $payload)
    {
        $this->payload = $payload;

        return $this;
    }

    /**
     * Gets the payload.
     *
     * @return PayloadInterface
     */
    public function getPayload()
    {
        return $this->payload;
    }

    /**
     * Sets the max allowed retries.
     *
     * @param int|Retries $retries
     *
     * @return $this
     */
    public function setMaxRetries($retries)
    {
        switch (gettype($retries)) {
            case 'integer':
                $this->maxRetries = new Retries($retries);
                break;
            case 'object':
                $this->maxRetries = $retries;
                break;
            default:
                throw new InvalidArgumentException('Invalid type for max retries given.');
                break;
        }
    }

    /**
     * Gets the max allowed retries.
     *
     * @return Retries
     */
    public function getMaxRetries()
    {
        return $this->maxRetries;
    }

    /**
     * Marks the task as executed.
     * Marker is triggered by task runner.
     *
     * @return $this
     */
    public function markAsSuccessfullyExecuted()
    {
        $this->successfullyExecuted = true;

        return $this;
    }

    /**
     * Returns boolean true, when task was executed.
     *
     * @return bool
     */
    public function isSuccessfullyExecuted()
    {
        return $this->successfullyExecuted;
    }

    /**
     * Set to true, when task should fail,
     * when a different exit code than 0 is given.
     *
     * @param bool $failOnError
     *
     * @return $this
     */
    public function setFailOnError($failOnError)
    {
        $this->failOnError = (bool)$failOnError;

        return $this;
    }

    /**
     * Returns boolean true, when task should fail,
     * when a different exit code than 0 is given.
     *
     * @return boolean
     */
    public function isFailOnError()
    {
        return $this->failOnError;
    }

    /**
     * Run one or more precondition checks, before a task is executed.
     * If return value is boolean false, execution of task will be skipped.
     *
     * @return bool
     */
    public function unless()
    {
        return true;
    }

    /**
     * Sets up the task, for example, open a network connection.
     * This method is called before the task is executed.
     *
     * @return null
     */
    public function setUp()
    {
    }

    /**
     * Tear down the task, for example, close a network connection.
     * This method is called after the task was executed.
     *
     * @return null
     */
    public function tearDown()
    {
    }

    /**
     * Method call marks task as skipped.
     *
     * @return null
     * @throws SkipException
     */
    protected function skip()
    {
        throw new SkipException();
    }

    /**
     * Method call marks task as failed.
     *
     * @return null
     * @throws FailException
     */
    protected function fail()
    {
        throw new FailException();
    }

    /**
     * Method call triggers a task retry.
     *
     * @return null
     * @throws RetryException
     */
    protected function retry()
    {
        throw new RetryException();
    }

    /**
     * Task cloning is not allowed.
     *
     * @return null
     * @throws LogicException
     */
    public function __clone()
    {
        throw new LogicException('Cloning a task is not allowed.');
    }
}


<?php

/*
 * This file is part of the Typhoon package.
 *
 * Copyright © 2013 Erin Millard
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Eloquent\Typhoon\Parameter;

use Eloquent\Typhax\Type\Type;
use Eloquent\Typhoon\TypeCheck\TypeCheck;

class Parameter
{
    /**
     * @param string      $name
     * @param Type        $type
     * @param string|null $description
     * @param boolean     $optional
     * @param boolean     $byReference
     */
    public function __construct(
        $name,
        Type $type,
        $description = null,
        $optional = false,
        $byReference = false
    ) {
        $this->typeCheck = TypeCheck::get(__CLASS__, func_get_args());
        $this->name = $name;
        $this->type = $type;
        $this->description = $description;
        $this->optional = $optional;
        $this->byReference = $byReference;
    }

    /**
     * @return string
     */
    public function name()
    {
        $this->typeCheck->name(func_get_args());

        return $this->name;
    }

    /**
     * @return Type
     */
    public function type()
    {
        $this->typeCheck->type(func_get_args());

        return $this->type;
    }

    /**
     * @return string|null
     */
    public function description()
    {
        $this->typeCheck->description(func_get_args());

        return $this->description;
    }

    /**
     * @return boolean
     */
    public function isOptional()
    {
        $this->typeCheck->isOptional(func_get_args());

        return $this->optional;
    }

    /**
     * @return boolean
     */
    public function isByReference()
    {
        $this->typeCheck->isByReference(func_get_args());

        return $this->byReference;
    }

    /**
     * @param Visitor $visitor
     *
     * @return mixed
     */
    public function accept(Visitor $visitor)
    {
        $this->typeCheck->accept(func_get_args());

        return $visitor->visitParameter($this);
    }

    private $name;
    private $type;
    private $description;
    private $optional;
    private $byReference;
    private $typeCheck;
}


<?php declare(strict_types=1);

/**
 * @copyright (c) 2015-present brian ridley
 * @author brian ridley <ptlis@ptlis.net>
 * @license http://opensource.org/licenses/MIT MIT
 */

namespace ptlis\ShellCommand;

use ptlis\ShellCommand\Exceptions\CommandExecutionException;
use ptlis\ShellCommand\Interfaces\EnvironmentInterface;
use ptlis\ShellCommand\Interfaces\ProcessObserverInterface;
use ptlis\ShellCommand\Interfaces\ProcessInterface;
use ptlis\ShellCommand\Interfaces\ProcessOutputInterface;
use ptlis\ShellCommand\Logger\NullProcessObserver;
use React\EventLoop\LoopInterface;
use React\EventLoop\Timer\TimerInterface;
use React\Promise\Deferred;
use React\Promise\Promise;

/**
 * Class encapsulating the lifetime of a process.
 */
final class Process implements ProcessInterface
{
    /** @var EnvironmentInterface */
    private $environment;

    /** @var string */
    private $command;

    /** @var ProcessObserverInterface */
    private $observer;

    /** @var int */
    private $timeout;

    /** @var int */
    private $pollTimeout;

    /** @var float */
    private $startTime;

    /** @var int */
    private $exitCode;

    /** @var string */
    private $fullStdOut = '';

    /** @var string */
    private $fullStdErr = '';

    /** @var array */
    private $pipeList = [];

    /** @var resource */
    private $process = null;

    /** @var int */
    private $pid;

    /** @var ProcessOutputInterface|null*/
    private $output = null;

    public function __construct(
        EnvironmentInterface $environment,
        string $command,
        string $cwdOverride,
        int $timeout = -1,
        int $pollTimeout = 1000,
        ProcessObserverInterface $observer = null
    ) {
        $this->environment = $environment;
        $this->command = $command;
        $this->observer = $observer;
        if (is_null($this->observer)) {
            $this->observer = new NullProcessObserver();
        }

        // Store CWD, set to override
        $prevCwd = getcwd();
        chdir($cwdOverride);

        $this->process = proc_open(
            $command,
            [
                self::STDOUT => ['pipe', 'w'],
                self::STDERR => ['pipe', 'w']
            ],
            $this->pipeList
        );

        if (!is_resource($this->process)) {
            throw new CommandExecutionException('Call to proc_open failed for unknown reason.');
        }

        $this->pid = $this->getStatus()['pid'];

        $this->startTime = microtime(true);

        // Mark pipe streams as non-blocking
        foreach ($this->pipeList as $pipe) {
            stream_set_blocking($pipe, false);
        }

        // Notify observer of process creation.
        $this->observer->processCreated($this->pid, $command);

        // Reset CWD to previous
        chdir($prevCwd);

        $this->timeout = $timeout;
        $this->pollTimeout = $pollTimeout;
    }

    public function isRunning(): bool
    {
        $status = $this->getStatus();

        $this->observer->processPolled($this->pid, (int)floor((microtime(true) - $this->startTime) * 1000));

        return $status['running'];
    }

    public function wait(\Closure $callback = null): ProcessOutputInterface
    {
        while ($this->isRunning()) {
            if ($this->hasExceededTimeout()) {
                $this->stop();
            }

            $this->readStreams($callback);
            usleep($this->pollTimeout);
        }

        // Mark pipe streams as blocking
        foreach ($this->pipeList as $pipe) {
            stream_set_blocking($pipe, true);
        }

        $this->readStreams($callback);

        return $this->getProcessOutput();
    }

    public function stop(int $timeout = 1000000): ProcessOutputInterface
    {
        $originalTime = microtime(true);
        $this->sendSignal(ProcessInterface::SIGTERM);

        while ($this->isRunning()) {
            $time = microtime(true);

            // If term hasn't succeeded by the specified timeout then try and kill
            if (($time - $originalTime) * 1000000 > $timeout) {
                $this->sendSignal(ProcessInterface::SIGKILL);
            }

            usleep($this->pollTimeout);
        }

        return $this->getProcessOutput();
    }

    public function sendSignal(string $signal): void
    {
        $this->observer->sentSignal($this->pid, $signal);

        $this->environment->sendSignal($this->process, $signal);
    }

    public function readOutput(int $streamId): string
    {
        $data = stream_get_contents($this->pipeList[$streamId]);
        return $data;
    }

    public function getPid(): int
    {
        return $this->pid;
    }

    public function getCommand(): string
    {
        return $this->command;
    }

    public function getPromise(LoopInterface $eventLoop): Promise
    {
        $deferred = new Deferred();

        // Poll the process for completion
        $eventLoop->addPeriodicTimer(
            0.1,
            function (TimerInterface $timer) use ($eventLoop, $deferred) {
                $this->readStreams();

                // Process has terminated
                if (!$this->isRunning()) {
                    $eventLoop->cancelTimer($timer);
                    $output = $this->getProcessOutput();

                    // Resolve or reject promise
                    if (0 === $output->getExitCode()) {
                        $deferred->resolve($output);
                    } else {
                        $deferred->reject($output);
                    }
                }
            }
        );

        return $deferred->promise();
    }

    /**
     * Returns true if the process has been running for longer than the specified timeout.
     */
    private function hasExceededTimeout(): bool
    {
        return -1 !== $this->timeout && (microtime(true) - $this->startTime) * 1000000 > $this->timeout;
    }

    /**
     * Proxy method to proc_get_status.
     *
     * This is used so that we always update the exit code when retrieving process status. This works around the issue
     * where only the last call after process termination contains the real exit code.
     *
     * See http://stackoverflow.com/a/7841550 For more information.
     */
    private function getStatus(): array
    {
        $status = proc_get_status($this->process);

        if (!$status['running'] && is_null($this->exitCode)) {
            $this->exitCode = $status['exitcode'];
        }

        return $status;
    }

    /**
     * Read from the stdout & stderr streams, passing data to callback if provided.
     */
    private function readStreams(\Closure $callback = null): void
    {
        $stdOut = $this->readOutput(self::STDOUT);
        $stdErr = $this->readOutput(self::STDERR);

        $this->fullStdOut .= $stdOut;
        $this->fullStdErr .= $stdErr;

        if (!is_null($callback)) {
            $callback($stdOut, $stdErr);
        }

        $this->observer->stdOutRead($this->pid, $stdOut);
        $this->observer->stdErrRead($this->pid, $stdErr);
    }

    /**
     * Returns the output of the process (exit code, stdout & stderr).
     */
    private function getProcessOutput(): ProcessOutputInterface
    {
        if (is_null($this->output)) {
            $this->output = new ProcessOutput($this->exitCode, $this->fullStdOut, $this->fullStdErr);
            $this->observer->processExited($this->pid, $this->output);
        }

        return $this->output;
    }
}