Skip to content

Commit 7c80c62

Browse files
committed
[FEATURE] Add support for PSR/Log
- Add PSR/Log as dependency - Add a new SimpleLogger - Use the NullLogger per default Helps with #461 Signed-off-by: Daniel Ziegenberg <[email protected]>
1 parent 3163c9f commit 7c80c62

11 files changed

+126
-12
lines changed

Diff for: CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
66
## x.y.z
77

88
### Added
9+
10+
- Add support for PSR/Log with a new `SimpleLogger` and use `NullLogger` per default
911
- Add support for inserting an item in a CSS list (#545)
1012
- Add a class diagram to the README (#482)
1113
- Add support for the `dvh`, `lvh` and `svh` length units (#415)

Diff for: composer.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
],
2525
"require": {
2626
"php": ">=7.2.0",
27-
"ext-iconv": "*"
27+
"ext-iconv": "*",
28+
"psr/log": "*"
2829
},
2930
"require-dev": {
3031
"codacy/coverage": "^1.4.3",

Diff for: src/CSSList/CSSList.php

+15-2
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,21 @@
2323
use Sabberworm\CSS\Value\URL;
2424
use Sabberworm\CSS\Value\Value;
2525

26+
use Psr\Log\LoggerInterface;
27+
use Psr\Log\LoggerAwareInterface;
28+
use Psr\Log\LoggerAwareTrait;
29+
use Psr\Log\NullLogger;
30+
2631
/**
2732
* This is the most generic container available. It can contain `DeclarationBlock`s (rule sets with a selector),
2833
* `RuleSet`s as well as other `CSSList` objects.
2934
*
3035
* It can also contain `Import` and `Charset` objects stemming from at-rules.
3136
*/
32-
abstract class CSSList implements Renderable, Commentable
37+
abstract class CSSList implements Renderable, Commentable, LoggerAwareInterface
3338
{
39+
use LoggerAwareTrait;
40+
3441
/**
3542
* @var array<array-key, Comment>
3643
*/
@@ -51,6 +58,8 @@ abstract class CSSList implements Renderable, Commentable
5158
*/
5259
public function __construct($iLineNo = 0)
5360
{
61+
$this->logger = new NullLogger();
62+
5463
$this->aComments = [];
5564
$this->aContents = [];
5665
$this->iLineNo = $iLineNo;
@@ -62,7 +71,7 @@ public function __construct($iLineNo = 0)
6271
* @throws UnexpectedTokenException
6372
* @throws SourceException
6473
*/
65-
public static function parseList(ParserState $oParserState, CSSList $oList)
74+
public static function parseList(ParserState $oParserState, CSSList $oList, ?LoggerInterface $logger = null)
6675
{
6776
$bIsRoot = $oList instanceof Document;
6877
if (is_string($oParserState)) {
@@ -382,6 +391,10 @@ public function removeDeclarationBlockBySelector($mSelector, $bRemoveAll = false
382391
foreach ($mSelector as $iKey => &$mSel) {
383392
if (!($mSel instanceof Selector)) {
384393
if (!Selector::isValid($mSel)) {
394+
$this->logger->error(
395+
'Selector did not match {rx}.',
396+
['rx' => Selector::SELECTOR_VALIDATION_RX]
397+
);
385398
throw new UnexpectedTokenException(
386399
"Selector did not match '" . Selector::SELECTOR_VALIDATION_RX . "'.",
387400
$mSel,

Diff for: src/CSSList/Document.php

+5-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
use Sabberworm\CSS\RuleSet\RuleSet;
1111
use Sabberworm\CSS\Value\Value;
1212

13+
use Psr\Log\LoggerInterface;
14+
use Psr\Log\NullLogger;
15+
1316
/**
1417
* This class represents the root of a parsed CSS file. It contains all top-level CSS contents: mostly declaration
1518
* blocks, but also any at-rules encountered (`Import` and `Charset`).
@@ -27,10 +30,10 @@ public function __construct($iLineNo = 0)
2730
/**
2831
* @throws SourceException
2932
*/
30-
public static function parse(ParserState $oParserState): Document
33+
public static function parse(ParserState $oParserState, ?LoggerInterface $logger = null): Document
3134
{
3235
$oDocument = new Document($oParserState->currentLine());
33-
CSSList::parseList($oParserState, $oDocument);
36+
CSSList::parseList($oParserState, $oDocument, $logger ?? new NullLogger());
3437
return $oDocument;
3538
}
3639

Diff for: src/OutputFormat.php

+11-2
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,20 @@
22

33
namespace Sabberworm\CSS;
44

5+
use Psr\Log\LoggerAwareInterface;
6+
use Psr\Log\LoggerAwareTrait;
7+
use Psr\Log\NullLogger;
8+
59
/**
610
* Class OutputFormat
711
*
812
* @method OutputFormat setSemicolonAfterLastRule(bool $bSemicolonAfterLastRule) Set whether semicolons are added after
913
* last rule.
1014
*/
11-
class OutputFormat
15+
class OutputFormat implements LoggerAwareInterface
1216
{
17+
use LoggerAwareTrait;
18+
1319
/**
1420
* Value format: `"` means double-quote, `'` means single-quote
1521
*
@@ -165,7 +171,9 @@ class OutputFormat
165171
*/
166172
private $iIndentationLevel = 0;
167173

168-
public function __construct() {}
174+
public function __construct() {
175+
$this->logger = new NullLogger();
176+
}
169177

170178
/**
171179
* @param string $sName
@@ -237,6 +245,7 @@ public function __call($sMethodName, array $aArguments)
237245
} elseif (method_exists(OutputFormatter::class, $sMethodName)) {
238246
return call_user_func_array([$this->getFormatter(), $sMethodName], $aArguments);
239247
} else {
248+
$this->logger->error('Unknown OutputFormat method called: {method}', ['method' => $sMethodName]);
240249
throw new \Exception('Unknown OutputFormat method called: ' . $sMethodName);
241250
}
242251
}

Diff for: src/Parser.php

+9-2
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,16 @@
66
use Sabberworm\CSS\Parsing\ParserState;
77
use Sabberworm\CSS\Parsing\SourceException;
88

9+
use Psr\Log\LoggerAwareInterface;
10+
use Psr\Log\LoggerAwareTrait;
11+
use Psr\Log\NullLogger;
912
/**
1013
* This class parses CSS from text into a data structure.
1114
*/
12-
class Parser
15+
class Parser implements LoggerAwareInterface
1316
{
17+
use LoggerAwareTrait;
18+
1419
/**
1520
* @var ParserState
1621
*/
@@ -23,6 +28,8 @@ class Parser
2328
*/
2429
public function __construct($sText, Settings $oParserSettings = null, $iLineNo = 1)
2530
{
31+
$this->logger = new NullLogger();
32+
2633
if ($oParserSettings === null) {
2734
$oParserSettings = Settings::create();
2835
}
@@ -61,6 +68,6 @@ public function getCharset()
6168
*/
6269
public function parse()
6370
{
64-
return Document::parse($this->oParserState);
71+
return Document::parse($this->oParserState, $this->logger);
6572
}
6673
}

Diff for: src/Parsing/ParserState.php

+21-1
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@
66
use Sabberworm\CSS\Parsing\Anchor;
77
use Sabberworm\CSS\Settings;
88

9-
class ParserState
9+
use Psr\Log\LoggerAwareInterface;
10+
use Psr\Log\LoggerAwareTrait;
11+
use Psr\Log\NullLogger;
12+
13+
class ParserState implements LoggerAwareInterface
1014
{
15+
use LoggerAwareTrait;
16+
1117
/**
1218
* @var null
1319
*
@@ -58,6 +64,8 @@ class ParserState
5864
*/
5965
public function __construct($sText, Settings $oParserSettings, $iLineNo = 1)
6066
{
67+
$this->logger = new NullLogger();
68+
6169
$this->oParserSettings = $oParserSettings;
6270
$this->sText = $sText;
6371
$this->iCurrentPosition = 0;
@@ -141,10 +149,12 @@ public function setPosition($iPosition)
141149
public function parseIdentifier($bIgnoreCase = true)
142150
{
143151
if ($this->isEnd()) {
152+
$this->logger->error('Unexpected end of file while parsing identifier at line {line}', ['line' => $this->iLineNo]);
144153
throw new UnexpectedEOFException('', '', 'identifier', $this->iLineNo);
145154
}
146155
$sResult = $this->parseCharacter(true);
147156
if ($sResult === null) {
157+
$this->logger->error('Unexpected token while parsing identifier at line {line}', ['line' => $this->iLineNo]);
148158
throw new UnexpectedTokenException($sResult, $this->peek(5), 'identifier', $this->iLineNo);
149159
}
150160
$sCharacter = null;
@@ -291,13 +301,15 @@ public function consume($mValue = 1): string
291301
$iLineCount = substr_count($mValue, "\n");
292302
$iLength = $this->strlen($mValue);
293303
if (!$this->streql($this->substr($this->iCurrentPosition, $iLength), $mValue)) {
304+
$this->logger->error('Unexpected token "{token}" at line {line}', ['token' => $mValue, 'line' => $this->iLineNo]);
294305
throw new UnexpectedTokenException($mValue, $this->peek(max($iLength, 5)), $this->iLineNo);
295306
}
296307
$this->iLineNo += $iLineCount;
297308
$this->iCurrentPosition += $this->strlen($mValue);
298309
return $mValue;
299310
} else {
300311
if ($this->iCurrentPosition + $mValue > $this->iLength) {
312+
$this->logger->error('Unexpected end of file while consuming {count} chars at line {line}', ['count' => $mValue, 'line' => $this->iLineNo]);
301313
throw new UnexpectedEOFException($mValue, $this->peek(5), 'count', $this->iLineNo);
302314
}
303315
$sResult = $this->substr($this->iCurrentPosition, $mValue);
@@ -324,6 +336,10 @@ public function consumeExpression($mExpression, $iMaxLength = null)
324336
if (preg_match($mExpression, $sInput, $aMatches, PREG_OFFSET_CAPTURE) === 1) {
325337
return $this->consume($aMatches[0][0]);
326338
}
339+
$this->logger->error(
340+
'Unexpected expression "{token}" instead of {expression} at line {line}',
341+
['token' => $this->peek(5), 'expression' =>$mExpression, 'line' => $this->iLineNo]
342+
);
327343
throw new UnexpectedTokenException($mExpression, $this->peek(5), 'expression', $this->iLineNo);
328344
}
329345

@@ -397,6 +413,10 @@ public function consumeUntil($aEnd, $bIncludeEnd = false, $consumeEnd = false, a
397413
}
398414

399415
$this->iCurrentPosition = $start;
416+
$this->logger->error(
417+
'Unexpected end of file while searching for one of "{end}" at line {line}',
418+
['end' => implode('","', $aEnd), 'line' => $this->iLineNo]
419+
);
400420
throw new UnexpectedEOFException(
401421
'One of ("' . implode('","', $aEnd) . '")',
402422
$this->peek(5),

Diff for: src/RuleSet/DeclarationBlock.php

+9
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ public function setSelectors($mSelector, $oList = null)
104104
if (!($mSelector instanceof Selector)) {
105105
if ($oList === null || !($oList instanceof KeyFrame)) {
106106
if (!Selector::isValid($mSelector)) {
107+
$this->logger->error(
108+
"Selector did not match '{regexp}'. Found: '{selector}'",
109+
['regexp' => Selector::SELECTOR_VALIDATION_RX, 'selector' => $mSelector]
110+
);
107111
throw new UnexpectedTokenException(
108112
"Selector did not match '" . Selector::SELECTOR_VALIDATION_RX . "'.",
109113
$mSelector,
@@ -113,6 +117,10 @@ public function setSelectors($mSelector, $oList = null)
113117
$this->aSelectors[$iKey] = new Selector($mSelector);
114118
} else {
115119
if (!KeyframeSelector::isValid($mSelector)) {
120+
$this->logger->error(
121+
"Selector did not match '{regexp}'. Found: '{selector}'",
122+
['regexp' => KeyframeSelector::SELECTOR_VALIDATION_RX, 'selector' => $mSelector]
123+
);
116124
throw new UnexpectedTokenException(
117125
"Selector did not match '" . KeyframeSelector::SELECTOR_VALIDATION_RX . "'.",
118126
$mSelector,
@@ -818,6 +826,7 @@ public function render(OutputFormat $oOutputFormat): string
818826
$sResult = $oOutputFormat->comments($this);
819827
if (count($this->aSelectors) === 0) {
820828
// If all the selectors have been removed, this declaration block becomes invalid
829+
$this->logger->error("Attempt to print declaration block with missing selector at line {line}", ["line" => $this->iLineNo]);
821830
throw new OutputException("Attempt to print declaration block with missing selector", $this->iLineNo);
822831
}
823832
$sResult .= $oOutputFormat->sBeforeDeclarationBlock;

Diff for: src/RuleSet/RuleSet.php

+9-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
use Sabberworm\CSS\Renderable;
1212
use Sabberworm\CSS\Rule\Rule;
1313

14+
use Psr\Log\LoggerAwareInterface;
15+
use Psr\Log\LoggerAwareTrait;
16+
use Psr\Log\NullLogger;
17+
1418
/**
1519
* This class is a container for individual 'Rule's.
1620
*
@@ -20,8 +24,10 @@
2024
* If you want to manipulate a `RuleSet`, use the methods `addRule(Rule $rule)`, `getRules()` and `removeRule($rule)`
2125
* (which accepts either a `Rule` or a rule name; optionally suffixed by a dash to remove all related rules).
2226
*/
23-
abstract class RuleSet implements Renderable, Commentable
27+
abstract class RuleSet implements Renderable, Commentable, LoggerAwareInterface
2428
{
29+
use LoggerAwareTrait;
30+
2531
/**
2632
* @var array<string, Rule>
2733
*/
@@ -42,6 +48,8 @@ abstract class RuleSet implements Renderable, Commentable
4248
*/
4349
public function __construct($iLineNo = 0)
4450
{
51+
$this->logger = new NullLogger();
52+
4553
$this->aRules = [];
4654
$this->iLineNo = $iLineNo;
4755
$this->aComments = [];

Diff for: src/SimpleLogger.php

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace Sabberworm\CSS;
4+
5+
use Psr\Log\AbstractLogger;
6+
7+
/**
8+
* This class provides a simple logging facility.
9+
*/
10+
class SimpleLogger extends AbstractLogger {
11+
12+
public function log($level, $message, array $context = array())
13+
{
14+
echo self::interpolate($message, $context) . PHP_EOL;
15+
}
16+
17+
/**
18+
* Interpolates context values into the message placeholders.
19+
*/
20+
private function interpolate(string $message, array $context = array()): string
21+
{
22+
// build a replacement array with braces around the context keys
23+
$replace = array();
24+
foreach ($context as $key => $val) {
25+
// check that the value can be cast to string
26+
if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) {
27+
$replace['{' . $key . '}'] = $val;
28+
}
29+
}
30+
31+
// interpolate replacement values into the message and return
32+
return strtr($message, $replace);
33+
}
34+
35+
}

Diff for: src/Value/Value.php

+8-1
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,18 @@
99
use Sabberworm\CSS\Value\CSSFunction;
1010
use Sabberworm\CSS\Renderable;
1111

12+
use Psr\Log\LoggerAwareInterface;
13+
use Psr\Log\LoggerAwareTrait;
14+
use Psr\Log\NullLogger;
15+
1216
/**
1317
* Abstract base class for specific classes of CSS values: `Size`, `Color`, `CSSString` and `URL`, and another
1418
* abstract subclass `ValueList`.
1519
*/
16-
abstract class Value implements Renderable
20+
abstract class Value implements Renderable, LoggerAwareInterface
1721
{
22+
use LoggerAwareTrait;
23+
1824
/**
1925
* @var int
2026
*/
@@ -25,6 +31,7 @@ abstract class Value implements Renderable
2531
*/
2632
public function __construct($iLineNo = 0)
2733
{
34+
$this->logger = new NullLogger();
2835
$this->iLineNo = $iLineNo;
2936
}
3037

0 commit comments

Comments
 (0)