Browse Source

Add JsonManipulator class for doing raw manipulation of json strings

Jordi Boggiano 12 years ago
parent
commit
f5e0918104
2 changed files with 254 additions and 0 deletions
  1. 120 0
      src/Composer/Json/JsonManipulator.php
  2. 134 0
      tests/Composer/Test/Json/JsonManipulatorTest.php

+ 120 - 0
src/Composer/Json/JsonManipulator.php

@@ -0,0 +1,120 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Json;
+
+/**
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class JsonManipulator
+{
+    private $contents;
+    private $newline;
+    private $indent;
+
+    public function __construct($contents)
+    {
+        if (!preg_match('#^\{(.*)\}$#s', trim($contents), $match)) {
+            throw new \InvalidArgumentException('The json file must be an object ({})');
+        }
+        $this->newline = false !== strpos("\r\n", $contents) ? "\r\n": "\n";
+        $this->contents = '{' . $match[1] . '}';
+        $this->detectIndenting();
+    }
+
+    public function getContents()
+    {
+        return $this->contents . $this->newline;
+    }
+
+    public function addLink($type, $package, $constraint)
+    {
+        // no link of that type yet
+        if (!preg_match('#"'.$type.'":\s*\{#', $this->contents)) {
+            $this->addMainKey($type, $this->format(array($package => $constraint)));
+
+            return true;
+        }
+
+        $linksRegex = '#("'.$type.'":\s*\{)([^}]+)(\})#s';
+        if (!preg_match($linksRegex, $this->contents, $match)) {
+            return false;
+        }
+
+        $links = $match[2];
+        $packageRegex = str_replace('/', '\\\\?/', preg_quote($package));
+
+        // link exists already
+        if (preg_match('{"'.$packageRegex.'"\s*:}i', $links)) {
+            $links = preg_replace('{"'.$packageRegex.'"(\s*:\s*)"[^"]+"}i', JsonFile::encode($package).'$1"'.$constraint.'"', $links);
+        } elseif (preg_match('#[^\s](\s*)$#', $links, $match)) {
+            // link missing but non empty links
+            $links = preg_replace(
+                '#'.$match[1].'$#',
+                ',' . $this->newline . $this->indent . $this->indent . JsonFile::encode($package).': '.JsonFile::encode($constraint) . $match[1],
+                $links
+            );
+        } else {
+            // links empty
+            $links = $this->newline . $this->indent . $this->indent . JsonFile::encode($package).': '.JsonFile::encode($constraint) . $links;
+        }
+
+        $this->contents = preg_replace($linksRegex, '$1'.$links.'$3', $this->contents);
+
+        return true;
+    }
+
+    public function addMainKey($key, $content)
+    {
+        if (preg_match('#[^{\s](\s*)\}$#', $this->contents, $match)) {
+            $this->contents = preg_replace(
+                '#'.$match[1].'\}$#',
+                ',' . $this->newline . $this->indent . JsonFile::encode($key). ': '. $content . $this->newline . '}',
+                $this->contents
+            );
+        } else {
+            $this->contents = preg_replace(
+                '#\}$#',
+                $this->indent . JsonFile::encode($key). ': '.$content . $this->newline . '}',
+                $this->contents
+            );
+        }
+    }
+
+    protected function format($data)
+    {
+        if (is_array($data)) {
+            reset($data);
+
+            if (is_numeric(key($data))) {
+                return '['.implode(', ', $data).']';
+            }
+
+            $out = '{' . $this->newline;
+            foreach ($data as $key => $val) {
+                $elems[] = $this->indent . $this->indent . JsonFile::encode($key). ': '.$this->format($val);
+            }
+            return $out . implode(','.$this->newline, $elems) . $this->newline . $this->indent . '}';
+        }
+
+        return JsonFile::encode($data);
+    }
+
+    protected function detectIndenting()
+    {
+        if (preg_match('{^(\s+)"}', $this->contents, $match)) {
+            $this->indent = $match[1];
+        } else {
+            $this->indent = '    ';
+        }
+    }
+}

+ 134 - 0
tests/Composer/Test/Json/JsonManipulatorTest.php

@@ -0,0 +1,134 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Test\Json;
+
+use Composer\Json\JsonManipulator;
+
+class JsonManipulatorTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @dataProvider linkProvider
+     */
+    public function testAddLink($json, $type, $package, $constraint, $expected)
+    {
+        $manipulator = new JsonManipulator($json);
+        $this->assertTrue($manipulator->addLink($type, $package, $constraint));
+        $this->assertEquals($expected, $manipulator->getContents());
+    }
+
+    public function linkProvider()
+    {
+        return array(
+            array(
+                '{
+}',
+                'require',
+                'vendor/baz',
+                'qux',
+                '{
+    "require": {
+        "vendor/baz": "qux"
+    }
+}
+'
+            ),
+            array(
+                '{
+    "foo": "bar"
+}',
+                'require',
+                'vendor/baz',
+                'qux',
+                '{
+    "foo": "bar",
+    "require": {
+        "vendor/baz": "qux"
+    }
+}
+'
+            ),
+            array(
+                '{
+    "require": {
+    }
+}',
+                'require',
+                'vendor/baz',
+                'qux',
+                '{
+    "require": {
+        "vendor/baz": "qux"
+    }
+}
+'
+            ),
+            array(
+                '{
+    "require": {
+        "foo": "bar"
+    }
+}',
+                'require',
+                'vendor/baz',
+                'qux',
+                '{
+    "require": {
+        "foo": "bar",
+        "vendor/baz": "qux"
+    }
+}
+'
+            ),
+            array(
+                '{
+    "require":
+    {
+        "foo": "bar",
+        "vendor/baz": "baz"
+    }
+}',
+                'require',
+                'vendor/baz',
+                'qux',
+                '{
+    "require":
+    {
+        "foo": "bar",
+        "vendor/baz": "qux"
+    }
+}
+'
+            ),
+            array(
+                '{
+    "require":
+    {
+        "foo": "bar",
+        "vendor\/baz": "baz"
+    }
+}',
+                'require',
+                'vendor/baz',
+                'qux',
+                '{
+    "require":
+    {
+        "foo": "bar",
+        "vendor/baz": "qux"
+    }
+}
+'
+            ),
+        );
+    }
+}