Skip to content

Commit

Permalink
[FEATURE] Add support for PSR/Log
Browse files Browse the repository at this point in the history
- Add PSR/Log as dependency
- Add a new SimpleLogger
- Use the NullLogger per default

Helps with #461

Signed-off-by: Daniel Ziegenberg <daniel@ziegenberg.at>
  • Loading branch information
ziegenberg committed Jun 24, 2024
1 parent cc53c85 commit 256d09b
Show file tree
Hide file tree
Showing 11 changed files with 133 additions and 12 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
## x.y.z

### Added

- Add support for PSR/Log with a new `SimpleLogger` and use `NullLogger` per default (#596)
- Support arithmetic operators in CSS function arguments (#607)
- Add support for inserting an item in a CSS list (#545)
- Add a class diagram to the README (#482)
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
],
"require": {
"php": ">=7.2.0",
"ext-iconv": "*"
"ext-iconv": "*",
"psr/log": "*"
},
"require-dev": {
"codacy/coverage": "^1.4.3",
Expand Down
17 changes: 15 additions & 2 deletions src/CSSList/CSSList.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,21 @@
use Sabberworm\CSS\Value\URL;
use Sabberworm\CSS\Value\Value;

use Psr\Log\LoggerInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;

/**
* This is the most generic container available. It can contain `DeclarationBlock`s (rule sets with a selector),
* `RuleSet`s as well as other `CSSList` objects.
*
* It can also contain `Import` and `Charset` objects stemming from at-rules.
*/
abstract class CSSList implements Renderable, Commentable
abstract class CSSList implements Renderable, Commentable, LoggerAwareInterface
{
use LoggerAwareTrait;

/**
* @var array<array-key, Comment>
*/
Expand All @@ -51,6 +58,8 @@ abstract class CSSList implements Renderable, Commentable
*/
public function __construct($iLineNo = 0)
{
$this->logger = new NullLogger();

$this->aComments = [];
$this->aContents = [];
$this->iLineNo = $iLineNo;
Expand All @@ -60,7 +69,7 @@ public function __construct($iLineNo = 0)
* @throws UnexpectedTokenException
* @throws SourceException
*/
public static function parseList(ParserState $oParserState, CSSList $oList): void
public static function parseList(ParserState $oParserState, CSSList $oList, ?LoggerInterface $logger = null): void
{
$bIsRoot = $oList instanceof Document;
if (is_string($oParserState)) {
Expand Down Expand Up @@ -372,6 +381,10 @@ public function removeDeclarationBlockBySelector($mSelector, $bRemoveAll = false
foreach ($mSelector as $iKey => &$mSel) {
if (!($mSel instanceof Selector)) {
if (!Selector::isValid($mSel)) {
$this->logger->error(
'Selector did not match {rx}.',
['rx' => Selector::SELECTOR_VALIDATION_RX]
);
throw new UnexpectedTokenException(
"Selector did not match '" . Selector::SELECTOR_VALIDATION_RX . "'.",
$mSel,
Expand Down
7 changes: 5 additions & 2 deletions src/CSSList/Document.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
use Sabberworm\CSS\RuleSet\RuleSet;
use Sabberworm\CSS\Value\Value;

use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;

/**
* This class represents the root of a parsed CSS file. It contains all top-level CSS contents: mostly declaration
* blocks, but also any at-rules encountered (`Import` and `Charset`).
Expand All @@ -27,10 +30,10 @@ public function __construct($iLineNo = 0)
/**
* @throws SourceException
*/
public static function parse(ParserState $oParserState): Document
public static function parse(ParserState $oParserState, ?LoggerInterface $logger = null): Document
{
$oDocument = new Document($oParserState->currentLine());
CSSList::parseList($oParserState, $oDocument);
CSSList::parseList($oParserState, $oDocument, $logger ?? new NullLogger());
return $oDocument;
}

Expand Down
14 changes: 12 additions & 2 deletions src/OutputFormat.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@

namespace Sabberworm\CSS;

use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;

/**
* Class OutputFormat
*
* @method OutputFormat setSemicolonAfterLastRule(bool $bSemicolonAfterLastRule) Set whether semicolons are added after
* last rule.
*/
class OutputFormat
class OutputFormat implements LoggerAwareInterface
{
use LoggerAwareTrait;

/**
* Value format: `"` means double-quote, `'` means single-quote
*
Expand Down Expand Up @@ -165,7 +171,10 @@ class OutputFormat
*/
private $iIndentationLevel = 0;

public function __construct() {}
public function __construct()
{
$this->logger = new NullLogger();
}

/**
* @param string $sName
Expand Down Expand Up @@ -237,6 +246,7 @@ public function __call($sMethodName, array $aArguments)
} elseif (method_exists(OutputFormatter::class, $sMethodName)) {
return call_user_func_array([$this->getFormatter(), $sMethodName], $aArguments);
} else {
$this->logger->error('Unknown OutputFormat method called: {method}', ['method' => $sMethodName]);
throw new \Exception('Unknown OutputFormat method called: ' . $sMethodName);
}
}
Expand Down
12 changes: 10 additions & 2 deletions src/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@
use Sabberworm\CSS\Parsing\ParserState;
use Sabberworm\CSS\Parsing\SourceException;

use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;

/**
* This class parses CSS from text into a data structure.
*/
class Parser
class Parser implements LoggerAwareInterface
{
use LoggerAwareTrait;

/**
* @var ParserState
*/
Expand All @@ -23,6 +29,8 @@ class Parser
*/
public function __construct($sText, Settings $oParserSettings = null, $iLineNo = 1)
{
$this->logger = new NullLogger();

if ($oParserSettings === null) {
$oParserSettings = Settings::create();
}
Expand Down Expand Up @@ -55,6 +63,6 @@ public function getCharset(): void
*/
public function parse(): Document
{
return Document::parse($this->oParserState);
return Document::parse($this->oParserState, $this->logger);
}
}
26 changes: 25 additions & 1 deletion src/Parsing/ParserState.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@
use Sabberworm\CSS\Parsing\Anchor;
use Sabberworm\CSS\Settings;

class ParserState
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;

class ParserState implements LoggerAwareInterface
{
use LoggerAwareTrait;

/**
* @var null
*
Expand Down Expand Up @@ -58,6 +64,8 @@ class ParserState
*/
public function __construct($sText, Settings $oParserSettings, $iLineNo = 1)
{
$this->logger = new NullLogger();

$this->oParserSettings = $oParserSettings;
$this->sText = $sText;
$this->iCurrentPosition = 0;
Expand Down Expand Up @@ -137,10 +145,12 @@ public function setPosition($iPosition): void
public function parseIdentifier($bIgnoreCase = true)
{
if ($this->isEnd()) {
$this->logger->error('Unexpected end of file while parsing identifier at line {line}', ['line' => $this->iLineNo]);
throw new UnexpectedEOFException('', '', 'identifier', $this->iLineNo);
}
$sResult = $this->parseCharacter(true);
if ($sResult === null) {
$this->logger->error('Unexpected token while parsing identifier at line {line}', ['line' => $this->iLineNo]);
throw new UnexpectedTokenException($sResult, $this->peek(5), 'identifier', $this->iLineNo);
}
$sCharacter = null;
Expand Down Expand Up @@ -287,13 +297,15 @@ public function consume($mValue = 1): string
$iLineCount = substr_count($mValue, "\n");
$iLength = $this->strlen($mValue);
if (!$this->streql($this->substr($this->iCurrentPosition, $iLength), $mValue)) {
$this->logger->error('Unexpected token "{token}" at line {line}', ['token' => $mValue, 'line' => $this->iLineNo]);
throw new UnexpectedTokenException($mValue, $this->peek(max($iLength, 5)), $this->iLineNo);
}
$this->iLineNo += $iLineCount;
$this->iCurrentPosition += $this->strlen($mValue);
return $mValue;
} else {
if ($this->iCurrentPosition + $mValue > $this->iLength) {
$this->logger->error('Unexpected end of file while consuming {count} chars at line {line}', ['count' => $mValue, 'line' => $this->iLineNo]);
throw new UnexpectedEOFException($mValue, $this->peek(5), 'count', $this->iLineNo);
}
$sResult = $this->substr($this->iCurrentPosition, $mValue);
Expand All @@ -318,6 +330,14 @@ public function consumeExpression($mExpression, $iMaxLength = null): string
if (preg_match($mExpression, $sInput, $aMatches, PREG_OFFSET_CAPTURE) === 1) {
return $this->consume($aMatches[0][0]);
}
$this->logger->error(
'Unexpected expression "{token}" instead of {expression} at line {line}',
[
'token' => $this->peek(5),
'expression' => $mExpression,
'line' => $this->iLineNo,
]
);
throw new UnexpectedTokenException($mExpression, $this->peek(5), 'expression', $this->iLineNo);
}

Expand Down Expand Up @@ -391,6 +411,10 @@ public function consumeUntil($aEnd, $bIncludeEnd = false, $consumeEnd = false, a
}

$this->iCurrentPosition = $start;
$this->logger->error(
'Unexpected end of file while searching for one of "{end}" at line {line}',
['end' => implode('","', $aEnd), 'line' => $this->iLineNo]
);
throw new UnexpectedEOFException(
'One of ("' . implode('","', $aEnd) . '")',
$this->peek(5),
Expand Down
9 changes: 9 additions & 0 deletions src/RuleSet/DeclarationBlock.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ public function setSelectors($mSelector, $oList = null): void
if (!($mSelector instanceof Selector)) {
if ($oList === null || !($oList instanceof KeyFrame)) {
if (!Selector::isValid($mSelector)) {
$this->logger->error(
"Selector did not match '{regexp}'. Found: '{selector}'",
['regexp' => Selector::SELECTOR_VALIDATION_RX, 'selector' => $mSelector]
);
throw new UnexpectedTokenException(
"Selector did not match '" . Selector::SELECTOR_VALIDATION_RX . "'.",
$mSelector,
Expand All @@ -113,6 +117,10 @@ public function setSelectors($mSelector, $oList = null): void
$this->aSelectors[$iKey] = new Selector($mSelector);
} else {
if (!KeyframeSelector::isValid($mSelector)) {
$this->logger->error(
"Selector did not match '{regexp}'. Found: '{selector}'",
['regexp' => KeyframeSelector::SELECTOR_VALIDATION_RX, 'selector' => $mSelector]
);
throw new UnexpectedTokenException(
"Selector did not match '" . KeyframeSelector::SELECTOR_VALIDATION_RX . "'.",
$mSelector,
Expand Down Expand Up @@ -790,6 +798,7 @@ public function render(OutputFormat $oOutputFormat): string
$sResult = $oOutputFormat->comments($this);
if (count($this->aSelectors) === 0) {
// If all the selectors have been removed, this declaration block becomes invalid
$this->logger->error("Attempt to print declaration block with missing selector at line {line}", ["line" => $this->iLineNo]);
throw new OutputException("Attempt to print declaration block with missing selector", $this->iLineNo);
}
$sResult .= $oOutputFormat->sBeforeDeclarationBlock;
Expand Down
10 changes: 9 additions & 1 deletion src/RuleSet/RuleSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
use Sabberworm\CSS\Renderable;
use Sabberworm\CSS\Rule\Rule;

use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;

/**
* This class is a container for individual 'Rule's.
*
Expand All @@ -20,8 +24,10 @@
* If you want to manipulate a `RuleSet`, use the methods `addRule(Rule $rule)`, `getRules()` and `removeRule($rule)`
* (which accepts either a `Rule` or a rule name; optionally suffixed by a dash to remove all related rules).
*/
abstract class RuleSet implements Renderable, Commentable
abstract class RuleSet implements Renderable, Commentable, LoggerAwareInterface
{
use LoggerAwareTrait;

/**
* @var array<string, Rule>
*/
Expand All @@ -42,6 +48,8 @@ abstract class RuleSet implements Renderable, Commentable
*/
public function __construct($iLineNo = 0)
{
$this->logger = new NullLogger();

$this->aRules = [];
$this->iLineNo = $iLineNo;
$this->aComments = [];
Expand Down
36 changes: 36 additions & 0 deletions src/SimpleLogger.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace Sabberworm\CSS;

use Psr\Log\AbstractLogger;

/**
* This class provides a simple logging facility.
*/
class SimpleLogger extends AbstractLogger
{

public function log($level, $message, array $context = []): void
{
echo self::interpolate($message, $context) . PHP_EOL;
}

/**
* Interpolates context values into the message placeholders.
*/
private function interpolate(string $message, array $context = []): string
{
// build a replacement array with braces around the context keys
$replace = [];
foreach ($context as $key => $val) {
// check that the value can be cast to string
if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) {
$replace['{' . $key . '}'] = $val;
}
}

// interpolate replacement values into the message and return
return strtr($message, $replace);
}

}
9 changes: 8 additions & 1 deletion src/Value/Value.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@
use Sabberworm\CSS\Value\CSSFunction;
use Sabberworm\CSS\Renderable;

use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;

/**
* Abstract base class for specific classes of CSS values: `Size`, `Color`, `CSSString` and `URL`, and another
* abstract subclass `ValueList`.
*/
abstract class Value implements Renderable
abstract class Value implements Renderable, LoggerAwareInterface
{
use LoggerAwareTrait;

/**
* @var int
*/
Expand All @@ -25,6 +31,7 @@ abstract class Value implements Renderable
*/
public function __construct($iLineNo = 0)
{
$this->logger = new NullLogger();
$this->iLineNo = $iLineNo;
}

Expand Down

0 comments on commit 256d09b

Please sign in to comment.