From 90a2646bbf3596ef601ead89802a54425d99dc73 Mon Sep 17 00:00:00 2001 From: Chuck Hagenbuch Date: Sat, 15 Nov 2008 09:55:28 -0500 Subject: [PATCH] Add Horde_Yaml to git --- framework/Yaml/CVS/Entries | 4 + framework/Yaml/CVS/Repository | 1 + framework/Yaml/CVS/Root | 1 + framework/Yaml/CVS/Template | 8 + framework/Yaml/examples/CVS/Entries | 1 + framework/Yaml/examples/CVS/Repository | 1 + framework/Yaml/examples/CVS/Root | 1 + framework/Yaml/examples/CVS/Template | 8 + framework/Yaml/examples/Horde/CVS/Entries | 1 + framework/Yaml/examples/Horde/CVS/Repository | 1 + framework/Yaml/examples/Horde/CVS/Root | 1 + framework/Yaml/examples/Horde/CVS/Template | 8 + framework/Yaml/examples/Horde/Yaml/CVS/Entries | 4 + framework/Yaml/examples/Horde/Yaml/CVS/Repository | 1 + framework/Yaml/examples/Horde/Yaml/CVS/Root | 1 + framework/Yaml/examples/Horde/Yaml/CVS/Template | 8 + framework/Yaml/examples/Horde/Yaml/dump.php | 19 + framework/Yaml/examples/Horde/Yaml/example.yaml | 116 +++ framework/Yaml/examples/Horde/Yaml/load.php | 10 + framework/Yaml/lib/CVS/Entries | 1 + framework/Yaml/lib/CVS/Repository | 1 + framework/Yaml/lib/CVS/Root | 1 + framework/Yaml/lib/CVS/Template | 8 + framework/Yaml/lib/Horde/CVS/Entries | 2 + framework/Yaml/lib/Horde/CVS/Repository | 1 + framework/Yaml/lib/Horde/CVS/Root | 1 + framework/Yaml/lib/Horde/CVS/Template | 8 + framework/Yaml/lib/Horde/Yaml.php | 158 ++++ framework/Yaml/lib/Horde/Yaml/CVS/Entries | 5 + framework/Yaml/lib/Horde/Yaml/CVS/Repository | 1 + framework/Yaml/lib/Horde/Yaml/CVS/Root | 1 + framework/Yaml/lib/Horde/Yaml/CVS/Template | 8 + framework/Yaml/lib/Horde/Yaml/Dumper.php | 209 ++++++ framework/Yaml/lib/Horde/Yaml/Exception.php | 45 ++ framework/Yaml/lib/Horde/Yaml/Loader.php | 751 +++++++++++++++++++ framework/Yaml/lib/Horde/Yaml/Node.php | 58 ++ framework/Yaml/package.xml | 74 ++ framework/Yaml/test/CVS/Entries | 1 + framework/Yaml/test/CVS/Repository | 1 + framework/Yaml/test/CVS/Root | 1 + framework/Yaml/test/CVS/Template | 8 + framework/Yaml/test/Horde/CVS/Entries | 1 + framework/Yaml/test/Horde/CVS/Repository | 1 + framework/Yaml/test/Horde/CVS/Root | 1 + framework/Yaml/test/Horde/CVS/Template | 8 + framework/Yaml/test/Horde/Yaml/AllTests.php | 68 ++ framework/Yaml/test/Horde/Yaml/CVS/Entries | 6 + framework/Yaml/test/Horde/Yaml/CVS/Repository | 1 + framework/Yaml/test/Horde/Yaml/CVS/Root | 1 + framework/Yaml/test/Horde/Yaml/CVS/Template | 8 + framework/Yaml/test/Horde/Yaml/DumperTest.php | 191 +++++ framework/Yaml/test/Horde/Yaml/Helpers.php | 47 ++ framework/Yaml/test/Horde/Yaml/LoaderTest.php | 810 +++++++++++++++++++++ framework/Yaml/test/Horde/Yaml/NodeTest.php | 26 + .../Yaml/test/Horde/Yaml/fixtures/CVS/Entries | 3 + .../Yaml/test/Horde/Yaml/fixtures/CVS/Repository | 1 + framework/Yaml/test/Horde/Yaml/fixtures/CVS/Root | 1 + .../Yaml/test/Horde/Yaml/fixtures/CVS/Template | 8 + framework/Yaml/test/Horde/Yaml/fixtures/basic.yml | 14 + .../Yaml/test/Horde/Yaml/fixtures/references.yml | 12 + 60 files changed, 2747 insertions(+) create mode 100644 framework/Yaml/CVS/Entries create mode 100644 framework/Yaml/CVS/Repository create mode 100644 framework/Yaml/CVS/Root create mode 100644 framework/Yaml/CVS/Template create mode 100644 framework/Yaml/examples/CVS/Entries create mode 100644 framework/Yaml/examples/CVS/Repository create mode 100644 framework/Yaml/examples/CVS/Root create mode 100644 framework/Yaml/examples/CVS/Template create mode 100644 framework/Yaml/examples/Horde/CVS/Entries create mode 100644 framework/Yaml/examples/Horde/CVS/Repository create mode 100644 framework/Yaml/examples/Horde/CVS/Root create mode 100644 framework/Yaml/examples/Horde/CVS/Template create mode 100644 framework/Yaml/examples/Horde/Yaml/CVS/Entries create mode 100644 framework/Yaml/examples/Horde/Yaml/CVS/Repository create mode 100644 framework/Yaml/examples/Horde/Yaml/CVS/Root create mode 100644 framework/Yaml/examples/Horde/Yaml/CVS/Template create mode 100755 framework/Yaml/examples/Horde/Yaml/dump.php create mode 100755 framework/Yaml/examples/Horde/Yaml/example.yaml create mode 100755 framework/Yaml/examples/Horde/Yaml/load.php create mode 100644 framework/Yaml/lib/CVS/Entries create mode 100644 framework/Yaml/lib/CVS/Repository create mode 100644 framework/Yaml/lib/CVS/Root create mode 100644 framework/Yaml/lib/CVS/Template create mode 100644 framework/Yaml/lib/Horde/CVS/Entries create mode 100644 framework/Yaml/lib/Horde/CVS/Repository create mode 100644 framework/Yaml/lib/Horde/CVS/Root create mode 100644 framework/Yaml/lib/Horde/CVS/Template create mode 100644 framework/Yaml/lib/Horde/Yaml.php create mode 100644 framework/Yaml/lib/Horde/Yaml/CVS/Entries create mode 100644 framework/Yaml/lib/Horde/Yaml/CVS/Repository create mode 100644 framework/Yaml/lib/Horde/Yaml/CVS/Root create mode 100644 framework/Yaml/lib/Horde/Yaml/CVS/Template create mode 100644 framework/Yaml/lib/Horde/Yaml/Dumper.php create mode 100644 framework/Yaml/lib/Horde/Yaml/Exception.php create mode 100644 framework/Yaml/lib/Horde/Yaml/Loader.php create mode 100644 framework/Yaml/lib/Horde/Yaml/Node.php create mode 100644 framework/Yaml/package.xml create mode 100644 framework/Yaml/test/CVS/Entries create mode 100644 framework/Yaml/test/CVS/Repository create mode 100644 framework/Yaml/test/CVS/Root create mode 100644 framework/Yaml/test/CVS/Template create mode 100644 framework/Yaml/test/Horde/CVS/Entries create mode 100644 framework/Yaml/test/Horde/CVS/Repository create mode 100644 framework/Yaml/test/Horde/CVS/Root create mode 100644 framework/Yaml/test/Horde/CVS/Template create mode 100644 framework/Yaml/test/Horde/Yaml/AllTests.php create mode 100644 framework/Yaml/test/Horde/Yaml/CVS/Entries create mode 100644 framework/Yaml/test/Horde/Yaml/CVS/Repository create mode 100644 framework/Yaml/test/Horde/Yaml/CVS/Root create mode 100644 framework/Yaml/test/Horde/Yaml/CVS/Template create mode 100644 framework/Yaml/test/Horde/Yaml/DumperTest.php create mode 100644 framework/Yaml/test/Horde/Yaml/Helpers.php create mode 100644 framework/Yaml/test/Horde/Yaml/LoaderTest.php create mode 100644 framework/Yaml/test/Horde/Yaml/NodeTest.php create mode 100644 framework/Yaml/test/Horde/Yaml/fixtures/CVS/Entries create mode 100644 framework/Yaml/test/Horde/Yaml/fixtures/CVS/Repository create mode 100644 framework/Yaml/test/Horde/Yaml/fixtures/CVS/Root create mode 100644 framework/Yaml/test/Horde/Yaml/fixtures/CVS/Template create mode 100644 framework/Yaml/test/Horde/Yaml/fixtures/basic.yml create mode 100644 framework/Yaml/test/Horde/Yaml/fixtures/references.yml diff --git a/framework/Yaml/CVS/Entries b/framework/Yaml/CVS/Entries new file mode 100644 index 000000000..ecad70e90 --- /dev/null +++ b/framework/Yaml/CVS/Entries @@ -0,0 +1,4 @@ +/package.xml/1.10/Sat Feb 16 18:11:07 2008// +D/examples//// +D/lib//// +D/test//// diff --git a/framework/Yaml/CVS/Repository b/framework/Yaml/CVS/Repository new file mode 100644 index 000000000..2e1773094 --- /dev/null +++ b/framework/Yaml/CVS/Repository @@ -0,0 +1 @@ +framework/Yaml diff --git a/framework/Yaml/CVS/Root b/framework/Yaml/CVS/Root new file mode 100644 index 000000000..5d636129a --- /dev/null +++ b/framework/Yaml/CVS/Root @@ -0,0 +1 @@ +chuck@cvs.horde.org:/repository diff --git a/framework/Yaml/CVS/Template b/framework/Yaml/CVS/Template new file mode 100644 index 000000000..3971591f9 --- /dev/null +++ b/framework/Yaml/CVS/Template @@ -0,0 +1,8 @@ + +Bug: +Submitted by: +Merge after: +CVS: ---------------------------------------------------------------------- +CVS: Bug: Fill this in if a listed bug is affected by the change. +CVS: Submitted by: Fill this in if someone else sent in the change. +CVS: Merge after: N [day[s]|week[s]|month[s]] (days assumed by default) diff --git a/framework/Yaml/examples/CVS/Entries b/framework/Yaml/examples/CVS/Entries new file mode 100644 index 000000000..ae2779e30 --- /dev/null +++ b/framework/Yaml/examples/CVS/Entries @@ -0,0 +1 @@ +D/Horde//// diff --git a/framework/Yaml/examples/CVS/Repository b/framework/Yaml/examples/CVS/Repository new file mode 100644 index 000000000..1a9d9fe92 --- /dev/null +++ b/framework/Yaml/examples/CVS/Repository @@ -0,0 +1 @@ +framework/Yaml/examples diff --git a/framework/Yaml/examples/CVS/Root b/framework/Yaml/examples/CVS/Root new file mode 100644 index 000000000..5d636129a --- /dev/null +++ b/framework/Yaml/examples/CVS/Root @@ -0,0 +1 @@ +chuck@cvs.horde.org:/repository diff --git a/framework/Yaml/examples/CVS/Template b/framework/Yaml/examples/CVS/Template new file mode 100644 index 000000000..3971591f9 --- /dev/null +++ b/framework/Yaml/examples/CVS/Template @@ -0,0 +1,8 @@ + +Bug: +Submitted by: +Merge after: +CVS: ---------------------------------------------------------------------- +CVS: Bug: Fill this in if a listed bug is affected by the change. +CVS: Submitted by: Fill this in if someone else sent in the change. +CVS: Merge after: N [day[s]|week[s]|month[s]] (days assumed by default) diff --git a/framework/Yaml/examples/Horde/CVS/Entries b/framework/Yaml/examples/Horde/CVS/Entries new file mode 100644 index 000000000..e1642cf06 --- /dev/null +++ b/framework/Yaml/examples/Horde/CVS/Entries @@ -0,0 +1 @@ +D/Yaml//// diff --git a/framework/Yaml/examples/Horde/CVS/Repository b/framework/Yaml/examples/Horde/CVS/Repository new file mode 100644 index 000000000..65a7aec8f --- /dev/null +++ b/framework/Yaml/examples/Horde/CVS/Repository @@ -0,0 +1 @@ +framework/Yaml/examples/Horde diff --git a/framework/Yaml/examples/Horde/CVS/Root b/framework/Yaml/examples/Horde/CVS/Root new file mode 100644 index 000000000..5d636129a --- /dev/null +++ b/framework/Yaml/examples/Horde/CVS/Root @@ -0,0 +1 @@ +chuck@cvs.horde.org:/repository diff --git a/framework/Yaml/examples/Horde/CVS/Template b/framework/Yaml/examples/Horde/CVS/Template new file mode 100644 index 000000000..3971591f9 --- /dev/null +++ b/framework/Yaml/examples/Horde/CVS/Template @@ -0,0 +1,8 @@ + +Bug: +Submitted by: +Merge after: +CVS: ---------------------------------------------------------------------- +CVS: Bug: Fill this in if a listed bug is affected by the change. +CVS: Submitted by: Fill this in if someone else sent in the change. +CVS: Merge after: N [day[s]|week[s]|month[s]] (days assumed by default) diff --git a/framework/Yaml/examples/Horde/Yaml/CVS/Entries b/framework/Yaml/examples/Horde/Yaml/CVS/Entries new file mode 100644 index 000000000..9d350722b --- /dev/null +++ b/framework/Yaml/examples/Horde/Yaml/CVS/Entries @@ -0,0 +1,4 @@ +/dump.php/1.1/Wed Mar 5 20:37:57 2008// +/example.yaml/1.1/Wed Mar 5 20:37:57 2008// +/load.php/1.1/Wed Mar 5 20:37:57 2008// +D diff --git a/framework/Yaml/examples/Horde/Yaml/CVS/Repository b/framework/Yaml/examples/Horde/Yaml/CVS/Repository new file mode 100644 index 000000000..3f9671c73 --- /dev/null +++ b/framework/Yaml/examples/Horde/Yaml/CVS/Repository @@ -0,0 +1 @@ +framework/Yaml/examples/Horde/Yaml diff --git a/framework/Yaml/examples/Horde/Yaml/CVS/Root b/framework/Yaml/examples/Horde/Yaml/CVS/Root new file mode 100644 index 000000000..5d636129a --- /dev/null +++ b/framework/Yaml/examples/Horde/Yaml/CVS/Root @@ -0,0 +1 @@ +chuck@cvs.horde.org:/repository diff --git a/framework/Yaml/examples/Horde/Yaml/CVS/Template b/framework/Yaml/examples/Horde/Yaml/CVS/Template new file mode 100644 index 000000000..3971591f9 --- /dev/null +++ b/framework/Yaml/examples/Horde/Yaml/CVS/Template @@ -0,0 +1,8 @@ + +Bug: +Submitted by: +Merge after: +CVS: ---------------------------------------------------------------------- +CVS: Bug: Fill this in if a listed bug is affected by the change. +CVS: Submitted by: Fill this in if someone else sent in the change. +CVS: Merge after: N [day[s]|week[s]|month[s]] (days assumed by default) diff --git a/framework/Yaml/examples/Horde/Yaml/dump.php b/framework/Yaml/examples/Horde/Yaml/dump.php new file mode 100755 index 000000000..b85d2b2c8 --- /dev/null +++ b/framework/Yaml/examples/Horde/Yaml/dump.php @@ -0,0 +1,19 @@ + 'A sequence','second' => 'of mapped values'); +$array['Mapped'] = array('A sequence','which is mapped'); +$array['A Note'] = 'What if your text is too long?'; +$array['Another Note'] = 'If that is the case, the dumper will probably fold your text by using a block. Kinda like this.'; +$array['The trick?'] = 'The trick is that we overrode the default indent, 2, to 4 and the default wordwrap, 40, to 60.'; +$array['Old Dog'] = "And if you want\n to preserve line breaks, \ngo ahead!"; + +echo "A PHP array run through Horde_Yaml::dump():\n"; +var_dump(Horde_Yaml::dump($array, 4, 60)); diff --git a/framework/Yaml/examples/Horde/Yaml/example.yaml b/framework/Yaml/examples/Horde/Yaml/example.yaml new file mode 100755 index 000000000..add2a4c2e --- /dev/null +++ b/framework/Yaml/examples/Horde/Yaml/example.yaml @@ -0,0 +1,116 @@ +# +# S P Y C +# a simple php yaml class +# v0.2(.4) +# +# author: [chris wanstrath, chris@ozmm.org] +# websites: [http://www.yaml.org, http://spyc.sourceforge.net/] +# license: [MIT License, http://www.opensource.org/licenses/mit-license.php] +# copyright: (c) 2005-2006 Chris Wanstrath +# +# spyc.yml - A file containing the YAML that Spyc understands. + +# Mappings - with proper types +String: Anyone's name, really. +Int: 13 +True: true +False: false +Zero: 0 +Null: NULL +Float: 5.34 + +# A sequence +- PHP Class +- Basic YAML Loader +- Very Basic YAML Dumper + +# A sequence of a sequence +- + - YAML is so easy to learn. + - Your config files will never be the same. + +# Sequence of mappings +- + cpu: 1.5ghz + ram: 1 gig + os : os x 10.4.1 + +# Mapped sequence +domains: + - yaml.org + - php.net + +# A sequence like this. +- program: Adium + platform: OS X + type: Chat Client + +# A folded block as a mapped value +no time: > + There isn't any time + for your tricks! + + Do you understand? + +# A literal block as a mapped value +some time: | + There is nothing but time + for your tricks. + +# Crazy combinations +databases: + - name: spartan + notes: + - Needs to be backed up + - Needs to be normalized + type: mysql + +# You can be a bit tricky +"if: you'd": like + +# Inline sequences +- [One, Two, Three, Four] + +# Nested Inline Sequences +- [One, [Two, And, Three], Four, Five] + +# Nested Nested Inline Sequences +- [This, [Is, Getting, [Ridiculous, Guys]], Seriously, [Show, Mercy]] + +# Inline mappings +- {name: chris, age: young, brand: lucky strike} + +# Nested inline mappings +- {name: mark, age: older than chris, brand: [marlboro, lucky strike]} + +# References -- they're shaky, but functional +dynamic languages: &DLANGS + - Perl + - Python + - PHP + - Ruby +compiled languages: &CLANGS + - C/C++ + - Java +all languages: + - *DLANGS + - *CLANGS + +# Added in .2.2: Escaped quotes +- you know, this shouldn't work. but it does. +- 'that''s my value.' +- 'again, that\'s my value.' +- "here's to \"quotes\", boss." + +# added in .2.3 +- {name: "Foo, Bar's", age: 20} + +# Added in .2.4: bug [ 1418193 ] Quote Values in Nested Arrays +- [a, ['1', "2"], b] + +# Added in .2.4: malformed YAML +all + javascripts: [dom1.js, dom.js] + +# Added in .2 +1040: Ooo, a numeric key! # And working comments? Wow! \ No newline at end of file diff --git a/framework/Yaml/examples/Horde/Yaml/load.php b/framework/Yaml/examples/Horde/Yaml/load.php new file mode 100755 index 000000000..ffd392859 --- /dev/null +++ b/framework/Yaml/examples/Horde/Yaml/load.php @@ -0,0 +1,10 @@ + + * @author Chuck Hagenbuch + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Yaml + */ + +/** + * Horde YAML parser. + * + * This class can be used to read a YAML file and convert its contents + * into a PHP array. The native PHP parser supports a limited + * subsection of the YAML spec, but if the syck extension is present, + * that will be used for parsing. + * + * @category Horde + * @package Horde_Yaml + */ +class Horde_Yaml +{ + /** + * Callback used for alternate YAML loader, typically exported + * by a faster PHP extension. This function's first argument + * must accept a string with YAML content. + * + * @var callback + */ + public static $loadfunc = 'syck_load'; + + /** + * Callback used for alternate YAML dumper, typically exported + * by a faster PHP extension. This function's first argument + * must accept a mixed variable to be dumped. + * + * @var callback + */ + public static $dumpfunc = 'syck_dump'; + + /** + * Whitelist of classes that can be instantiated automatically + * when loading YAML docs that include serialized PHP objects. + * + * @var array + */ + public static $allowedClasses = array('ArrayObject'); + + /** + * Load a string containing YAML and parse it into a PHP array. + * Returns an empty array on failure. + * + * @param string $yaml String containing YAML + * @return array PHP array representation of YAML content + */ + public static function load($yaml) + { + if (!is_string($yaml) || !strlen($yaml)) { + $msg = 'YAML to parse must be a string and cannot be empty.'; + throw new InvalidArgumentException($msg); + } + + if (is_callable(self::$loadfunc)) { + return call_user_func(self::$loadfunc, $yaml); + return is_array($array) ? $array : array(); + } + + if (strpos($yaml, "\r") !== false) { + $yaml = str_replace(array("\r\n", "\r"), array("\n", "\n"), $yaml); + } + $lines = explode("\n", $yaml); + $loader = new Horde_Yaml_Loader; + + while (list(,$line) = each($lines)) { + $loader->parse($line); + } + + return $loader->toArray(); + } + + /** + * Load a file containing YAML and parse it into a PHP array. + * + * If the file cannot be opened, an exception is thrown. If the + * file is read but parsing fails, an empty array is returned. + * + * @param string $filename Filename to load + * @return array PHP array representation of YAML content + * @throws IllegalArgumentException If $filename is invalid + * @throws Horde_Yaml_Exception If the file cannot be opened. + */ + public static function loadFile($filename) + { + if (!is_string($filename) || !strlen($filename)) { + $msg = 'Filename must be a string and cannot be empty'; + throw new InvalidArgumentException($msg); + } + + $stream = @fopen($filename, 'rb'); + if (!$stream) { + throw new Horde_Yaml_Exception('Failed to open file: ', error_get_last()); + } + + return self::loadStream($stream); + } + + /** + * Load YAML from a PHP stream resource. + * + * @param resource $stream PHP stream resource + * @return array PHP array representation of YAML content + */ + public static function loadStream($stream) + { + if (! is_resource($stream) || get_resource_type($stream) != 'stream') { + throw new InvalidArgumentException('Stream must be a stream resource'); + } + + if (is_callable(self::$loadfunc)) { + return call_user_func(self::$loadfunc, stream_get_contents($stream)); + } + + $loader = new Horde_Yaml_Loader; + while (!feof($stream)) { + $loader->parse(stream_get_line($stream, 100000, "\n")); + } + + return $loader->toArray(); + } + + /** + * Dump a PHP array to YAML. + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. + * + * @param array|Traversable $array PHP array or traversable object + * @param integer $options Options to pass to dumper + * @return string YAML representation of $value + */ + public static function dump($value, $options = array()) + { + if (is_callable(self::$dumpfunc)) { + return call_user_func(self::$dumpfunc, $value); + } + + $dumper = new Horde_Yaml_Dumper(); + return $dumper->dump($value, $options); + } + +} diff --git a/framework/Yaml/lib/Horde/Yaml/CVS/Entries b/framework/Yaml/lib/Horde/Yaml/CVS/Entries new file mode 100644 index 000000000..ec45e3194 --- /dev/null +++ b/framework/Yaml/lib/Horde/Yaml/CVS/Entries @@ -0,0 +1,5 @@ +/Dumper.php/1.5/Wed Jun 18 19:05:26 2008// +/Exception.php/1.3/Wed Jun 18 19:05:26 2008// +/Loader.php/1.6/Wed Jun 18 19:05:26 2008// +/Node.php/1.3/Wed Jun 18 19:05:26 2008// +D diff --git a/framework/Yaml/lib/Horde/Yaml/CVS/Repository b/framework/Yaml/lib/Horde/Yaml/CVS/Repository new file mode 100644 index 000000000..14f1e4c73 --- /dev/null +++ b/framework/Yaml/lib/Horde/Yaml/CVS/Repository @@ -0,0 +1 @@ +framework/Yaml/lib/Horde/Yaml diff --git a/framework/Yaml/lib/Horde/Yaml/CVS/Root b/framework/Yaml/lib/Horde/Yaml/CVS/Root new file mode 100644 index 000000000..5d636129a --- /dev/null +++ b/framework/Yaml/lib/Horde/Yaml/CVS/Root @@ -0,0 +1 @@ +chuck@cvs.horde.org:/repository diff --git a/framework/Yaml/lib/Horde/Yaml/CVS/Template b/framework/Yaml/lib/Horde/Yaml/CVS/Template new file mode 100644 index 000000000..3971591f9 --- /dev/null +++ b/framework/Yaml/lib/Horde/Yaml/CVS/Template @@ -0,0 +1,8 @@ + +Bug: +Submitted by: +Merge after: +CVS: ---------------------------------------------------------------------- +CVS: Bug: Fill this in if a listed bug is affected by the change. +CVS: Submitted by: Fill this in if someone else sent in the change. +CVS: Merge after: N [day[s]|week[s]|month[s]] (days assumed by default) diff --git a/framework/Yaml/lib/Horde/Yaml/Dumper.php b/framework/Yaml/lib/Horde/Yaml/Dumper.php new file mode 100644 index 000000000..40ff02131 --- /dev/null +++ b/framework/Yaml/lib/Horde/Yaml/Dumper.php @@ -0,0 +1,209 @@ + + * @author Chuck Hagenbuch + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Yaml + */ + +/** + * Dump PHP data structures to YAML. + * + * @category Horde + * @package Horde_Yaml + */ +class Horde_Yaml_Dumper +{ + protected $_options = array(); + + /** + * Dump PHP array to YAML + * + * The dump method, when supplied with an array, will do its best + * to convert the array into valid YAML. + * + * Options: + * `indent`: + * number of spaces to indent children (default 2) + * `wordwrap`: + * wordwrap column number (default 40) + * + * @param array|Traversable $array PHP array or traversable object + * @param integer $options Options for dumping + * @return string YAML representation of $value + */ + public function dump($value, $options = array()) + { + // validate & merge default options + if (!is_array($options)) { + throw new InvalidArgumentException('Options must be an array'); + } + + $defaults = array('indent' => 2, + 'wordwrap' => 40); + $this->_options = array_merge($defaults, $options); + + if (! is_int($this->_options['indent'])) { + throw new InvalidArgumentException('Indent must be an integer'); + } + + if (! is_int($this->_options['wordwrap'])) { + throw new InvalidArgumentException('Wordwrap column must be an integer'); + } + + // new YAML document + $dump = "---\n"; + + // iterate through array and yamlize it + foreach ($value as $key => $value) { + $dump .= $this->_yamlize($key, $value, 0); + } + return $dump; + } + + /** + * Attempts to convert a key / value array item to YAML + * + * @param string $key The name of the key + * @param string|array $value The value of the item + * @param integer $indent The indent of the current node + * @return string + */ + protected function _yamlize($key, $value, $indent) + { + if ($value instanceof Serializable) { + // Dump serializable objects as !php/object::classname serialize_data + $data = '!php/object::' . get_class($value) . ' ' . $value->serialize(); + $string = $this->_dumpNode($key, $data, $indent); + } elseif (is_array($value) || $value instanceof Traversable) { + // It has children. Make it the right kind of item. + $string = $this->_dumpNode($key, null, $indent); + + // Add the indent. + $indent += $this->_options['indent']; + + // Yamlize the array. + $string .= $this->_yamlizeArray($value, $indent); + } elseif (!is_array($value)) { + // No children. + $string = $this->_dumpNode($key, $value, $indent); + } + + return $string; + } + + /** + * Attempts to convert an array to YAML + * + * @param array $array The array you want to convert + * @param integer $indent The indent of the current level + * @return string + */ + protected function _yamlizeArray($array, $indent) + { + if (!is_array($array)) { + return false; + } + + $string = ''; + foreach ($array as $key => $value) { + $string .= $this->_yamlize($key, $value, $indent); + } + return $string; + } + + /** + * Returns YAML from a key and a value + * + * @param string $key The name of the key + * @param string $value The value of the item + * @param integer $indent The indent of the current node + * @return string + */ + protected function _dumpNode($key, $value, $indent) + { + // Do some folding here, for blocks. + if (strpos($value, "\n") !== false + || strpos($value, ': ') !== false + || strpos($value, '- ') !== false) { + $value = $this->_doLiteralBlock($value, $indent); + } else { + $value = $this->_fold($value, $indent); + } + + if (is_bool($value)) { + $value = ($value) ? 'true' : 'false'; + } elseif (is_float($value)) { + if (is_nan($value)) { + $value = '.NAN'; + } elseif ($value === INF) { + $value = '.INF'; + } elseif ($value === -INF) { + $value = '-.INF'; + } + } + + $spaces = str_repeat(' ', $indent); + + if (is_int($key)) { + // It's a sequence. + $string = $spaces . '- ' . $value . "\n"; + } else { + // It's mapped. + $string = $spaces . $key . ': ' . $value . "\n"; + } + + return $string; + } + + /** + * Creates a literal block for dumping + * + * @param string $value + * @param integer $indent The value of the indent. + * @return string + */ + protected function _doLiteralBlock($value, $indent) + { + $exploded = explode("\n", $value); + $newValue = '|'; + $indent += $this->_options['indent']; + $spaces = str_repeat(' ', $indent); + foreach ($exploded as $line) { + $newValue .= "\n" . $spaces . trim($line); + } + return $newValue; + } + + /** + * Folds a string of text, if necessary + * + * @param $value The string you wish to fold + * @return string + */ + protected function _fold($value, $indent) + { + // Don't do anything if wordwrap is set to 0 + if (! $this->_options['wordwrap']) { + return $value; + } + + if (strlen($value) > $this->_options['wordwrap']) { + $indent += $this->_options['indent']; + $indent = str_repeat(' ', $indent); + $wrapped = wordwrap($value, $this->_options['wordwrap'], "\n$indent"); + $value = ">\n" . $indent . $wrapped; + } + + return $value; + } + +} diff --git a/framework/Yaml/lib/Horde/Yaml/Exception.php b/framework/Yaml/lib/Horde/Yaml/Exception.php new file mode 100644 index 000000000..b53159b65 --- /dev/null +++ b/framework/Yaml/lib/Horde/Yaml/Exception.php @@ -0,0 +1,45 @@ + + * @author Chuck Hagenbuch + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Yaml + */ + +/** + * Exception class for exceptions thrown by Horde_Yaml + * + * @category Horde + * @package Horde_Yaml + */ +class Horde_Yaml_Exception extends Exception +{ + + public function __construct($message = null, $code_or_lasterror = 0) + { + if (is_array($code_or_lasterror)) { + if ($message) { + $message .= $code_or_lasterror['message']; + } else { + $message = $code_or_lasterror['message']; + } + + $this->file = $code_or_lasterror['file']; + $this->line = $code_or_lasterror['line']; + $code = $code_or_lasterror['type']; + } else { + $code = $code_or_lasterror; + } + + parent::__construct($message, $code); + } + +} diff --git a/framework/Yaml/lib/Horde/Yaml/Loader.php b/framework/Yaml/lib/Horde/Yaml/Loader.php new file mode 100644 index 000000000..ce056a663 --- /dev/null +++ b/framework/Yaml/lib/Horde/Yaml/Loader.php @@ -0,0 +1,751 @@ + + * @author Chuck Hagenbuch + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Yaml + */ + +/** + * Parse YAML strings into PHP data structures + * + * @category Horde + * @package Horde_Yaml + */ +class Horde_Yaml_Loader +{ + /** + * List of nodes with references + * @var array + */ + protected $_haveRefs = array(); + + /** + * All nodes + * @var array + */ + protected $_allNodes = array(); + + /** + * Array of node parents + * @var array + */ + protected $_allParent = array(); + + /** + * Last indent level + * @var integer + */ + protected $_lastIndent = 0; + + /** + * Last node id + * @var integer + */ + protected $_lastNode = null; + + /** + * Is the parser inside a block? + * @var boolean + */ + protected $_inBlock = false; + + /** + * @var boolean + */ + protected $_isInline = false; + + /** + * Next node id to use + * @var integer + */ + protected $_nodeId = 1; + + /** + * Last line number parsed. + * @var integer + */ + protected $_lineNumber = 0; + + /** + * Create a new YAML parser. + */ + public function __construct() + { + $base = new Horde_Yaml_Node($this->_nodeId++); + $base->indent = 0; + $this->_lastNode = $base->id; + } + + /** + * Return the PHP built from all YAML parsed so far. + * + * @return array PHP version of parsed YAML + */ + public function toArray() + { + // Here we travel through node-space and pick out references + // (& and *). + $this->_linkReferences(); + + // Build the PHP array out of node-space. + return $this->_buildArray(); + } + + /** + * Parse a line of a YAML file. + * + * @param string $line The line of YAML to parse. + * @return Horde_Yaml_Node YAML Node + */ + public function parse($line) + { + // Keep track of how many lines we've parsed for friendlier + // error messages. + ++$this->_lineNumber; + + $trimmed = trim($line); + + // If the line starts with a tab (instead of a space), throw a fit. + if (preg_match('/^ *(\t) *[^\t ]/', $line)) { + $msg = "Line {$this->_lineNumber} indent contains a tab. " + . 'YAML only allows spaces for indentation.'; + throw new Horde_Yaml_Exception($msg); + } + + if (!$this->_inBlock && empty($trimmed)) { + return; + } elseif ($this->_inBlock && empty($trimmed)) { + $last =& $this->_allNodes[$this->_lastNode]; + $last->data[key($last->data)] .= "\n"; + } elseif ($trimmed[0] != '#' && substr($trimmed, 0, 3) != '---') { + // Create a new node and get its indent + $node = new Horde_Yaml_Node($this->_nodeId++); + $node->indent = $this->_getIndent($line); + + // Check where the node lies in the hierarchy + if ($this->_lastIndent == $node->indent) { + // If we're in a block, add the text to the parent's data + if ($this->_inBlock) { + $parent =& $this->_allNodes[$this->_lastNode]; + $parent->data[key($parent->data)] .= trim($line) . $this->_blockEnd; + } else { + // The current node's parent is the same as the previous node's + if (isset($this->_allNodes[$this->_lastNode])) { + $node->parent = $this->_allNodes[$this->_lastNode]->parent; + } + } + } elseif ($this->_lastIndent < $node->indent) { + if ($this->_inBlock) { + $parent =& $this->_allNodes[$this->_lastNode]; + $parent->data[key($parent->data)] .= trim($line) . $this->_blockEnd; + } elseif (!$this->_inBlock) { + // The current node's parent is the previous node + $node->parent = $this->_lastNode; + + // If the value of the last node's data was > or | + // we need to start blocking i.e. taking in all + // lines as a text value until we drop our indent. + $parent =& $this->_allNodes[$node->parent]; + $this->_allNodes[$node->parent]->children = true; + if (is_array($parent->data)) { + if (isset($parent->data[key($parent->data)])) { + $chk = $parent->data[key($parent->data)]; + if ($chk === '>') { + $this->_inBlock = true; + $this->_blockEnd = ''; + $parent->data[key($parent->data)] = + str_replace('>', '', $parent->data[key($parent->data)]); + $parent->data[key($parent->data)] .= trim($line) . ' '; + $this->_allNodes[$node->parent]->children = false; + $this->_lastIndent = $node->indent; + } elseif ($chk === '|') { + $this->_inBlock = true; + $this->_blockEnd = "\n"; + $parent->data[key($parent->data)] = + str_replace('|', '', $parent->data[key($parent->data)]); + $parent->data[key($parent->data)] .= trim($line) . "\n"; + $this->_allNodes[$node->parent]->children = false; + $this->_lastIndent = $node->indent; + } + } + } + } + } elseif ($this->_lastIndent > $node->indent) { + // Any block we had going is dead now + if ($this->_inBlock) { + $this->_inBlock = false; + if ($this->_blockEnd == "\n") { + $last =& $this->_allNodes[$this->_lastNode]; + $last->data[key($last->data)] = + trim($last->data[key($last->data)]); + } + } + + // We don't know the parent of the node so we have to + // find it + foreach ($this->_indentSort[$node->indent] as $n) { + if ($n->indent == $node->indent) { + $node->parent = $n->parent; + } + } + } + + if (!$this->_inBlock) { + // Set these properties with information from our + // current node + $this->_lastIndent = $node->indent; + + // Set the last node + $this->_lastNode = $node->id; + + // Parse the YAML line and return its data + $node->data = $this->_parseLine($line); + + // Add the node to the master list + $this->_allNodes[$node->id] = $node; + + // Add a reference to the parent list + $this->_allParent[intval($node->parent)][] = $node->id; + + // Add a reference to the node in an indent array + $this->_indentSort[$node->indent][] =& $this->_allNodes[$node->id]; + + // Add a reference to the node in a References array + // if this node has a YAML reference in it. + $is_array = is_array($node->data); + $key = key($node->data); + $isset = isset($node->data[$key]); + if ($isset) { + $nodeval = $node->data[$key]; + } + if (($is_array && $isset && !is_array($nodeval) && !is_object($nodeval)) + && (strlen($nodeval) && ($nodeval[0] == '&' || $nodeval[0] == '*') && $nodeval[1] != ' ')) { + $this->_haveRefs[] =& $this->_allNodes[$node->id]; + } elseif ($is_array && $isset && is_array($nodeval)) { + // Incomplete reference making code. Needs to be + // cleaned up. + foreach ($node->data[$key] as $d) { + if (!is_array($d) && strlen($d) && (($d[0] == '&' || $d[0] == '*') && $d[1] != ' ')) { + $this->_haveRefs[] =& $this->_allNodes[$node->id]; + } + } + } + } + } + } + + /** + * Finds and returns the indentation of a YAML line + * + * @param string $line A line from the YAML file + * @return int Indentation level + */ + protected function _getIndent($line) + { + if (preg_match('/^\s+/', $line, $match)) { + return strlen($match[0]); + } else { + return 0; + } + } + + /** + * Parses YAML code and returns an array for a node + * + * @param string $line A line from the YAML file + * @return array + */ + protected function _parseLine($line) + { + $array = array(); + + $line = trim($line); + if (preg_match('/^-(.*):$/', $line)) { + // It's a mapped sequence + $key = trim(substr(substr($line, 1), 0, -1)); + $array[$key] = ''; + } elseif ($line[0] == '-' && substr($line, 0, 3) != '---') { + // It's a list item but not a new stream + if (strlen($line) > 1) { + // Set the type of the value. Int, string, etc + $array[] = $this->_toType(trim(substr($line, 1))); + } else { + $array[] = array(); + } + } elseif (preg_match('/^(.+):/', $line, $key)) { + // It's a key/value pair most likely + // If the key is in double quotes pull it out + if (preg_match('/^(["\'](.*)["\'](\s)*:)/', $line, $matches)) { + $value = trim(str_replace($matches[1], '', $line)); + $key = $matches[2]; + } else { + // Do some guesswork as to the key and the value + $explode = explode(':', $line); + $key = trim(array_shift($explode)); + $value = trim(implode(':', $explode)); + } + + // Set the type of the value. Int, string, etc + $value = $this->_toType($value); + if (empty($key)) { + $array[] = $value; + } else { + $array[$key] = $value; + } + } + + return $array; + } + + /** + * Finds the type of the passed value, returns the value as the new type. + * + * @param string $value + * @return mixed + */ + protected function _toType($value) + { + // Check for PHP specials + self::_unserialize($value); + if (!is_scalar($value)) { + return $value; + } + + // Used in a lot of cases. + $lower_value = strtolower($value); + + if (preg_match('/^("(.*)"|\'(.*)\')/', $value, $matches)) { + $value = (string)str_replace(array('\'\'', '\\\''), "'", end($matches)); + $value = str_replace('\\"', '"', $value); + } elseif (preg_match('/^\\[(\s*)\\]$/', $value)) { + // empty inline mapping + $value = array(); + } elseif (preg_match('/^\\[(.+)\\]$/', $value, $matches)) { + // Inline Sequence + + // Take out strings sequences and mappings + $explode = $this->_inlineEscape($matches[1]); + + // Propogate value array + $value = array(); + foreach ($explode as $v) { + $value[] = $this->_toType($v); + } + } elseif (preg_match('/^\\{(\s*)\\}$/', $value)) { + // empty inline mapping + $value = array(); + } elseif (strpos($value, ': ') !== false && !preg_match('/^{(.+)/', $value)) { + // inline mapping + $array = explode(': ', $value); + $key = trim($array[0]); + array_shift($array); + $value = trim(implode(': ', $array)); + $value = $this->_toType($value); + $value = array($key => $value); + } elseif (preg_match("/{(.+)}$/", $value, $matches)) { + // Inline Mapping + + // Take out strings sequences and mappings + $explode = $this->_inlineEscape($matches[1]); + + // Propogate value array + $array = array(); + foreach ($explode as $v) { + $array = $array + $this->_toType($v); + } + $value = $array; + } elseif ($lower_value == 'null' || $value == '' || $value == '~') { + $value = null; + } elseif ($lower_value == '.nan') { + $value = NAN; + } elseif ($lower_value == '.inf') { + $value = INF; + } elseif ($lower_value == '-.inf') { + $value = -INF; + } elseif (ctype_digit($value)) { + $value = (int)$value; + } elseif (in_array($lower_value, + array('true', 'on', '+', 'yes', 'y'))) { + $value = true; + } elseif (in_array($lower_value, + array('false', 'off', '-', 'no', 'n'))) { + $value = false; + } elseif (is_numeric($value)) { + $value = (float)$value; + } else { + // Just a normal string, right? + if (($pos = strpos($value, '#')) !== false) { + $value = substr($value, 0, $pos); + } + $value = trim($value); + } + + return $value; + } + + /** + * Handle PHP serialized data. + * + * @param string &$data Data to check for serialized PHP types. + */ + protected function _unserialize(&$data) + { + if (substr($data, 0, 5) != '!php/') { + return; + } + + $first_space = strpos($data, ' '); + $type = substr($data, 5, $first_space - 5); + $class = null; + if (strpos($type, '::') !== false) { + list($type, $class) = explode('::', $type); + + if (!in_array($class, Horde_Yaml::$allowedClasses)) { + throw new Horde_Yaml_Exception("$class is not in the list of allowed classes"); + } + } + + switch ($type) { + case 'object': + if (!class_exists($class)) { + throw new Horde_Yaml_Exception("$class is not defined"); + } + + $reflector = new ReflectionClass($class); + if (!$reflector->implementsInterface('Serializable')) { + throw new Horde_Yaml_Exception("$class does not implement Serializable"); + } + + $class_data = substr($data, $first_space + 1); + $serialized = 'C:' . strlen($class) . ':"' . $class . '":' . strlen($class_data) . ':{' . $class_data . '}'; + $data = unserialize($serialized); + break; + + case 'array': + case 'hash': + $array_data = substr($data, $first_space + 1); + $array_data = Horde_Yaml::load('a: ' . $array_data); + + if (is_null($class)) { + $data = $array_data['a']; + } else { + if (!class_exists($class)) { + throw new Horde_Yaml_Exception("$class is not defined"); + } + + $array = new $class; + if (!$array instanceof ArrayAccess) { + throw new Horde_Yaml_Exception("$class does not implement ArrayAccess"); + } + + foreach ($array_data['a'] as $key => $val) { + $array[$key] = $val; + } + + $data = $array; + } + break; + } + } + + /** + * Used in inlines to check for more inlines or quoted strings + * + * @todo There should be a cleaner way to do this. While + * pure sequences seem to be nesting just fine, + * pure mappings and mappings with sequences inside + * can't go very deep. This needs to be fixed. + * + * @param string $inline Inline data + * @return array + */ + protected function _inlineEscape($inline) + { + $saved_strings = array(); + + // Check for strings + $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/'; + if (preg_match_all($regex, $inline, $strings)) { + $saved_strings = $strings[0]; + $inline = preg_replace($regex, 'YAMLString', $inline); + } + + // Check for sequences + if (preg_match_all('/\[(.+)\]/U', $inline, $seqs)) { + $inline = preg_replace('/\[(.+)\]/U', 'YAMLSeq', $inline); + $seqs = $seqs[0]; + } + + // Check for mappings + if (preg_match_all('/{(.+)}/U', $inline, $maps)) { + $inline = preg_replace('/{(.+)}/U', 'YAMLMap', $inline); + $maps = $maps[0]; + } + + $explode = explode(', ', $inline); + + // Re-add the sequences + if (!empty($seqs)) { + $i = 0; + foreach ($explode as $key => $value) { + if (strpos($value, 'YAMLSeq') !== false) { + $explode[$key] = str_replace('YAMLSeq', $seqs[$i], $value); + ++$i; + } + } + } + + // Re-add the mappings + if (!empty($maps)) { + $i = 0; + foreach ($explode as $key => $value) { + if (strpos($value, 'YAMLMap') !== false) { + $explode[$key] = str_replace('YAMLMap', $maps[$i], $value); + ++$i; + } + } + } + + // Re-add the strings + if (!empty($saved_strings)) { + $i = 0; + foreach ($explode as $key => $value) { + while (strpos($value, 'YAMLString') !== false) { + $explode[$key] = preg_replace('/YAMLString/', $saved_strings[$i], $value, 1); + ++$i; + $value = $explode[$key]; + } + } + } + + return $explode; + } + + /** + * Builds the PHP array from all the YAML nodes we've gathered + * + * @return array + */ + protected function _buildArray() + { + $trunk = array(); + if (!isset($this->_indentSort[0])) { + return $trunk; + } + + foreach ($this->_indentSort[0] as $n) { + if (empty($n->parent)) { + $this->_nodeArrayizeData($n); + + // Check for references and copy the needed data to complete them. + $this->_makeReferences($n); + + // Merge our data with the big array we're building + $trunk = $this->_array_kmerge($trunk, $n->data); + } + } + + return $trunk; + } + + /** + * Traverses node-space and sets references (& and *) accordingly + * + * @return bool + */ + protected function _linkReferences() + { + if (is_array($this->_haveRefs)) { + foreach ($this->_haveRefs as $node) { + if (!empty($node->data)) { + $key = key($node->data); + // If it's an array, don't check. + if (is_array($node->data[$key])) { + foreach ($node->data[$key] as $k => $v) { + $this->_linkRef($node, $key, $k, $v); + } + } else { + $this->_linkRef($node, $key); + } + } + } + } + + return true; + } + + /** + * Helper for _linkReferences() + * + * @param Horde_Yaml_Node $n Node + * @param string $k Key + * @param mixed $v Value + * @return void + */ + function _linkRef(&$n, $key, $k = null, $v = null) + { + if (empty($k) && empty($v)) { + // Look for &refs + if (preg_match('/^&([^ ]+)/', $n->data[$key], $matches)) { + // Flag the node so we know it's a reference + $this->_allNodes[$n->id]->ref = substr($matches[0], 1); + $this->_allNodes[$n->id]->data[$key] = + substr($n->data[$key], strlen($matches[0]) + 1); + // Look for *refs + } elseif (preg_match('/^\*([^ ]+)/', $n->data[$key], $matches)) { + $ref = substr($matches[0], 1); + // Flag the node as having a reference + $this->_allNodes[$n->id]->refKey = $ref; + } + } elseif (!empty($k) && !empty($v)) { + if (preg_match('/^&([^ ]+)/', $v, $matches)) { + // Flag the node so we know it's a reference + $this->_allNodes[$n->id]->ref = substr($matches[0], 1); + $this->_allNodes[$n->id]->data[$key][$k] = + substr($v, strlen($matches[0]) + 1); + // Look for *refs + } elseif (preg_match('/^\*([^ ]+)/', $v, $matches)) { + $ref = substr($matches[0], 1); + // Flag the node as having a reference + $this->_allNodes[$n->id]->refKey = $ref; + } + } + } + + /** + * Finds the children of a node and aids in the building of the PHP array + * + * @param int $nid The id of the node whose children we're gathering + * @return array + */ + protected function _gatherChildren($nid) + { + $return = array(); + $node =& $this->_allNodes[$nid]; + if (is_array ($this->_allParent[$node->id])) { + foreach ($this->_allParent[$node->id] as $nodeZ) { + $z =& $this->_allNodes[$nodeZ]; + // We found a child + $this->_nodeArrayizeData($z); + + // Check for references + $this->_makeReferences($z); + + // Merge with the big array we're returning, the big + // array being all the data of the children of our + // parent node + $return = $this->_array_kmerge($return, $z->data); + } + } + return $return; + } + + /** + * Turns a node's data and its children's data into a PHP array + * + * @param array $node The node which you want to arrayize + * @return boolean + */ + protected function _nodeArrayizeData(&$node) + { + if ($node->children == true) { + if (is_array($node->data)) { + // This node has children, so we need to find them + $children = $this->_gatherChildren($node->id); + + // We've gathered all our children's data and are ready to use it + $key = key($node->data); + $key = empty($key) ? 0 : $key; + // If it's an array, add to it of course + if (isset($node->data[$key])) { + if (is_array($node->data[$key])) { + $node->data[$key] = $this->_array_kmerge($node->data[$key], $children); + } else { + $node->data[$key] = $children; + } + } else { + $node->data[$key] = $children; + } + } else { + // Same as above, find the children of this node + $children = $this->_gatherChildren($node->id); + $node->data = array(); + $node->data[] = $children; + } + } else { + // The node is a single string. See if we need to unserialize it. + if (is_array($node->data)) { + $key = key($node->data); + $key = empty($key) ? 0 : $key; + + if (!isset($node->data[$key]) || is_array($node->data[$key]) || is_object($node->data[$key])) { + return true; + } + + self::_unserialize($node->data[$key]); + } elseif (is_string($node->data)) { + self::_unserialize($node->data); + } + } + + // We edited $node by reference, so just return true + return true; + } + + /** + * Traverses node-space and copies references to / from this object. + * + * @param Horde_Yaml_Node $z A node whose references we wish to make real + * @return bool + */ + protected function _makeReferences(&$z) + { + // It is a reference + if (isset($z->ref)) { + $key = key($z->data); + // Copy the data to this object for easy retrieval later + $this->ref[$z->ref] =& $z->data[$key]; + // It has a reference + } elseif (isset($z->refKey)) { + if (isset($this->ref[$z->refKey])) { + $key = key($z->data); + // Copy the data from this object to make the node a real reference + $z->data[$key] =& $this->ref[$z->refKey]; + } + } + + return true; + } + + /** + * Merges two arrays, maintaining numeric keys. If two numeric + * keys clash, the second one will be appended to the resulting + * array. If string keys clash, the last one wins. + * + * @param array $arr1 + * @param array $arr2 + * @return array + */ + protected function _array_kmerge($arr1, $arr2) + { + while (list($key, $val) = each($arr2)) { + if (isset($arr1[$key]) && is_int($key)) { + $arr1[] = $val; + } else { + $arr1[$key] = $val; + } + } + + return $arr1; + } + +} diff --git a/framework/Yaml/lib/Horde/Yaml/Node.php b/framework/Yaml/lib/Horde/Yaml/Node.php new file mode 100644 index 000000000..8cbaee6d2 --- /dev/null +++ b/framework/Yaml/lib/Horde/Yaml/Node.php @@ -0,0 +1,58 @@ + + * @author Chuck Hagenbuch + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Yaml + */ + +/** + * A node, used for parsing YAML. + * + * @category Horde + * @package Horde_Yaml + */ +class Horde_Yaml_Node +{ + /** + * @var string + */ + public $parent; + + /** + */ + public $id; + + /** + * @var mixed + */ + public $data; + + /** + * @var integer + */ + public $indent; + + /** + * @var bool + */ + public $children = false; + + /** + * The constructor assigns the node a unique ID. + * @return void + */ + public function __construct($nodeId) + { + $this->id = $nodeId; + } + +} diff --git a/framework/Yaml/package.xml b/framework/Yaml/package.xml new file mode 100644 index 000000000..123fddf81 --- /dev/null +++ b/framework/Yaml/package.xml @@ -0,0 +1,74 @@ + + + Yaml + pear.horde.org + Horde YAML parsing and dumping routines + This package provides classes for parsing YAML files + into PHP arrays, and dumping PHP arrays into YAML encoding. + + + Chuck Hagenbuch + chuck + chuck@horde.org + yes + + + Mike Naberezny + mnaberez + mike@maintainable.com + yes + + 2008-01-06 + + 1.0.1 + 1.0.0 + + + stable + stable + + BSD + + * Fix extra spaces at the end of lines that have been unfolded. + * Support dumping and loading PHP classes that implement Serializable + * Support loading serialized ArrayObject and other !php constructs + supported by pecl/syck + + + + + + + + + + + + + + + + + + + + 5.1.0 + + + 1.4.0b1 + + + + + + + + + + + + + diff --git a/framework/Yaml/test/CVS/Entries b/framework/Yaml/test/CVS/Entries new file mode 100644 index 000000000..ae2779e30 --- /dev/null +++ b/framework/Yaml/test/CVS/Entries @@ -0,0 +1 @@ +D/Horde//// diff --git a/framework/Yaml/test/CVS/Repository b/framework/Yaml/test/CVS/Repository new file mode 100644 index 000000000..a25c8e8f9 --- /dev/null +++ b/framework/Yaml/test/CVS/Repository @@ -0,0 +1 @@ +framework/Yaml/test diff --git a/framework/Yaml/test/CVS/Root b/framework/Yaml/test/CVS/Root new file mode 100644 index 000000000..5d636129a --- /dev/null +++ b/framework/Yaml/test/CVS/Root @@ -0,0 +1 @@ +chuck@cvs.horde.org:/repository diff --git a/framework/Yaml/test/CVS/Template b/framework/Yaml/test/CVS/Template new file mode 100644 index 000000000..3971591f9 --- /dev/null +++ b/framework/Yaml/test/CVS/Template @@ -0,0 +1,8 @@ + +Bug: +Submitted by: +Merge after: +CVS: ---------------------------------------------------------------------- +CVS: Bug: Fill this in if a listed bug is affected by the change. +CVS: Submitted by: Fill this in if someone else sent in the change. +CVS: Merge after: N [day[s]|week[s]|month[s]] (days assumed by default) diff --git a/framework/Yaml/test/Horde/CVS/Entries b/framework/Yaml/test/Horde/CVS/Entries new file mode 100644 index 000000000..e1642cf06 --- /dev/null +++ b/framework/Yaml/test/Horde/CVS/Entries @@ -0,0 +1 @@ +D/Yaml//// diff --git a/framework/Yaml/test/Horde/CVS/Repository b/framework/Yaml/test/Horde/CVS/Repository new file mode 100644 index 000000000..25f134107 --- /dev/null +++ b/framework/Yaml/test/Horde/CVS/Repository @@ -0,0 +1 @@ +framework/Yaml/test/Horde diff --git a/framework/Yaml/test/Horde/CVS/Root b/framework/Yaml/test/Horde/CVS/Root new file mode 100644 index 000000000..5d636129a --- /dev/null +++ b/framework/Yaml/test/Horde/CVS/Root @@ -0,0 +1 @@ +chuck@cvs.horde.org:/repository diff --git a/framework/Yaml/test/Horde/CVS/Template b/framework/Yaml/test/Horde/CVS/Template new file mode 100644 index 000000000..3971591f9 --- /dev/null +++ b/framework/Yaml/test/Horde/CVS/Template @@ -0,0 +1,8 @@ + +Bug: +Submitted by: +Merge after: +CVS: ---------------------------------------------------------------------- +CVS: Bug: Fill this in if a listed bug is affected by the change. +CVS: Submitted by: Fill this in if someone else sent in the change. +CVS: Merge after: N [day[s]|week[s]|month[s]] (days assumed by default) diff --git a/framework/Yaml/test/Horde/Yaml/AllTests.php b/framework/Yaml/test/Horde/Yaml/AllTests.php new file mode 100644 index 000000000..2f2924a6f --- /dev/null +++ b/framework/Yaml/test/Horde/Yaml/AllTests.php @@ -0,0 +1,68 @@ + + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Yaml + * @subpackage UnitTests + */ + +if (!defined('PHPUnit_MAIN_METHOD')) { + define('PHPUnit_MAIN_METHOD', 'Horde_Yaml_AllTests::main'); +} + +require_once 'PHPUnit/Framework/TestSuite.php'; +require_once 'PHPUnit/TextUI/TestRunner.php'; + +/** + * @category Horde + * @package Horde_Yaml + * @subpackage UnitTests + */ +class Horde_Yaml_AllTests { + + public static function main() + { + PHPUnit_TextUI_TestRunner::run(self::suite()); + } + + public static function suite() + { + // Catch strict standards + error_reporting(E_ALL | E_STRICT); + + // Set up autoload + set_include_path(dirname(dirname(dirname(dirname(__FILE__)))) . DIRECTORY_SEPARATOR . 'lib' . PATH_SEPARATOR . get_include_path()); + if (!spl_autoload_functions()) { + spl_autoload_register(create_function('$class', '$filename = str_replace(array(\'::\', \'_\'), \'/\', $class); @include_once "$filename.php";')); + } + + // Test helpers + require_once dirname(__FILE__) . '/Helpers.php'; + + $suite = new PHPUnit_Framework_TestSuite('Horde Framework - Horde_Yaml'); + + $basedir = dirname(__FILE__); + $baseregexp = preg_quote($basedir . DIRECTORY_SEPARATOR, '/'); + + foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($basedir)) as $file) { + if ($file->isFile() && preg_match('/Test.php$/', $file->getFilename())) { + $pathname = $file->getPathname(); + require $pathname; + + $class = str_replace(DIRECTORY_SEPARATOR, '_', + preg_replace("/^$baseregexp(.*)\.php/", '\\1', $pathname)); + $suite->addTestSuite('Horde_Yaml_' . $class); + } + } + + return $suite; + } + +} + +if (PHPUnit_MAIN_METHOD == 'Horde_Yaml_AllTests::main') { + Horde_Yaml_AllTests::main(); +} diff --git a/framework/Yaml/test/Horde/Yaml/CVS/Entries b/framework/Yaml/test/Horde/Yaml/CVS/Entries new file mode 100644 index 000000000..4da40b782 --- /dev/null +++ b/framework/Yaml/test/Horde/Yaml/CVS/Entries @@ -0,0 +1,6 @@ +/AllTests.php/1.2/Wed Jun 18 19:05:26 2008// +/DumperTest.php/1.2/Wed Jun 18 19:05:26 2008// +/Helpers.php/1.2/Wed Jun 18 19:05:26 2008// +/LoaderTest.php/1.2/Wed Jun 18 19:05:26 2008// +/NodeTest.php/1.2/Wed Jun 18 19:05:26 2008// +D/fixtures//// diff --git a/framework/Yaml/test/Horde/Yaml/CVS/Repository b/framework/Yaml/test/Horde/Yaml/CVS/Repository new file mode 100644 index 000000000..12a48492e --- /dev/null +++ b/framework/Yaml/test/Horde/Yaml/CVS/Repository @@ -0,0 +1 @@ +framework/Yaml/test/Horde/Yaml diff --git a/framework/Yaml/test/Horde/Yaml/CVS/Root b/framework/Yaml/test/Horde/Yaml/CVS/Root new file mode 100644 index 000000000..5d636129a --- /dev/null +++ b/framework/Yaml/test/Horde/Yaml/CVS/Root @@ -0,0 +1 @@ +chuck@cvs.horde.org:/repository diff --git a/framework/Yaml/test/Horde/Yaml/CVS/Template b/framework/Yaml/test/Horde/Yaml/CVS/Template new file mode 100644 index 000000000..3971591f9 --- /dev/null +++ b/framework/Yaml/test/Horde/Yaml/CVS/Template @@ -0,0 +1,8 @@ + +Bug: +Submitted by: +Merge after: +CVS: ---------------------------------------------------------------------- +CVS: Bug: Fill this in if a listed bug is affected by the change. +CVS: Submitted by: Fill this in if someone else sent in the change. +CVS: Merge after: N [day[s]|week[s]|month[s]] (days assumed by default) diff --git a/framework/Yaml/test/Horde/Yaml/DumperTest.php b/framework/Yaml/test/Horde/Yaml/DumperTest.php new file mode 100644 index 000000000..e7ed0eb94 --- /dev/null +++ b/framework/Yaml/test/Horde/Yaml/DumperTest.php @@ -0,0 +1,191 @@ + + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Yaml + * @subpackage UnitTests + */ + +/** + * @category Horde + * @package Horde_Yaml + * @subpackage UnitTests + */ +class Horde_Yaml_DumperTest extends PHPUnit_Framework_TestCase +{ + public function setUp() + { + $this->dumper = new Horde_Yaml_Dumper(); + } + + // Pass-thru in Horde_Yaml + + public function testPassThruConvenienceFunction() + { + $value = array(); + + // Disable any external dumper function + $oldDumpFunc = Horde_Yaml::$dumpfunc; + Horde_Yaml::$dumpfunc = false; + + $expected = $this->dumper->dump($value); + $actual = Horde_Yaml::dump($value); + + // Restore the dumper function + Horde_Yaml::$dumpfunc = $oldDumpFunc; + + $this->assertEquals($expected, $actual); + } + + // Argument checking + + public function testThrowsWhenOptionsIsNotArray() + { + $options = 42; + try { + $this->dumper->dump(array(), $options); + $this->fail(); + } catch (InvalidArgumentException $e) { + $this->assertRegExp('/options.*array/i', $e->getMessage()); + } + } + + public function testThrowsWhenOptionIndentIsNotAnInteger() + { + $options = array('indent' => 'foo'); + try { + $this->dumper->dump(array(), $options); + $this->fail(); + } catch (InvalidArgumentException $e) { + $this->assertRegExp('/indent.*integer/i', $e->getMessage()); + } + } + + public function testThrowsWhenOptionWordwrapIsNotAnInteger() + { + $options = array('wordwrap' => 'foo'); + try { + $this->dumper->dump(array(), $options); + $this->fail(); + } catch (InvalidArgumentException $e) { + $this->assertRegExp('/wordwrap.*integer/i', $e->getMessage()); + } + } + + // Dumping + + public function testDumpAlwaysStartsNewYamlDocument() + { + $dump = $this->dumper->dump(array()); + $this->assertRegExp("/^---\n/", $dump); + } + + public function testNumericStrings() + { + $value = array('foo' => '73,123'); + $this->assertSame($value, Horde_Yaml::load(Horde_Yaml::dump($value))); + } + + public function testDumpsArrayAsMap() + { + $value = array('foo' => 'bar'); + + $expected = "---\nfoo: bar\n"; + $actual = $this->dumper->dump($value); + $this->assertEquals($expected, $actual); + } + + public function testDumpsTraversableAsMap() + { + $value = new ArrayObject(array('foo' => 'bar')); + + $expected = "---\nfoo: bar\n"; + $actual = $this->dumper->dump($value); + $this->assertEquals($expected, $actual); + } + + public function testMovedArray() + { + return $this->markTestSkipped(); + + $arr = array_flip(range(1, 1000)); + $string = $this->dumper->dump($arr); + $arr2 = Horde_Yaml::load($string); + + $this->assertEquals($arr, $arr2); + } + + public function testMixedArray() + { + $arr = array('test', 'a' => 'test2', 'test3'); + + $string = $this->dumper->dump($arr); + $arr2 = Horde_Yaml::load($string); + + $this->assertEquals($arr2, $arr); + } + + public function testNegativeKeysArray() + { + $this->markTestSkipped(); + + $arr = array(-1 => 'test', -2 => 'test2', 0 => 'test3'); + + $string = $this->dumper->dump($arr); + $arr2 = Horde_Yaml::load($string); + + $this->assertEquals($arr, $arr2); + } + + public function testInfinity() + { + $value = array('foo' => INF); + + $expected = "---\nfoo: .INF\n"; + $actual = $this->dumper->dump($value); + $this->assertEquals($expected, $actual); + } + + public function testNegativeInfinity() + { + $value = array('foo' => -INF); + + $expected = "---\nfoo: -.INF\n"; + $actual = $this->dumper->dump($value); + $this->assertEquals($expected, $actual); + } + + public function testNan() + { + $value = array('foo' => NAN); + + $expected = "---\nfoo: .NAN\n"; + $actual = $this->dumper->dump($value); + $this->assertEquals($expected, $actual); + } + + public function testSerializable() + { + $value = array('obj' => new Horde_Yaml_Test_Serializable('s')); + + $expected = "---\nobj: >\n !php/object::Horde_Yaml_Test_Serializable\n s\n"; + $actual = $this->dumper->dump($value); + $this->assertEquals($expected, $actual); + } + + public function testDumpSequence() + { + $value = array('foo', 'bar'); + + $expected = "---\n" + . "- foo\n" + . "- bar\n"; + $actual = $this->dumper->dump($value); + + $this->assertEquals($expected, $actual); + } + +} diff --git a/framework/Yaml/test/Horde/Yaml/Helpers.php b/framework/Yaml/test/Horde/Yaml/Helpers.php new file mode 100644 index 000000000..7499ca858 --- /dev/null +++ b/framework/Yaml/test/Horde/Yaml/Helpers.php @@ -0,0 +1,47 @@ + + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Yaml + * @subpackage UnitTests + */ + +/** + * @category Horde + * @package Horde_Yaml + * @subpackage UnitTests + */ +class Horde_Yaml_Test_Serializable implements Serializable +{ + private $string = null; + + public function __construct($string = null) + { + if (null === $string) + throw new Exception('This is not supposed to be called implicitly'); + + $this->string = $string; + } + + public function serialize() + { + return $this->string; + } + + public function unserialize($serialized) + { + $this->string = $serialized; + } + + public function test() + { + return $this->string; + } + +} + +class Horde_Yaml_Test_NotSerializable +{} diff --git a/framework/Yaml/test/Horde/Yaml/LoaderTest.php b/framework/Yaml/test/Horde/Yaml/LoaderTest.php new file mode 100644 index 000000000..1146ec132 --- /dev/null +++ b/framework/Yaml/test/Horde/Yaml/LoaderTest.php @@ -0,0 +1,810 @@ + + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Yaml + * @subpackage UnitTests + */ + +/** + * @category Horde + * @package Horde_Yaml + * @subpackage UnitTests + */ +class Horde_Yaml_LoaderTest extends PHPUnit_Framework_TestCase +{ + public function setUp() + { + Horde_Yaml::$loadfunc = 'nonexistant_callback'; + } + + // Loading: load() + + public function testLoad() + { + $expected = array('foo' => 'bar'); + $actual = Horde_Yaml::load('foo: bar'); + + $this->assertEquals($expected, $actual); + } + + public function testLoadUsesCallbackForParsingIfAvailable() + { + Horde_Yaml::$loadfunc = 'Horde_Yaml_LoaderTest_MockLoader::returnArray'; + + $yaml = 'foo'; + $expected = Horde_Yaml_LoaderTest_MockLoader::returnArray($yaml); + $actual = Horde_Yaml::load($yaml); + + $this->assertEquals($expected, $actual); + } + + public function testLoadThrowsWhenInputStringIsNotString() + { + $notString = 42; + try { + Horde_Yaml::load($notString); + $this->fail(); + } catch (InvalidArgumentException $e) { + $this->assertRegExp('/must be a string/i', $e->getMessage()); + } + } + + public function testLoadThrowsWhenInputStringIsEmpty() + { + $emptyString = ''; + try { + Horde_Yaml::load($emptyString); + $this->fail(); + } catch (InvalidArgumentException $e) { + $this->assertRegExp('/cannot be empty/i', $e->getMessage()); + } + } + + public function testLoadReturnsEmptyArrayWhenStringCannotBeParsedAsYaml() + { + $notYaml = 'notyaml'; + $this->assertEquals(array(), Horde_Yaml::load($notYaml)); + } + + // Loading: loadFile() + + public function testLoadFile() + { + $parsed = Horde_Yaml::loadFile($this->fixture('basic')); + $this->assertEquals('bar', $parsed['foo']); + } + + public function testLoadFileThrowsWhenFilenameIsNotString() + { + $notString = 42; + try { + Horde_Yaml::loadFile($notString); + $this->fail(); + } catch (InvalidArgumentException $e) { + $this->assertRegExp('/must be a string/i', $e->getMessage()); + } + } + + public function testLoadFileThrowsWhenFilenameIsEmptyString() + { + $emptyString = ''; + try { + Horde_Yaml::loadFile($emptyString); + $this->fail(); + } catch (InvalidArgumentException $e) { + $this->assertRegExp('/cannot be empty/i', $e->getMessage()); + } + } + + public function testLoadFileThrowsWhenFilenameCannotBeOpened() + { + $nonexistant = '/path/to/a/nonexistant/filename'; + try { + Horde_Yaml::loadFile($nonexistant); + $this->fail(); + } catch (Horde_Yaml_Exception $e) { + $this->assertRegExp('/failed to open/i', $e->getMessage()); + } + } + + // Loading: loadStream() + + public function testLoadStream() + { + $fp = fopen($this->fixture('basic'), 'rb'); + $parsed = Horde_Yaml::loadStream($fp); + $this->assertEquals('bar', $parsed['foo']); + } + + public function testLoadStreamThrowsWhenStreamIsNotResource() + { + $notResource = 42; + try { + Horde_Yaml::loadStream($notResource); + } catch (InvalidArgumentException $e) { + $this->assertRegExp('/stream resource/i', $e->getMessage()); + } + } + + public function testLoadStreamThrowsWhenStreamIsResourceButNotStream() + { + $resourceButNotStream = xml_parser_create(); + $this->assertType('resource', $resourceButNotStream); + + try { + Horde_Yaml::loadStream($resourceButNotStream); + } catch (InvalidArgumentException $e) { + $this->assertRegExp('/stream resource/i', $e->getMessage()); + } + } + + public function testLoadStreamUsesCallbackForParsingIfAvailable() + { + Horde_Yaml::$loadfunc = 'Horde_Yaml_LoaderTest_MockLoader::returnArray'; + + $stream = fopen('php://memory', 'r'); + $expected = Horde_Yaml_LoaderTest_MockLoader::returnArray($stream); + $actual = Horde_Yaml::loadStream($stream); + + $this->assertEquals($expected, $actual); + } + + // Parsing: Mappings + + public function testMappingStringValue() + { + $yaml = "String: Anyone's name, really."; + $parsed = Horde_Yaml::load($yaml); + $this->assertEquals("Anyone's name, really.", $parsed['String']); + } + + public function testMappingIntegerValue() + { + $yaml = 'Int: 13'; + $parsed = Horde_Yaml::load($yaml); + $this->assertEquals(13, $parsed['Int']); + } + + public function testMappingIntegerZeroValue() + { + $yaml = 'Zero: 0'; + $parsed = Horde_Yaml::load($yaml); + $this->assertSame(0, $parsed['Zero']); + } + + public function testMappingFloatValue() + { + $yaml = 'Float: 5.34'; + $parsed = Horde_Yaml::load($yaml); + $this->assertEquals(5.34, $parsed['Float']); + } + + public function testMappingBooleanTrue() + { + $trues = array('TRUE', 'True', 'true', 'On', 'on', '+', 'YES', 'Yes', 'yes'); + foreach ($trues as $true) { + $yaml = "True: $true"; + $parsed = Horde_Yaml::load($yaml); + $this->assertTrue($parsed['True'], $true); + } + } + + public function testMappingBooleanFalse() + { + $falses = array('FALSE', 'False', 'false', 'Off', 'off', '-', 'NO', 'No', 'no'); + foreach ($falses as $false) { + $yaml = "False: $false"; + $parsed = Horde_Yaml::load($yaml); + $this->assertFalse($parsed['False'], $false); + } + } + + public function testMappingNullValue() + { + $nulls = array('NULL', 'Null', 'null', '', '~'); + foreach ($nulls as $null) { + $yaml = "Null: $null"; + $parsed = Horde_Yaml::load($yaml); + $this->assertNull($parsed['Null'], $null); + } + } + + public function testMappedValueWithFoldedBlock() + { + $parsed = Horde_Yaml::loadFile($this->fixture('basic')); + + $expected = "There isn't any time for your tricks!\nDo you understand?\n"; + $actual = $parsed['no time']; + $this->assertEquals($expected, $actual); + } + + public function testMappedValueWithMapping() + { + $yaml = "foo:\n" + . " bar: baz"; + $expected = array('foo' => array('bar' => 'baz')); + $actual = Horde_Yaml::load($yaml); + $this->assertEquals($expected, $actual); + } + + // Parsing: Types + + public function testFloatExponential() + { + $this->assertSame(array('e' => 10.0), Horde_Yaml::load('e: 1.0e+1')); + $this->assertSame(array('e' => 0.1), Horde_Yaml::load('e: 1.0e-1')); + } + + public function testInfinity() + { + $this->assertSame(array('i' => INF), Horde_Yaml::load('i: .inf')); + $this->assertSame(array('i' => INF), Horde_Yaml::load('i: .Inf')); + $this->assertSame(array('i' => INF), Horde_Yaml::load('i: .INF')); + } + + public function testNegativeInfinity() + { + $this->assertSame(array('i' => -INF), Horde_Yaml::load('i: -.inf')); + $this->assertSame(array('i' => -INF), Horde_Yaml::load('i: -.Inf')); + $this->assertSame(array('i' => -INF), Horde_Yaml::load('i: -.INF')); + } + + public function testNan() + { + // NAN !== NAN, but NAN == NAN + $this->assertEquals(array('n' => NAN), Horde_Yaml::load('n: .nan')); + $this->assertEquals(array('n' => NAN), Horde_Yaml::load('n: .NaN')); + $this->assertEquals(array('n' => NAN), Horde_Yaml::load('n: .NAN')); + } + + public function testArray() + { + $this->assertEquals(array('a' => array()), Horde_Yaml::load('a: []')); + $this->assertEquals(array('a' => array('a', 'b', 'c')), Horde_Yaml::load('a: [a, b, c]')); + $this->assertEquals(array('a' => array()), Horde_Yaml::load('a: !php/array []')); + + // ArrayObject implements ArrayAccess: OK + $this->assertEquals(array('ao' => new ArrayObject()), Horde_Yaml::load('ao: !php/array::ArrayObject []')); + $this->assertEquals(array('ao' => new ArrayObject(array(1, 2, 3))), Horde_Yaml::load('ao: !php/array::ArrayObject [1, 2, 3]')); + + // Horde_Yaml_Test_NotSerializable doesn't implement ArrayAccess: FAILURE + Horde_Yaml::$allowedClasses[] = 'Horde_Yaml_Test_NotSerializable'; + try { + Horde_Yaml::load('array: !php/array::Horde_Yaml_Test_NotSerializable []'); + $this->fail(); + } catch (Horde_Yaml_Exception $e) { + $this->assertEquals('Horde_Yaml_Test_NotSerializable does not implement ArrayAccess', $e->getMessage()); + } + + // Horde_Yaml_Test_OtherClass doesn't exist: FAILURE + Horde_Yaml::$allowedClasses[] = 'Horde_Yaml_Test_OtherClass'; + try { + Horde_Yaml::load('array: !php/array::Horde_Yaml_Test_OtherClass []'); + $this->fail(); + } catch (Horde_Yaml_Exception $e) { + $this->assertEquals('Horde_Yaml_Test_OtherClass is not defined', $e->getMessage()); + } + + // Horde_Yaml_Test_Disallowed is not whitelisted + try { + Horde_Yaml::load('array: !php/array::Horde_Yaml_Test_Disallowed []'); + $this->fail(); + } catch (Horde_Yaml_Exception $e) { + $this->assertEquals('Horde_Yaml_Test_Disallowed is not in the list of allowed classes', $e->getMessage()); + } + } + + public function testHash() + { + $this->assertEquals(array('a' => array()), Horde_Yaml::load('a: {}')); + $this->assertEquals(array('a' => array('a', 'b', 'c')), Horde_Yaml::load('a: {0: a, 1: b, 2: c}')); + + // ArrayObject implements ArrayAccess: OK + $this->assertEquals(array('ao' => new ArrayObject()), Horde_Yaml::load('ao: !php/hash::ArrayObject {}')); + $this->assertEquals( + array('ao' => new ArrayObject(array('a' => 1, 'b' => 2, 3 => 3, 4 => 'd', 'e' => 5))), + Horde_Yaml::load('ao: !php/hash::ArrayObject {a: 1, b: 2, 3: 3, 4: d, e: 5}') + ); + + // Horde_Yaml_Test_NotSerializable doesn't implement ArrayAccess: FAILURE + Horde_Yaml::$allowedClasses[] = 'Horde_Yaml_Test_NotSerializable'; + try { + Horde_Yaml::load('hash: !php/hash::Horde_Yaml_Test_NotSerializable {}'); + $this->fail(); + } catch (Horde_Yaml_Exception $e) { + $this->assertEquals('Horde_Yaml_Test_NotSerializable does not implement ArrayAccess', $e->getMessage()); + } + + // Horde_Yaml_Test_OtherClass doesn't exist: FAILURE + Horde_Yaml::$allowedClasses[] = 'Horde_Yaml_Test_OtherClass'; + try { + Horde_Yaml::load('hash: !php/hash::Horde_Yaml_Test_OtherClass {}'); + $this->fail(); + } catch (Horde_Yaml_Exception $e) { + $this->assertEquals('Horde_Yaml_Test_OtherClass is not defined', $e->getMessage()); + } + + // Horde_Yaml_Test_Disallowed is not whitelisted + try { + Horde_Yaml::load('hash: !php/hash::Horde_Yaml_Test_Disallowed []'); + $this->fail(); + } catch (Horde_Yaml_Exception $e) { + $this->assertEquals('Horde_Yaml_Test_Disallowed is not in the list of allowed classes', $e->getMessage()); + } + } + + public function testSerializable() + { + Horde_Yaml::$allowedClasses[] = 'Horde_Yaml_Test_Serializable'; + $result = Horde_Yaml::load('obj: > + !php/object::Horde_Yaml_Test_Serializable + string'); + + $this->assertType('Horde_Yaml_Test_Serializable', $result['obj']); + $this->assertSame('string', $result['obj']->test()); + + // Horde_Yaml_Test_NotSerializable doesn't implement Serializable: FAILURE + Horde_Yaml::$allowedClasses[] = 'Horde_Yaml_Test_NotSerializable'; + try { + Horde_Yaml::load('o: !php/object::Horde_Yaml_Test_NotSerializable string'); + $this->fail(); + } catch (Horde_Yaml_Exception $e) { + $this->assertEquals('Horde_Yaml_Test_NotSerializable does not implement Serializable', $e->getMessage()); + } + + // Horde_Yaml_Test_Disallowed is not whitelisted + try { + Horde_Yaml::load('o: !php/object::Horde_Yaml_Test_Disallowed string'); + $this->fail(); + } catch (Horde_Yaml_Exception $e) { + $this->assertEquals('Horde_Yaml_Test_Disallowed is not in the list of allowed classes', $e->getMessage()); + } + } + + // Parsing: Sequences + + public function testSequenceBasic() + { + $yaml = "- PHP Class\n" + . "- Basic YAML Loader\n" + . "- Very Basic YAML Dumper"; + $parsed = Horde_Yaml::load($yaml); + + $this->assertEquals("PHP Class", $parsed[0]); + $this->assertEquals("Basic YAML Loader", $parsed[1]); + $this->assertEquals("Very Basic YAML Dumper", $parsed[2]); + } + + public function testSequenceOfSequence() + { + $yaml = "-\n" + . " - YAML is so easy to learn.\n" + . " - Your config files will never be the same."; + $parsed = Horde_Yaml::load($yaml); + + $expected = array("YAML is so easy to learn.", + "Your config files will never be the same."); + $actual = $parsed[0]; + $this->assertEquals($expected, $actual); + } + + public function testSequenceofMappings() + { + $yaml = "-\n" + . " cpu: 1.5ghz\n" + . " ram: 1 gig\n" + . " os : os x 10.4.1"; + $parsed = Horde_Yaml::load($yaml); + + $expected = array("cpu" => "1.5ghz", "ram" => "1 gig", "os" => "os x 10.4.1"); + $actual = $parsed[0]; + $this->assertEquals($expected, $actual, 'Sequence of mappings'); + } + + public function testMappedSequence() + { + $yaml = "domains:\n" + . " - yaml.org\n" + . " - php.net\n"; + $parsed = Horde_Yaml::load($yaml); + + $expected = array("yaml.org", "php.net"); + $actual = $parsed['domains']; + $this->assertEquals($expected, $actual); + } + + public function testSequenceWithMappedValuesStartingWithCaps() + { + $yaml = "- program: Adium\n" + . " platform: OS X\n" + . " type: Chat Client\n"; + $parsed = Horde_Yaml::load($yaml); + + $expected = array("program" => "Adium", "platform" => "OS X", "type" => "Chat Client"); + $actual = $parsed[0]; + $this->assertEquals($expected, $actual); + } + + // Parsing: References + + public function testReferencesAssignment1() + { + $parsed = Horde_Yaml::loadFile($this->fixture('references')); + + $expected = array('Perl', 'Python', 'PHP', 'Ruby'); + $actual = $parsed['dynamic languages']; + $this->assertEquals($expected, $actual); + } + + public function testReferencesAssignment2() + { + $parsed = Horde_Yaml::loadFile($this->fixture('references')); + + $expected = array('C/C++', 'Java'); + $actual = $parsed['compiled languages']; + $this->assertEquals($expected, $actual); + } + + public function testReferenceUsage() + { + $parsed = Horde_Yaml::loadFile($this->fixture('references')); + + $assignment1 = array('Perl', 'Python', 'PHP', 'Ruby'); + $assignment2 = array('C/C++', 'Java'); + + $expected = array($assignment1, $assignment2); + $actual = $parsed['all languages']; + + $this->assertEquals($expected, $actual); + } + + // Parsing: Inlines + + public function testInlinedSequence() + { + $yaml = '- [One, Two, Three, Four]'; + $parsed = Horde_Yaml::load($yaml); + + $expected = array("One", "Two", "Three", "Four"); + $actual = $parsed[0]; + $this->assertEquals($expected, $actual); + } + + public function testInlineSequenceWithQuotes() + { + $yaml = "- ['complex: string', 'another [string]']"; + $parsed = Horde_Yaml::load($yaml); + + $expected = array('complex: string', 'another [string]'); + $actual = $parsed[0]; + $this->assertEquals($expected, $actual); + } + + public function testInlineSequenceOneDeep() + { + $yaml = '- [One, [Two, And, Three], Four, Five]'; + $parsed = Horde_Yaml::load($yaml); + + $expected = array("One", array("Two", "And", "Three"), "Four", "Five"); + $actual = $parsed[0]; + $this->assertEquals($expected, $actual); + } + + public function testInlineSequenceOneDeepWithQuotes() + { + $yaml = '- [a, [\'1\', "2"], b]'; + $parsed = Horde_Yaml::load($yaml); + + $expected = array('a', array('1', '2'), 'b'); + $actual = $parsed[0]; + $this->assertEquals($expected, $actual); + } + + public function testInlineSequenceTwoDeep() + { + $yaml = '- [This, [Is, Getting, [Ridiculous, Guys]], Seriously, [Show, Mercy]]'; + $parsed = Horde_Yaml::load($yaml); + + $expected = array("This", array("Is", "Getting", array("Ridiculous", "Guys")), + "Seriously", array("Show", "Mercy")); + $actual = $parsed[0]; + $this->assertEquals($expected, $actual); + } + + public function testInlineSequenceWhenEmpty() + { + $yaml = '- []'; + $parsed = Horde_Yaml::load($yaml); + + $expected = array(); + $actual = $parsed[0]; + $this->assertEquals($actual, $expected); + } + + public function testInlineSequenceWhenEmptyWithWhitespace() + { + $yaml = "- [ \t]"; + $parsed = Horde_Yaml::load($yaml); + + $expected = array(); + $actual = $parsed[0]; + $this->assertEquals($actual, $expected); + } + + public function testInlineMapping() + { + $yaml = '- {name: chris, age: young, brand: lucky strike}'; + $parsed = Horde_Yaml::load($yaml); + + $expected = array("name" => "chris", "age" => "young", "brand" => "lucky strike"); + $actual = $parsed[0]; + $this->assertEquals($expected, $actual); + } + + public function testInlineMappingWhenEmpty() + { + $yaml = '- {}'; + $parsed = Horde_Yaml::load($yaml); + + $expected = array(); + $actual = $parsed[0]; + $this->assertEquals($expected, $actual); + } + + public function testInlineMappingWhenEmptyWithWhitespace() + { + $yaml = "- { \t}"; + $parsed = Horde_Yaml::load($yaml); + + $expected = array(); + $actual = $parsed[0]; + $this->assertEquals($expected, $actual); + } + + public function testInlineMappingWithQuotesInlinedInSequence() + { + $yaml = '- {name: "Foo, Bar\'s", age: 20}'; + $parsed = Horde_Yaml::load($yaml); + + $expected = array('name' => "Foo, Bar's", 'age' => 20); + $actual = $parsed[0]; + + $this->assertEquals($expected, $actual); + } + + + public function testInlineMappingWithQuotesInlinedInMapping() + { + $yaml = 'outer: { inner1: "foo bar", inner2: \'baz qux\' }'; + + $expected = array('outer' => array('inner1' => "foo bar", 'inner2' => "baz qux")); + $actual = Horde_Yaml::load($yaml); + $this->assertEquals($expected, $actual); + } + + public function testInlineMappingOneDeep() + { + $yaml = "- {name: mark, age: older than chris, brand: [marlboro, lucky strike]}"; + $parsed = Horde_Yaml::load($yaml); + + $expected = array("name" => "mark", "age" => "older than chris", + "brand" => array("marlboro", "lucky strike")); + $actual = $parsed[0]; + $this->assertEquals($expected, $actual); + } + + // Parsing: Quotes + + public function testQuotesCanBeEscaped() + { + $yaml = "- one'apostrophe on line\n" + . "- two'apostrophes' on line\n" + . "- one\"quote on line\n" + . "- two\"quotes\" on line\n"; + $parsed = Horde_Yaml::load($yaml); + + $this->assertEquals("one'apostrophe on line", + $parsed[0]); + + $this->assertEquals("two'apostrophes' on line", + $parsed[1]); + + $this->assertEquals('one"quote on line', + $parsed[2]); + + $this->assertEquals('two"quotes" on line', + $parsed[3]); + } + + public function testQuotesCanBeUsedForComplexKeys() + { + $yaml = '"if: you\'d": like'; + $parsed = Horde_Yaml::load($yaml); + + $this->assertEquals("like", $parsed["if: you'd"]); + } + + public function testQuotesCanBeEmptyWhenQuotes() + { + $yaml = 'empty: ""'; + $parsed = Horde_Yaml::load($yaml); + + $this->assertSame('', $parsed['empty']); + } + + public function testQuotesCanBeEmptyWhenApostrophes() + { + $yaml = "empty: ''"; + $parsed = Horde_Yaml::load($yaml); + + $this->assertSame('', $parsed['empty']); + } + + public function testWhitespaceBetweenQuotesIsPreserved() + { + $yaml = 'empty: " "'; + $parsed = Horde_Yaml::load($yaml); + + $this->assertSame(' ', $parsed['empty']); + } + + public function testWhitespaceBetweenApostrophesIsPreserved() + { + $yaml = "empty: ' '"; + $parsed = Horde_Yaml::load($yaml); + + $this->assertSame(' ', $parsed['empty']); + } + + // Parsing: Keys + + public function testKeyAsNumeric() + { + // Added in Spyc .2 + $yaml = '1040: Ooo, a numeric key! # And working comments? Wow!'; + $parsed = Horde_Yaml::load($yaml); + + $this->assertEquals("Ooo, a numeric key!", $parsed[1040]); + } + + // Tab Detection + + public function testThrowsAnExceptionWhenFirstCharacterOfLineIsTab() + { + try { + Horde_Yaml::load("\tfoo: bar"); + $this->fail(); + } catch (Horde_Yaml_Exception $e) { + $this->assertRegExp('/indent contains a tab/i', $e->getMessage()); + } + } + + public function testThrowsExceptionWhenLineIndentContainsTab() + { + try { + Horde_Yaml::load(" \tfoo: bar"); + $this->fail(); + } catch (Horde_Yaml_Exception $e) { + $this->assertRegExp('/indent contains a tab/i', $e->getMessage()); + } + } + + public function testDoesNotThrowOnAnEmptyLineWithTabsOrSpaces() + { + Horde_Yaml::load(" "); + Horde_Yaml::load("\t"); + Horde_Yaml::load(" \t"); + Horde_Yaml::load("\t "); + } + + // Comments + + public function testCommentOnEmptyLine() + { + $yaml = "# foo\nbar: baz"; + $expected = array('bar' => 'baz'); + $actual = Horde_Yaml::load($yaml); + $this->assertEquals($expected, $actual); + } + + public function testCommentAtEndOfLine() + { + $yaml = 'foo: bar # baz'; + $parsed = Horde_Yaml::load($yaml); + + $expected = 'bar'; + $actual = $parsed['foo']; + $this->assertEquals($expected, $actual); + } + + public function testDecoyCommentEmbeddedInQuotes() + { + $yaml = 'foo: "bar # baz"'; + $parsed = Horde_Yaml::load($yaml); + + $expected = 'bar # baz'; + $actual = $parsed['foo']; + $this->assertEquals($expected, $actual); + } + + public function testDecoyCommentEmbeddedInQuotesAndEndOfLineComment() + { + $yaml = 'foo: "bar # baz" # qux'; + $parsed = Horde_Yaml::load($yaml); + + $expected = 'bar # baz'; + $actual = $parsed['foo']; + $this->assertEquals($expected, $actual); + } + + public function testDecoyCommentEmbeddedInApostrophes() + { + $yaml = "foo: 'bar # baz'"; + $parsed = Horde_Yaml::load($yaml); + + $expected = 'bar # baz'; + $actual = $parsed['foo']; + $this->assertEquals($expected, $actual); + } + + public function testDecoyCommentEmbeddedInApostrophesAndEndOfLineVersion() + { + $yaml = "foo: 'bar # baz' # qux"; + $parsed = Horde_Yaml::load($yaml); + + $expected = 'bar # baz'; + $actual = $parsed['foo']; + $this->assertEquals($expected, $actual); + } + + // Misc + + public function testComplexParse() + { + $yaml = "databases:\n" + . " - name: spartan\n" + . " notes:\n" + . " - Needs to be backed up\n" + . " - Needs to be normalized\n" + . " type: mysql\n"; + + $expected = array('databases' => array(array('name' => 'spartan', + 'notes' => array('Needs to be backed up', + 'Needs to be normalized'), + 'type' => 'mysql'))); + $actual = Horde_Yaml::load($yaml); + $this->assertEquals($expected, $actual); + } + + // Test Helpers + + public function fixture($name) + { + return dirname(__FILE__) . "/fixtures/{$name}.yml"; + } + +} + + +/** + * Used to test Horde_Yaml::$loadfunc callback. + * + * @package Horde_Yaml + * @subpackage UnitTests + */ +class Horde_Yaml_LoaderTest_MockLoader +{ + public static function returnArray($yaml) + { + return array('loaded'); + } + + public static function returnFalse($yaml) + { + return false; + } +} diff --git a/framework/Yaml/test/Horde/Yaml/NodeTest.php b/framework/Yaml/test/Horde/Yaml/NodeTest.php new file mode 100644 index 000000000..75effd3ab --- /dev/null +++ b/framework/Yaml/test/Horde/Yaml/NodeTest.php @@ -0,0 +1,26 @@ + + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Yaml + * @subpackage UnitTests + */ + +/** + * @category Horde + * @package Horde_Yaml + * @subpackage UnitTests + */ +class Horde_Yaml_NodeTest extends PHPUnit_Framework_TestCase +{ + public function testConstructorAssignsId() + { + $id = 'foo'; + $node = new Horde_Yaml_Node($id); + $this->assertEquals($id, $node->id); + } + +} diff --git a/framework/Yaml/test/Horde/Yaml/fixtures/CVS/Entries b/framework/Yaml/test/Horde/Yaml/fixtures/CVS/Entries new file mode 100644 index 000000000..308694dbb --- /dev/null +++ b/framework/Yaml/test/Horde/Yaml/fixtures/CVS/Entries @@ -0,0 +1,3 @@ +/basic.yml/1.1/Wed Mar 5 20:37:57 2008// +/references.yml/1.1/Wed Mar 5 20:37:57 2008// +D diff --git a/framework/Yaml/test/Horde/Yaml/fixtures/CVS/Repository b/framework/Yaml/test/Horde/Yaml/fixtures/CVS/Repository new file mode 100644 index 000000000..3f064d8b0 --- /dev/null +++ b/framework/Yaml/test/Horde/Yaml/fixtures/CVS/Repository @@ -0,0 +1 @@ +framework/Yaml/test/Horde/Yaml/fixtures diff --git a/framework/Yaml/test/Horde/Yaml/fixtures/CVS/Root b/framework/Yaml/test/Horde/Yaml/fixtures/CVS/Root new file mode 100644 index 000000000..5d636129a --- /dev/null +++ b/framework/Yaml/test/Horde/Yaml/fixtures/CVS/Root @@ -0,0 +1 @@ +chuck@cvs.horde.org:/repository diff --git a/framework/Yaml/test/Horde/Yaml/fixtures/CVS/Template b/framework/Yaml/test/Horde/Yaml/fixtures/CVS/Template new file mode 100644 index 000000000..3971591f9 --- /dev/null +++ b/framework/Yaml/test/Horde/Yaml/fixtures/CVS/Template @@ -0,0 +1,8 @@ + +Bug: +Submitted by: +Merge after: +CVS: ---------------------------------------------------------------------- +CVS: Bug: Fill this in if a listed bug is affected by the change. +CVS: Submitted by: Fill this in if someone else sent in the change. +CVS: Merge after: N [day[s]|week[s]|month[s]] (days assumed by default) diff --git a/framework/Yaml/test/Horde/Yaml/fixtures/basic.yml b/framework/Yaml/test/Horde/Yaml/fixtures/basic.yml new file mode 100644 index 000000000..da6a5841e --- /dev/null +++ b/framework/Yaml/test/Horde/Yaml/fixtures/basic.yml @@ -0,0 +1,14 @@ +foo: bar + +# A folded block as a mapped value +no time: > + There isn't any time + for your tricks! + + Do you understand? + +# A literal block as a mapped value +some time: | + There is nothing but time + for your tricks. + diff --git a/framework/Yaml/test/Horde/Yaml/fixtures/references.yml b/framework/Yaml/test/Horde/Yaml/fixtures/references.yml new file mode 100644 index 000000000..831bc4b53 --- /dev/null +++ b/framework/Yaml/test/Horde/Yaml/fixtures/references.yml @@ -0,0 +1,12 @@ +# References -- they're shaky, but functional +dynamic languages: &DLANGS + - Perl + - Python + - PHP + - Ruby +compiled languages: &CLANGS + - C/C++ + - Java +all languages: + - *DLANGS + - *CLANGS -- 2.11.0