Skip to content

Commit

Permalink
Support for PLAIN BB-Code and CODE=rich (#36)
Browse files Browse the repository at this point in the history
* had to reset my branch

* remove unused files in parser-directory after reset

* Fix typos/mistakes in code comments

* Properly set up phpunit testsuite and extract helper methods to trait

* Add phpunit to phan github action

* simplify the parser and make sure text-nodes are buffered

* stop parsing after unclosed bbcode

there is no reason the check the text-buffer after a unclosed bbcode,
because the unclosed bbcode consumes the rest of the input anyway.

* fix wording in test

* fix wording in test #2

* add comment about nested bbcodes in test

* added parser-helpers trait again

* used more descriptive variable-names in parser

* used more descriptive variable-names in parser

* hide phan progress-bar, because github cannot render it

* (wip) added support for nested code-blocks

* some fixes and adjusted node-export logic

* tests added

* fix code=rich export issue

* small adjustments in architecture + saner api

* test if pr is broken

* yep, pr is broken

* fix IteratorAggregate usage

* make tests type-safe

* added more tests and fixed some associated issues

* fix typo in file-path

* fixed some typos and added more tests

* use expectException in tests

Co-authored-by: JR Cologne <[email protected]>
  • Loading branch information
asccc and jr-cologne authored Jul 17, 2020
1 parent 8a1a862 commit 38ef1fe
Show file tree
Hide file tree
Showing 17 changed files with 2,908 additions and 448 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
name: Static analysis (phan)
name: Static analysis (phan) & Unit Testing (phpunit)

on:
pull_request:
paths:
- "**.php"

jobs:
phan:
phan-phpunit:
runs-on: ubuntu-latest
steps:
- name: Install PHP and dependencies
Expand All @@ -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
62 changes: 40 additions & 22 deletions app/CodeFormatterApp.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -22,7 +23,7 @@ class CodeFormatterApp
*/
public function __construct(Parser $parser = null)
{
$this->parser = $parser ?? new PregParser();
$this->parser = $parser ?? new Parser();
}

/**
Expand All @@ -32,59 +33,76 @@ 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<Token>
* @return iterable<Node>
*/
private function parseInput(): iterable
{
return $this->parser->parseFile('php://input');
}

/**
* 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);
}
}
63 changes: 63 additions & 0 deletions app/Parser/ElemAttrs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

declare(strict_types=1);
namespace DevCommunityDE\CodeFormatter\Parser;

/**
* this class represents element attributes in a
* raw (match) and a tokenized (pairs) form.
*
* @internal
*/
final class ElemAttrs
{
/** @var string */
private $match;

/** @var array */
private $pairs;

/**
* constructor.
*
* @param string $match
* @param array<string,string> $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);
}
}
124 changes: 124 additions & 0 deletions app/Parser/ElemNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?php

declare(strict_types=1);
namespace DevCommunityDE\CodeFormatter\Parser;

/**
* this class represents a bbcode.
*/
final class ElemNode extends Node
{
/** @var string */
private $name;

/** @var ElemAttrs */
private $attrs;

/** @var NodeList|string */
private $body;

/**
* constructs a new code-token.
*
* @param string $name
* @param ElemAttrs $attrs
* @param NodeList|string $body
*/
public function __construct(string $name, ElemAttrs $attrs, $body)
{
\assert(\is_string($body) || $body instanceof NodeList);
parent::__construct(Node::KIND_ELEM);
$this->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;
}
}
37 changes: 37 additions & 0 deletions app/Parser/Node.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace DevCommunityDE\CodeFormatter\Parser;

abstract class Node
{
/** text node */
public const KIND_TEXT = 1;

/** element node (bbcode) */
public const KIND_ELEM = 2;

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

/**
* constructs a new token.
*
* @param int $kind
*/
public function __construct(int $kind)
{
$this->kind = $kind;
}

/**
* returns the node kind.
*
* @return int
*/
public function getKind(): int
{
return $this->kind;
}
}
Loading

0 comments on commit 38ef1fe

Please sign in to comment.