Эх сурвалжийг харах

Allow JsonManipulator::addMainKey to update top level keys as well

Jordi Boggiano 12 жил өмнө
parent
commit
3bd6af690d

+ 42 - 12
src/Composer/Json/JsonManipulator.php

@@ -17,7 +17,9 @@ namespace Composer\Json;
  */
 class JsonManipulator
 {
-    private static $RECURSE_BLOCKS = '(?:[^{}]*|\{(?:[^{}]*|\{(?:[^{}]*|\{(?:[^{}]*|\{[^{}]*\})*\})*\})*\})*';
+    private static $RECURSE_BLOCKS;
+    private static $JSON_VALUE;
+    private static $JSON_STRING;
 
     private $contents;
     private $newline;
@@ -25,6 +27,12 @@ class JsonManipulator
 
     public function __construct($contents)
     {
+        if (!self::$RECURSE_BLOCKS) {
+            self::$RECURSE_BLOCKS = '(?:[^{}]*|\{(?:[^{}]*|\{(?:[^{}]*|\{(?:[^{}]*|\{[^{}]*\})*\})*\})*\})*';
+            self::$JSON_STRING = '"(?:\\\\["bfnrt/\\\\]|\\\\u[a-fA-F0-9]{4}|[^\0-\x09\x0a-\x1f\\\\"])*"';
+            self::$JSON_VALUE = '(?:[0-9.]+|null|true|false|'.self::$JSON_STRING.'|\[[^\]]*\]|\{'.self::$RECURSE_BLOCKS.'\})';
+        }
+
         $contents = trim($contents);
         if (!preg_match('#^\{(.*)\}$#s', $contents)) {
             throw new \InvalidArgumentException('The json file must be an object ({})');
@@ -43,7 +51,7 @@ class JsonManipulator
     {
         // no link of that type yet
         if (!preg_match('#"'.$type.'":\s*\{#', $this->contents)) {
-            $this->addMainKey($type, $this->format(array($package => $constraint)));
+            $this->addMainKey($type, array($package => $constraint));
 
             return true;
         }
@@ -100,7 +108,7 @@ class JsonManipulator
     {
         // no main node yet
         if (!preg_match('#"'.$mainNode.'":\s*\{#', $this->contents)) {
-            $this->addMainKey(''.$mainNode.'', $this->format(array($name => $value)));
+            $this->addMainKey(''.$mainNode.'', array($name => $value));
 
             return true;
         }
@@ -126,8 +134,8 @@ class JsonManipulator
         $that = $this;
 
         // child exists
-        if (preg_match('{("'.preg_quote($name).'"\s*:\s*)([0-9.]+|null|true|false|"[^"]+"|\[[^\]]*\]|\{'.self::$RECURSE_BLOCKS.'\})(,?)}', $children, $matches)) {
-            $children = preg_replace_callback('{("'.preg_quote($name).'"\s*:\s*)([0-9.]+|null|true|false|"[^"]+"|\[[^\]]*\]|\{'.self::$RECURSE_BLOCKS.'\})(,?)}', function ($matches) use ($name, $subName, $value, $that) {
+        if (preg_match('{("'.preg_quote($name).'"\s*:\s*)('.self::$JSON_VALUE.')(,?)}', $children, $matches)) {
+            $children = preg_replace_callback('{("'.preg_quote($name).'"\s*:\s*)('.self::$JSON_VALUE.')(,?)}', function ($matches) use ($name, $subName, $value, $that) {
                 if ($subName !== null) {
                     $curVal = json_decode($matches[2], true);
                     $curVal[$subName] = $value;
@@ -194,7 +202,7 @@ class JsonManipulator
         // try and find a match for the subkey
         if (preg_match('{"'.preg_quote($name).'"\s*:}i', $children)) {
             // find best match for the value of "name"
-            if (preg_match_all('{"'.preg_quote($name).'"\s*:\s*(?:[0-9.]+|null|true|false|"[^"]+"|\[[^\]]*\]|\{'.self::$RECURSE_BLOCKS.'\})}', $children, $matches)) {
+            if (preg_match_all('{"'.preg_quote($name).'"\s*:\s*(?:'.self::$JSON_VALUE.')}', $children, $matches)) {
                 $bestMatch = '';
                 foreach ($matches[0] as $match) {
                     if (strlen($bestMatch) < strlen($match)) {
@@ -241,19 +249,41 @@ class JsonManipulator
 
     public function addMainKey($key, $content)
     {
+        $content = $this->format($content);
+
+        // key exists already
+        $regex = '{^(\s*\{\s*(?:'.self::$JSON_STRING.'\s*:\s*'.self::$JSON_VALUE.'\s*,\s*)*?)'.
+            '('.preg_quote(JsonFile::encode($key)).'\s*:\s*'.self::$JSON_VALUE.')(.*)}s';
+        if (preg_match($regex, $this->contents, $matches)) {
+            // invalid match due to un-regexable content, abort
+            if (!json_decode('{'.$matches[2].'}')) {
+                return false;
+            }
+
+            $this->contents = $matches[1] . JsonFile::encode($key).': '.$content . $matches[3];
+
+            return true;
+        }
+
+        // append at the end of the file and keep whitespace
         if (preg_match('#[^{\s](\s*)\}$#', $this->contents, $match)) {
             $this->contents = preg_replace(
                 '#'.$match[1].'\}$#',
                 addcslashes(',' . $this->newline . $this->indent . JsonFile::encode($key). ': '. $content . $this->newline . '}', '\\'),
                 $this->contents
             );
-        } else {
-            $this->contents = preg_replace(
-                '#\}$#',
-                addcslashes($this->indent . JsonFile::encode($key). ': '.$content . $this->newline . '}', '\\'),
-                $this->contents
-            );
+
+            return true;
         }
+
+        // append at the end of the file
+        $this->contents = preg_replace(
+            '#\}$#',
+            addcslashes($this->indent . JsonFile::encode($key). ': '.$content . $this->newline . '}', '\\'),
+            $this->contents
+        );
+
+        return true;
     }
 
     public function format($data, $depth = 0)

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

@@ -611,6 +611,57 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase
         }
     }
 }
+', $manipulator->getContents());
+    }
+
+    public function testAddMainKey()
+    {
+        $manipulator = new JsonManipulator('{
+    "foo": "bar"
+}');
+
+        $this->assertTrue($manipulator->addMainKey('bar', 'baz'));
+        $this->assertEquals('{
+    "foo": "bar",
+    "bar": "baz"
+}
+', $manipulator->getContents());
+    }
+
+    public function testUpdateMainKey()
+    {
+        $manipulator = new JsonManipulator('{
+    "foo": "bar"
+}');
+
+        $this->assertTrue($manipulator->addMainKey('foo', 'baz'));
+        $this->assertEquals('{
+    "foo": "baz"
+}
+', $manipulator->getContents());
+    }
+
+    public function testUpdateMainKey2()
+    {
+        $manipulator = new JsonManipulator('{
+    "a": {
+        "foo": "bar",
+        "baz": "qux"
+    },
+    "foo": "bar",
+    "baz": "bar"
+}');
+
+        $this->assertTrue($manipulator->addMainKey('foo', 'baz'));
+        $this->assertTrue($manipulator->addMainKey('baz', 'quux'));
+        $this->assertEquals('{
+    "a": {
+        "foo": "bar",
+        "baz": "qux"
+    },
+    "foo": "baz",
+    "baz": "quux"
+}
 ', $manipulator->getContents());
     }
 }