Skip to content

Parameterhandler add json support #104

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 32 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
dbd4014
Added JSON support // Restructured directories / files / namespaces.
Sep 10, 2016
55de495
Aligned tests with new structure.
Sep 10, 2016
82c0887
Restructured tests and related fixtures // Adapting first tests for J…
Sep 10, 2016
78b4943
Added tests for custom key JSON.
Sep 10, 2016
18f5958
Added testcase existent.
Sep 11, 2016
b0c8db1
Added testcase existent_empty and existent_without_key.
Sep 11, 2016
d4948d4
Added testcase extra_keys.
Sep 11, 2016
ccf75ad
Added testcase interaction_existent.
Sep 11, 2016
38e2de9
Fixed expectation.
Sep 11, 2016
f2de569
Added testcase interaction_non_existent.
Sep 11, 2016
6bc8438
Added testcase interaction_with_environment.
Sep 11, 2016
b8cfbb2
Added testcase keep_outdated.
Sep 12, 2016
a1bd237
Added testcase non_existent.
Sep 12, 2016
18a6f37
Added testcase non_existent_with_environment.
Sep 12, 2016
8d709a0
Added testcase remove_outdated.
Sep 12, 2016
40d4177
Added testcase renamed.
Sep 12, 2016
0bbf577
Added testcase renamed_and_environment.
Sep 12, 2016
95617c8
Added testcase subfolder.
Sep 12, 2016
0b71c13
Added testcase subfolder_created.
Sep 12, 2016
776afbf
TidyUp removed deprecated interface.
Sep 12, 2016
c109e6b
TidyUp comments & minor refactoring.
Sep 12, 2016
3dd8dce
Removed some comments // Added new line parameter for pretty print JSON.
Sep 12, 2016
0d6c07f
Added nl to JSON files as expectation.
Sep 12, 2016
3b8403e
Fixes for PHP < 5.4 - missed some notations.
Sep 12, 2016
3441069
Fixes for PHP < 5.4 - missed some notations.
Sep 12, 2016
4d40e3c
Fixes for PHP < 5.4 - missed some notations.
Sep 12, 2016
e9dd59e
Fixes for PHP < 5.4 - missed some notations.
Sep 12, 2016
0fe9a34
Fixes for PHP < 5.4 - missed some notations.
Sep 12, 2016
9310096
Fixes for PHP < 5.4 - missed some notations.
Sep 12, 2016
ddc70fa
Fixes for PHP < 5.4 - missed some notations.
Sep 12, 2016
8cd469f
Fixes for PHP < 5.4 - json_decode.
Sep 12, 2016
e780cac
TidyUp // Fixed some bad comments and typehints.
Sep 15, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions Parser/JsonParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

namespace Incenteev\ParameterHandler\Parser;

class JsonParser implements ParserInterface
{
/**
* Parses a JSON string to a PHP value.
*
* @param string $value A JSON string
* @param int $flags Bitmask of JSON decode options. Currently only JSON_BIGINT_AS_STRING is supported (default is to cast large integers as floats)
* @param bool $assoc When TRUE, returned objects will be converted into associative arrays.
* @param int $depth User specified recursion depth.
*
* @return mixed A PHP value
*/
public function parse($value, $flags = 0, $assoc = true, $depth = 512)
{
if ('' === $value) {
$result = array();
} else {
$result = json_decode($value, $assoc, $depth);
if (null === $result) {
$result = false;
}
}

return $result;
}

/**
* Dumps a given array of data to JSON format.
*
* @param array $data Data to dump.
*
* @return string Dumped JSON data
*/
public function dump(array $data)
{
return $this->prettyPrint(json_encode($data));
}

/**
* Returns pretty printed JSON string.
* Custom implementation for compatibility with PHP < 5.4 and custom spacing.
*
* @param string $json JSON data to be pretty printed
* @param string $spacer Spacer used e.g. ' ' or "\t"
* @param int $spacing Multiplicand for spacer (count)
* @param bool $newLineAtEof Whether to write a nl at end of file or not
*
* @return string Pretty printed JSON data
*/
protected function prettyPrint($json, $spacer = ' ', $spacing = 2, $newLineAtEof = true)
{
$result = '';
$level = 0;
$in_quotes = false;
$in_escape = false;
$ends_line_level = null;
$json_length = strlen($json);

for ($i = 0; $i < $json_length; ++$i) {
$char = $json[$i];
$new_line_level = null;
$post = '';
if ($ends_line_level !== null) {
$new_line_level = $ends_line_level;
$ends_line_level = null;
}
if ($in_escape) {
$in_escape = false;
} elseif ($char === '"') {
$in_quotes = !$in_quotes;
} elseif (!$in_quotes) {
switch ($char) {
case '}':
case ']':
$level--;
$ends_line_level = null;
$new_line_level = $level;
break;

case '{':
case '[':
$level++;
case ',':
$ends_line_level = $level;
break;

case ':':
$post = ' ';
break;

case ' ':
case "\t":
case "\n":
case "\r":
$char = '';
$ends_line_level = $new_line_level;
$new_line_level = null;
break;
}
} elseif ($char === '\\') {
$in_escape = true;
}
if ($new_line_level !== null) {
$result .= "\n".str_repeat(str_repeat($spacer, $spacing), $new_line_level);
}
$result .= $char.$post;
}

$result = trim($result);

if (true === $newLineAtEof) {
$result .= "\n";
}

return $result;
}
}
18 changes: 18 additions & 0 deletions Parser/ParserInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Incenteev\ParameterHandler\Parser;

interface ParserInterface
{
/**
* Parses a string to a PHP value.
*
* @param string $value A JSON string.
* @param int $flags Bitmask of decode options.
*
* @return mixed A PHP value
*
* @throws ParseException If the string format is not valid
*/
public function parse($value, $flags = 0);
}
10 changes: 10 additions & 0 deletions Parser/YamlParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Incenteev\ParameterHandler\Parser;

use Symfony\Component\Yaml\Parser;

class YamlParser extends Parser implements ParserInterface
{
// Intentionally left empty.
}
114 changes: 87 additions & 27 deletions Processor.php → Processor/AbstractProcessor.php
Original file line number Diff line number Diff line change
@@ -1,50 +1,73 @@
<?php

namespace Incenteev\ParameterHandler;
namespace Incenteev\ParameterHandler\Processor;

use Composer\IO\IOInterface;
use Incenteev\ParameterHandler\Parser\ParseException;
use Incenteev\ParameterHandler\Parser\ParserInterface;
use Symfony\Component\Yaml\Inline;
use Symfony\Component\Yaml\Parser;
use Symfony\Component\Yaml\Yaml;

class Processor
abstract class AbstractProcessor
{
private $io;

public function __construct(IOInterface $io)
/**
* IO Interface of composer used for displaying messages and requesting input from user.
*
* @var IOInterface
*/
protected $io;

/**
* Parser.
*
* @var ParserInterface
*/
protected $parser;

/**
* Constructor.
*
* @param IOInterface $io Composer IO
* @param ParserInterface $parser Instance of parser for type YAML or JSON
*/
public function __construct(IOInterface $io, ParserInterface $parser)
{
$this->io = $io;
$this->parser = $parser;
$this->io = $io;
}

/**
* Processes single operations for a passed parameter file configuration.
*
* @throws ParseException|\InvalidArgumentException|\RuntimeException
*/
public function processFile(array $config)
{
$config = $this->processConfig($config);

$realFile = $config['file'];
$realFile = $config['file'];
$parameterKey = $config['parameter-key'];

$exists = is_file($realFile);

$yamlParser = new Parser();

$action = $exists ? 'Updating' : 'Creating';
$this->io->write(sprintf('<info>%s the "%s" file</info>', $action, $realFile));

// Find the expected params
$expectedValues = $yamlParser->parse(file_get_contents($config['dist-file']));
$expectedValues = $this->parser->parse(file_get_contents($config['dist-file']));

if (!isset($expectedValues[$parameterKey])) {
throw new \InvalidArgumentException(sprintf('The top-level key %s is missing.', $parameterKey));
}
$expectedParams = (array) $expectedValues[$parameterKey];

// find the actual params
$actualValues = array_merge(
// Preserve other top-level keys than `$parameterKey` in the file
// Preserve other top-level keys than `$parameterKey` in the file
$expectedValues,
array($parameterKey => array())
);
if ($exists) {
$existingValues = $yamlParser->parse(file_get_contents($realFile));
$existingValues = $this->parser->parse(file_get_contents($realFile));
if ($existingValues === null) {
$existingValues = array();
}
Expand All @@ -56,19 +79,24 @@ public function processFile(array $config)

$actualValues[$parameterKey] = $this->processParams($config, $expectedParams, (array) $actualValues[$parameterKey]);

if (!is_dir($dir = dirname($realFile))) {
mkdir($dir, 0755, true);
if (!is_dir($dir = dirname($realFile)) && (!@mkdir($dir, 0755, true) && !is_dir($dir))) {
throw new \RuntimeException(
sprintf('Error while creating directory "%s". Check path and permissions.', $dir)
);
}

file_put_contents($realFile, "# This file is auto-generated during the composer install\n" . Yaml::dump($actualValues, 99));
$this->writeFile($realFile, $actualValues);
}

private function processConfig(array $config)
/**
* @param array $config
*
* @return array
*
* @throws \InvalidArgumentException
*/
protected function processConfig(array $config)
{
if (empty($config['file'])) {
throw new \InvalidArgumentException('The extra.incenteev-parameters.file setting is required to use this script handler.');
}

if (empty($config['dist-file'])) {
$config['dist-file'] = $config['file'].'.dist';
}
Expand All @@ -84,10 +112,10 @@ private function processConfig(array $config)
return $config;
}

private function processParams(array $config, array $expectedParams, array $actualParams)
protected function processParams(array $config, array $expectedParams, array $actualParams)
{
// Grab values for parameters that were renamed
$renameMap = empty($config['rename-map']) ? array() : (array) $config['rename-map'];
$renameMap = empty($config['rename-map']) ? array() : (array) $config['rename-map'];
$actualParams = array_replace($actualParams, $this->processRenamedValues($renameMap, $actualParams));

$keepOutdatedParams = false;
Expand All @@ -107,7 +135,16 @@ private function processParams(array $config, array $expectedParams, array $actu
return $this->getParams($expectedParams, $actualParams);
}

private function getEnvValues(array $envMap)
/**
* Parses environments variables by map and resolves correct types.
* As environment variables can only be strings, they are also parsed to allow specifying null, false,
* true or numbers easily.
*
* @param array $envMap Map used to map data from environment variable name to parameter name.
*
* @return array
*/
protected function getEnvValues(array $envMap)
{
$params = array();
foreach ($envMap as $param => $env) {
Expand Down Expand Up @@ -137,6 +174,18 @@ private function processRenamedValues(array $renameMap, array $actualParams)
return $actualParams;
}

/**
* Returns the current set of parameters.
* If IO mode non interactive it simply sets the expected values, otherwise it asks user for defining missing
* parameters.
*
* @param array $expectedParams Parameters required
* @param array $actualParams Parameters defined already
*
* @return array Updated set of parameters
*
* @throws \RuntimeException
*/
private function getParams(array $expectedParams, array $actualParams)
{
// Simply use the expectedParams value as default for the missing params.
Expand All @@ -146,7 +195,7 @@ private function getParams(array $expectedParams, array $actualParams)

$isStarted = false;

foreach ($expectedParams as $key => $message) {
foreach ($expectedParams as $key => $value) {
if (array_key_exists($key, $actualParams)) {
continue;
}
Expand All @@ -156,12 +205,23 @@ private function getParams(array $expectedParams, array $actualParams)
$this->io->write('<comment>Some parameters are missing. Please provide them.</comment>');
}

$default = Inline::dump($message);
$default = Inline::dump($value);

$value = $this->io->ask(sprintf('<question>%s</question> (<comment>%s</comment>): ', $key, $default), $default);

$actualParams[$key] = Inline::parse($value);
}

return $actualParams;
}

/**
* Persists configuration.
*
* @param string $file Filename to persist configuration to.
* @param array $configuration Configuration to persist.
*
* @return bool TRUE after successful persisting the file, otherwise FALSE
*/
abstract protected function writeFile($file, array $configuration);
}
19 changes: 19 additions & 0 deletions Processor/JsonProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Incenteev\ParameterHandler\Processor;

class JsonProcessor extends AbstractProcessor
{
/**
* Persists an array to a file in JSON format.
*
* {@inheritdoc}
*/
protected function writeFile($filename, array $configuration)
{
return
false !== file_put_contents(
$filename, $this->parser->dump($configuration)
);
}
}
21 changes: 21 additions & 0 deletions Processor/YamlProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Incenteev\ParameterHandler\Processor;

use Symfony\Component\Yaml\Yaml;

class YamlProcessor extends AbstractProcessor
{
/**
* Persists an array to a file in YAML format.
*
* {@inheritdoc}
*/
protected function writeFile($filename, array $configuration)
{
return
false !== file_put_contents(
$filename, "# This file is auto-generated during the composer install\n".Yaml::dump($configuration, 99)
);
}
}
Loading