diff --git a/.github/workflows/phan.yml b/.github/workflows/phan-phpunit.yml similarity index 79% rename from .github/workflows/phan.yml rename to .github/workflows/phan-phpunit.yml index 1a77d0a..db90187 100644 --- a/.github/workflows/phan.yml +++ b/.github/workflows/phan-phpunit.yml @@ -1,4 +1,4 @@ -name: Static analysis (phan) +name: Static analysis (phan) & Unit Testing (phpunit) on: pull_request: @@ -6,7 +6,7 @@ on: - "**.php" jobs: - phan: + phan-phpunit: runs-on: ubuntu-latest steps: - name: Install PHP and dependencies @@ -25,4 +25,6 @@ jobs: - name: Install composer packages run: composer install - name: Run phan from vendor folder - run: vendor/bin/phan -S -k ./.phan/config.php + run: vendor/bin/phan -S -k ./.phan/config.php --no-progress-bar + - name: Run phpunit from vendor folder + run: vendor/bin/phpunit diff --git a/app/CodeFormatterApp.php b/app/CodeFormatterApp.php index a632189..158188e 100644 --- a/app/CodeFormatterApp.php +++ b/app/CodeFormatterApp.php @@ -3,9 +3,10 @@ namespace DevCommunityDE\CodeFormatter; use DevCommunityDE\CodeFormatter\CodeFormatter\CodeFormatter; +use DevCommunityDE\CodeFormatter\Parser\ElemNode; +use DevCommunityDE\CodeFormatter\Parser\Node; use DevCommunityDE\CodeFormatter\Parser\Parser; -use DevCommunityDE\CodeFormatter\Parser\PregParser; -use DevCommunityDE\CodeFormatter\Parser\Token; +use DevCommunityDE\CodeFormatter\Parser\TextNode; /** * Class CodeFormatterApp. @@ -22,7 +23,7 @@ class CodeFormatterApp */ public function __construct(Parser $parser = null) { - $this->parser = $parser ?? new PregParser(); + $this->parser = $parser ?? new Parser(); } /** @@ -32,17 +33,17 @@ public function __construct(Parser $parser = null) */ public function run() { - $tokens = $this->parseInput(); + $nodes = $this->parseInput(); - foreach ($tokens as $token) { - echo $this->formatToken($token); + foreach ($nodes as $node) { + echo $this->formatNode($node); } } /** * parses the code from stdin. * - * @return iterable + * @return iterable */ private function parseInput(): iterable { @@ -50,41 +51,58 @@ private function parseInput(): iterable } /** - * formats a token based on its type and language. + * formats a node based on its type and language. * - * @param Token $token + * @param Node $node * * @return string */ - private function formatToken(Token $token): string + private function formatNode(Node $node): string { - if ($token->isText()) { - return $token->getBody(); + if ($node instanceof TextNode) { + return $this->exportNode($node, null); } - $language = $token->getAttribute('lang'); - \assert(null !== $language); + \assert($node instanceof ElemNode); + if (!$node->isCode()) { + // export node as-is (PLAIN bbcode) + return $this->exportNode($node, null); + } + + if ($node->isRich()) { + // iterate over all child-nodes in this case + $buffer = ''; + foreach ($node->getBody() as $subNode) { + $buffer .= $this->formatNode($subNode); + } + return $this->exportNode($node, $buffer); + } + + $language = $node->getLang() ?: 'text'; $formatter = CodeFormatter::create($language); + if (null === $formatter) { - // no formatter found, return token as is - return $this->exportToken($token, null); + // no formatter found, return node as-is + return $this->exportNode($node, null); } - $result = $formatter->exec($token->getBody()); - return $this->exportToken($token, $result); + $nodeBody = $node->getBody(); + \assert(\is_string($nodeBody)); + $result = $formatter->exec($nodeBody); + return $this->exportNode($node, $result); } /** - * exports a token. + * exports a node. * - * @param Token $token + * @param Node $node * @param string|null $body * * @return string */ - private function exportToken(Token $token, ?string $body): string + private function exportNode(Node $node, ?string $body): string { - return $this->parser->exportToken($token, $body); + return $this->parser->exportNode($node, $body); } } diff --git a/app/Parser/ElemAttrs.php b/app/Parser/ElemAttrs.php new file mode 100644 index 0000000..8da7f4e --- /dev/null +++ b/app/Parser/ElemAttrs.php @@ -0,0 +1,63 @@ + $pairs + */ + public function __construct(string $match, array $pairs) + { + $this->match = $match; + $this->pairs = $pairs; + } + + /** + * returns a value. + * + * @param string $name + * + * @return string|null + */ + public function getValue(string $name): ?string + { + return $this->pairs[$name] ?? null; + } + + /** + * returns the matched attribute string. + * + * @return string + */ + public function getMatch(): string + { + return $this->match; + } + + /** + * checks if some attributes are set. + * + * @return bool + */ + public function hasMatch(): bool + { + return !empty($this->match); + } +} diff --git a/app/Parser/ElemNode.php b/app/Parser/ElemNode.php new file mode 100644 index 0000000..01e2197 --- /dev/null +++ b/app/Parser/ElemNode.php @@ -0,0 +1,124 @@ +name = $name; + $this->attrs = $attrs; + $this->body = $body; + } + + /** + * checks if this node represents a [CODE] bbcode. + * + * @return bool + */ + public function isCode(): bool + { + return 0 === strcasecmp($this->name, 'code'); + } + + /** + * checks if this node represents a [CODE=rich] bbcode. + * + * @return bool + */ + public function isRich(): bool + { + if (0 !== strcasecmp($this->name, 'code')) { + return false; + } + + $langAttr = $this->getLang(); + + if (empty($langAttr)) { + /* defaults to "text" */ + return false; + } + + return 0 === strcasecmp($langAttr, 'rich'); + } + + /** + * utility method to retrieve a lang-attribute. + * + * @return string|null + */ + public function getLang(): ?string + { + $langAttr = $this->getAttr('lang'); + + if (null === $langAttr && $this->isCode()) { + $langAttr = $this->getAttr('@value'); + } + + return $langAttr; + } + + /** + * returns the element name. + * + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * returns a single node attribute. + * + * @param string $name + * + * @return string|null + */ + public function getAttr(string $name): ?string + { + return $this->attrs->getValue($name); + } + + /** + * returns the node attributes. + * + * @return string + */ + public function getAttrMatch(): string + { + return $this->attrs->getMatch(); + } + + /** + * returns the node-body. + * + * @return NodeList|string + */ + public function getBody() + { + return $this->body; + } +} diff --git a/app/Parser/Node.php b/app/Parser/Node.php new file mode 100644 index 0000000..5179c7d --- /dev/null +++ b/app/Parser/Node.php @@ -0,0 +1,37 @@ +kind = $kind; + } + + /** + * returns the node kind. + * + * @return int + */ + public function getKind(): int + { + return $this->kind; + } +} diff --git a/app/Parser/NodeList.php b/app/Parser/NodeList.php new file mode 100644 index 0000000..2fe9a19 --- /dev/null +++ b/app/Parser/NodeList.php @@ -0,0 +1,62 @@ +nodes = $nodes; + } + + /** + * appends a node. + * + * @param Node|string $node + * + * @return void + */ + public function append($node) + { + if (\is_string($node)) { + $this->nodes[] = new TextNode($node); + return; + } + + \assert($node instanceof Node); + $this->nodes[] = $node; + } + + /** + * returns the node-list as array. + * this method is mainly used for testing. + * + * @return array + */ + public function toArray(): array + { + return $this->nodes; + } + + /** + * returns the node-list as traversable. + * + * @return Traversable + */ + public function getIterator(): Traversable + { + yield from $this->nodes; + } +} diff --git a/app/Parser/Parser.php b/app/Parser/Parser.php index 6d414dd..4e0c0c0 100644 --- a/app/Parser/Parser.php +++ b/app/Parser/Parser.php @@ -4,33 +4,307 @@ namespace DevCommunityDE\CodeFormatter\Parser; -interface Parser +use DevCommunityDE\CodeFormatter\Exceptions\Exception; + +final class Parser { + /** pattern used to tokenize bbcode elements */ + private const RE_ELEM = '/\G\[(code|plain)(=\w+|(?:\s+\w+=(?:"[^"]*"|\S+?))*)\]/i'; + + /** pattern used to tokenize attributes */ + private const RE_ATTR = '/\G\s*(\w+)=("[^"]*"|\S+)/'; + /** - * parses a file. - * * @param string $filePath * - * @return iterable + * @return iterable */ - public function parseFile(string $filePath): iterable; + public function parseFile(string $filePath): iterable + { + // note: we cannot stat php://input + // therefore we have to give it a try + $fileHandle = @fopen($filePath, 'r'); + + if (false === $fileHandle) { + throw new Exception('unable to open file: ' . $filePath); + } + + $textData = stream_get_contents($fileHandle); + fclose($fileHandle); + + if (false === $textData) { + throw new Exception('unable to read file: ' . $filePath); + } + + return $this->parseText($textData); + } /** - * parses a string (text). + * parses the given text. * * @param string $textData * - * @return iterable + * @return iterable + */ + public function parseText(string $textData): iterable + { + $state = new State($textData); + return $this->parseNorm($state); + } + + /** + * parses and normalizes a prepared state. + * + * @param State $state + * + * @return iterable + */ + private function parseNorm(State $state): iterable + { + $textBuffer = ''; + while ($state->valid()) { + $node = $this->parseNode($state); + + if ($node instanceof ElemNode) { + // yield a buffered text-node first (if any) + if (!empty($textBuffer)) { + yield new TextNode($textBuffer); + $textBuffer = ''; + } + + yield $node; + continue; + } + + \assert($node instanceof TextNode); + $textBuffer .= $node->getBody(); + } + + if (!empty($textBuffer)) { + // yield a buffered text-node (if any) + yield new TextNode($textBuffer); + } + } + + /** + * parses a node. + * + * @param State $state + * + * @return Node + */ + private function parseNode(State $state): Node + { + $input = $state->input; + $offset = $state->offset; + + if (preg_match(self::RE_ELEM, $input, $m, 0, $offset)) { + // read from $textData until we see a closing tag + $elemName = $m[1]; + $elemAttrs = $this->parseAttrs($m[2]); + $bodyStart = $offset + \strlen($m[0]); + + if ($this->isRichCode($elemName, $elemAttrs)) { + // CODE=rich allows nested CODE bbcodes ... yay. + // switch to the recursive parser and collect all + // child-nodes as tag-body + $state->offset = $bodyStart; + $elemBody = $this->parseRich($state); + return new ElemNode($elemName, $elemAttrs, $elemBody); + } + + $closeTag = "[/{$elemName}]"; + $closePos = stripos($input, $closeTag, $bodyStart); + + if (false === $closePos) { + // edge case: + // no closing tag found... treat the rest of the input + // as bbcode-body and stop the parsing entirely + $elemBody = substr($input, $bodyStart) ?: ''; + $state->finish(); + return new ElemNode($elemName, $elemAttrs, $elemBody); + } + + $bodySpan = $closePos - $bodyStart; + $elemBody = substr($input, $bodyStart, $bodySpan) ?: ''; + $state->offset = $closePos + \strlen($closeTag); + return new ElemNode($elemName, $elemAttrs, $elemBody); + } + + $textSpan = max(1, strcspn($input, '[', $offset)); + $textBody = substr($input, $offset, $textSpan) ?: ''; + $state->offset += (int) $textSpan; + return new TextNode($textBody); + } + + /** + * parses a CODE=rich body. + * + * @param State $state + * + * @return NodeList + */ + private function parseRich(State $state): NodeList + { + $nodeList = new NodeList(); + $textBuffer = ''; + while ($state->valid()) { + $node = $this->parseNode($state); + + if ($node instanceof ElemNode) { + // yield a buffered text-node first (if any) + if (!empty($textBuffer)) { + $nodeList->append($textBuffer); + $textBuffer = ''; + } + + $nodeList->append($node); + continue; + } + + \assert($node instanceof TextNode); + $textBody = $node->getBody(); + + if ('[' === $textBody) { + // a single bracket was parsed as text-node + $input = $state->input; + $offset = $state->offset; + + if (0 === substr_compare($input, '/code]', $offset, 6, true)) { + // found a closing tag, stop rich-parsing + $state->offset += 6; + break; + } + } + + $textBuffer .= $textBody; + } + + if (!empty($textBuffer)) { + // append a buffered text-node (if any) + $nodeList->append($textBuffer); + } + + return $nodeList; + } + + /** + * checks if the given node is a CODE=rich node. + * + * @param string $tagName + * @param ElemAttrs $tagAttrs + * + * @return bool + */ + private function isRichCode(string $tagName, ElemAttrs $tagAttrs): bool + { + if (0 !== strcasecmp($tagName, 'code')) { + return false; + } + + $lang = $tagAttrs->getValue('lang') ?: + $tagAttrs->getValue('@value'); + + return $lang && 0 === strcasecmp($lang, 'rich'); + } + + /** + * parses an attribute string from a bbcode. + * + * @param string $attrs + * + * @return ElemAttrs + */ + private function parseAttrs(string $attrs): ElemAttrs + { + $pairs = []; + $match = $attrs; + + if (empty($attrs = trim($attrs))) { + return new ElemAttrs($match, $pairs); + } + + if ('=' === substr($attrs, 0, 1)) { + // [bbcode=value] notation + $pairs['@value'] = substr($attrs, 1); + return new ElemAttrs($match, $pairs); + } + + $offset = 0; + $length = \strlen($attrs); + while ($offset < $length) { + if (!preg_match(self::RE_ATTR, $attrs, $m, 0, $offset)) { + // we could throw here, but the text-input is in most + // cases provided by users. so we just discard it + break; + } + $pairs[$m[1]] = $this->parseAttr($m[2]); + $offset += \strlen($m[0]); + } + + return new ElemAttrs($match, $pairs); + } + + /** + * parses an attribute (value). + * + * @param string $value + * + * @return string */ - public function parseText(string $textData): iterable; + private function parseAttr(string $value): string + { + switch (substr($value, 0, 1)) { + case '"': + case "'": + return substr($value, 1, -1) ?: ''; + default: + return $value; + } + } /** - * exports a parsed token to its string-form. + * exports node. * - * @param Token $token + * @param Node $node * @param string|null $body * * @return string */ - public function exportToken(Token $token, ?string $body): string; + public function exportNode(Node $node, ?string $body): string + { + if ($node instanceof TextNode) { + return $body ?: $node->getBody(); + } + + \assert($node instanceof ElemNode); + $bbcode = $node->getName(); + $buffer = "[{$bbcode}"; + $buffer .= $node->getAttrMatch(); + $buffer .= ']'; + $buffer .= $body ?: $this->exportBody($node->getBody()); + return $buffer . "[/{$bbcode}]"; + } + + /** + * exports a node body. + * + * @param NodeList|string $nodeBody + * + * @return string + */ + private function exportBody($nodeBody): string + { + if (\is_string($nodeBody)) { + return $nodeBody; + } + + \assert($nodeBody instanceof NodeList); + + $buffer = ''; + foreach ($nodeBody as $subNode) { + $buffer .= $this->exportNode($subNode, null); + } + return $buffer; + } } diff --git a/app/Parser/PregParser.php b/app/Parser/PregParser.php deleted file mode 100644 index df8533c..0000000 --- a/app/Parser/PregParser.php +++ /dev/null @@ -1,159 +0,0 @@ - - */ - public function parseFile(string $filePath): iterable - { - $textData = file_get_contents($filePath); - - if (false === $textData) { - throw new Exception('unable to parse file: ' . $filePath); - } - - yield from $this->parseText($textData); - } - - /** - * {@inheritdoc} - * - * @param string $textData - * - * @return @return iterable - */ - public function parseText(string $textData): iterable - { - // the structure looks like this after the split: - // 0 => text - // 1 => code-tag attributes - // 2 => code-tag body - // 3 => text - // 4 => ... - - $split = preg_split(self::RE_SPLIT, $textData, -1, - PREG_SPLIT_DELIM_CAPTURE); - - if (0 === ($count = \count($split))) { - return; - } - - $index = 0; - while ($index < $count) { - $attrs = null; - - if (0 !== $index % 3) { - $attrs = $this->parseAttrs($split[$index++]); - $kind = Token::T_CODE; - } else { - $kind = Token::T_TEXT; - } - - $body = $split[$index++]; - yield new Token($kind, $body, $attrs); - } - } - - /** - * parses an attribute string from a code-tag. - * - * @param string $attrs - * - * @return array|null - */ - private function parseAttrs(string $attrs): ?array - { - // ensure a language - $attrMap = ['lang' => 'text']; - - if (empty($attrs = trim($attrs))) { - return $attrMap; - } - - if ('=' === substr($attrs, 0, 1)) { - // [code=lang] notation - $attrMap['lang'] = substr($attrs, 1); - return $attrMap; - } - - $offset = 0; - $length = \strlen($attrs); - while ($offset < $length) { - if (!preg_match(self::RE_ATTRS, $attrs, $m, 0, $offset)) { - // we could throw here, but the text-input is in most - // cases provided by users. so we just discard it - break; - } - - $attrMap[$m[1]] = $this->parseAttr($m[2]); - $offset += \strlen($m[0]); - } - - return $attrMap; - } - - /** - * parses an attribute (value). - * - * @param string $value - * - * @return string - */ - private function parseAttr(string $value): string - { - switch (substr($value, 0, 1)) { - case '"': - case "'": - return substr($value, 1, -1) ?: ''; - default: - return $value; - } - } - - /** - * {@inheritdoc} - * - * @param Token $token - * @param string|null $body - * - * @return string - */ - public function exportToken(Token $token, ?string $body): string - { - if ($token->isText()) { - return $body ?: $token->getBody(); - } - - $buffer = '[CODE'; - $attrMap = $token->getAttributes(); - - foreach ($attrMap as $name => $value) { - $buffer .= ' ' . $name . '="' . $value . '"'; - } - - $buffer .= ']'; - $buffer .= $body ?: $token->getBody(); - return $buffer . '[/CODE]'; - } -} diff --git a/app/Parser/State.php b/app/Parser/State.php new file mode 100644 index 0000000..ab01f7e --- /dev/null +++ b/app/Parser/State.php @@ -0,0 +1,53 @@ +input = $input; + $this->length = \strlen($input); + $this->offset = 0; + } + + /** + * checks if this state is still valid. + * + * @return bool + */ + public function valid(): bool + { + return $this->offset < $this->length; + } + + /** + * marks the state a finished. + * + * @return void + */ + public function finish() + { + $this->offset = $this->length; + } +} diff --git a/app/Parser/TextNode.php b/app/Parser/TextNode.php new file mode 100644 index 0000000..8dfb662 --- /dev/null +++ b/app/Parser/TextNode.php @@ -0,0 +1,34 @@ +body = $body; + } + + /** + * returns the node-body. + * + * @return string + */ + public function getBody(): string + { + return $this->body; + } +} diff --git a/app/Parser/Token.php b/app/Parser/Token.php deleted file mode 100644 index 41ec9c1..0000000 --- a/app/Parser/Token.php +++ /dev/null @@ -1,92 +0,0 @@ - */ - private $attrs = []; - - /** - * constructor. - * - * @param int $kind - * @param string $body - * @param array|null $attrs - */ - public function __construct(int $kind, string $body, ?array $attrs) - { - $this->kind = $kind; - $this->body = $body; - - if (null !== $attrs) { - $this->attrs = $attrs; - } - } - - /** - * returns true if this token represents a text-token. - * - * @return bool - */ - public function isText(): bool - { - return self::T_TEXT === $this->kind; - } - - /** - * returns true if this token represents a code-token. - * - * @return bool - */ - public function isCode(): bool - { - return self::T_CODE === $this->kind; - } - - /** - * returns the token body. - * - * @return string - */ - public function getBody(): string - { - return $this->body; - } - - /** - * returns a single attribute. - * - * @param string $name - * - * @return string|null - */ - public function getAttribute(string $name): ?string - { - return $this->attrs[$name] ?? null; - } - - /** - * returns a COW reference to this tokens attributes. - * - * @return array - */ - public function getAttributes(): array - { - return $this->attrs; - } -} diff --git a/composer.json b/composer.json index 7344668..dcecc59 100644 --- a/composer.json +++ b/composer.json @@ -9,8 +9,14 @@ "DevCommunityDE\\CodeFormatter\\": "app" } }, + "autoload-dev": { + "psr-4": { + "DevCommunityDE\\CodeFormatter\\Tests\\": "tests/" + } + }, "require-dev": { "symfony/var-dumper": "^5.0", + "phpunit/phpunit": "^9.2", "phan/phan": "^3.0" } } diff --git a/composer.lock b/composer.lock index 49b022a..69502d7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8cc714456f7638686a09ab023631b60d", + "content-hash": "8b81cfd21bdd0ccc280ba04b9c0152c9", "packages": [], "packages-dev": [ { @@ -113,6 +113,76 @@ ], "time": "2020-06-04T11:16:35+00:00" }, + { + "name": "doctrine/instantiator", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "f350df0268e904597e3bd9c4685c53e0e333feea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f350df0268e904597e3bd9c4685c53e0e333feea", + "reference": "f350df0268e904597e3bd9c4685c53e0e333feea", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2020-05-29T17:27:14+00:00" + }, { "name": "felixfbecker/advanced-json-rpc", "version": "v3.1.1", @@ -195,6 +265,60 @@ "description": "Tolerant PHP-to-AST parser designed for IDE usage scenarios", "time": "2020-02-18T02:57:19+00:00" }, + { + "name": "myclabs/deep-copy", + "version": "1.10.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", + "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2020-06-29T13:22:24+00:00" + }, { "name": "netresearch/jsonmapper", "version": "v2.1.0", @@ -293,300 +417,1656 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "MIT" + ], + "authors": [ + { + "name": "Tyson Andre" + }, + { + "name": "Rasmus Lerdorf" + }, + { + "name": "Andrew S. Morrison" + } + ], + "description": "A static analyzer for PHP", + "keywords": [ + "analyzer", + "php", + "static" + ], + "time": "2020-07-03T23:01:25+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^2.0", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2018-07-08T19:23:20+00:00" + }, + { + "name": "phar-io/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2018-07-08T19:19:57+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", + "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", + "shasum": "" + }, + "require": { + "ext-filter": "^7.1", + "php": "^7.2", + "phpdocumentor/reflection-common": "^2.0", + "phpdocumentor/type-resolver": "^1.0", + "webmozart/assert": "^1" + }, + "require-dev": { + "doctrine/instantiator": "^1", + "mockery/mockery": "^1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2020-02-22T12:28:44+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "e878a14a65245fbe78f8080eba03b47c3b705651" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e878a14a65245fbe78f8080eba03b47c3b705651", + "reference": "e878a14a65245fbe78f8080eba03b47c3b705651", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "time": "2020-06-27T10:12:23+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.11.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "b20034be5efcdab4fb60ca3a29cba2949aead160" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/b20034be5efcdab4fb60ca3a29cba2949aead160", + "reference": "b20034be5efcdab4fb60ca3a29cba2949aead160", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.2", + "php": "^7.2", + "phpdocumentor/reflection-docblock": "^5.0", + "sebastian/comparator": "^3.0 || ^4.0", + "sebastian/recursion-context": "^3.0 || ^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^6.0", + "phpunit/phpunit": "^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2020-07-08T12:44:21+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "8.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "ca6647ffddd2add025ab3f21644a441d7c146cdc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca6647ffddd2add025ab3f21644a441d7c146cdc", + "reference": "ca6647ffddd2add025ab3f21644a441d7c146cdc", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.3", + "phpunit/php-file-iterator": "^3.0", + "phpunit/php-text-template": "^2.0", + "phpunit/php-token-stream": "^4.0", + "sebastian/code-unit-reverse-lookup": "^2.0", + "sebastian/environment": "^5.0", + "sebastian/version": "^3.0", + "theseer/tokenizer": "^1.1.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "suggest": { + "ext-pcov": "*", + "ext-xdebug": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-05-23T08:02:54+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "25fefc5b19835ca653877fe081644a3f8c1d915e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/25fefc5b19835ca653877fe081644a3f8c1d915e", + "reference": "25fefc5b19835ca653877fe081644a3f8c1d915e", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-07-11T05:18:21+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "f6eedfed1085dd1f4c599629459a0277d25f9a66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f6eedfed1085dd1f4c599629459a0277d25f9a66", + "reference": "f6eedfed1085dd1f4c599629459a0277d25f9a66", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-06-26T11:53:53+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "6ff9c8ea4d3212b88fcf74e25e516e2c51c99324" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/6ff9c8ea4d3212b88fcf74e25e516e2c51c99324", + "reference": "6ff9c8ea4d3212b88fcf74e25e516e2c51c99324", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-06-26T11:55:37+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "cc49734779cbb302bf51a44297dab8c4bbf941e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/cc49734779cbb302bf51a44297dab8c4bbf941e7", + "reference": "cc49734779cbb302bf51a44297dab8c4bbf941e7", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-06-26T11:58:13+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "4.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "5672711b6b07b14d5ab694e700c62eeb82fcf374" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/5672711b6b07b14d5ab694e700c62eeb82fcf374", + "reference": "5672711b6b07b14d5ab694e700c62eeb82fcf374", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-06-27T06:36:25+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.2.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "ad7cc5ec3ab2597b329880e30442d9054526023b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ad7cc5ec3ab2597b329880e30442d9054526023b", + "reference": "ad7cc5ec3ab2597b329880e30442d9054526023b", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.2.0", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.9.1", + "phar-io/manifest": "^1.0.3", + "phar-io/version": "^2.0.1", + "php": "^7.3", + "phpspec/prophecy": "^1.8.1", + "phpunit/php-code-coverage": "^8.0.1", + "phpunit/php-file-iterator": "^3.0", + "phpunit/php-invoker": "^3.0", + "phpunit/php-text-template": "^2.0", + "phpunit/php-timer": "^5.0", + "sebastian/code-unit": "^1.0.2", + "sebastian/comparator": "^4.0", + "sebastian/diff": "^4.0", + "sebastian/environment": "^5.0.1", + "sebastian/exporter": "^4.0", + "sebastian/global-state": "^4.0", + "sebastian/object-enumerator": "^4.0", + "sebastian/resource-operations": "^3.0", + "sebastian/type": "^2.1", + "sebastian/version": "^3.0" + }, + "require-dev": { + "ext-pdo": "*", + "phpspec/prophecy-phpunit": "^2.0" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ], + "files": [ + "src/Framework/Assert/Functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "funding": [ + { + "url": "https://phpunit.de/donate.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-06-22T07:10:55+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "psr/log", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2020-03-23T09:12:05+00:00" + }, + { + "name": "sabre/event", + "version": "5.1.0", + "source": { + "type": "git", + "url": "https://github.com/sabre-io/event.git", + "reference": "d00a17507af0e7544cfe17096372f5d733e3b276" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sabre-io/event/zipball/d00a17507af0e7544cfe17096372f5d733e3b276", + "reference": "d00a17507af0e7544cfe17096372f5d733e3b276", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.16.1", + "phpunit/phpunit": "^7 || ^8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Sabre\\Event\\": "lib/" + }, + "files": [ + "lib/coroutine.php", + "lib/Loop/functions.php", + "lib/Promise/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + } + ], + "description": "sabre/event is a library for lightweight event-based programming", + "homepage": "http://sabre.io/event/", + "keywords": [ + "EventEmitter", + "async", + "coroutine", + "eventloop", + "events", + "hooks", + "plugin", + "promise", + "reactor", + "signal" + ], + "time": "2020-01-31T18:52:29+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "c1e2df332c905079980b119c4db103117e5e5c90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/c1e2df332c905079980b119c4db103117e5e5c90", + "reference": "c1e2df332c905079980b119c4db103117e5e5c90", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-06-26T12:50:45+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ee51f9bb0c6d8a43337055db3120829fa14da819" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ee51f9bb0c6d8a43337055db3120829fa14da819", + "reference": "ee51f9bb0c6d8a43337055db3120829fa14da819", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-06-26T12:04:00+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "dcc580eadfaa4e7f9d2cf9ae1922134ea962e14f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/dcc580eadfaa4e7f9d2cf9ae1922134ea962e14f", + "reference": "dcc580eadfaa4e7f9d2cf9ae1922134ea962e14f", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-06-26T12:05:46+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "1e90b4cf905a7d06c420b1d2e9d11a4dc8a13113" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/1e90b4cf905a7d06c420b1d2e9d11a4dc8a13113", + "reference": "1e90b4cf905a7d06c420b1d2e9d11a4dc8a13113", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-06-30T04:46:02+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "0a757cab9d5b7ef49a619f1143e6c9c1bc0fe9d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/0a757cab9d5b7ef49a619f1143e6c9c1bc0fe9d2", + "reference": "0a757cab9d5b7ef49a619f1143e6c9c1bc0fe9d2", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-06-26T12:07:24+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "571d721db4aec847a0e59690b954af33ebf9f023" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/571d721db4aec847a0e59690b954af33ebf9f023", + "reference": "571d721db4aec847a0e59690b954af33ebf9f023", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-06-26T12:08:55+00:00" + }, + { + "name": "sebastian/global-state", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bdb1e7c79e592b8c82cb1699be3c8743119b8a72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bdb1e7c79e592b8c82cb1699be3c8743119b8a72", + "reference": "bdb1e7c79e592b8c82cb1699be3c8743119b8a72", + "shasum": "" + }, + "require": { + "php": "^7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" ], "authors": [ { - "name": "Tyson Andre" - }, - { - "name": "Rasmus Lerdorf" - }, - { - "name": "Andrew S. Morrison" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], - "description": "A static analyzer for PHP", + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", "keywords": [ - "analyzer", - "php", - "static" + "global state" ], - "time": "2020-07-03T23:01:25+00:00" + "time": "2020-02-07T06:11:37+00:00" }, { - "name": "phpdocumentor/reflection-common", - "version": "2.2.0", + "name": "sebastian/object-enumerator", + "version": "4.0.2", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "074fed2d0a6d08e1677dd8ce9d32aecb384917b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/074fed2d0a6d08e1677dd8ce9d32aecb384917b8", + "reference": "074fed2d0a6d08e1677dd8ce9d32aecb384917b8", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": "^7.3 || ^8.0", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-2.x": "2.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", - "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2020-06-27T09:03:43+00:00" + "time": "2020-06-26T12:11:32+00:00" }, { - "name": "phpdocumentor/reflection-docblock", - "version": "5.1.0", + "name": "sebastian/object-reflector", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e" + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "127a46f6b057441b201253526f81d5406d6c7840" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", - "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/127a46f6b057441b201253526f81d5406d6c7840", + "reference": "127a46f6b057441b201253526f81d5406d6c7840", "shasum": "" }, "require": { - "ext-filter": "^7.1", - "php": "^7.2", - "phpdocumentor/reflection-common": "^2.0", - "phpdocumentor/type-resolver": "^1.0", - "webmozart/assert": "^1" + "php": "^7.3 || ^8.0" }, "require-dev": { - "doctrine/instantiator": "^1", - "mockery/mockery": "^1" + "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.x-dev" + "dev-master": "2.0-dev" } }, "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - }, + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "funding": [ { - "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" + "url": "https://github.com/sebastianbergmann", + "type": "github" } ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2020-02-22T12:28:44+00:00" + "time": "2020-06-26T12:12:55+00:00" }, { - "name": "phpdocumentor/type-resolver", - "version": "1.3.0", + "name": "sebastian/recursion-context", + "version": "4.0.2", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "e878a14a65245fbe78f8080eba03b47c3b705651" + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "062231bf61d2b9448c4fa5a7643b5e1829c11d63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e878a14a65245fbe78f8080eba03b47c3b705651", - "reference": "e878a14a65245fbe78f8080eba03b47c3b705651", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/062231bf61d2b9448c4fa5a7643b5e1829c11d63", + "reference": "062231bf61d2b9448c4fa5a7643b5e1829c11d63", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.0" + "php": "^7.3 || ^8.0" }, "require-dev": { - "ext-tokenizer": "*" + "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-1.x": "1.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" } ], - "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "time": "2020-06-27T10:12:23+00:00" + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-06-26T12:14:17+00:00" }, { - "name": "psr/container", - "version": "1.0.0", + "name": "sebastian/resource-operations", + "version": "3.0.2", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0653718a5a629b065e91f774595267f8dc32e213" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0653718a5a629b065e91f774595267f8dc32e213", + "reference": "0653718a5a629b065e91f774595267f8dc32e213", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2017-02-14T16:28:37+00:00" + "time": "2020-06-26T12:16:22+00:00" }, { - "name": "psr/log", - "version": "1.1.3", + "name": "sebastian/type", + "version": "2.2.1", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "86991e2b33446cd96e648c18bcdb1e95afb2c05a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/86991e2b33446cd96e648c18bcdb1e95afb2c05a", + "reference": "86991e2b33446cd96e648c18bcdb1e95afb2c05a", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "2.2-dev" } }, "autoload": { - "psr-4": { - "Psr\\Log\\": "Psr/Log/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2020-03-23T09:12:05+00:00" + "time": "2020-07-05T08:31:53+00:00" }, { - "name": "sabre/event", - "version": "5.1.0", + "name": "sebastian/version", + "version": "3.0.1", "source": { "type": "git", - "url": "https://github.com/sabre-io/event.git", - "reference": "d00a17507af0e7544cfe17096372f5d733e3b276" + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "626586115d0ed31cb71483be55beb759b5af5a3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sabre-io/event/zipball/d00a17507af0e7544cfe17096372f5d733e3b276", - "reference": "d00a17507af0e7544cfe17096372f5d733e3b276", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/626586115d0ed31cb71483be55beb759b5af5a3c", + "reference": "626586115d0ed31cb71483be55beb759b5af5a3c", "shasum": "" }, "require": { - "php": "^7.1" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~2.16.1", - "phpunit/phpunit": "^7 || ^8" + "php": "^7.3 || ^8.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, "autoload": { - "psr-4": { - "Sabre\\Event\\": "lib/" - }, - "files": [ - "lib/coroutine.php", - "lib/Loop/functions.php", - "lib/Promise/functions.php" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -595,27 +2075,20 @@ ], "authors": [ { - "name": "Evert Pot", - "email": "me@evertpot.com", - "homepage": "http://evertpot.com/", - "role": "Developer" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "sabre/event is a library for lightweight event-based programming", - "homepage": "http://sabre.io/event/", - "keywords": [ - "EventEmitter", - "async", - "coroutine", - "eventloop", - "events", - "hooks", - "plugin", - "promise", - "reactor", - "signal" + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2020-01-31T18:52:29+00:00" + "time": "2020-06-26T12:18:43+00:00" }, { "name": "symfony/console", @@ -1317,6 +2790,52 @@ ], "time": "2020-05-30T20:35:19+00:00" }, + { + "name": "theseer/tokenizer", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "75a63c33a8577608444246075ea0af0d052e452a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a", + "reference": "75a63c33a8577608444246075ea0af0d052e452a", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2020-07-12T23:59:07+00:00" + }, { "name": "webmozart/assert", "version": "1.9.1", @@ -1373,7 +2892,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=7.3" + "php": "^7.4" }, "platform-dev": [], "plugin-api-version": "1.1.0" diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..49331cc --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,12 @@ + + + + + tests + + + diff --git a/tests/Parser/Helpers/ParserTestHelpers.php b/tests/Parser/Helpers/ParserTestHelpers.php new file mode 100644 index 0000000..84a9b50 --- /dev/null +++ b/tests/Parser/Helpers/ParserTestHelpers.php @@ -0,0 +1,36 @@ +parseFile($input)); + } + + /** + * parses a text and returns it as array of nodes. + * + * @param string $input + * + * @return Node[] + */ + private function parseTextToArray(string $input): array + { + $parser = new Parser(); + return iterator_to_array($parser->parseText($input)); + } +} diff --git a/tests/Parser/ParserTest.php b/tests/Parser/ParserTest.php new file mode 100644 index 0000000..26f0238 --- /dev/null +++ b/tests/Parser/ParserTest.php @@ -0,0 +1,468 @@ +parseTextToArray(file_get_contents($input)); + $fromFile = $this->parseFileToArray($input); + $this->assertEquals($fromText, $fromFile); + } + + /** + * @testdox Parsing a non-existent file should throw + * + * @return void + */ + public function testFileParsing() + { + $input = __DIR__ . '/non-existent.txt'; + $this->assertFalse(file_exists($input)); + $this->expectException(Exception::class); + $this->expectExceptionMessage('unable to open file: ' . $input); + $this->parseFileToArray($input); + } + + /** + * @testdox PLAIN bbcodes should consume other bbcodes + * + * @return void + */ + public function testPlainElement() + { + $nodes = $this->parseTextToArray( + 'before' . + '[plain][code]test[/code][/plain]' . + 'after' + ) + ; + $this->assertCount(3, $nodes); + + $node0 = $nodes[0]; + \assert($node0 instanceof TextNode); + $this->assertEquals('before', $node0->getBody()); + + $node1 = $nodes[1]; + \assert($node1 instanceof ElemNode); + $this->assertNotTrue($node1->isCode()); + $this->assertNotTrue($node1->isRich()); + $this->assertNull($node1->getLang()); + $this->assertEquals('plain', $node1->getName()); + $this->assertEquals('', $node1->getAttrMatch()); + + $elemBody = $node1->getBody(); + $this->assertIsString($elemBody); + $this->assertEquals('[code]test[/code]', $elemBody); + + $node2 = $nodes[2]; + \assert($node2 instanceof TextNode); + $this->assertEquals('after', $node2->getBody()); + } + + /** + * @testdox Brackets inside text shouldn't yield additional text-nodes + * + * @return void + */ + public function testBracketsInsideText() + { + $nodes = $this->parseTextToArray('hello[[[[['); + $this->assertCount(1, $nodes); + + $node0 = $nodes[0]; + \assert($node0 instanceof TextNode); + $this->assertEquals('hello[[[[[', $node0->getBody()); + + $nodes = $this->parseTextToArray('hello[[[[[code]test[/code]'); + $this->assertCount(2, $nodes); + + $node0 = $nodes[0]; + \assert($node0 instanceof TextNode); + $this->assertEquals('hello[[[[', $node0->getBody()); + + $node1 = $nodes[1]; + \assert($node1 instanceof ElemNode); + $this->assertTrue($node1->isCode()); + $this->assertFalse($node1->isRich()); + $this->assertNull($node1->getLang()); + $this->assertEquals('', $node1->getAttrMatch()); + + $elemBody = $node1->getBody(); + $this->assertIsString($elemBody); + $this->assertEquals('test', $elemBody); + } + + /** + * @testdox Unmatched closing bbcodes should be treated as text + * + * @return void + */ + public function testUnmatchedClosingElements() + { + $nodes = $this->parseTextToArray('[code]a[/code]b[/code]'); + $this->assertCount(2, $nodes); + + $node0 = $nodes[0]; + \assert($node0 instanceof ElemNode); + $this->assertTrue($node0->isCode()); + $this->assertFalse($node0->isRich()); + $this->assertNull($node0->getLang()); + $this->assertEquals('', $node0->getAttrMatch()); + + $elemBody = $node0->getBody(); + $this->assertIsString($elemBody); + $this->assertEquals('a', $elemBody); + + $node1 = $nodes[1]; + \assert($node1 instanceof TextNode); + $this->assertEquals('b[/code]', $node1->getBody()); + } + + /** + * @testdox Nested CODE bbcodes should behave the same as in XenForo + * + * @return void + */ + public function testNestedCodeElements() + { + $nodes = $this->parseTextToArray('[code][code]test[/code][/code]'); + $this->assertCount(2, $nodes); + + $node0 = $nodes[0]; + \assert($node0 instanceof ElemNode); + $this->assertTrue($node0->isCode()); + $this->assertFalse($node0->isRich()); + $this->assertNull($node0->getLang()); + $this->assertEquals('', $node0->getAttrMatch()); + + $elemBody = $node0->getBody(); + $this->assertIsString($elemBody); + $this->assertEquals('[code]test', $elemBody); + + $node1 = $nodes[1]; + \assert($node1 instanceof TextNode); + $this->assertEquals('[/code]', $node1->getBody()); + } + + /** + * @testdox Unclosed bbcodes should consume the rest of the input as body + * + * @return void + */ + public function testUnclosedElements() + { + $nodes = $this->parseTextToArray('[code]test'); + $this->assertCount(1, $nodes); + + $node0 = $nodes[0]; + \assert($node0 instanceof ElemNode); + $this->assertTrue($node0->isCode()); + $this->assertFalse($node0->isRich()); + $this->assertNull($node0->getLang()); + $this->assertEquals('', $node0->getAttrMatch()); + + $elemBody = $node0->getBody(); + $this->assertIsString($elemBody); + $this->assertEquals('test', $elemBody); + + // it would be possible to treat any opening bbcode + // as an implicit closing bbcode (if another bbcode is open). + // currently, the parser just consumes everything ignoring + // other bbcodes inside. + $nodes = $this->parseTextToArray('[code]a[plain]b[/plain]'); + $this->assertCount(1, $nodes); + + $node0 = $nodes[0]; + \assert($node0 instanceof ElemNode); + $this->assertTrue($node0->isCode()); + + $elemBody = $node0->getBody(); + $this->assertIsString($elemBody); + $this->assertEquals('a[plain]b[/plain]', $elemBody); + } + + /** + * @testdox Verify that attributes are parsed correctly and are exported as-is + * + * @return void + */ + public function testAttributeParsing() + { + $nodes = $this->parseTextToArray('[code=css]test{}[/code]'); + $this->assertCount(1, $nodes); + + $node0 = $nodes[0]; + \assert($node0 instanceof ElemNode); + $this->assertTrue($node0->isCode()); + $this->assertFalse($node0->isRich()); + $this->assertEquals('css', $node0->getLang()); + $this->assertEquals('=css', $node0->getAttrMatch()); + $this->assertEquals('css', $node0->getAttr('@value')); + + $elemBody = $node0->getBody(); + $this->assertIsString($elemBody); + $this->assertEquals('test{}', $elemBody); + + $nodes = $this->parseTextToArray('[code lang="css" title="Test 123"]test{}[/code]'); + $this->assertCount(1, $nodes); + + $node0 = $nodes[0]; + \assert($node0 instanceof ElemNode); + $this->assertTrue($node0->isCode()); + $this->assertFalse($node0->isRich()); + $this->assertEquals('css', $node0->getLang()); + $this->assertEquals(' lang="css" title="Test 123"', $node0->getAttrMatch()); + $this->assertEquals('css', $node0->getAttr('lang')); + $this->assertEquals('Test 123', $node0->getAttr('title')); + + $elemBody = $node0->getBody(); + $this->assertIsString($elemBody); + $this->assertEquals('test{}', $elemBody); + } + + /** + * @testdox Verify that parsed bbcodes are exported as-is + * + * @return void + */ + public function testElementExports() + { + $parser = new Parser(); + $inputs = [ + '[code=css]test{}[/code]', + '[code lang=css title="Test"]test{}[/code]', + '[plain][code=css]test{}[/code][/plain]', + '[CODE]test[/CODE]', + '[code]test[/code]', + '[code=rich][code]test[/code]test[/code]', + '[CODE=css]test[[[[/CODE]', + ]; + + foreach ($inputs as $input) { + $nodes = $this->parseTextToArray($input); + $this->assertCount(1, $nodes); + $this->assertInstanceOf(ElemNode::class, $nodes[0]); + $export = $parser->exportNode($nodes[0], null); + $this->assertEquals($input, $export); + } + } + + /** + * @testdox Verify that CODE=rich is parsed correctly + * + * @return void + */ + public function testRichCode() + { + $nodes = $this->parseTextToArray( + 'before' . + '[code=rich]' . + 'hello' . + '[code=css].test { color: red; }[/code]' . + 'world' . + '[/code]' . + 'after' + ); + + $this->assertCount(3, $nodes); + + $node0 = $nodes[0]; + \assert($node0 instanceof TextNode); + $this->assertEquals('before', $node0->getBody()); + + $node2 = $nodes[2]; + \assert($node2 instanceof TextNode); + $this->assertEquals('after', $node2->getBody()); + + $node1 = $nodes[1]; + \assert($node1 instanceof ElemNode); + $this->assertTrue($node1->isCode()); + $this->assertTrue($node1->isRich()); + $this->assertEquals('rich', $node1->getLang()); + $this->assertEquals('=rich', $node1->getAttrMatch()); + $this->assertEquals('rich', $node1->getAttr('@value')); + + $nodeList = $node1->getBody(); + \assert($nodeList instanceof NodeList); + $nodeIter = $nodeList->getIterator(); + $this->assertInstanceOf(Traversable::class, $nodeIter); + + $nodeListArray = iterator_to_array($nodeIter); + $this->assertIsArray($nodeListArray); + + $elemBody = $nodeList->toArray(); + $this->assertIsArray($elemBody); + $this->assertCount(3, $elemBody); + $this->assertEquals($elemBody, $nodeListArray); + + $subNode0 = $elemBody[0]; + \assert($subNode0 instanceof TextNode); + $this->assertEquals('hello', $subNode0->getBody()); + + $subNode1 = $elemBody[1]; + \assert($subNode1 instanceof ElemNode); + $this->assertTrue($subNode1->isCode()); + $this->assertFalse($subNode1->isRich()); + $this->assertEquals('css', $subNode1->getLang()); + $this->assertEquals('=css', $subNode1->getAttrMatch()); + $this->assertEquals('css', $subNode1->getAttr('@value')); + + $subElemBody = $subNode1->getBody(); + $this->assertIsString($subElemBody); + $this->assertEquals('.test { color: red; }', $subElemBody); + + $subNode2 = $elemBody[2]; + \assert($subNode2 instanceof TextNode); + $this->assertEquals('world', $subNode2->getBody()); + } + + /** + * @testdox Check nested CODE=rich elements + * + * @return void + */ + public function testNestedRichCode() + { + $nodes = $this->parseTextToArray( + 'before' . + '[code=rich]' . + '[code lang="rich" title="inner"]' . + '[code]test[/code]' . + '[/code]' . + 'between' . + '[code=rich][b]test2[/b][/code]' . + '[/code]' . + 'after' + ); + + $this->assertCount(3, $nodes); + + $node0 = $nodes[0]; + \assert($node0 instanceof TextNode); + $this->assertEquals('before', $node0->getBody()); + + $node1 = $nodes[1]; + \assert($node1 instanceof ElemNode); + $this->assertTrue($node1->isCode()); + $this->assertTrue($node1->isRich()); + $this->assertEquals('rich', $node1->getLang()); + $this->assertEquals('=rich', $node1->getAttrMatch()); + $this->assertEquals('rich', $node1->getAttr('@value')); + + $nodeList = $node1->getBody(); + \assert($nodeList instanceof NodeList); + + $elemBody = $nodeList->toArray(); + $this->assertCount(3, $elemBody); + + $subNode0 = $elemBody[0]; + \assert($subNode0 instanceof ElemNode); + $this->assertTrue($subNode0->isCode()); + $this->assertTrue($subNode0->isRich()); + $this->assertEquals('rich', $subNode0->getLang()); + $this->assertEquals('rich', $subNode0->getAttr('lang')); + $this->assertEquals('inner', $subNode0->getAttr('title')); + $this->assertEquals(' lang="rich" title="inner"', $subNode0->getAttrMatch()); + $this->assertNull($subNode0->getAttr('@value')); + + $subNodeList = $subNode0->getBody(); + \assert($subNodeList instanceof NodeList); + + $subElemBody = $subNodeList->toArray(); + $this->assertCount(1, $subElemBody); + + $subSubNode0 = $subElemBody[0]; + \assert($subSubNode0 instanceof ElemNode); + $this->assertTrue($subSubNode0->isCode()); + $this->assertFalse($subSubNode0->isRich()); + + $subSubElemBody = $subSubNode0->getBody(); + $this->assertIsString($subSubElemBody); + $this->assertEquals('test', $subSubElemBody); + + $subNode1 = $elemBody[1]; + \assert($subNode1 instanceof TextNode); + $this->assertEquals('between', $subNode1->getBody()); + + $subNode2 = $elemBody[2]; + \assert($subNode2 instanceof ElemNode); + $this->assertTrue($subNode2->isCode()); + $this->assertTrue($subNode2->isRich()); + $this->assertEquals('rich', $subNode2->getLang()); + $this->assertNull($subNode2->getAttr('lang')); + $this->assertEquals('=rich', $subNode2->getAttrMatch()); + $this->assertEquals('rich', $subNode2->getAttr('@value')); + + $subNodeList = $subNode2->getBody(); + \assert($subNodeList instanceof NodeList); + + $subElemBody = $subNodeList->toArray(); + $this->assertCount(1, $subElemBody); + + $subSubNode0 = $subElemBody[0]; + \assert($subSubNode0 instanceof TextNode); + $this->assertEquals('[b]test2[/b]', $subSubNode0->getBody()); + + $node2 = $nodes[2]; + \assert($node2 instanceof TextNode); + $this->assertEquals('after', $node2->getBody()); + } + + /** + * @testdox The parser should make the best out of invalid inputs + * + * @return void + */ + public function testInvalidInputs() + { + $nodes = $this->parseTextToArray('[code lang=""""title="test"ing][/code]'); + $this->assertCount(1, $nodes); + + // TODO XenForo does not recognize this as a bbcode ... + $node0 = $nodes[0]; + \assert($node0 instanceof ElemNode); + $this->assertTrue($node0->isCode()); + $this->assertFalse($node0->isRich()); + + // the `lang=""` part gets parsed correctly, because the parser + // matched the attribute string not containing white-space (\S+) + $this->assertEquals('', $node0->getLang()); + $this->assertEquals('', $node0->getAttr('lang')); + + $this->assertEquals(' lang=""""title="test"ing', $node0->getAttrMatch()); + $this->assertNull($node0->getAttr('@value')); + $this->assertNull($node0->getAttr('title')); + + $parser = new Parser(); + $export = $parser->exportNode($node0, null); + $this->assertEquals('[code lang=""""title="test"ing][/code]', $export); + + $nodes = $this->parseTextToArray('[code lang= """"title="test"ing][/code]'); + // note the white-space ---------------------^ + $this->assertCount(1, $nodes); + + $node0 = $nodes[0]; + \assert($node0 instanceof TextNode); + $this->assertEquals('[code lang= """"title="test"ing][/code]', $node0->getBody()); + } +} diff --git a/tests/Parser/input.txt b/tests/Parser/input.txt new file mode 100644 index 0000000..37571d2 --- /dev/null +++ b/tests/Parser/input.txt @@ -0,0 +1,3 @@ +hello +[code]world[/code] +[plain][code]testing[/code][/plain]