Skip to content
Snippets Groups Projects
Unverified Commit a77d83e3 authored by Alex Pott's avatar Alex Pott
Browse files

Issue #3085972 by andypost, jhodgdon: Replace Drupal\help_topics\FrontMatter...

Issue #3085972 by andypost, jhodgdon: Replace Drupal\help_topics\FrontMatter with Drupal\Component\Utility\FrontMatter
parent 6d4a2069
No related branches found
No related tags found
No related merge requests found
<?php
namespace Drupal\help_topics;
/**
* Extracts Front Matter from the beginning of a source.
*
* @internal
* This front matter extractor only supports help topic discovery and is not
* part of the public API.
*/
final class FrontMatter {
/**
* The separator used to indicate front matter data.
*
* @var string
*/
const FRONT_MATTER_SEPARATOR = '---';
/**
* The regular expression used to extract the YAML front matter content.
*
* @var string
*/
const FRONT_MATTER_REGEXP = "{^(?:" . self::FRONT_MATTER_SEPARATOR . ")[\r\n|\n]*(.*?)[\r\n|\n]+(?:" . self::FRONT_MATTER_SEPARATOR . ")[\r\n|\n]*(.*)$}s";
/**
* The parsed source.
*
* @var array
*/
protected $parsed;
/**
* A serializer class.
*
* @var string
*/
protected $serializer;
/**
* The source.
*
* @var string
*/
protected $source;
/**
* FrontMatter constructor.
*
* @param string $source
* A string source.
* @param string $serializer
* A class that implements
* \Drupal\Component\Serialization\SerializationInterface.
*/
public function __construct($source, $serializer = '\Drupal\Component\Serialization\Yaml') {
assert(is_string($source), '$source must be a string');
assert(is_string($serializer), '$serializer must be a string');
if (!is_subclass_of($serializer, '\Drupal\Component\Serialization\SerializationInterface')) {
throw new \InvalidArgumentException('The $serializer parameter must reference a class that implements \Drupal\Component\Serialization\SerializationInterface.');
}
$this->serializer = $serializer;
$this->source = $source;
}
/**
* Creates a new FrontMatter instance.
*
* @param string $source
* A string source.
* @param string $serializer
* A class that implements
* \Drupal\Component\Serialization\SerializationInterface.
*
* @return static
*/
public static function load($source, $serializer = '\Drupal\Component\Serialization\Yaml') {
return new static($source, $serializer);
}
/**
* Parses the source.
*
* @return array
* An associative array containing:
* - code: The real source code.
* - data: The front matter data extracted and decoded.
* - line: The line number where the real source code starts.
*
* @throws \Drupal\Component\Serialization\Exception\InvalidDataTypeException
* Exception thrown when the Front Matter cannot be parsed.
*/
private function parse() {
if (!$this->parsed) {
$this->parsed = [
'code' => $this->source,
'data' => [],
'line' => 1,
];
// Check for front matter data.
$len = strlen(static::FRONT_MATTER_SEPARATOR);
$matches = [];
if (substr($this->parsed['code'], 0, $len + 1) === static::FRONT_MATTER_SEPARATOR . "\n" || substr($this->parsed['code'], 0, $len + 2) === static::FRONT_MATTER_SEPARATOR . "\r\n") {
preg_match(static::FRONT_MATTER_REGEXP, $this->parsed['code'], $matches);
$matches = array_map('trim', $matches);
}
// Immediately return if the code doesn't contain front matter data.
if (empty($matches)) {
return $this->parsed;
}
// Set the extracted source code.
$this->parsed['code'] = $matches[2];
// Set the extracted front matter data. Do not catch any exceptions here
// as doing so would only obfuscate any errors found in the front matter
// data. Typecast to an array to ensure top level scalars are in an array.
if ($matches[1]) {
$this->parsed['data'] = (array) $this->serializer::decode($matches[1]);
}
// Determine the real source line by counting newlines from the data and
// then adding 2 to account for the front matter separator (---) wrappers
// and then adding 1 more for the actual line number after the data.
$this->parsed['line'] = count(preg_split('/\r\n|\n/', $matches[1])) + 3;
}
return $this->parsed;
}
/**
* Retrieves the extracted source code.
*
* @return string
* The extracted source code.
*
* @throws \Drupal\Component\Serialization\Exception\InvalidDataTypeException
* Exception thrown when the Front Matter cannot be parsed.
*/
public function getCode() {
return $this->parse()['code'];
}
/**
* Retrieves the extracted front matter data.
*
* @return array
* The extracted front matter data.
*
* @throws \Drupal\Component\Serialization\Exception\InvalidDataTypeException
* Exception thrown when the Front Matter cannot be parsed.
*/
public function getData() {
return $this->parse()['data'];
}
/**
* Retrieves the line where the source code starts, after any data.
*
* @return int
* The source code line.
*
* @throws \Drupal\Component\Serialization\Exception\InvalidDataTypeException
* Exception thrown when the Front Matter cannot be parsed.
*/
public function getLine() {
return $this->parse()['line'];
}
}
......@@ -5,6 +5,7 @@
use Drupal\Component\Discovery\DiscoveryException;
use Drupal\Component\FileCache\FileCacheFactory;
use Drupal\Component\FileSystem\RegexDirectoryIterator;
use Drupal\Component\FrontMatter\FrontMatter;
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
use Drupal\Component\Plugin\Discovery\DiscoveryTrait;
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
......@@ -118,7 +119,7 @@ public function findAll() {
// Get the rest of the plugin definition from front matter contained in
// the help topic Twig file.
try {
$front_matter = FrontMatter::load(file_get_contents($file), Yaml::class)->getData();
$front_matter = FrontMatter::create(file_get_contents($file), Yaml::class)->getData();
}
catch (InvalidDataTypeException $e) {
throw new DiscoveryException(sprintf('Malformed YAML in help topic "%s": %s.', $file, $e->getMessage()));
......
......@@ -2,6 +2,7 @@
namespace Drupal\help_topics;
use Drupal\Component\FrontMatter\FrontMatter;
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
......@@ -77,14 +78,14 @@ public function getSourceContext($name) {
// "serializer.yaml" service. This allows the core serializer to utilize
// core related functionality which isn't available as the standalone
// component based serializer.
$front_matter = FrontMatter::load($contents, Yaml::class);
$front_matter = new FrontMatter($contents, Yaml::class);
// Reconstruct the content if there is front matter data detected. Prepend
// the source with {% line \d+ %} to inform Twig that the source code
// actually starts on a different line past the front matter data. This is
// particularly useful when used in error reporting.
if ($front_matter->getData() && ($line = $front_matter->getLine())) {
$contents = "{% line $line %}" . $front_matter->getCode();
$contents = "{% line $line %}" . $front_matter->getContent();
}
}
catch (InvalidDataTypeException $e) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment