HippoPHP/Hippo

View on GitHub
src/CLI/ArgParser.php

Summary

Maintainability
A
50 mins
Test Coverage
<?php

/*
 * This file is part of Hippo.
 *
 * (c) James Brooks <james@alt-three.com>
 * (c) Marcin Kurczewski <rr-@sakuya.pl>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace HippoPHP\Hippo\CLI;

/**
 * A factory of ArgContainer.
 */
class ArgParser
{
    /**
     * @var bool
     */
    private $stopParsing;

    /**
     * @var ArgContainer
     */
    private $argContainer;

    /**
     * @var ArgParserOptions
     */
    private $argParserOptions;

    /**
     * @param string[]                             $argv
     * @param \HippoPHP\Hippo\CLI\ArgParserOptions $argParserOptions
     *
     * @return \HippoPHP\Hippo\CLI\ArgContainer
     */
    public static function parse(array $argv, ArgParserOptions $argParserOptions = null)
    {
        $parser = new self($argParserOptions);

        return $parser->parseArgs($argv);
    }

    /**
     * @param \HippoPHP\Hippo\CLI\ArgParserOptions $argParserOptions
     */
    private function __construct(ArgParserOptions $argParserOptions = null)
    {
        $this->argParserOptions = $argParserOptions === null
            ? new ArgParserOptions()
            : $argParserOptions;
    }

    /**
     * @param string[] $argv
     *
     * @return \HippoPHP\Hippo\CLI\ArgContainer
     */
    private function parseArgs(array $argv)
    {
        $this->stopParsing = false;
        $this->argContainer = new ArgContainer();

        $argCount = count($argv);

        for ($i = 0; $i < $argCount; $i++) {
            $arg = $argv[$i];
            $nextArg = isset($argv[$i + 1]) ? $argv[$i + 1] : null;
            $hasUsedNextArg = $this->processArg($arg, $nextArg);
            if ($hasUsedNextArg) {
                $i++;
            }
        }

        return $this->argContainer;
    }

    /**
     * @param string $arg
     * @param string $nextArg
     *
     * @return bool whether the next arg was used
     */
    private function processArg($arg, $nextArg)
    {
        if ($arg === '--') {
            $this->stopParsing = true;

            return false;
        }

        if (!$this->stopParsing) {
            if ($this->isLongArgument($arg)) {
                $this->argContainer->setLongOption(
                    $this->normalizeArg($arg),
                    $this->extractArgValue($arg, $nextArg, $hasUsedNextArg));

                return $hasUsedNextArg;
            }

            if ($this->isShortArgument($arg)) {
                $this->argContainer->setShortOption(
                    $this->normalizeArg($arg),
                    $this->extractArgValue($arg, $nextArg, $hasUsedNextArg));

                return $hasUsedNextArg;
            }
        }

        $this->argContainer->addStrayArgument($arg);

        return false;
    }

    /**
     * @param string $arg
     * @param string $nextArg
     * @param bool   $hasUsedNextArg
     *
     * @return mixed
     */
    private function extractArgValue($arg, $nextArg, &$hasUsedNextArg)
    {
        $hasUsedNextArg = false;
        $normalizedArg = $this->normalizeArg($arg);

        $index = strpos($arg, '=');
        if ($index !== false) {
            return $this->processStringValue($normalizedArg, substr($arg, $index + 1));
        } elseif ($this->argParserOptions->isFlag($normalizedArg)) {
            if ($this->isBool($nextArg)) {
                $hasUsedNextArg = true;

                return $this->processStringValue($normalizedArg, $nextArg);
            }

            return true;
        } elseif ($nextArg !== null && !$this->isArgument($nextArg)) {
            $hasUsedNextArg = true;

            return $this->processStringValue($normalizedArg, $nextArg);
        }
    }

    /**
     * @param string $normalizedArg
     * @param string $value
     *
     * @return mixed
     */
    private function processStringValue($normalizedArg, $value)
    {
        if ($this->argParserOptions->isFlag($normalizedArg)) {
            return $this->toBool($value);
        } elseif ($this->argParserOptions->isArray($normalizedArg)) {
            return preg_split('/[\s,;]+/', $value);
        }

        return $value;
    }

    /**
     * @param string $arg
     *
     * @return bool
     */
    private function isLongArgument($arg)
    {
        return substr($arg, 0, 2) === '--';
    }

    /**
     * @param string $arg
     *
     * @return bool
     */
    private function isShortArgument($arg)
    {
        return !$this->isLongArgument($arg) && $arg[0]
        === '-';
    }

    /**
     * Normalizes an argument key.
     *
     * @param string $arg
     *
     * @return string
     */
    private function normalizeArg($arg)
    {
        if (strpos($arg, '=') !== false) {
            $arg = substr($arg, 0, strpos($arg, '='));
        }

        return ltrim($arg, '-');
    }

    /**
     * @param string $arg
     *
     * @return bool
     */
    private function isArgument($arg)
    {
        return $this->isLongArgument($arg) || $this->isShortArgument($arg);
    }

    /**
     * @param string $arg
     *
     * @return bool
     */
    private function isBool($arg)
    {
        return $this->toBool($arg) !== null;
    }

    /**
     * @param string $arg
     *
     * @return bool
     */
    private function toBool($arg)
    {
        if ($arg === '0') {
            return false;
        } elseif ($arg === '1') {
            return true;
        }
    }
}