Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added CommandParser and accompanying test.
Added ConversionChainBuilder that uses new CommandParser and accompanying test. Added ConverterFactory and accompanying test. Updated architecture diagram.
- Loading branch information
Showing
14 changed files
with
434 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<?php | ||
namespace ScriptFUSION\OpenDash\Command; | ||
|
||
class Argument { | ||
private | ||
$value, | ||
$quote | ||
; | ||
|
||
public function __construct($value, $quote = '') { | ||
$this->value = Unescaper::unescape($value); | ||
$this->quote = $quote; | ||
} | ||
|
||
/** | ||
* @return mixed | ||
*/ | ||
public function getValue() { | ||
return $this->value; | ||
} | ||
|
||
public function getQuote() { | ||
return $this->quote; | ||
} | ||
|
||
public function isQuoted() { | ||
return !!$this->quote; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
<?php | ||
namespace ScriptFUSION\OpenDash\Command; | ||
|
||
class Command { | ||
private | ||
$name, | ||
$arguments | ||
; | ||
|
||
public function __construct($name, array $arguments = []) { | ||
$this->name = "$name"; | ||
$this->arguments = $arguments; | ||
} | ||
|
||
public function __toString() { | ||
return "$this->name"; | ||
} | ||
|
||
/** | ||
* @return string | ||
*/ | ||
public function getName() { | ||
return "$this"; | ||
} | ||
|
||
/** | ||
* @return Argument[] | ||
*/ | ||
public function getArguments() { | ||
return $this->arguments; | ||
} | ||
|
||
/** | ||
* @return array | ||
*/ | ||
public function renderArguments() { | ||
return array_map(function(Argument $arg) { | ||
return $arg->getValue(); | ||
}, $this->arguments); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
<?php | ||
namespace ScriptFUSION\OpenDash\Command; | ||
|
||
/** | ||
* Parses a string command specification into a tree of Command and Argument objects. | ||
*/ | ||
class CommandParser { | ||
const | ||
PARSE_COMMANDS = ' | ||
#Command name. | ||
(?<command>[a-z][a-z\d]*+) | ||
#Optional arguments list. | ||
(?<args>(?:%s)*) | ||
#Optional whitespace followed by pipe or end of expression. | ||
\s*+(?:\||\z) | ||
', | ||
|
||
PARSE_ARGUMENTS = ' | ||
#Each argument must proceed whitespace. | ||
\s++ | ||
(?J: | ||
#Single or double quoted form. | ||
(?<quote>[\'"]) | ||
#Only even quantities of backslahes may end the argument. | ||
(?<arg>.*?(?<!\\\\)(?:\\\\\\\\)*+) | ||
#Closing quote must match opening quote. | ||
\k{quote} | ||
| | ||
#Unquoted form. Argument may not contain whitespace or pipes. | ||
(?<arg>[^\s|]++) | ||
) | ||
', | ||
|
||
REGEX_SHELL = '[%s]xS' | ||
; | ||
|
||
/** | ||
* Parses the specified command line into a list of Commands. | ||
* | ||
* @param string $commandLine Command line. | ||
* @return Command[] Command list. | ||
*/ | ||
public function parseCommands($commandLine) { | ||
$matches = []; | ||
if (false === preg_match_all( | ||
sprintf(static::REGEX_SHELL, sprintf(static::PARSE_COMMANDS, static::PARSE_ARGUMENTS)), | ||
$commandLine, $matches | ||
)) | ||
throw new ParseException("Unable to parse command line: $commandLine"); | ||
|
||
$commands = []; | ||
foreach ($matches['command'] as $index => $command) | ||
$commands[$index] = new Command($command, $this->parseArguments($matches['args'][$index])); | ||
|
||
return $commands; | ||
} | ||
|
||
/** | ||
* Parses the specified arguments into a list of Arguments. | ||
* | ||
* Note: arguments have to be parsed in a separate pass since PCRE does not | ||
* support capturing multiple matches from quantified groups (see | ||
* <http://www.rexegg.com/regex-capture.html#groupnumbers>). | ||
* | ||
* @param string $args Arguments. | ||
* @return Argument[] Arguments list. | ||
*/ | ||
private function parseArguments($args) { | ||
$matches = []; | ||
if (false === preg_match_all( | ||
sprintf(static::REGEX_SHELL, static::PARSE_ARGUMENTS), $args, $matches, PREG_SET_ORDER | ||
)) | ||
throw new ParseException("Unable to parse arguments: $args"); | ||
|
||
$args = []; | ||
foreach ($matches as $index => $arg) | ||
$args[$index] = new Argument($arg['arg'], $arg['quote']); | ||
|
||
return $args; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
<?php | ||
namespace ScriptFUSION\OpenDash\Command; | ||
|
||
class ParseException extends \RuntimeException {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<?php | ||
namespace ScriptFUSION\OpenDash\Command; | ||
|
||
class Unescaper { | ||
public static function unescape($string) { | ||
return preg_replace('[\\\\(.)]', '\1', $string); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
<?php | ||
namespace ScriptFUSION\OpenDash\Convert; | ||
|
||
use ScriptFUSION\OpenDash\Command\Command; | ||
use ScriptFUSION\OpenDash\Command\CommandParser; | ||
|
||
/** | ||
* Builds a ConversionChain from a string specification. | ||
*/ | ||
class ConversionChainBuilder { | ||
private | ||
$factory, | ||
$parser | ||
; | ||
|
||
public function __construct(ConverterFactory $factory) { | ||
$this->parser = new CommandParser; | ||
$this->factory = $factory; | ||
} | ||
|
||
/** | ||
* Builds a ConversionChain from the specified specification. | ||
* | ||
* @param string $specification <converter> [arg1 [arg2 ...]][ | <converter>]... | ||
* @return ConversionChain New ConversionChain. | ||
*/ | ||
public function build($specification) { | ||
$commands = $this->parser->parseCommands($specification); | ||
|
||
$chain = new ConversionChain; | ||
|
||
/** @var Command $command */ | ||
foreach ($commands as $command) | ||
$chain->addConverter($this->factory->createConverter("$command", $command->renderArguments())); | ||
|
||
return $chain; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
<?php | ||
namespace ScriptFUSION\OpenDash\Convert; | ||
|
||
/** | ||
* Creates concrete instances of Convert. | ||
*/ | ||
class ConverterFactory { | ||
private $converters; | ||
|
||
/** | ||
* Initializes ConverterFactory. | ||
*/ | ||
public function __construct() { | ||
$this->converters = iterator_to_array($this->findConverters()); | ||
} | ||
|
||
/** | ||
* Creates a converter matching the specified name constructed with the | ||
* specified arguments. | ||
* | ||
* @param string $name Converter name. | ||
* @param array $arguments Converter arguments. | ||
* @return Convert New Convert instance. | ||
*/ | ||
public function createConverter($name, array $arguments = []) { | ||
if (!isset($this->converters[$name])) | ||
throw new \RuntimeException("Converter not found: $name"); | ||
|
||
return (new \ReflectionClass($this->converters[$name]))->newInstanceArgs($arguments); | ||
} | ||
|
||
/** | ||
* Gets a list of available converters. | ||
* | ||
* @return array Lower-case converter name => fully qualified class name. | ||
*/ | ||
public function getConverters() { | ||
return $this->converters; | ||
} | ||
|
||
/** | ||
* Searches for classes implementing Convert in the same directory as this class. | ||
* | ||
* @return \Generator Lower-case converter name => class name. | ||
*/ | ||
private function findConverters() { | ||
/** @var \DirectoryIterator $file */ | ||
foreach (new \DirectoryIterator(__DIR__) as $file) { | ||
// Filter non-PHP files. | ||
if ($file->getExtension() !== 'php') | ||
continue; | ||
|
||
$reflector = new \ReflectionClass(__NAMESPACE__ . '\\' . $basename = $file->getBasename('.php')); | ||
|
||
if ($reflector->isInstantiable() && $reflector->implementsInterface(Convert::class)) | ||
yield strtolower($basename) => $reflector->getName(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.