Add Horde_Yaml to git
authorChuck Hagenbuch <chuck@horde.org>
Sat, 15 Nov 2008 14:55:28 +0000 (09:55 -0500)
committerChuck Hagenbuch <chuck@horde.org>
Sat, 15 Nov 2008 14:55:28 +0000 (09:55 -0500)
60 files changed:
framework/Yaml/CVS/Entries [new file with mode: 0644]
framework/Yaml/CVS/Repository [new file with mode: 0644]
framework/Yaml/CVS/Root [new file with mode: 0644]
framework/Yaml/CVS/Template [new file with mode: 0644]
framework/Yaml/examples/CVS/Entries [new file with mode: 0644]
framework/Yaml/examples/CVS/Repository [new file with mode: 0644]
framework/Yaml/examples/CVS/Root [new file with mode: 0644]
framework/Yaml/examples/CVS/Template [new file with mode: 0644]
framework/Yaml/examples/Horde/CVS/Entries [new file with mode: 0644]
framework/Yaml/examples/Horde/CVS/Repository [new file with mode: 0644]
framework/Yaml/examples/Horde/CVS/Root [new file with mode: 0644]
framework/Yaml/examples/Horde/CVS/Template [new file with mode: 0644]
framework/Yaml/examples/Horde/Yaml/CVS/Entries [new file with mode: 0644]
framework/Yaml/examples/Horde/Yaml/CVS/Repository [new file with mode: 0644]
framework/Yaml/examples/Horde/Yaml/CVS/Root [new file with mode: 0644]
framework/Yaml/examples/Horde/Yaml/CVS/Template [new file with mode: 0644]
framework/Yaml/examples/Horde/Yaml/dump.php [new file with mode: 0755]
framework/Yaml/examples/Horde/Yaml/example.yaml [new file with mode: 0755]
framework/Yaml/examples/Horde/Yaml/load.php [new file with mode: 0755]
framework/Yaml/lib/CVS/Entries [new file with mode: 0644]
framework/Yaml/lib/CVS/Repository [new file with mode: 0644]
framework/Yaml/lib/CVS/Root [new file with mode: 0644]
framework/Yaml/lib/CVS/Template [new file with mode: 0644]
framework/Yaml/lib/Horde/CVS/Entries [new file with mode: 0644]
framework/Yaml/lib/Horde/CVS/Repository [new file with mode: 0644]
framework/Yaml/lib/Horde/CVS/Root [new file with mode: 0644]
framework/Yaml/lib/Horde/CVS/Template [new file with mode: 0644]
framework/Yaml/lib/Horde/Yaml.php [new file with mode: 0644]
framework/Yaml/lib/Horde/Yaml/CVS/Entries [new file with mode: 0644]
framework/Yaml/lib/Horde/Yaml/CVS/Repository [new file with mode: 0644]
framework/Yaml/lib/Horde/Yaml/CVS/Root [new file with mode: 0644]
framework/Yaml/lib/Horde/Yaml/CVS/Template [new file with mode: 0644]
framework/Yaml/lib/Horde/Yaml/Dumper.php [new file with mode: 0644]
framework/Yaml/lib/Horde/Yaml/Exception.php [new file with mode: 0644]
framework/Yaml/lib/Horde/Yaml/Loader.php [new file with mode: 0644]
framework/Yaml/lib/Horde/Yaml/Node.php [new file with mode: 0644]
framework/Yaml/package.xml [new file with mode: 0644]
framework/Yaml/test/CVS/Entries [new file with mode: 0644]
framework/Yaml/test/CVS/Repository [new file with mode: 0644]
framework/Yaml/test/CVS/Root [new file with mode: 0644]
framework/Yaml/test/CVS/Template [new file with mode: 0644]
framework/Yaml/test/Horde/CVS/Entries [new file with mode: 0644]
framework/Yaml/test/Horde/CVS/Repository [new file with mode: 0644]
framework/Yaml/test/Horde/CVS/Root [new file with mode: 0644]
framework/Yaml/test/Horde/CVS/Template [new file with mode: 0644]
framework/Yaml/test/Horde/Yaml/AllTests.php [new file with mode: 0644]
framework/Yaml/test/Horde/Yaml/CVS/Entries [new file with mode: 0644]
framework/Yaml/test/Horde/Yaml/CVS/Repository [new file with mode: 0644]
framework/Yaml/test/Horde/Yaml/CVS/Root [new file with mode: 0644]
framework/Yaml/test/Horde/Yaml/CVS/Template [new file with mode: 0644]
framework/Yaml/test/Horde/Yaml/DumperTest.php [new file with mode: 0644]
framework/Yaml/test/Horde/Yaml/Helpers.php [new file with mode: 0644]
framework/Yaml/test/Horde/Yaml/LoaderTest.php [new file with mode: 0644]
framework/Yaml/test/Horde/Yaml/NodeTest.php [new file with mode: 0644]
framework/Yaml/test/Horde/Yaml/fixtures/CVS/Entries [new file with mode: 0644]
framework/Yaml/test/Horde/Yaml/fixtures/CVS/Repository [new file with mode: 0644]
framework/Yaml/test/Horde/Yaml/fixtures/CVS/Root [new file with mode: 0644]
framework/Yaml/test/Horde/Yaml/fixtures/CVS/Template [new file with mode: 0644]
framework/Yaml/test/Horde/Yaml/fixtures/basic.yml [new file with mode: 0644]
framework/Yaml/test/Horde/Yaml/fixtures/references.yml [new file with mode: 0644]

diff --git a/framework/Yaml/CVS/Entries b/framework/Yaml/CVS/Entries
new file mode 100644 (file)
index 0000000..ecad70e
--- /dev/null
@@ -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 (file)
index 0000000..2e17730
--- /dev/null
@@ -0,0 +1 @@
+framework/Yaml
diff --git a/framework/Yaml/CVS/Root b/framework/Yaml/CVS/Root
new file mode 100644 (file)
index 0000000..5d63612
--- /dev/null
@@ -0,0 +1 @@
+chuck@cvs.horde.org:/repository
diff --git a/framework/Yaml/CVS/Template b/framework/Yaml/CVS/Template
new file mode 100644 (file)
index 0000000..3971591
--- /dev/null
@@ -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 (file)
index 0000000..ae2779e
--- /dev/null
@@ -0,0 +1 @@
+D/Horde////
diff --git a/framework/Yaml/examples/CVS/Repository b/framework/Yaml/examples/CVS/Repository
new file mode 100644 (file)
index 0000000..1a9d9fe
--- /dev/null
@@ -0,0 +1 @@
+framework/Yaml/examples
diff --git a/framework/Yaml/examples/CVS/Root b/framework/Yaml/examples/CVS/Root
new file mode 100644 (file)
index 0000000..5d63612
--- /dev/null
@@ -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 (file)
index 0000000..3971591
--- /dev/null
@@ -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 (file)
index 0000000..e1642cf
--- /dev/null
@@ -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 (file)
index 0000000..65a7aec
--- /dev/null
@@ -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 (file)
index 0000000..5d63612
--- /dev/null
@@ -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 (file)
index 0000000..3971591
--- /dev/null
@@ -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 (file)
index 0000000..9d35072
--- /dev/null
@@ -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 (file)
index 0000000..3f9671c
--- /dev/null
@@ -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 (file)
index 0000000..5d63612
--- /dev/null
@@ -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 (file)
index 0000000..3971591
--- /dev/null
@@ -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 (executable)
index 0000000..b85d2b2
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+/**
+ * @package Horde_Yaml
+ */
+
+require_once dirname(dirname(dirname(__FILE__))) . '/Yaml/__autoload.php';
+
+$array[] = 'Sequence item';
+$array['The Key'] = 'Mapped value';
+$array[] = array('A sequence','of a sequence');
+$array[] = array('first' => '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 (executable)
index 0000000..add2a4c
--- /dev/null
@@ -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 (executable)
index 0000000..ffd3928
--- /dev/null
@@ -0,0 +1,10 @@
+<?php
+/**
+ * @package Horde_Yaml
+ */
+
+require_once dirname(dirname(dirname(__FILE__))) . '/Yaml/__autoload.php';
+
+$file = dirname(__FILE__) . '/example.yaml';
+echo "$file loaded into PHP:\n";
+var_dump(Horde_Yaml::loadFile($file));
diff --git a/framework/Yaml/lib/CVS/Entries b/framework/Yaml/lib/CVS/Entries
new file mode 100644 (file)
index 0000000..ae2779e
--- /dev/null
@@ -0,0 +1 @@
+D/Horde////
diff --git a/framework/Yaml/lib/CVS/Repository b/framework/Yaml/lib/CVS/Repository
new file mode 100644 (file)
index 0000000..c9a0ce6
--- /dev/null
@@ -0,0 +1 @@
+framework/Yaml/lib
diff --git a/framework/Yaml/lib/CVS/Root b/framework/Yaml/lib/CVS/Root
new file mode 100644 (file)
index 0000000..5d63612
--- /dev/null
@@ -0,0 +1 @@
+chuck@cvs.horde.org:/repository
diff --git a/framework/Yaml/lib/CVS/Template b/framework/Yaml/lib/CVS/Template
new file mode 100644 (file)
index 0000000..3971591
--- /dev/null
@@ -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/CVS/Entries b/framework/Yaml/lib/Horde/CVS/Entries
new file mode 100644 (file)
index 0000000..96f105f
--- /dev/null
@@ -0,0 +1,2 @@
+/Yaml.php/1.5/Wed Jun 18 19:05:25 2008//
+D/Yaml////
diff --git a/framework/Yaml/lib/Horde/CVS/Repository b/framework/Yaml/lib/Horde/CVS/Repository
new file mode 100644 (file)
index 0000000..d899339
--- /dev/null
@@ -0,0 +1 @@
+framework/Yaml/lib/Horde
diff --git a/framework/Yaml/lib/Horde/CVS/Root b/framework/Yaml/lib/Horde/CVS/Root
new file mode 100644 (file)
index 0000000..5d63612
--- /dev/null
@@ -0,0 +1 @@
+chuck@cvs.horde.org:/repository
diff --git a/framework/Yaml/lib/Horde/CVS/Template b/framework/Yaml/lib/Horde/CVS/Template
new file mode 100644 (file)
index 0000000..3971591
--- /dev/null
@@ -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.php b/framework/Yaml/lib/Horde/Yaml.php
new file mode 100644 (file)
index 0000000..e2d7947
--- /dev/null
@@ -0,0 +1,158 @@
+<?php
+/**
+ * Horde YAML package
+ *
+ * This package is heavily inspired by the Spyc PHP YAML
+ * implementation (http://spyc.sourceforge.net/), and portions are
+ * copyright 2005-2006 Chris Wanstrath.
+ *
+ * @author   Chris Wanstrath <chris@ozmm.org>
+ * @author   Chuck Hagenbuch <chuck@horde.org>
+ * @author   Mike Naberezny <mike@maintainable.com>
+ * @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 (file)
index 0000000..ec45e31
--- /dev/null
@@ -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 (file)
index 0000000..14f1e4c
--- /dev/null
@@ -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 (file)
index 0000000..5d63612
--- /dev/null
@@ -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 (file)
index 0000000..3971591
--- /dev/null
@@ -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 (file)
index 0000000..40ff021
--- /dev/null
@@ -0,0 +1,209 @@
+<?php
+/**
+ * Horde YAML package
+ *
+ * This package is heavily inspired by the Spyc PHP YAML
+ * implementation (http://spyc.sourceforge.net/), and portions are
+ * copyright 2005-2006 Chris Wanstrath.
+ *
+ * @author   Chris Wanstrath <chris@ozmm.org>
+ * @author   Chuck Hagenbuch <chuck@horde.org>
+ * @author   Mike Naberezny <mike@maintainable.com>
+ * @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 (file)
index 0000000..b53159b
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+/**
+ * Horde YAML package
+ *
+ * This package is heavily inspired by the Spyc PHP YAML
+ * implementation (http://spyc.sourceforge.net/), and portions are
+ * copyright 2005-2006 Chris Wanstrath.
+ *
+ * @author   Chris Wanstrath <chris@ozmm.org>
+ * @author   Chuck Hagenbuch <chuck@horde.org>
+ * @author   Mike Naberezny <mike@maintainable.com>
+ * @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 (file)
index 0000000..ce056a6
--- /dev/null
@@ -0,0 +1,751 @@
+<?php
+/**
+ * Horde YAML package
+ *
+ * This package is heavily inspired by the Spyc PHP YAML
+ * implementation (http://spyc.sourceforge.net/), and portions are
+ * copyright 2005-2006 Chris Wanstrath.
+ *
+ * @author   Chris Wanstrath <chris@ozmm.org>
+ * @author   Chuck Hagenbuch <chuck@horde.org>
+ * @author   Mike Naberezny <mike@maintainable.com>
+ * @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 (file)
index 0000000..8cbaee6
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Horde YAML package
+ *
+ * This package is heavily inspired by the Spyc PHP YAML
+ * implementation (http://spyc.sourceforge.net/), and portions are
+ * copyright 2005-2006 Chris Wanstrath.
+ *
+ * @author   Chris Wanstrath <chris@ozmm.org>
+ * @author   Chuck Hagenbuch <chuck@horde.org>
+ * @author   Mike Naberezny <mike@maintainable.com>
+ * @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 (file)
index 0000000..123fddf
--- /dev/null
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<package packagerversion="1.4.9" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
+http://pear.php.net/dtd/tasks-1.0.xsd
+http://pear.php.net/dtd/package-2.0
+http://pear.php.net/dtd/package-2.0.xsd">
+ <name>Yaml</name>
+ <channel>pear.horde.org</channel>
+ <summary>Horde YAML parsing and dumping routines</summary>
+ <description>This package provides classes for parsing YAML files
+ into PHP arrays, and dumping PHP arrays into YAML encoding.
+ </description>
+ <lead>
+  <name>Chuck Hagenbuch</name>
+  <user>chuck</user>
+  <email>chuck@horde.org</email>
+  <active>yes</active>
+ </lead>
+ <lead>
+  <name>Mike Naberezny</name>
+  <user>mnaberez</user>
+  <email>mike@maintainable.com</email>
+  <active>yes</active>
+ </lead>
+ <date>2008-01-06</date>
+ <version>
+  <release>1.0.1</release>
+  <api>1.0.0</api>
+ </version>
+ <stability>
+  <release>stable</release>
+  <api>stable</api>
+ </stability>
+ <license uri="http://opensource.org/licenses/bsd-license.php">BSD</license>
+ <notes>
+   * 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
+ </notes>
+ <contents>
+  <dir name="/">
+   <dir name="lib">
+    <dir name="Horde">
+     <dir name="Yaml">
+      <file name="Dumper.php" role="php" />
+      <file name="Exception.php" role="php" />
+      <file name="Loader.php" role="php" />
+      <file name="Node.php" role="php" />
+     </dir> <!-- /lib/Horde/Yaml -->
+     <file name="Yaml.php" role="php" />
+    </dir> <!-- /lib/Horde -->
+   </dir> <!-- /lib -->
+  </dir> <!-- / -->
+ </contents>
+ <dependencies>
+  <required>
+   <php>
+    <min>5.1.0</min>
+   </php>
+   <pearinstaller>
+    <min>1.4.0b1</min>
+   </pearinstaller>
+  </required>
+ </dependencies>
+ <phprelease>
+  <filelist>
+   <install name="lib/Horde/Yaml/Dumper.php" as="Horde/Yaml/Dumper.php" />
+   <install name="lib/Horde/Yaml/Exception.php" as="Horde/Yaml/Exception.php" />
+   <install name="lib/Horde/Yaml/Loader.php" as="Horde/Yaml/Loader.php" />
+   <install name="lib/Horde/Yaml/Node.php" as="Horde/Yaml/Node.php" />
+   <install name="lib/Horde/Yaml.php" as="Horde/Yaml.php" />
+  </filelist>
+ </phprelease>
+</package>
diff --git a/framework/Yaml/test/CVS/Entries b/framework/Yaml/test/CVS/Entries
new file mode 100644 (file)
index 0000000..ae2779e
--- /dev/null
@@ -0,0 +1 @@
+D/Horde////
diff --git a/framework/Yaml/test/CVS/Repository b/framework/Yaml/test/CVS/Repository
new file mode 100644 (file)
index 0000000..a25c8e8
--- /dev/null
@@ -0,0 +1 @@
+framework/Yaml/test
diff --git a/framework/Yaml/test/CVS/Root b/framework/Yaml/test/CVS/Root
new file mode 100644 (file)
index 0000000..5d63612
--- /dev/null
@@ -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 (file)
index 0000000..3971591
--- /dev/null
@@ -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 (file)
index 0000000..e1642cf
--- /dev/null
@@ -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 (file)
index 0000000..25f1341
--- /dev/null
@@ -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 (file)
index 0000000..5d63612
--- /dev/null
@@ -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 (file)
index 0000000..3971591
--- /dev/null
@@ -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 (file)
index 0000000..2f2924a
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+/**
+ * Horde_Yaml test suite
+ *
+ * @author  Mike Naberezny <mike@maintainable.com>
+ * @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 (file)
index 0000000..4da40b7
--- /dev/null
@@ -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 (file)
index 0000000..12a4849
--- /dev/null
@@ -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 (file)
index 0000000..5d63612
--- /dev/null
@@ -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 (file)
index 0000000..3971591
--- /dev/null
@@ -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 (file)
index 0000000..e7ed0eb
--- /dev/null
@@ -0,0 +1,191 @@
+<?php
+/**
+ * Horde_Yaml_Node test
+ *
+ * @author  Mike Naberezny <mike@maintainable.com>
+ * @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 (file)
index 0000000..7499ca8
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Horde_Yaml test helpers
+ *
+ * @author     Mike Naberezny <mike@maintainable.com>
+ * @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 (file)
index 0000000..1146ec1
--- /dev/null
@@ -0,0 +1,810 @@
+<?php
+/**
+ * Horde_Yaml_Loader test
+ *
+ * @author     Mike Naberezny <mike@maintainable.com>
+ * @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 (file)
index 0000000..75effd3
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+/**
+ * Horde_Yaml_Node test
+ *
+ * @author  Mike Naberezny <mike@maintainable.com>
+ * @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 (file)
index 0000000..308694d
--- /dev/null
@@ -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 (file)
index 0000000..3f064d8
--- /dev/null
@@ -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 (file)
index 0000000..5d63612
--- /dev/null
@@ -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 (file)
index 0000000..3971591
--- /dev/null
@@ -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 (file)
index 0000000..da6a584
--- /dev/null
@@ -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 (file)
index 0000000..831bc4b
--- /dev/null
@@ -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