1
0
Fork 0

Preserve indentation when writing JSON files (#11390)

Refs #11341
pull/11558/head
MaximAL 2023-07-19 15:13:06 +03:00 committed by GitHub
parent 16d1b11c26
commit 1c9fbeb978
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 58 additions and 8 deletions

View File

@ -42,12 +42,16 @@ class JsonFile
public const COMPOSER_SCHEMA_PATH = __DIR__ . '/../../../res/composer-schema.json'; public const COMPOSER_SCHEMA_PATH = __DIR__ . '/../../../res/composer-schema.json';
public const INDENT_DEFAULT = ' ';
/** @var string */ /** @var string */
private $path; private $path;
/** @var ?HttpDownloader */ /** @var ?HttpDownloader */
private $httpDownloader; private $httpDownloader;
/** @var ?IOInterface */ /** @var ?IOInterface */
private $io; private $io;
/** @var string */
private $indent = self::INDENT_DEFAULT;
/** /**
* Initializes json file reader/parser. * Initializes json file reader/parser.
@ -117,6 +121,8 @@ class JsonFile
throw new \RuntimeException('Could not read '.$this->path); throw new \RuntimeException('Could not read '.$this->path);
} }
$this->indent = self::detectIndenting($json);
return static::parseJson($json, $this->path); return static::parseJson($json, $this->path);
} }
@ -131,7 +137,7 @@ class JsonFile
public function write(array $hash, int $options = JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) public function write(array $hash, int $options = JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
{ {
if ($this->path === 'php://memory') { if ($this->path === 'php://memory') {
file_put_contents($this->path, static::encode($hash, $options)); file_put_contents($this->path, static::encode($hash, $options, $this->indent));
return; return;
} }
@ -153,7 +159,7 @@ class JsonFile
$retries = 3; $retries = 3;
while ($retries--) { while ($retries--) {
try { try {
$this->filePutContentsIfModified($this->path, static::encode($hash, $options). ($options & JSON_PRETTY_PRINT ? "\n" : '')); $this->filePutContentsIfModified($this->path, static::encode($hash, $options, $this->indent). ($options & JSON_PRETTY_PRINT ? "\n" : ''));
break; break;
} catch (\Exception $e) { } catch (\Exception $e) {
if ($retries > 0) { if ($retries > 0) {
@ -262,15 +268,28 @@ class JsonFile
* *
* @param mixed $data Data to encode into a formatted JSON string * @param mixed $data Data to encode into a formatted JSON string
* @param int $options json_encode options (defaults to JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) * @param int $options json_encode options (defaults to JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
* @param string $indent Indentation string
* @return string Encoded json * @return string Encoded json
*/ */
public static function encode($data, int $options = 448) public static function encode($data, int $options = 448, string $indent = self::INDENT_DEFAULT): string
{ {
$json = json_encode($data, $options); $json = json_encode($data, $options);
if (false === $json) { if (false === $json) {
self::throwEncodeError(json_last_error()); self::throwEncodeError(json_last_error());
} }
if (($options & JSON_PRETTY_PRINT) > 0 && $indent !== self::INDENT_DEFAULT ) {
// Pretty printing and not using default indentation
return Preg::replaceCallback(
'#^ {4,}#m',
static function ($match) use ($indent): string {
return str_repeat($indent, (int)(strlen($match[0] ?? '') / 4));
},
$json
);
}
return $json; return $json;
} }
@ -279,6 +298,7 @@ class JsonFile
* *
* @param int $code return code of json_last_error function * @param int $code return code of json_last_error function
* @throws \RuntimeException * @throws \RuntimeException
* @return never
*/ */
private static function throwEncodeError(int $code): void private static function throwEncodeError(int $code): void
{ {
@ -356,4 +376,12 @@ class JsonFile
$result->getDetails()); $result->getDetails());
} }
} }
public static function detectIndenting(?string $json): string
{
if (Preg::isMatchStrictGroups('#^([ \t]+)"#m', $json ?? '', $match)) {
return $match[1];
}
return self::INDENT_DEFAULT;
}
} }

View File

@ -561,10 +561,6 @@ class JsonManipulator
protected function detectIndenting(): void protected function detectIndenting(): void
{ {
if (Preg::isMatchStrictGroups('{^([ \t]+)"}m', $this->contents, $match)) { $this->indent = JsonFile::detectIndenting($this->contents);
$this->indent = $match[1];
} else {
$this->indent = ' ';
}
} }
} }

View File

@ -0,0 +1,3 @@
{
"foo": "bar"
}

View File

@ -364,6 +364,29 @@ class JsonFileTest extends TestCase
$this->assertEquals($data, $doubleData); $this->assertEquals($data, $doubleData);
} }
public function testPreserveIndentationAfterRead(): void
{
copy(__DIR__.'/Fixtures/tabs.json', __DIR__.'/Fixtures/tabs2.json');
$jsonFile = new JsonFile(__DIR__.'/Fixtures/tabs2.json');
$data = $jsonFile->read();
$jsonFile->write(['foo' => 'baz']);
self::assertSame("{\n\t\"foo\": \"baz\"\n}\n", file_get_contents(__DIR__.'/Fixtures/tabs2.json'));
unlink(__DIR__.'/Fixtures/tabs2.json');
}
public function testOverwritesIndentationByDefault(): void
{
copy(__DIR__.'/Fixtures/tabs.json', __DIR__.'/Fixtures/tabs2.json');
$jsonFile = new JsonFile(__DIR__.'/Fixtures/tabs2.json');
$jsonFile->write(['foo' => 'baz']);
self::assertSame("{\n \"foo\": \"baz\"\n}\n", file_get_contents(__DIR__.'/Fixtures/tabs2.json'));
unlink(__DIR__.'/Fixtures/tabs2.json');
}
private function expectParseException(string $text, string $json): void private function expectParseException(string $text, string $json): void
{ {
try { try {