Skip to content

Commit 256d09b

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 cc53c85 commit 256d09b

11 files changed

+133
-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 (#596)
911
- Support arithmetic operators in CSS function arguments (#607)
1012
- Add support for inserting an item in a CSS list (#545)
1113
- Add a class diagram to the README (#482)

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;
@@ -60,7 +69,7 @@ public function __construct($iLineNo = 0)
6069
* @throws UnexpectedTokenException
6170
* @throws SourceException
6271
*/
63-
public static function parseList(ParserState $oParserState, CSSList $oList): void
72+
public static function parseList(ParserState $oParserState, CSSList $oList, ?LoggerInterface $logger = null): void
6473
{
6574
$bIsRoot = $oList instanceof Document;
6675
if (is_string($oParserState)) {
@@ -372,6 +381,10 @@ public function removeDeclarationBlockBySelector($mSelector, $bRemoveAll = false
372381
foreach ($mSelector as $iKey => &$mSel) {
373382
if (!($mSel instanceof Selector)) {
374383
if (!Selector::isValid($mSel)) {
384+
$this->logger->error(
385+
'Selector did not match {rx}.',
386+
['rx' => Selector::SELECTOR_VALIDATION_RX]
387+
);
375388
throw new UnexpectedTokenException(
376389
"Selector did not match '" . Selector::SELECTOR_VALIDATION_RX . "'.",
377390
$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

+12-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,10 @@ class OutputFormat
165171
*/
166172
private $iIndentationLevel = 0;
167173

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

170179
/**
171180
* @param string $sName
@@ -237,6 +246,7 @@ public function __call($sMethodName, array $aArguments)
237246
} elseif (method_exists(OutputFormatter::class, $sMethodName)) {
238247
return call_user_func_array([$this->getFormatter(), $sMethodName], $aArguments);
239248
} else {
249+
$this->logger->error('Unknown OutputFormat method called: {method}', ['method' => $sMethodName]);
240250
throw new \Exception('Unknown OutputFormat method called: ' . $sMethodName);
241251
}
242252
}

Diff for: src/Parser.php

+10-2
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,17 @@
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;
12+
913
/**
1014
* This class parses CSS from text into a data structure.
1115
*/
12-
class Parser
16+
class Parser implements LoggerAwareInterface
1317
{
18+
use LoggerAwareTrait;
19+
1420
/**
1521
* @var ParserState
1622
*/
@@ -23,6 +29,8 @@ class Parser
2329
*/
2430
public function __construct($sText, Settings $oParserSettings = null, $iLineNo = 1)
2531
{
32+
$this->logger = new NullLogger();
33+
2634
if ($oParserSettings === null) {
2735
$oParserSettings = Settings::create();
2836
}
@@ -55,6 +63,6 @@ public function getCharset(): void
5563
*/
5664
public function parse(): Document
5765
{
58-
return Document::parse($this->oParserState);
66+
return Document::parse($this->oParserState, $this->logger);
5967
}
6068
}

Diff for: src/Parsing/ParserState.php

+25-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;
@@ -137,10 +145,12 @@ public function setPosition($iPosition): void
137145
public function parseIdentifier($bIgnoreCase = true)
138146
{
139147
if ($this->isEnd()) {
148+
$this->logger->error('Unexpected end of file while parsing identifier at line {line}', ['line' => $this->iLineNo]);
140149
throw new UnexpectedEOFException('', '', 'identifier', $this->iLineNo);
141150
}
142151
$sResult = $this->parseCharacter(true);
143152
if ($sResult === null) {
153+
$this->logger->error('Unexpected token while parsing identifier at line {line}', ['line' => $this->iLineNo]);
144154
throw new UnexpectedTokenException($sResult, $this->peek(5), 'identifier', $this->iLineNo);
145155
}
146156
$sCharacter = null;
@@ -287,13 +297,15 @@ public function consume($mValue = 1): string
287297
$iLineCount = substr_count($mValue, "\n");
288298
$iLength = $this->strlen($mValue);
289299
if (!$this->streql($this->substr($this->iCurrentPosition, $iLength), $mValue)) {
300+
$this->logger->error('Unexpected token "{token}" at line {line}', ['token' => $mValue, 'line' => $this->iLineNo]);
290301
throw new UnexpectedTokenException($mValue, $this->peek(max($iLength, 5)), $this->iLineNo);
291302
}
292303
$this->iLineNo += $iLineCount;
293304
$this->iCurrentPosition += $this->strlen($mValue);
294305
return $mValue;
295306
} else {
296307
if ($this->iCurrentPosition + $mValue > $this->iLength) {
308+
$this->logger->error('Unexpected end of file while consuming {count} chars at line {line}', ['count' => $mValue, 'line' => $this->iLineNo]);
297309
throw new UnexpectedEOFException($mValue, $this->peek(5), 'count', $this->iLineNo);
298310
}
299311
$sResult = $this->substr($this->iCurrentPosition, $mValue);
@@ -318,6 +330,14 @@ public function consumeExpression($mExpression, $iMaxLength = null): string
318330
if (preg_match($mExpression, $sInput, $aMatches, PREG_OFFSET_CAPTURE) === 1) {
319331
return $this->consume($aMatches[0][0]);
320332
}
333+
$this->logger->error(
334+
'Unexpected expression "{token}" instead of {expression} at line {line}',
335+
[
336+
'token' => $this->peek(5),
337+
'expression' => $mExpression,
338+
'line' => $this->iLineNo,
339+
]
340+
);
321341
throw new UnexpectedTokenException($mExpression, $this->peek(5), 'expression', $this->iLineNo);
322342
}
323343

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

393413
$this->iCurrentPosition = $start;
414+
$this->logger->error(
415+
'Unexpected end of file while searching for one of "{end}" at line {line}',
416+
['end' => implode('","', $aEnd), 'line' => $this->iLineNo]
417+
);
394418
throw new UnexpectedEOFException(
395419
'One of ("' . implode('","', $aEnd) . '")',
396420
$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): void
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): void
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,
@@ -790,6 +798,7 @@ public function render(OutputFormat $oOutputFormat): string
790798
$sResult = $oOutputFormat->comments($this);
791799
if (count($this->aSelectors) === 0) {
792800
// If all the selectors have been removed, this declaration block becomes invalid
801+
$this->logger->error("Attempt to print declaration block with missing selector at line {line}", ["line" => $this->iLineNo]);
793802
throw new OutputException("Attempt to print declaration block with missing selector", $this->iLineNo);
794803
}
795804
$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

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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+
13+
public function log($level, $message, array $context = []): void
14+
{
15+
echo self::interpolate($message, $context) . PHP_EOL;
16+
}
17+
18+
/**
19+
* Interpolates context values into the message placeholders.
20+
*/
21+
private function interpolate(string $message, array $context = []): string
22+
{
23+
// build a replacement array with braces around the context keys
24+
$replace = [];
25+
foreach ($context as $key => $val) {
26+
// check that the value can be cast to string
27+
if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) {
28+
$replace['{' . $key . '}'] = $val;
29+
}
30+
}
31+
32+
// interpolate replacement values into the message and return
33+
return strtr($message, $replace);
34+
}
35+
36+
}

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)