[YAML] resolve variables in inlined YAML

This commit is contained in:
Christian Flothmann 2014-08-15 14:16:41 +02:00
parent 65862c9947
commit 45a5863508
4 changed files with 104 additions and 24 deletions

View File

@ -32,12 +32,13 @@ class Inline
* @param string $value A YAML string * @param string $value A YAML string
* @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
* @param bool $objectSupport true if object support is enabled, false otherwise * @param bool $objectSupport true if object support is enabled, false otherwise
* @param array $references Mapping of variable names to values
* *
* @return array A PHP array representing the YAML string * @return array A PHP array representing the YAML string
* *
* @throws ParseException * @throws ParseException
*/ */
public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false) public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $references = array())
{ {
self::$exceptionOnInvalidType = $exceptionOnInvalidType; self::$exceptionOnInvalidType = $exceptionOnInvalidType;
self::$objectSupport = $objectSupport; self::$objectSupport = $objectSupport;
@ -56,15 +57,15 @@ class Inline
$i = 0; $i = 0;
switch ($value[0]) { switch ($value[0]) {
case '[': case '[':
$result = self::parseSequence($value, $i); $result = self::parseSequence($value, $i, $references);
++$i; ++$i;
break; break;
case '{': case '{':
$result = self::parseMapping($value, $i); $result = self::parseMapping($value, $i, $references);
++$i; ++$i;
break; break;
default: default:
$result = self::parseScalar($value, null, array('"', "'"), $i); $result = self::parseScalar($value, null, array('"', "'"), $i, true, $references);
} }
// some comments are allowed at the end // some comments are allowed at the end
@ -184,14 +185,15 @@ class Inline
* @param scalar $scalar * @param scalar $scalar
* @param string $delimiters * @param string $delimiters
* @param array $stringDelimiters * @param array $stringDelimiters
* @param int &$i * @param int &$i
* @param bool $evaluate * @param bool $evaluate
* @param array $references
* *
* @return string A YAML string * @return string A YAML string
* *
* @throws ParseException When malformed inline YAML string is parsed * @throws ParseException When malformed inline YAML string is parsed
*/ */
public static function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true) public static function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true, $references = array())
{ {
if (in_array($scalar[$i], $stringDelimiters)) { if (in_array($scalar[$i], $stringDelimiters)) {
// quoted scalar // quoted scalar
@ -221,7 +223,7 @@ class Inline
} }
if ($evaluate) { if ($evaluate) {
$output = self::evaluateScalar($output); $output = self::evaluateScalar($output, $references);
} }
} }
@ -262,13 +264,14 @@ class Inline
* Parses a sequence to a YAML string. * Parses a sequence to a YAML string.
* *
* @param string $sequence * @param string $sequence
* @param int &$i * @param int &$i
* @param array $references
* *
* @return string A YAML string * @return string A YAML string
* *
* @throws ParseException When malformed inline YAML string is parsed * @throws ParseException When malformed inline YAML string is parsed
*/ */
private static function parseSequence($sequence, &$i = 0) private static function parseSequence($sequence, &$i = 0, $references = array())
{ {
$output = array(); $output = array();
$len = strlen($sequence); $len = strlen($sequence);
@ -279,11 +282,11 @@ class Inline
switch ($sequence[$i]) { switch ($sequence[$i]) {
case '[': case '[':
// nested sequence // nested sequence
$output[] = self::parseSequence($sequence, $i); $output[] = self::parseSequence($sequence, $i, $references);
break; break;
case '{': case '{':
// nested mapping // nested mapping
$output[] = self::parseMapping($sequence, $i); $output[] = self::parseMapping($sequence, $i, $references);
break; break;
case ']': case ']':
return $output; return $output;
@ -292,12 +295,14 @@ class Inline
break; break;
default: default:
$isQuoted = in_array($sequence[$i], array('"', "'")); $isQuoted = in_array($sequence[$i], array('"', "'"));
$value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i); $value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i, true, $references);
if (!$isQuoted && false !== strpos($value, ': ')) { // the value can be an array if a reference has been resolved to an array var
if (!is_array($value) && !$isQuoted && false !== strpos($value, ': ')) {
// embedded mapping? // embedded mapping?
try { try {
$value = self::parseMapping('{'.$value.'}'); $pos = 0;
$value = self::parseMapping('{'.$value.'}', $pos, $references);
} catch (\InvalidArgumentException $e) { } catch (\InvalidArgumentException $e) {
// no, it's not // no, it's not
} }
@ -318,13 +323,14 @@ class Inline
* Parses a mapping to a YAML string. * Parses a mapping to a YAML string.
* *
* @param string $mapping * @param string $mapping
* @param int &$i * @param int &$i
* @param array $references
* *
* @return string A YAML string * @return string A YAML string
* *
* @throws ParseException When malformed inline YAML string is parsed * @throws ParseException When malformed inline YAML string is parsed
*/ */
private static function parseMapping($mapping, &$i = 0) private static function parseMapping($mapping, &$i = 0, $references = array())
{ {
$output = array(); $output = array();
$len = strlen($mapping); $len = strlen($mapping);
@ -350,19 +356,19 @@ class Inline
switch ($mapping[$i]) { switch ($mapping[$i]) {
case '[': case '[':
// nested sequence // nested sequence
$output[$key] = self::parseSequence($mapping, $i); $output[$key] = self::parseSequence($mapping, $i, $references);
$done = true; $done = true;
break; break;
case '{': case '{':
// nested mapping // nested mapping
$output[$key] = self::parseMapping($mapping, $i); $output[$key] = self::parseMapping($mapping, $i, $references);
$done = true; $done = true;
break; break;
case ':': case ':':
case ' ': case ' ':
break; break;
default: default:
$output[$key] = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i); $output[$key] = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i, true, $references);
$done = true; $done = true;
--$i; --$i;
} }
@ -382,15 +388,31 @@ class Inline
* Evaluates scalars and replaces magic values. * Evaluates scalars and replaces magic values.
* *
* @param string $scalar * @param string $scalar
* @param array $references
* *
* @return string A YAML string * @return string A YAML string
* *
* @throws ParseException when object parsing support was disabled and the parser detected a PHP object * @throws ParseException when object parsing support was disabled and the parser detected a PHP object
*/ */
private static function evaluateScalar($scalar) private static function evaluateScalar($scalar, $references = array())
{ {
$scalar = trim($scalar); $scalar = trim($scalar);
$scalarLower = strtolower($scalar); $scalarLower = strtolower($scalar);
if (0 === strpos($scalar, '*')) {
if (false !== $pos = strpos($scalar, '#')) {
$value = substr($scalar, 1, $pos - 2);
} else {
$value = substr($scalar, 1);
}
if (!array_key_exists($value, $references)) {
throw new ParseException(sprintf('Reference "%s" does not exist.', $value));
}
return $references[$value];
}
switch (true) { switch (true) {
case 'null' === $scalarLower: case 'null' === $scalarLower:
case '' === $scalar: case '' === $scalar:

View File

@ -121,7 +121,7 @@ class Parser
$context = 'mapping'; $context = 'mapping';
// force correct settings // force correct settings
Inline::parse(null, $exceptionOnInvalidType, $objectSupport); Inline::parse(null, $exceptionOnInvalidType, $objectSupport, $this->refs);
try { try {
$key = Inline::parseScalar($values['key']); $key = Inline::parseScalar($values['key']);
} catch (ParseException $e) { } catch (ParseException $e) {
@ -197,7 +197,7 @@ class Parser
$lineCount = count($this->lines); $lineCount = count($this->lines);
if (1 === $lineCount || (2 === $lineCount && empty($this->lines[1]))) { if (1 === $lineCount || (2 === $lineCount && empty($this->lines[1]))) {
try { try {
$value = Inline::parse($this->lines[0], $exceptionOnInvalidType, $objectSupport); $value = Inline::parse($this->lines[0], $exceptionOnInvalidType, $objectSupport, $this->refs);
} catch (ParseException $e) { } catch (ParseException $e) {
$e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setParsedLine($this->getRealCurrentLineNb() + 1);
$e->setSnippet($this->currentLine); $e->setSnippet($this->currentLine);
@ -404,7 +404,7 @@ class Parser
} }
try { try {
return Inline::parse($value, $exceptionOnInvalidType, $objectSupport); return Inline::parse($value, $exceptionOnInvalidType, $objectSupport, $this->refs);
} catch (ParseException $e) { } catch (ParseException $e) {
$e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setParsedLine($this->getRealCurrentLineNb() + 1);
$e->setSnippet($this->currentLine); $e->setSnippet($this->currentLine);

View File

@ -115,6 +115,38 @@ class InlineTest extends \PHPUnit_Framework_TestCase
$this->assertSame($expect, Inline::parseScalar($value)); $this->assertSame($expect, Inline::parseScalar($value));
} }
/**
* @dataProvider getDataForParseReferences
*/
public function testParseReferences($yaml, $expected)
{
$this->assertSame($expected, Inline::parse($yaml, false, false, array('var' => 'var-value')));
}
public function getDataForParseReferences()
{
return array(
'scalar' => array('*var', 'var-value'),
'list' => array('[ *var ]', array('var-value')),
'list-in-list' => array('[[ *var ]]', array(array('var-value'))),
'map-in-list' => array('[ { key: *var } ]', array(array('key' => 'var-value'))),
'embedded-mapping-in-list' => array('[ key: *var ]', array(array('key' => 'var-value'))),
'map' => array('{ key: *var }', array('key' => 'var-value')),
'list-in-map' => array('{ key: [*var] }', array('key' => array('var-value'))),
'map-in-map' => array('{ foo: { bar: *var } }', array('foo' => array('bar' => 'var-value'))),
);
}
public function testParseMapReferenceInSequence()
{
$foo = array(
'a' => 'Steve',
'b' => 'Clark',
'c' => 'Brian',
);
$this->assertSame(array($foo), Inline::parse('[*foo]', false, false, array('foo' => $foo)));
}
protected function getTestsForParse() protected function getTestsForParse()
{ {
return array( return array(

View File

@ -602,6 +602,32 @@ EOT
</body> </body>
footer # comment3 footer # comment3
EOF
));
}
public function testReferenceResolvingInInlineStrings()
{
$this->assertEquals(array(
'var' => 'var-value',
'scalar' => 'var-value',
'list' => array('var-value'),
'list_in_list' => array(array('var-value')),
'map_in_list' => array(array('key' => 'var-value')),
'embedded_mapping' => array(array('key' => 'var-value')),
'map' => array('key' => 'var-value'),
'list_in_map' => array('key' => array('var-value')),
'map_in_map' => array('foo' => array('bar' => 'var-value')),
), Yaml::parse(<<<EOF
var: &var var-value
scalar: *var
list: [ *var ]
list_in_list: [[ *var ]]
map_in_list: [ { key: *var } ]
embedded_mapping: [ key: *var ]
map: { key: *var }
list_in_map: { key: [*var] }
map_in_map: { foo: { bar: *var } }
EOF EOF
)); ));
} }