From f6c45e8c17452b059498a60bd61d8aced2be4458 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Sat, 23 Jul 2022 20:53:07 -0400 Subject: [PATCH 1/6] Dynamic names implementation. --- src/Mustache/Compiler.php | 43 ++++++++++-- src/Mustache/Engine.php | 14 ++-- src/Mustache/Parser.php | 69 ++++++++++++++++++- src/Mustache/Tokenizer.php | 1 + .../MustacheDynamicNamesSpecTest.php | 52 ++++++++++++++ vendor/spec | 2 +- 6 files changed, 165 insertions(+), 16 deletions(-) create mode 100644 test/Mustache/Test/Functional/MustacheDynamicNamesSpecTest.php diff --git a/src/Mustache/Compiler.php b/src/Mustache/Compiler.php index 93a295a..2b0d1f9 100644 --- a/src/Mustache/Compiler.php +++ b/src/Mustache/Compiler.php @@ -117,6 +117,7 @@ class Mustache_Compiler case Mustache_Tokenizer::T_PARTIAL: $code .= $this->partial( $node[Mustache_Tokenizer::NAME], + isset($node[Mustache_Tokenizer::DYNAMIC]) ? $node[Mustache_Tokenizer::DYNAMIC] : false, isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '', $level ); @@ -125,6 +126,7 @@ class Mustache_Compiler case Mustache_Tokenizer::T_PARENT: $code .= $this->parent( $node[Mustache_Tokenizer::NAME], + isset($node[Mustache_Tokenizer::DYNAMIC]) ? $node[Mustache_Tokenizer::DYNAMIC] : false, isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '', $node[Mustache_Tokenizer::NODES], $level @@ -419,6 +421,30 @@ class Mustache_Compiler return sprintf($this->prepare(self::INVERTED_SECTION, $level), $method, $id, $filters, $this->walk($nodes, $level)); } + const DYNAMIC_NAME = '$this->resolveValue($context->%s(%s), $context)'; + + /** + * Generate Mustache Template dynamic name resolution PHP source. + * + * @param string $id Tag name + * @param bool $dynamic True if the name is dynamic + * + * @return string Dynamic name resolution PHP source code + */ + private function resolveDynamicName($id, $dynamic) + { + if (!$dynamic) { + return var_export($id, true); + } + + $method = $this->getFindMethod($id); + $id = ($method !== 'last') ? var_export($id, true) : ''; + + // TODO: filters? + + return sprintf(self::DYNAMIC_NAME, $method, $id); + } + const PARTIAL_INDENT = ', $indent . %s'; const PARTIAL = ' if ($partial = $this->mustache->loadPartial(%s)) { @@ -429,13 +455,14 @@ class Mustache_Compiler /** * Generate Mustache Template partial call PHP source. * - * @param string $id Partial name - * @param string $indent Whitespace indent to apply to partial + * @param string $id Partial name + * @param bool $dynamic Partial name is dynamic + * @param string $indent Whitespace indent to apply to partial * @param int $level * * @return string Generated partial call PHP source code */ - private function partial($id, $indent, $level) + private function partial($id, $dynamic, $indent, $level) { if ($indent !== '') { $indentParam = sprintf(self::PARTIAL_INDENT, var_export($indent, true)); @@ -445,7 +472,7 @@ class Mustache_Compiler return sprintf( $this->prepare(self::PARTIAL, $level), - var_export($id, true), + $this->resolveDynamicName($id, $dynamic), $indentParam ); } @@ -469,23 +496,25 @@ class Mustache_Compiler * Generate Mustache Template inheritance parent call PHP source. * * @param string $id Parent tag name + * @param bool $dynamic Tag name is dynamic * @param string $indent Whitespace indent to apply to parent * @param array $children Child nodes * @param int $level * * @return string Generated PHP source code */ - private function parent($id, $indent, array $children, $level) + private function parent($id, $dynamic, $indent, array $children, $level) { $realChildren = array_filter($children, array(__CLASS__, 'onlyBlockArgs')); + $partialName = $this->resolveDynamicName($id, $dynamic); if (empty($realChildren)) { - return sprintf($this->prepare(self::PARENT_NO_CONTEXT, $level), var_export($id, true)); + return sprintf($this->prepare(self::PARENT_NO_CONTEXT, $level), $partialName); } return sprintf( $this->prepare(self::PARENT, $level), - var_export($id, true), + $partialName, $this->walk($realChildren, $level + 1) ); } diff --git a/src/Mustache/Engine.php b/src/Mustache/Engine.php index bbdc975..dc6d7db 100644 --- a/src/Mustache/Engine.php +++ b/src/Mustache/Engine.php @@ -26,15 +26,17 @@ class Mustache_Engine const VERSION = '2.14.2'; const SPEC_VERSION = '1.2.2'; - const PRAGMA_FILTERS = 'FILTERS'; - const PRAGMA_BLOCKS = 'BLOCKS'; - const PRAGMA_ANCHORED_DOT = 'ANCHORED-DOT'; + const PRAGMA_FILTERS = 'FILTERS'; + const PRAGMA_BLOCKS = 'BLOCKS'; + const PRAGMA_ANCHORED_DOT = 'ANCHORED-DOT'; + const PRAGMA_DYNAMIC_NAMES = 'DYNAMIC-NAMES'; // Known pragmas private static $knownPragmas = array( - self::PRAGMA_FILTERS => true, - self::PRAGMA_BLOCKS => true, - self::PRAGMA_ANCHORED_DOT => true, + self::PRAGMA_FILTERS => true, + self::PRAGMA_BLOCKS => true, + self::PRAGMA_ANCHORED_DOT => true, + self::PRAGMA_DYNAMIC_NAMES => true, ); // Template cache diff --git a/src/Mustache/Parser.php b/src/Mustache/Parser.php index 0ec4192..bf8bb40 100644 --- a/src/Mustache/Parser.php +++ b/src/Mustache/Parser.php @@ -23,6 +23,7 @@ class Mustache_Parser private $pragmaFilters; private $pragmaBlocks; + private $pragmaDynamicNames; /** * Process an array of Mustache tokens and convert them into a parse tree. @@ -37,8 +38,9 @@ class Mustache_Parser $this->lineTokens = 0; $this->pragmas = $this->defaultPragmas; - $this->pragmaFilters = isset($this->pragmas[Mustache_Engine::PRAGMA_FILTERS]); - $this->pragmaBlocks = isset($this->pragmas[Mustache_Engine::PRAGMA_BLOCKS]); + $this->pragmaFilters = isset($this->pragmas[Mustache_Engine::PRAGMA_FILTERS]); + $this->pragmaBlocks = isset($this->pragmas[Mustache_Engine::PRAGMA_BLOCKS]); + $this->pragmaDynamicNames = isset($this->pragmas[Mustache_Engine::PRAGMA_DYNAMIC_NAMES]); return $this->buildTree($tokens); } @@ -84,6 +86,14 @@ class Mustache_Parser $this->lineTokens = 0; } + if ($this->pragmaDynamicNames && isset($token[Mustache_Tokenizer::NAME])) { + list($name, $isDynamic) = $this->getDynamicName($token); + if ($isDynamic) { + $token[Mustache_Tokenizer::NAME] = $name; + $token[Mustache_Tokenizer::DYNAMIC] = true; + } + } + if ($this->pragmaFilters && isset($token[Mustache_Tokenizer::NAME])) { list($name, $filters) = $this->getNameAndFilters($token[Mustache_Tokenizer::NAME]); if (!empty($filters)) { @@ -280,6 +290,57 @@ class Mustache_Parser } } + /** + * Parse dynamic names. + * + * @throws Mustache_Exception_SyntaxException when a tag does not allow * + * @throws Mustache_Exception_SyntaxException on multiple *s, or dots or filters with * + */ + private function getDynamicName(array $token) + { + $name = $token[Mustache_Tokenizer::NAME]; + $isDynamic = false; + + if (preg_match('/^\s*\*\s*/', $name)) { + $this->ensureTagAllowsDynamicNames($token); + $name = preg_replace('/^\s*\*\s*/', '', $name); + $isDynamic = true; + } + + // Two stars is two many! + if (preg_match('/^\s*\*\s*/', $name) || preg_match('/[|.]\s*\*/', $name)) { + $msg = sprintf('Invalid dynamic name: %s', $name); + throw new Mustache_Exception_SyntaxException($msg, $token); + } + + return array($name, $isDynamic); + } + + /** + * Check whether the given token supports dynamic tag names. + * + * @throws Mustache_Exception_SyntaxException when a tag does not allow * + * + * @param array $token + */ + private function ensureTagAllowsDynamicNames(array $token) + { + switch ($token[Mustache_Tokenizer::TYPE]) { + case Mustache_Tokenizer::T_PARTIAL: + case Mustache_Tokenizer::T_PARENT: + return; + } + + $msg = sprintf( + 'Invalid dynamic name: %s in %s tag', + $token[Mustache_Tokenizer::NAME], + $token[Mustache_Tokenizer::TYPE] + ); + + throw new Mustache_Exception_SyntaxException($msg, $token); + } + + /** * Split a tag name into name and filters. * @@ -312,6 +373,10 @@ class Mustache_Parser case Mustache_Engine::PRAGMA_FILTERS: $this->pragmaFilters = true; break; + + case Mustache_Engine::PRAGMA_DYNAMIC_NAMES: + $this->pragmaDynamicNames = true; + break; } } } diff --git a/src/Mustache/Tokenizer.php b/src/Mustache/Tokenizer.php index e59d30b..d97e36f 100644 --- a/src/Mustache/Tokenizer.php +++ b/src/Mustache/Tokenizer.php @@ -56,6 +56,7 @@ class Mustache_Tokenizer // Token properties const TYPE = 'type'; const NAME = 'name'; + const DYNAMIC = 'dynamic'; const OTAG = 'otag'; const CTAG = 'ctag'; const LINE = 'line'; diff --git a/test/Mustache/Test/Functional/MustacheDynamicNamesSpecTest.php b/test/Mustache/Test/Functional/MustacheDynamicNamesSpecTest.php new file mode 100644 index 0000000..e26c029 --- /dev/null +++ b/test/Mustache/Test/Functional/MustacheDynamicNamesSpecTest.php @@ -0,0 +1,52 @@ + array(Mustache_Engine::PRAGMA_DYNAMIC_NAMES), + )); + } + + /** + * For some reason data providers can't mark tests skipped, so this test exists + * simply to provide a 'skipped' test if the `spec` submodule isn't initialized. + */ + public function testSpecInitialized() + { + if (!file_exists(dirname(__FILE__) . '/../../../../vendor/spec/specs/')) { + $this->markTestSkipped('Mustache spec submodule not initialized: run "git submodule update --init"'); + } + } + + /** + * @group dynamic-names + * @dataProvider loadDynamicNamesSpec + */ + public function testDynamicNamesSpec($desc, $source, $partials, $data, $expected) + { + $template = self::loadTemplate($source, $partials); + $this->assertEquals($expected, $template->render($data), $desc); + } + + public function loadDynamicNamesSpec() + { + return $this->loadSpec('~dynamic-names'); + } +} diff --git a/vendor/spec b/vendor/spec index b2aeb3c..1704964 160000 --- a/vendor/spec +++ b/vendor/spec @@ -1 +1 @@ -Subproject commit b2aeb3c283de931a7004b5f7a2cb394b89382369 +Subproject commit 1704964eb6cd0c8b1a3da3de6222e0f0480284ec From 454b4c63e980df9315afcd861acb6ae56ce9bd01 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Sat, 23 Jul 2022 22:37:25 -0400 Subject: [PATCH 2/6] Better error message. --- src/Mustache/Parser.php | 2 +- src/Mustache/Tokenizer.php | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/Mustache/Parser.php b/src/Mustache/Parser.php index bf8bb40..1ad7e6d 100644 --- a/src/Mustache/Parser.php +++ b/src/Mustache/Parser.php @@ -334,7 +334,7 @@ class Mustache_Parser $msg = sprintf( 'Invalid dynamic name: %s in %s tag', $token[Mustache_Tokenizer::NAME], - $token[Mustache_Tokenizer::TYPE] + Mustache_Tokenizer::getTagName($token[Mustache_Tokenizer::TYPE]) ); throw new Mustache_Exception_SyntaxException($msg, $token); diff --git a/src/Mustache/Tokenizer.php b/src/Mustache/Tokenizer.php index d97e36f..d96f129 100644 --- a/src/Mustache/Tokenizer.php +++ b/src/Mustache/Tokenizer.php @@ -53,6 +53,22 @@ class Mustache_Tokenizer self::T_BLOCK_VAR => true, ); + private static $tagNames = array( + self::T_SECTION => 'section', + self::T_INVERTED => 'inverted section', + self::T_END_SECTION => 'section end', + self::T_COMMENT => 'comment', + self::T_PARTIAL => 'partial', + self::T_PARENT => 'parent', + self::T_DELIM_CHANGE => 'set delimiter', + self::T_ESCAPED => 'variable', + self::T_UNESCAPED => 'unescaped variable', + self::T_UNESCAPED_2 => 'unescaped variable', + self::T_PRAGMA => 'pragma', + self::T_BLOCK_VAR => 'block variable', + self::T_BLOCK_ARG => 'block variable', + ); + // Token properties const TYPE = 'type'; const NAME = 'name'; @@ -358,6 +374,7 @@ class Mustache_Tokenizer return $end + $this->ctagLen - 1; } + private function throwUnclosedTagException() { $name = trim($this->buffer); @@ -376,4 +393,16 @@ class Mustache_Tokenizer self::INDEX => $this->seenTag - $this->otagLen, )); } + + /** + * Get the human readable name for a tag type. + * + * @param string $tagType One of the tokenizer T_* constants + * + * @return string + */ + static function getTagName($tagType) + { + return isset(self::$tagNames[$tagType]) ? self::$tagNames[$tagType] : 'unknown'; + } } From 53944831cf826a6f0fc08ab83960bc0a25ebba12 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Sat, 23 Jul 2022 22:38:45 -0400 Subject: [PATCH 3/6] Fix dynamic name (and filter!) validation accidentally applying to comments. --- src/Mustache/Parser.php | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/Mustache/Parser.php b/src/Mustache/Parser.php index 1ad7e6d..563316e 100644 --- a/src/Mustache/Parser.php +++ b/src/Mustache/Parser.php @@ -86,19 +86,21 @@ class Mustache_Parser $this->lineTokens = 0; } - if ($this->pragmaDynamicNames && isset($token[Mustache_Tokenizer::NAME])) { - list($name, $isDynamic) = $this->getDynamicName($token); - if ($isDynamic) { - $token[Mustache_Tokenizer::NAME] = $name; - $token[Mustache_Tokenizer::DYNAMIC] = true; + if ($token[Mustache_Tokenizer::TYPE] !== Mustache_Tokenizer::T_COMMENT) { + if ($this->pragmaDynamicNames && isset($token[Mustache_Tokenizer::NAME])) { + list($name, $isDynamic) = $this->getDynamicName($token); + if ($isDynamic) { + $token[Mustache_Tokenizer::NAME] = $name; + $token[Mustache_Tokenizer::DYNAMIC] = true; + } } - } - if ($this->pragmaFilters && isset($token[Mustache_Tokenizer::NAME])) { - list($name, $filters) = $this->getNameAndFilters($token[Mustache_Tokenizer::NAME]); - if (!empty($filters)) { - $token[Mustache_Tokenizer::NAME] = $name; - $token[Mustache_Tokenizer::FILTERS] = $filters; + if ($this->pragmaFilters && isset($token[Mustache_Tokenizer::NAME])) { + list($name, $filters) = $this->getNameAndFilters($token[Mustache_Tokenizer::NAME]); + if (!empty($filters)) { + $token[Mustache_Tokenizer::NAME] = $name; + $token[Mustache_Tokenizer::FILTERS] = $filters; + } } } From af4f8f7c0e04eeadfe9c1ef3a3613971284885f9 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Sat, 23 Jul 2022 22:39:34 -0400 Subject: [PATCH 4/6] Add more tests, fix a bug with block tags. --- src/Mustache/Parser.php | 7 +- .../Test/Functional/DynamicPartialsTest.php | 114 ++++++++++++++++++ 2 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 test/Mustache/Test/Functional/DynamicPartialsTest.php diff --git a/src/Mustache/Parser.php b/src/Mustache/Parser.php index 563316e..de43fcd 100644 --- a/src/Mustache/Parser.php +++ b/src/Mustache/Parser.php @@ -127,7 +127,11 @@ class Mustache_Parser throw new Mustache_Exception_SyntaxException($msg, $token); } - if ($token[Mustache_Tokenizer::NAME] !== $parent[Mustache_Tokenizer::NAME]) { + $sameName = $token[Mustache_Tokenizer::NAME] !== $parent[Mustache_Tokenizer::NAME]; + $tokenDynamic = isset($token[Mustache_Tokenizer::DYNAMIC]) && $token[Mustache_Tokenizer::DYNAMIC]; + $parentDynamic = isset($parent[Mustache_Tokenizer::DYNAMIC]) && $parent[Mustache_Tokenizer::DYNAMIC]; + + if ($sameName || ($tokenDynamic !== $parentDynamic)) { $msg = sprintf( 'Nesting error: %s (on line %d) vs. %s (on line %d)', $parent[Mustache_Tokenizer::NAME], @@ -330,6 +334,7 @@ class Mustache_Parser switch ($token[Mustache_Tokenizer::TYPE]) { case Mustache_Tokenizer::T_PARTIAL: case Mustache_Tokenizer::T_PARENT: + case Mustache_Tokenizer::T_END_SECTION: return; } diff --git a/test/Mustache/Test/Functional/DynamicPartialsTest.php b/test/Mustache/Test/Functional/DynamicPartialsTest.php new file mode 100644 index 0000000..9887132 --- /dev/null +++ b/test/Mustache/Test/Functional/DynamicPartialsTest.php @@ -0,0 +1,114 @@ +mustache = new Mustache_Engine(array( + 'pragmas' => array(Mustache_Engine::PRAGMA_DYNAMIC_NAMES), + )); + } + + public function getInvalidDynamicNamesExamples() + { + return array( + array('{{> **foo}}'), + array('{{> *foo.*bar}}'), + array('{{> foo.*bar}}'), + array('{{ *foo }}'), + array('{{{ *foo }}}'), + array('{{& *foo }}'), + array('{{# *foo }}{{/ *foo }}'), + array('{{^ *foo }}{{/ *foo }}'), + array('{{% FILTERS}}{{> *foo | *bar}}'), + array('{{% FILTERS}}{{> foo | *bar}}'), + array('{{% BLOCKS }}{{$ *foo }}{{/ *foo }}'), + ); + } + + /** + * @dataProvider getInvalidDynamicNamesExamples + * @expectedException Mustache_Exception_SyntaxException + * @expectedExceptionMessage Invalid dynamic name: + */ + public function testInvalidDynamicNamesExamples($template) + { + $this->mustache->render($template); + } + + + public function getValidDynamicNamesExamples() + { + // technically not all dynamic names, but also not invalid + return array( + array('{{>* foo }}'), + array('{{>* foo.bar.baz }}'), + array('{{=* *=}}'), + array('{{! *foo }}'), + array('{{! foo.*bar }}'), + array('{{% FILTERS }}{{! foo | *bar }}'), + array('{{% BLOCKS }}{{< *foo }}{{/ *foo }}'), + ); + } + + /** + * @dataProvider getValidDynamicNamesExamples + */ + public function testLegalInheritanceExamples($template) + { + $this->assertSame('', $this->mustache->render($template)); + } + + public function getDynamicNameParseErrors() + { + return array( + array('{{# foo }}{{/ *foo }}'), + array('{{^ foo }}{{/ *foo }}'), + array('{{% BLOCKS }}{{< foo }}{{/ *foo }}'), + array('{{% BLOCKS }}{{$ foo }}{{/ *foo }}'), + ); + } + + /** + * @dataProvider getDynamicNameParseErrors + * @expectedException Mustache_Exception_SyntaxException + * @expectedExceptionMessage Nesting error: + */ + public function testDynamicNameParseErrors($template) + { + $this->mustache->render($template); + } + + + public function testDynamicBlocks() + { + $tpl = '{{% BLOCKS }}{{< *partial }}{{$ bar }}{{ value }}{{/ bar }}{{/ *partial }}'; + + $this->mustache->setPartials(array( + 'foobarbaz' => '{{% BLOCKS }}{{$ foo }}foo{{/ foo }}{{$ bar }}bar{{/ bar }}{{$ baz }}baz{{/ baz }}', + 'qux' => 'qux', + )); + + $result = $this->mustache->render($tpl, array( + 'partial' => 'foobarbaz', + 'value' => 'BAR', + )); + + $this->assertSame($result, 'fooBARbaz'); + } +} From 93f824d09aa8194960c6c1889fdbe17f0dfde5f3 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Tue, 9 Aug 2022 11:17:36 -0700 Subject: [PATCH 5/6] Remove exceptions on multiple * in dynamic names. Per the finalized spec, these should be treated as lookup failures (i.e. rendered as empty strings) rather than exceptions. --- src/Mustache/Parser.php | 6 ---- .../Test/Functional/DynamicPartialsTest.php | 28 ------------------- 2 files changed, 34 deletions(-) diff --git a/src/Mustache/Parser.php b/src/Mustache/Parser.php index de43fcd..02e3d89 100644 --- a/src/Mustache/Parser.php +++ b/src/Mustache/Parser.php @@ -313,12 +313,6 @@ class Mustache_Parser $isDynamic = true; } - // Two stars is two many! - if (preg_match('/^\s*\*\s*/', $name) || preg_match('/[|.]\s*\*/', $name)) { - $msg = sprintf('Invalid dynamic name: %s', $name); - throw new Mustache_Exception_SyntaxException($msg, $token); - } - return array($name, $isDynamic); } diff --git a/test/Mustache/Test/Functional/DynamicPartialsTest.php b/test/Mustache/Test/Functional/DynamicPartialsTest.php index 9887132..88e1fd8 100644 --- a/test/Mustache/Test/Functional/DynamicPartialsTest.php +++ b/test/Mustache/Test/Functional/DynamicPartialsTest.php @@ -24,34 +24,6 @@ class Mustache_Test_Functional_DynamicPartialsTest extends PHPUnit_Framework_Tes )); } - public function getInvalidDynamicNamesExamples() - { - return array( - array('{{> **foo}}'), - array('{{> *foo.*bar}}'), - array('{{> foo.*bar}}'), - array('{{ *foo }}'), - array('{{{ *foo }}}'), - array('{{& *foo }}'), - array('{{# *foo }}{{/ *foo }}'), - array('{{^ *foo }}{{/ *foo }}'), - array('{{% FILTERS}}{{> *foo | *bar}}'), - array('{{% FILTERS}}{{> foo | *bar}}'), - array('{{% BLOCKS }}{{$ *foo }}{{/ *foo }}'), - ); - } - - /** - * @dataProvider getInvalidDynamicNamesExamples - * @expectedException Mustache_Exception_SyntaxException - * @expectedExceptionMessage Invalid dynamic name: - */ - public function testInvalidDynamicNamesExamples($template) - { - $this->mustache->render($template); - } - - public function getValidDynamicNamesExamples() { // technically not all dynamic names, but also not invalid From 30853456a5956758acbcd447cc63cd5f434781b8 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Tue, 23 Aug 2022 09:03:46 -0400 Subject: [PATCH 6/6] Update to v1.3.0 Mustache spec. --- src/Mustache/Engine.php | 4 ++-- vendor/spec | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mustache/Engine.php b/src/Mustache/Engine.php index dc6d7db..7a31ac0 100644 --- a/src/Mustache/Engine.php +++ b/src/Mustache/Engine.php @@ -23,8 +23,8 @@ */ class Mustache_Engine { - const VERSION = '2.14.2'; - const SPEC_VERSION = '1.2.2'; + const VERSION = '2.14.2'; + const SPEC_VERSION = '1.3.0'; const PRAGMA_FILTERS = 'FILTERS'; const PRAGMA_BLOCKS = 'BLOCKS'; diff --git a/vendor/spec b/vendor/spec index 1704964..5d3b58e 160000 --- a/vendor/spec +++ b/vendor/spec @@ -1 +1 @@ -Subproject commit 1704964eb6cd0c8b1a3da3de6222e0f0480284ec +Subproject commit 5d3b58ea35ae309c40d7a8111bfedc4c5bcd43a6