From 145b89da1e7106e22408f3373b95d962c8b61243 Mon Sep 17 00:00:00 2001 From: Adam Pippin Date: Sun, 14 Feb 2021 15:58:57 -0800 Subject: [PATCH] Add generic methods for converting DOM<->PHP, updated places where this is useful --- app/Dom/Document.php | 66 ++++++++++++++++++++------------ app/Dom/Node.php | 89 +++++++++++++++++++++++++++++++++++++++---- app/Engine/Engine.php | 19 +++++++-- 3 files changed, 138 insertions(+), 36 deletions(-) diff --git a/app/Dom/Document.php b/app/Dom/Document.php index b477515..1d180f6 100644 --- a/app/Dom/Document.php +++ b/app/Dom/Document.php @@ -11,11 +11,40 @@ namespace App\Dom; */ class Document extends Node { - public function __construct() + /** + * The original name of this document, as they will relate to each other + * via include/stack refs, etc. + * @var string + */ + protected $document_name; + + public function __construct(?string $document_name = null) { + $this->document_name = $document_name; $this->children = []; } + /** + * Get this document's name. + * + * @return string + */ + public function getDocumentName(): string + { + return $this->document_name; + } + + /** + * Set this document's name. + * + * @param string $document_name + * @return void + */ + public function setDocumentName(string $document_name): void + { + $this->document_name = $document_name; + } + /** * Retrieve a value from the meta `cfnpp` block in the document. * @@ -29,29 +58,18 @@ class Document extends Node { throw new \Exception('Path not found: '.$path); } - if ($node instanceof NodeValue) - { - return $node->getValue(); - } - elseif ($node->isArray()) - { - $values = []; - foreach ($node as $child) - { - $values[] = $child->getValue(); - } - return $values; - } - elseif ($node->isMap()) - { - $values = []; - foreach ($node as $child) - { - $values[$child->getName()] = $child->getValue(); - } - return $values; - } + return Node::toPhp($node); + } - throw new \Exception('Cannot retrieve meta value: '.$path); + /** + * Set a value on the meta `cfnpp` block in the document. + * + * @param string $path a dot-separate path to the value to set/replace + * @param mixed $data the data to set on that node + * @return void + */ + public function setMeta(string $path, $data): void + { + $this->setChildByPath('cfnpp.'.$path, Node::fromPhp($data)); } } diff --git a/app/Dom/Node.php b/app/Dom/Node.php index 10f36d3..376c303 100644 --- a/app/Dom/Node.php +++ b/app/Dom/Node.php @@ -199,17 +199,17 @@ class Node implements \ArrayAccess, \Iterator } /** - * Set a child of this node by a dot-separate path. + * Add/replace a child of this node by a dot-separated path. * * Each element of the path can either be a node name or a numeric * index representing the offset into an array * * @todo will explode if you try to write using a numeric index to an array - * element that doesn't exist - * @param string $path - * @param Node $node + * @param string $path + * @param Node $node + * @return void */ - public function addChildUnderPath(string $path, Node $node): void + public function setChildByPath(string $path, Node $node): void { $path = explode('.', $path); $current = $this; @@ -229,17 +229,24 @@ class Node implements \ArrayAccess, \Iterator { $next_node = new Node($current, $next); $current->addChild($next_node); - $current = $next_node; } + $current = $next_node; } if (!isset($current)) { - return; + throw new \Exception('Cannot write to non-existent array index by path'); } } - $current->addChild($node); + // Replace current with the passed in node + $current->remove(); + $current->getParent()->addChild($node); + $node->setParent($current->getParent()); + if ($current->hasName()) + { + $node->setName($current->getName()); + } } /** @@ -343,6 +350,72 @@ class Node implements \ArrayAccess, \Iterator $this->getParent()->removeChild($this); } + public static function toPhp(Node $node) + { + if ($node instanceof NodeValue) + { + return $node->getValue(); + } + elseif ($node->isArray()) + { + $result = []; + foreach ($node as $child) + { + $result[] = static::toPhp($child); + } + return $result; + } + elseif ($node->isMap()) + { + $result = []; + foreach ($node as $child) + { + $result[$child->getName()] = static::toPhp($child); + } + return $result; + } + + throw new \Exception('Cannot convert from DOM node'); + } + + public static function fromPhp($value): Node + { + if (is_scalar($value)) + { + return new NodeValue(null, null, $value); + } + elseif (is_array($value)) + { + $contains_only_numeric_keys = array_reduce(array_keys($value), static function($carry, $item) { + return $carry && is_numeric($item); + }, true); + + if ($contains_only_numeric_keys) + { + $node = new Node(null, null); + foreach ($value as $item) + { + $child = static::fromPhp($item); + $child->setParent($node); + $node->addChild($child); + } + return $node; + } + + $node = new Node(null, null); + foreach ($value as $key => $item) + { + $child = static::fromPhp($item); + $child->setName($key); + $child->setParent($node); + $node->addChild($child); + } + return $node; + } + + throw new \Exception('Cannot convert passed value into DOM nodes'); + } + // ArrayAccess public function offsetExists($offset): bool diff --git a/app/Engine/Engine.php b/app/Engine/Engine.php index 3627843..e2493f7 100644 --- a/app/Engine/Engine.php +++ b/app/Engine/Engine.php @@ -56,13 +56,14 @@ class Engine $documents = []; // Figure out which files to process and load all files. - $files = [basename($input_file)]; + $files = [realpath($input_file)]; for ($i = 0; $i < sizeof($files); $i++) { if (!isset($documents[$files[$i]])) { - $documents[$files[$i]] = $document = $this->unserialize->unserialize(file_get_contents($file_helper->resolve($files[$i]))); + $documents[$files[$i]] = $document = $this->unserialize->unserialize(file_get_contents($files[$i])); + $document->setDocumentName($files[$i]); $includes = []; try { @@ -73,17 +74,27 @@ class Engine } if (sizeof($includes)) { + $file_helper->clear(); + $file_helper->appendPath(dirname($files[$i])); + foreach ($includes as &$include) + { + $include = $file_helper->resolve($include); + } + unset($include); + $document->setMeta('stack', $includes); array_splice($files, $i, 0, $includes); --$i; } } } - // Now actually process all the documents + $documents = array_values($documents); + + // Pass it all in and compile $document = $this->compileDocuments($documents, $options); // Set metadata in output - $document->addChildUnderPath('Metadata', new \App\Dom\NodeValue(null, 'Stack', implode(', ', $files))); + $document->setChildByPath('Metadata.Stack', $files); // Remove cfnpp block, that doesn't go to CloudFormation $cfnpp = $document->getChildByName('cfnpp');