Skip to content

Commit 11a2edf

Browse files
committed
Fixes JSON schema $ref support
fix(TwigPatternLibraryParser)
1 parent 04dc07f commit 11a2edf

File tree

4 files changed

+152
-123
lines changed

4 files changed

+152
-123
lines changed

src/PatternLibraryCollector.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,9 @@ protected function getLibraryMetadata(): array {
428428
/** \Drupal\Core\Extension\Extension[] $extension_list */
429429
$extension_list = $container->get($service_name)->getList();
430430
foreach ($extension_list as $extension_name => $extension) {
431+
if (empty($extension->status)) {
432+
continue;
433+
}
431434
$libraries = $this->buildByExtension($extension_type, $extension_name);
432435
foreach ($libraries as $library_name => $library) {
433436
$pattern_libraries = $library['patterns'] ?? [];

src/PatternLibraryJSONParserTrait.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace Drupal\patternkit;
4+
5+
/**
6+
* Provides methods for parsing JSON schema.
7+
*/
8+
trait PatternLibraryJSONParserTrait {
9+
10+
/**
11+
* Serializes and de-serializes data.
12+
*
13+
* @var \Drupal\Component\Serialization\Json
14+
*/
15+
protected $serializer;
16+
17+
/**
18+
* Parses schema properties for $ref and updates their location.
19+
*
20+
* @param object $properties
21+
* The properties to parse.
22+
* @param array &$metadata
23+
* The library metadata for looking up referenced patterns.
24+
*
25+
* @return object
26+
* The updated schema properties object.
27+
*/
28+
public static function schemaDereference($properties, array &$metadata) {
29+
foreach ($properties as $property => $value) {
30+
if (!is_scalar($value)) {
31+
$new_value = static::schemaDereference($value, $metadata);
32+
if (is_object($properties)) {
33+
$properties->{$property} = $new_value;
34+
}
35+
if (is_array($properties)) {
36+
$properties[$property] = $new_value;
37+
}
38+
continue;
39+
}
40+
if ($property !== '$ref') {
41+
continue;
42+
}
43+
$pattern = strstr($value, '.json', TRUE);
44+
$ref = substr($value, strlen("$pattern.json"));
45+
if (!isset($metadata[$pattern])) {
46+
unset($properties[$property]);
47+
continue;
48+
}
49+
$properties[$property] = $metadata[$pattern]->url . $ref;
50+
}
51+
return $properties;
52+
}
53+
54+
}

src/PatternLibraryParser/TwigPatternLibraryParser.php

Lines changed: 40 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -8,53 +8,46 @@
88
use Drupal\Core\Theme\ThemeManagerInterface;
99
use Drupal\patternkit\Pattern;
1010
use Drupal\patternkit\PatternEditorConfig;
11+
use Drupal\patternkit\PatternLibraryJSONParserTrait;
1112
use Drupal\patternkit\PatternLibraryParserBase;
12-
use Symfony\Component\Finder\Iterator\RecursiveDirectoryIterator;
1313

1414
/**
1515
* Parses a Twig pattern library collection into usable metadata.
1616
*/
1717
class TwigPatternLibraryParser extends PatternLibraryParserBase {
18-
19-
/** @var \Drupal\Component\Serialization\SerializationInterface */
20-
protected $serializer;
18+
use PatternLibraryJSONParserTrait;
2119

2220
/**
2321
* TwigPatternLibraryParser constructor.
2422
*
2523
* @param \Drupal\Component\Serialization\SerializationInterface $serializer
26-
* Serialization service.
27-
*
28-
* {@inheritdoc}
24+
* Serializes and de-serializes data.
25+
* @param string $root
26+
* The application root path.
27+
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
28+
* Allows modules to alter library parsing.
29+
* @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
30+
* Allows themes to alter library parsing.
2931
*/
30-
public function __construct(SerializationInterface $serializer, $root, ModuleHandlerInterface $module_handler, ThemeManagerInterface $theme_manager) {
32+
public function __construct(
33+
SerializationInterface $serializer,
34+
$root,
35+
ModuleHandlerInterface $module_handler,
36+
ThemeManagerInterface $theme_manager) {
37+
3138
$this->serializer = $serializer;
3239
parent::__construct($root, $module_handler, $theme_manager);
3340
}
3441

35-
/**
36-
* Returns a new Patternkit Pattern.
37-
*
38-
* @param $name
39-
* The name of the pattern.
40-
* @param $schema
41-
* The optional schema for the pattern.
42-
*
43-
* @return \Drupal\patternkit\Pattern
44-
*/
45-
public function createPattern($name, $schema): Pattern {
46-
return new Pattern($name, $schema);
47-
}
48-
4942
/**
5043
* Fetches all assets for a pattern.
5144
*
52-
* @param Pattern $pattern
45+
* @param \Drupal\patternkit\Pattern $pattern
5346
* The pattern to use for asset retrieval.
54-
* @param PatternEditorConfig $config
47+
* @param \Drupal\patternkit\PatternEditorConfig $config
5548
* The configuration object to use for provisioning the pattern.
5649
*
57-
* @return Pattern
50+
* @return \Drupal\patternkit\Pattern
5851
* The pattern parameter with updated asset references.
5952
*/
6053
public function fetchPatternAssets(Pattern $pattern,
@@ -74,16 +67,12 @@ public function fetchPatternAssets(Pattern $pattern,
7467
* and defines the following elements:
7568
* - patterns: A list of pattern libraries and subtypes to include. Each
7669
* subtype is keyed by the subtype path.
77-
* @code
70+
* @code
7871
* patterns:
79-
* library:
80-
* atoms:
81-
* path/atoms: {}
82-
* molecules:
83-
* path/molecules: {}
84-
* organisms:
85-
* path/organisms: {}
86-
* @endcode
72+
* path/atoms: {type: twig, category: atoms}
73+
* path/molecules: {type: twig, category: molecules}
74+
* path/organisms: {}
75+
* @endcode
8776
* - dependencies: A list of libraries this library depends on.
8877
* - version: The library version. The string "VERSION" can be used to mean
8978
* the current Drupal core version.
@@ -121,50 +110,26 @@ public function parseLibraryInfo($library, $path, array $library_data = []): arr
121110
if (!file_exists($path)) {
122111
throw new InvalidLibraryFileException("Path $path does not exist.");
123112
}
124-
$rdit = new RecursiveDirectoryIterator($path, \FilesystemIterator::KEY_AS_PATHNAME | \FilesystemIterator::CURRENT_AS_FILEINFO);
125-
$filter = ['json', 'twig'];
126113
$metadata = [];
127-
$components = [];
128-
129-
/** @var \SplFileInfo $file */
130-
foreach (new \RecursiveIteratorIterator($rdit) as $file) {
131-
// Skip directories and non-files.
132-
if (!$file->isFile()) {
114+
foreach (self::discoverComponents($path, ['json', 'twig']) as $name => $data) {
115+
if (empty($data['twig']) || !file_exists($data['twig'])) {
133116
continue;
134117
}
135-
$file_path = $file->getPath();
136-
137-
// Skip tests folders.
138-
if (strpos($file_path, '/tests') !== FALSE) {
139-
continue;
140-
}
141-
142-
// Get the file extension for the file.
143-
$file_ext = $file->getExtension();
144-
if (!in_array(strtolower($file_ext), $filter, TRUE)) {
145-
continue;
146-
}
147-
148-
// We use file_basename as a unique key, it is required that the
149-
// JSON and twig file share this basename.
150-
$file_basename = $file->getBasename('.' . $file_ext);
151-
152-
// Build an array of all the filenames of interest, keyed by name.
153-
$components[$file_basename][$file_ext] = "$file_path/$file_basename.$file_ext";
154-
}
155-
156-
foreach ($components as $name => $data) {
157118
// If the component has a json file, create the pattern from it.
119+
$category = $library_data['category'] ?? 'default';
158120
if (!empty($data['json']) && $file_contents = file_get_contents($data['json'])) {
159121
$pattern = $this->createPattern($name, (array) $this->serializer::decode($file_contents) + $library_data);
160-
$pattern->name = $name;
122+
$pattern_path = trim(substr($data['json'], strlen($path), -strlen('.json')), '/\\');
123+
$category_guess = $library_data['category'] ?? strstr($pattern_path, DIRECTORY_SEPARATOR, TRUE);
124+
$pattern->category = $pattern->category ?? $category_guess;
161125
}
162126
else {
163127
// Create the pattern from defaults.
128+
// @todo Have this cleverly infer defaults from the template.
164129
$pattern = $this->createPattern($name,
165130
[
166131
'$schema' => 'http =>//json-schema.org/draft-04/schema#',
167-
'category' => 'atom',
132+
'category' => $category,
168133
'title' => $name,
169134
'type' => 'object',
170135
'format' => 'grid',
@@ -174,21 +139,15 @@ public function parseLibraryInfo($library, $path, array $library_data = []): arr
174139
] + $library_data
175140
);
176141
}
177-
178-
if (!empty($data['twig'])) {
179-
$twig_file = $data['twig'];
180-
if (file_exists($twig_file)) {
181-
$pattern->filename = trim(substr($twig_file, strlen($path)), '/\\');
182-
$pattern->template = file_get_contents($twig_file);
183-
// URL is redundant for the twig based components, so we use it to
184-
// store namespace.
185-
$pattern->url = $library;
186-
// @todo add default of library version fallback to extension version.
187-
$pattern->version = $pattern->version ?? 'VERSION';
188-
}
189-
}
190-
191-
$metadata[$name] = $pattern;
142+
$pattern->filename = trim(substr($data['twig'], strlen($path)), '/\\');
143+
$pattern_path = substr($pattern->filename, 0, -strlen('.twig'));
144+
$pattern->template = file_get_contents($data['twig']);
145+
// URL is redundant for the twig based components, so we use it to
146+
// store namespace.
147+
$pattern->url = $library;
148+
// @todo add default of library version fallback to extension version.
149+
$pattern->version = $pattern->version ?? 'VERSION';
150+
$metadata["@$library/$pattern_path"] = $pattern;
192151
}
193152

194153
foreach ($metadata as $pattern_type => $pattern) {

src/PatternLibraryParserBase.php

Lines changed: 55 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Drupal\patternkit;
44

55
use Drupal\Core\Asset\LibraryDiscoveryParser;
6+
use Symfony\Component\Finder\Iterator\RecursiveDirectoryIterator;
67

78
/**
89
* Base functionality for a pattern library parser.
@@ -12,13 +13,62 @@
1213
abstract class PatternLibraryParserBase extends LibraryDiscoveryParser {
1314

1415
/**
15-
* Additional library data override.
16+
* Returns a new Patternkit Pattern.
1617
*
17-
* {@inheritDoc}
18+
* @param string $name
19+
* The name of the pattern.
20+
* @param array|object $schema
21+
* The optional schema for the pattern.
22+
*
23+
* @return \Drupal\patternkit\Pattern
24+
* A fully-instantiated Patternkit Pattern.
1825
*/
19-
public function parseLibraryInfo($extension, $path, array $library_data = []): array {
20-
return parent::parseLibraryInfo($extension,
21-
$path);
26+
public function createPattern($name, $schema): Pattern {
27+
return new Pattern($name, $schema);
28+
}
29+
30+
/**
31+
* Returns an array of file components grouped by file basename and extension.
32+
*
33+
* @param string $path
34+
* The fully-qualified path to discover component files.
35+
* @param array $filter
36+
* An optional filter of file extensions to search for.
37+
*
38+
* @return array
39+
* Array of file components.
40+
* [basename][extension] = filename.
41+
*/
42+
public static function discoverComponents($path, array $filter = []): array {
43+
$components = [];
44+
$rdit = new RecursiveDirectoryIterator($path, \FilesystemIterator::KEY_AS_PATHNAME | \FilesystemIterator::CURRENT_AS_FILEINFO);
45+
/** @var \SplFileInfo $file */
46+
foreach (new \RecursiveIteratorIterator($rdit) as $file) {
47+
// Skip directories and non-files.
48+
if (!$file->isFile()) {
49+
continue;
50+
}
51+
$file_path = $file->getPath();
52+
53+
// Skip tests folders.
54+
if (strpos($file_path, '/tests') !== FALSE) {
55+
continue;
56+
}
57+
58+
// Get the file extension for the file.
59+
$file_ext = $file->getExtension();
60+
if (!in_array(strtolower($file_ext), $filter, TRUE)) {
61+
continue;
62+
}
63+
64+
// We use file_basename as a unique key, it is required that the
65+
// JSON and twig file share this basename.
66+
$file_basename = $file->getBasename('.' . $file_ext);
67+
68+
// Build an array of all the filenames of interest, keyed by name.
69+
$components[$file_basename][$file_ext] = "$file_path/$file_basename.$file_ext";
70+
}
71+
return $components;
2272
}
2373

2474
/**
@@ -358,7 +408,6 @@ function fetchFragmentAssets($subtype, $config, &$pk_obj) {
358408
return $pk_obj;
359409
}
360410

361-
362411
/**
363412
* Merge js dependency arrays.
364413
*
@@ -401,40 +450,4 @@ public function mergeJs(array $js1, array $js2): array {
401450
return $ret;
402451
}
403452

404-
/**
405-
* Parses schema properties for $ref and updates their location.
406-
*
407-
* @param object $properties
408-
* The properties to parse.
409-
* @param array &$metadata
410-
* The library metadata for looking up referenced patterns.
411-
*
412-
* @return object
413-
* The updated schema properties object.
414-
*/
415-
public static function schemaDereference($properties, array &$metadata) {
416-
foreach ($properties as $property => $value) {
417-
if (!is_scalar($value)) {
418-
$new_value = static::schemaDereference($value, $metadata);
419-
if (is_object($properties)) {
420-
$properties->{$property} = $new_value;
421-
}
422-
if (is_array($properties)) {
423-
$properties[$property] = $new_value;
424-
}
425-
continue;
426-
}
427-
if ($property !== '$ref') {
428-
continue;
429-
}
430-
$pattern = strstr($value, '.json', TRUE);
431-
$ref = substr($value, strlen("$pattern.json"));
432-
if (!isset($metadata[$pattern])) {
433-
unset($properties[$property]);
434-
continue;
435-
}
436-
$properties[$property] = $metadata[$pattern]->url . $ref;
437-
}
438-
return $properties;
439-
}
440453
}

0 commit comments

Comments
 (0)