Browse Source

Add generic methods for converting DOM<->PHP, updated places where this is useful

master
Adam Pippin 3 years ago
parent
commit
145b89da1e
  1. 66
      app/Dom/Document.php
  2. 89
      app/Dom/Node.php
  3. 19
      app/Engine/Engine.php

66
app/Dom/Document.php

@ -11,11 +11,40 @@ namespace App\Dom;
*/ */
class Document extends Node 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 = []; $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. * 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); throw new \Exception('Path not found: '.$path);
} }
if ($node instanceof NodeValue) return Node::toPhp($node);
{ }
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;
}
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));
} }
} }

89
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 * Each element of the path can either be a node name or a numeric
* index representing the offset into an array * index representing the offset into an array
* *
* @todo will explode if you try to write using a numeric index to 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 string $path * @param Node $node
* @param Node $node * @return void
*/ */
public function addChildUnderPath(string $path, Node $node): void public function setChildByPath(string $path, Node $node): void
{ {
$path = explode('.', $path); $path = explode('.', $path);
$current = $this; $current = $this;
@ -229,17 +229,24 @@ class Node implements \ArrayAccess, \Iterator
{ {
$next_node = new Node($current, $next); $next_node = new Node($current, $next);
$current->addChild($next_node); $current->addChild($next_node);
$current = $next_node;
} }
$current = $next_node;
} }
if (!isset($current)) 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); $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 // ArrayAccess
public function offsetExists($offset): bool public function offsetExists($offset): bool

19
app/Engine/Engine.php

@ -56,13 +56,14 @@ class Engine
$documents = []; $documents = [];
// Figure out which files to process and load all files. // 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++) for ($i = 0; $i < sizeof($files); $i++)
{ {
if (!isset($documents[$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 = []; $includes = [];
try try
{ {
@ -73,17 +74,27 @@ class Engine
} }
if (sizeof($includes)) 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); array_splice($files, $i, 0, $includes);
--$i; --$i;
} }
} }
} }
// Now actually process all the documents $documents = array_values($documents);
// Pass it all in and compile
$document = $this->compileDocuments($documents, $options); $document = $this->compileDocuments($documents, $options);
// Set metadata in output // 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 // Remove cfnpp block, that doesn't go to CloudFormation
$cfnpp = $document->getChildByName('cfnpp'); $cfnpp = $document->getChildByName('cfnpp');

Loading…
Cancel
Save