Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Add support for PSR/Log #596

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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": "*"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For dependencies that are actual packages (instead of PHP extensions), please always use specific versions:

Suggested change
"psr/log": "*"
"psr/log": "^1.0.0 || ^2.0.0 || ^3.0.0"

},
"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
34 changes: 34 additions & 0 deletions src/SimpleLogger.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?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);
}
}
Comment on lines +1 to +34
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to provide this? Shouldn't it be up to the users of the package to provide their own logger, or one from a library, should they want logging?

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