no message

This commit is contained in:
BarboraFleg
2019-04-04 18:34:40 +02:00
commit ce48ca4a21
589 changed files with 82184 additions and 0 deletions
+35
View File
@@ -0,0 +1,35 @@
{
"name": "nette/neon",
"description": "🍸 Nette NEON: encodes and decodes NEON file format.",
"keywords": ["nette", "neon", "import", "export", "yaml"],
"homepage": "http://ne-on.org",
"license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"],
"authors": [
{
"name": "David Grudl",
"homepage": "https://davidgrudl.com"
},
{
"name": "Nette Community",
"homepage": "https://nette.org/contributors"
}
],
"require": {
"php": ">=7.0",
"ext-iconv": "*",
"ext-json": "*"
},
"require-dev": {
"nette/tester": "^2.0",
"tracy/tracy": "^2.3"
},
"autoload": {
"classmap": ["src/"]
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
}
}
+33
View File
@@ -0,0 +1,33 @@
How to contribute & use the issue tracker
=========================================
Nette welcomes your contributions. There are several ways to help out:
* Create an issue on GitHub, if you have found a bug
* Write test cases for open bug issues
* Write fixes for open bug/feature issues, preferably with test cases included
* Contribute to the [documentation](https://nette.org/en/writing)
Issues
------
Please **do not use the issue tracker to ask questions**. We will be happy to help you
on [Nette forum](https://forum.nette.org) or chat with us on [Gitter](https://gitter.im/nette/nette).
A good bug report shouldn't leave others needing to chase you up for more
information. Please try to be as detailed as possible in your report.
**Feature requests** are welcome. But take a moment to find out whether your idea
fits with the scope and aims of the project. It's up to *you* to make a strong
case to convince the project's developers of the merits of this feature.
Contributing
------------
If you'd like to contribute, please take a moment to read [the contributing guide](https://nette.org/en/contributing).
The best way to propose a feature is to discuss your ideas on [Nette forum](https://forum.nette.org) before implementing them.
Please do not fix whitespace, format code, or make a purely cosmetic patch.
Thanks! :heart:
+60
View File
@@ -0,0 +1,60 @@
Licenses
========
Good news! You may use Nette Framework under the terms of either
the New BSD License or the GNU General Public License (GPL) version 2 or 3.
The BSD License is recommended for most projects. It is easy to understand and it
places almost no restrictions on what you can do with the framework. If the GPL
fits better to your project, you can use the framework under this license.
You don't have to notify anyone which license you are using. You can freely
use Nette Framework in commercial projects as long as the copyright header
remains intact.
Please be advised that the name "Nette Framework" is a protected trademark and its
usage has some limitations. So please do not use word "Nette" in the name of your
project or top-level domain, and choose a name that stands on its own merits.
If your stuff is good, it will not take long to establish a reputation for yourselves.
New BSD License
---------------
Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of "Nette Framework" nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
This software is provided by the copyright holders and contributors "as is" and
any express or implied warranties, including, but not limited to, the implied
warranties of merchantability and fitness for a particular purpose are
disclaimed. In no event shall the copyright owner or contributors be liable for
any direct, indirect, incidental, special, exemplary, or consequential damages
(including, but not limited to, procurement of substitute goods or services;
loss of use, data, or profits; or business interruption) however caused and on
any theory of liability, whether in contract, strict liability, or tort
(including negligence or otherwise) arising in any way out of the use of this
software, even if advised of the possibility of such damage.
GNU General Public License
--------------------------
GPL licenses are very very long, so instead of including them here we offer
you URLs with full text:
- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)
+96
View File
@@ -0,0 +1,96 @@
[NEON](http://ne-on.org): Nette Object Notation
===============================================
[![Downloads this Month](https://img.shields.io/packagist/dm/nette/neon.svg)](https://packagist.org/packages/nette/neon)
[![Build Status](https://travis-ci.org/nette/neon.svg?branch=master)](https://travis-ci.org/nette/neon)
[![Coverage Status](https://coveralls.io/repos/github/nette/neon/badge.svg?branch=master)](https://coveralls.io/github/nette/neon?branch=master)
[![Latest Stable Version](https://poser.pugx.org/nette/neon/v/stable)](https://github.com/nette/neon/releases)
[![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/neon/blob/master/license.md)
Introduction
------------
NEON is a human-readable data serialization language. It is commonly used for configuration files, but could be used in many applications where data is being stored
NEON is very similar to YAML.The main difference is that the NEON supports "entities" (so can be used e.g. to parse phpDoc annotations) and tab characters for indentation.
NEON syntax is a little simpler and the parsing is faster.
Documentation can be found on the [website](https://doc.nette.org/neon).
If you like Nette, **[please make a donation now](https://nette.org/donate)**. Thank you!
NEON language
-------------
Try NEON [in sandbox](https://ne-on.org)!
Example of NEON code:
```
# my web application config
php:
date.timezone: Europe/Prague
zlib.output_compression: yes # use gzip
database:
driver: mysql
username: root
password: beruska92
users:
- Dave
- Kryten
- Rimmer
```
Installation
------------
The recommended way to install is via Composer:
```
composer require nette/neon
```
It requires PHP version 5.6 and supports PHP up to 7.3. The dev-master version requires PHP 7.0.
Usage
-----
`Nette\Neon\Neon` is a static class for encoding and decoding NEON files.
`Neon::encode()` returns $value encoded into NEON. Flags accepts Neon::BLOCK which formats NEON in multiline format.
```php
use Nette\Neon\Neon;
$neon = Neon::encode($value); // Returns $value encoded in NEON
$neon = Neon::encode($value, Neon::BLOCK); // Returns formatted $value encoded in NEON
```
`Neon::decode()` converts given NEON to PHP value:
```php
$value = Neon::decode('hello: world'); // Returns an array ['hello' => 'world']
```
Both methods throw `Nette\Neon\Exception` on error.
Editors plugins
---------------
- NetBeans IDE has built-in support
- [PhpStorm](https://plugins.jetbrains.com/plugin/7060?pr)
- [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=Kasik96.latte)
- [Emacs](https://github.com/Fuco1/neon-mode)
Other languages
---------------
- [Neon for Javascript](https://github.com/matej21/neon-js)
- [Neon for Python](https://github.com/paveldedik/neon-py)
+358
View File
@@ -0,0 +1,358 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\Neon;
/**
* Parser for Nette Object Notation.
* @internal
*/
final class Decoder
{
const PATTERNS = [
'
\'\'\'\n (?:(?: [^\n] | \n(?![\t\ ]*+\'\'\') )*+ \n)?[\t\ ]*+\'\'\' |
"""\n (?:(?: [^\n] | \n(?![\t\ ]*+""") )*+ \n)?[\t\ ]*+""" |
\'[^\'\n]*+\' |
" (?: \\\\. | [^"\\\\\n] )*+ "
', // string
'
(?: [^#"\',:=[\]{}()\x00-\x20!`-] | [:-][^"\',\]})\s] )
(?:
[^,:=\]})(\x00-\x20]++ |
:(?! [\s,\]})] | $ ) |
[\ \t]++ [^#,:=\]})(\x00-\x20]
)*+
', // literal / boolean / integer / float
'
[,:=[\]{}()-]
', // symbol
'?:\#.*+', // comment
'\n[\t\ ]*+', // new line + indent
'?:[\t\ ]++', // whitespace
];
const PATTERN_DATETIME = '#\d\d\d\d-\d\d?-\d\d?(?:(?:[Tt]| ++)\d\d?:\d\d:\d\d(?:\.\d*+)? *+(?:Z|[-+]\d\d?(?::?\d\d)?)?)?\z#A';
const PATTERN_HEX = '#0x[0-9a-fA-F]++\z#A';
const PATTERN_OCTAL = '#0o[0-7]++\z#A';
const PATTERN_BINARY = '#0b[0-1]++\z#A';
const SIMPLE_TYPES = [
'true' => 'TRUE', 'True' => 'TRUE', 'TRUE' => 'TRUE', 'yes' => 'TRUE', 'Yes' => 'TRUE', 'YES' => 'TRUE', 'on' => 'TRUE', 'On' => 'TRUE', 'ON' => 'TRUE',
'false' => 'FALSE', 'False' => 'FALSE', 'FALSE' => 'FALSE', 'no' => 'FALSE', 'No' => 'FALSE', 'NO' => 'FALSE', 'off' => 'FALSE', 'Off' => 'FALSE', 'OFF' => 'FALSE',
'null' => 'NULL', 'Null' => 'NULL', 'NULL' => 'NULL',
];
const ESCAPE_SEQUENCES = [
't' => "\t", 'n' => "\n", 'r' => "\r", 'f' => "\x0C", 'b' => "\x08", '"' => '"', '\\' => '\\', '/' => '/', '_' => "\u{A0}",
];
const BRACKETS = [
'[' => ']',
'{' => '}',
'(' => ')',
];
/** @var string */
private $input;
/** @var array */
private $tokens;
/** @var int */
private $pos;
/**
* Decodes a NEON string.
* @return mixed
*/
public function decode(string $input)
{
if (!is_string($input)) {
throw new \InvalidArgumentException(sprintf('Argument must be a string, %s given.', gettype($input)));
} elseif (substr($input, 0, 3) === "\u{FEFF}") { // BOM
$input = substr($input, 3);
}
$this->input = "\n" . str_replace("\r", '', $input); // \n forces indent detection
$pattern = '~(' . implode(')|(', self::PATTERNS) . ')~Amix';
$this->tokens = preg_split($pattern, $this->input, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE | PREG_SPLIT_DELIM_CAPTURE);
$last = end($this->tokens);
if ($this->tokens && !preg_match($pattern, $last[0])) {
$this->pos = count($this->tokens) - 1;
$this->error();
}
$this->pos = 0;
$res = $this->parse(null);
while (isset($this->tokens[$this->pos])) {
if ($this->tokens[$this->pos][0][0] === "\n") {
$this->pos++;
} else {
$this->error();
}
}
return $res;
}
/**
* @param string|bool|null $indent indentation (for block-parser)
* @return mixed
*/
private function parse($indent, array $result = null, $key = null, bool $hasKey = false)
{
$inlineParser = $indent === false;
$value = null;
$hasValue = false;
$tokens = $this->tokens;
$n = &$this->pos;
$count = count($tokens);
$mainResult = &$result;
for (; $n < $count; $n++) {
$t = $tokens[$n][0];
if ($t === ',') { // ArrayEntry separator
if ((!$hasKey && !$hasValue) || !$inlineParser) {
$this->error();
}
$this->addValue($result, $hasKey ? $key : null, $hasValue ? $value : null);
$hasKey = $hasValue = false;
} elseif ($t === ':' || $t === '=') { // KeyValuePair separator
if ($hasValue && (is_array($value) || is_object($value))) {
$this->error('Unacceptable key');
} elseif ($hasKey && $key === null && $hasValue && !$inlineParser) {
$n++;
$result[] = $this->parse($indent . ' ', [], $value, true);
$newIndent = isset($tokens[$n], $tokens[$n + 1]) ? (string) substr($tokens[$n][0], 1) : ''; // not last
if (strlen($newIndent) > strlen($indent)) {
$n++;
$this->error('Bad indentation');
} elseif (strlen($newIndent) < strlen($indent)) {
return $mainResult; // block parser exit point
}
$hasKey = $hasValue = false;
} elseif ($hasKey || !$hasValue) {
$this->error();
} else {
$key = (string) $value;
$hasKey = true;
$hasValue = false;
$result = &$mainResult;
}
} elseif ($t === '-') { // BlockArray bullet
if ($hasKey || $hasValue || $inlineParser) {
$this->error();
}
$key = null;
$hasKey = true;
} elseif (isset(self::BRACKETS[$t])) { // Opening bracket [ ( {
if ($hasValue) {
if ($t !== '(') {
$this->error();
}
$n++;
if ($value instanceof Entity && $value->value === Neon::CHAIN) {
end($value->attributes)->attributes = $this->parse(false, []);
} else {
$value = new Entity($value, $this->parse(false, []));
}
} else {
$n++;
$value = $this->parse(false, []);
}
$hasValue = true;
if (!isset($tokens[$n]) || $tokens[$n][0] !== self::BRACKETS[$t]) { // unexpected type of bracket or block-parser
$this->error();
}
} elseif ($t === ']' || $t === '}' || $t === ')') { // Closing bracket ] ) }
if (!$inlineParser) {
$this->error();
}
break;
} elseif ($t[0] === "\n") { // Indent
if ($inlineParser) {
if ($hasKey || $hasValue) {
$this->addValue($result, $hasKey ? $key : null, $hasValue ? $value : null);
$hasKey = $hasValue = false;
}
} else {
while (isset($tokens[$n + 1]) && $tokens[$n + 1][0][0] === "\n") {
$n++; // skip to last indent
}
if (!isset($tokens[$n + 1])) {
break;
}
$newIndent = (string) substr($tokens[$n][0], 1);
if ($indent === null) { // first iteration
$indent = $newIndent;
}
$minlen = min(strlen($newIndent), strlen($indent));
if ($minlen && (string) substr($newIndent, 0, $minlen) !== (string) substr($indent, 0, $minlen)) {
$n++;
$this->error('Invalid combination of tabs and spaces');
}
if (strlen($newIndent) > strlen($indent)) { // open new block-array or hash
if ($hasValue || !$hasKey) {
$n++;
$this->error('Bad indentation');
}
$this->addValue($result, $key, $this->parse($newIndent));
$newIndent = isset($tokens[$n], $tokens[$n + 1]) ? (string) substr($tokens[$n][0], 1) : ''; // not last
if (strlen($newIndent) > strlen($indent)) {
$n++;
$this->error('Bad indentation');
}
$hasKey = false;
} else {
if ($hasValue && !$hasKey) { // block items must have "key"; null key means list item
break;
} elseif ($hasKey) {
$this->addValue($result, $key, $hasValue ? $value : null);
if ($key !== null && !$hasValue && $newIndent === $indent && isset($tokens[$n + 1]) && $tokens[$n + 1][0] === '-') {
$result = &$result[$key];
}
$hasKey = $hasValue = false;
}
}
if (strlen($newIndent) < strlen($indent)) { // close block
return $mainResult; // block parser exit point
}
}
} else { // Value
if ($t[0] === '"' || $t[0] === "'") {
if (preg_match('#^...\n++([\t ]*+)#', $t, $m)) {
$converted = substr($t, 3, -3);
$converted = str_replace("\n" . $m[1], "\n", $converted);
$converted = preg_replace('#^\n|\n[\t ]*+\z#', '', $converted);
} else {
$converted = substr($t, 1, -1);
}
if ($t[0] === '"') {
$converted = preg_replace_callback('#\\\\(?:ud[89ab][0-9a-f]{2}\\\\ud[c-f][0-9a-f]{2}|u[0-9a-f]{4}|x[0-9a-f]{2}|.)#i', [$this, 'cbString'], $converted);
}
} elseif (($fix56 = self::SIMPLE_TYPES) && isset($fix56[$t]) && (!isset($tokens[$n + 1][0]) || ($tokens[$n + 1][0] !== ':' && $tokens[$n + 1][0] !== '='))) {
$converted = constant(self::SIMPLE_TYPES[$t]);
} elseif (is_numeric($t)) {
$converted = $t * 1;
} elseif (preg_match(self::PATTERN_HEX, $t)) {
$converted = hexdec($t);
} elseif (preg_match(self::PATTERN_OCTAL, $t)) {
$converted = octdec($t);
} elseif (preg_match(self::PATTERN_BINARY, $t)) {
$converted = bindec($t);
} elseif (preg_match(self::PATTERN_DATETIME, $t)) {
$converted = new \DateTimeImmutable($t);
} else { // literal
$converted = $t;
}
if ($hasValue) {
if ($value instanceof Entity) { // Entity chaining
if ($value->value !== Neon::CHAIN) {
$value = new Entity(Neon::CHAIN, [$value]);
}
$value->attributes[] = new Entity($converted);
} else {
$this->error();
}
} else {
$value = $converted;
$hasValue = true;
}
}
}
if ($inlineParser) {
if ($hasKey || $hasValue) {
$this->addValue($result, $hasKey ? $key : null, $hasValue ? $value : null);
}
} else {
if ($hasValue && !$hasKey) { // block items must have "key"
if ($result === null) {
$result = $value; // simple value parser
} else {
$this->error();
}
} elseif ($hasKey) {
$this->addValue($result, $key, $hasValue ? $value : null);
}
}
return $mainResult;
}
private function addValue(&$result, $key, $value)
{
if ($key === null) {
$result[] = $value;
} elseif ($result && array_key_exists($key, $result)) {
$this->error("Duplicated key '$key'");
} else {
$result[$key] = $value;
}
}
private function cbString(array $m): string
{
$sq = $m[0];
if (($fix56 = self::ESCAPE_SEQUENCES) && isset($fix56[$sq[1]])) { // workaround for PHP 5.6
return self::ESCAPE_SEQUENCES[$sq[1]];
} elseif ($sq[1] === 'u' && strlen($sq) >= 6) {
$lead = hexdec(substr($sq, 2, 4));
$tail = hexdec(substr($sq, 8, 4));
$code = $tail ? (0x2400 + (($lead - 0xD800) << 10) + $tail) : $lead;
if ($code >= 0xD800 && $code <= 0xDFFF) {
$this->error("Invalid UTF-8 (lone surrogate) $sq");
}
return iconv('UTF-32BE', 'UTF-8//IGNORE', pack('N', $code));
} elseif ($sq[1] === 'x' && strlen($sq) === 4) {
return chr(hexdec(substr($sq, 2)));
} else {
$this->error("Invalid escaping sequence $sq");
return '';
}
}
private function error(string $message = "Unexpected '%s'")
{
$last = isset($this->tokens[$this->pos]) ? $this->tokens[$this->pos] : null;
$offset = $last ? $last[1] : strlen($this->input);
$text = substr($this->input, 0, $offset);
$line = substr_count($text, "\n");
$col = $offset - strrpos("\n" . $text, "\n") + 1;
$token = $last ? str_replace("\n", '<new line>', substr($last[0], 0, 40)) : 'end';
throw new Exception(str_replace('%s', $token, $message) . " on line $line, column $col.");
}
}
+84
View File
@@ -0,0 +1,84 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\Neon;
/**
* Simple generator for Nette Object Notation.
*/
final class Encoder
{
const BLOCK = 1;
/**
* Returns the NEON representation of a value.
*/
public function encode($var, int $flags = 0): string
{
if ($var instanceof \DateTimeInterface) {
return $var->format('Y-m-d H:i:s O');
} elseif ($var instanceof Entity) {
if ($var->value === Neon::CHAIN) {
return implode('', array_map([$this, 'encode'], $var->attributes));
}
return $this->encode($var->value) . '('
. (is_array($var->attributes) ? substr($this->encode($var->attributes), 1, -1) : '') . ')';
}
if (is_object($var)) {
$obj = $var;
$var = [];
foreach ($obj as $k => $v) {
$var[$k] = $v;
}
}
if (is_array($var)) {
$isList = !$var || array_keys($var) === range(0, count($var) - 1);
$s = '';
if ($flags & self::BLOCK) {
if (count($var) === 0) {
return '[]';
}
foreach ($var as $k => $v) {
$v = $this->encode($v, self::BLOCK);
$s .= ($isList ? '-' : $this->encode($k) . ':')
. (strpos($v, "\n") === false
? ' ' . $v . "\n"
: "\n" . preg_replace('#^(?=.)#m', "\t", $v) . (substr($v, -2, 1) === "\n" ? '' : "\n"));
}
return $s;
} else {
foreach ($var as $k => $v) {
$s .= ($isList ? '' : $this->encode($k) . ': ') . $this->encode($v) . ', ';
}
return ($isList ? '[' : '{') . substr($s, 0, -2) . ($isList ? ']' : '}');
}
} elseif (
is_string($var)
&& !is_numeric($var)
&& !preg_match('~[\x00-\x1F]|^\d{4}|^(true|false|yes|no|on|off|null)\z~i', $var)
&& preg_match('~^' . Decoder::PATTERNS[1] . '\z~x', $var) // 1 = literals
) {
return $var;
} elseif (is_float($var)) {
$var = json_encode($var);
return strpos($var, '.') === false ? $var . '.0' : $var;
} else {
return json_encode($var, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\Neon;
/**
* Representation of 'foo(bar=1)' literal
*/
final class Entity extends \stdClass
{
/** @var mixed */
public $value;
/** @var array */
public $attributes;
public function __construct($value, array $attrs = [])
{
$this->value = $value;
$this->attributes = $attrs;
}
public static function __set_state(array $properties)
{
return new self($properties['value'], $properties['attributes']);
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\Neon;
/**
* The exception that indicates error of NEON processing.
*/
class Exception extends \Exception
{
}
+42
View File
@@ -0,0 +1,42 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\Neon;
/**
* Simple parser & generator for Nette Object Notation.
*/
final class Neon
{
const BLOCK = Encoder::BLOCK;
const CHAIN = '!!chain';
/**
* Returns the NEON representation of a value.
*/
public static function encode($var, int $flags = 0): string
{
$encoder = new Encoder;
return $encoder->encode($var, $flags);
}
/**
* Decodes a NEON string.
* @return mixed
*/
public static function decode(string $input)
{
$decoder = new Decoder;
return $decoder->decode($input);
}
}
+9
View File
@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
require __DIR__ . '/Neon/Decoder.php';
require __DIR__ . '/Neon/Encoder.php';
require __DIR__ . '/Neon/Entity.php';
require __DIR__ . '/Neon/Exception.php';
require __DIR__ . '/Neon/Neon.php';
+32
View File
@@ -0,0 +1,32 @@
Preprocessing
-------------
- tabs are converted to single space
- \r is removed
Comment
-------
Comment ::= '#' .*
Values
------
Value ::= Boolean | Null | integer | float | String | DateTime | Literal | InlineArray | Entity
Boolean ::= 'true' | 'TRUE' | 'false' | 'FALSE' | 'yes' | 'YES' | 'no' | 'NO'
Null ::= 'null' | 'NULL' | ''
String ::= "word\u231" | 'word'
Literal ::= trimmed stream of characters [^#"',:=@[\]{}()\s!`] ( [^#,:=\]})(] | ':' [^\s,\]})] | \S '#' )*
Entity ::= Value '(' ( ArrayEntry ',' )* ')'
InlineArray
-----------
InlineArray ::= '{' ( ArrayEntry ',' )* '}' | '[' ( ArrayEntry ',' )* ']' | '(' ( ArrayEntry ',' )* ')'
ArrayEntry ::= Value | KeyValuePair
KeyValuePair ::= Value '=' Value | Value ': ' Value
BlockArray
----------
BlockArray ::= Indent ( '- ' Value | KeyValuePair ) EOL