Add support for section reordering and formatting options to the PreservingIniWriter
The PreservingIniWriter needs to be aware of the the order of the sections and update the section declarations in the configuration file when the order has changed. Therefore add functions to correctly reorder sections. refs #4615
This commit is contained in:
parent
a9452d30e7
commit
5b58d5488b
|
@ -47,14 +47,47 @@ class IniEditor
|
|||
*/
|
||||
private $nestSeparator = '.';
|
||||
|
||||
/**
|
||||
* The indentation level of the comments
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $commentIndentation;
|
||||
|
||||
/**
|
||||
* The indentation level of the values
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $valueIndentation;
|
||||
|
||||
/**
|
||||
* The number of new lines between sections
|
||||
*
|
||||
* @var number
|
||||
*/
|
||||
private $sectionSeparators;
|
||||
|
||||
/**
|
||||
* Create a new IniEditor
|
||||
*
|
||||
* @param string $content The content of the ini as string
|
||||
* @param string $content The content of the ini as string
|
||||
* @param array $options Optional formatting options used when changing the ini file
|
||||
* * valueIndentation: The indentation level of the values
|
||||
* * commentIndentation: The indentation level of the comments
|
||||
* * sectionSeparators: The amount of newlines between sections
|
||||
*/
|
||||
public function __construct($content)
|
||||
{
|
||||
public function __construct(
|
||||
$content,
|
||||
array $options = array()
|
||||
) {
|
||||
$this->text = explode("\n", $content);
|
||||
$this->valueIndentation = array_key_exists('valueIndentation', $options)
|
||||
? $options['valueIndentation'] : 19;
|
||||
$this->commentIndentation = array_key_exists('commentIndentation', $options)
|
||||
? $options['commentIndentation'] : 43;
|
||||
$this->sectionSeparators = array_key_exists('sectionSeparators', $options)
|
||||
? $options['sectionSeparators'] : 2;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -103,6 +136,9 @@ class IniEditor
|
|||
if (isset($section)) {
|
||||
$this->updateLine($line, $this->formatKeyValuePair($key, $value));
|
||||
} else {
|
||||
/*
|
||||
* Move into new section to avoid ambiguous configurations
|
||||
*/
|
||||
$section = $key[0];
|
||||
unset($key[0]);
|
||||
$this->deleteLine($line);
|
||||
|
@ -180,6 +216,92 @@ class IniEditor
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the section order of the ini file
|
||||
*
|
||||
* @param array $order An array containing the section names in the new order
|
||||
* Example: array(0 => 'FirstSection', 1 => 'SecondSection')
|
||||
*/
|
||||
public function refreshSectionOrder(array $order)
|
||||
{
|
||||
$sections = $this->createSectionMap($this->text);
|
||||
/*
|
||||
* Move section-less properties to the start of the ordered text
|
||||
*/
|
||||
$orderedText = array();
|
||||
foreach ($sections['[section-less]'] as $line) {
|
||||
array_push($orderedText, $line);
|
||||
}
|
||||
/*
|
||||
* Reorder the sections
|
||||
*/
|
||||
$len = count($order);
|
||||
for ($i = 0; $i < $len; $i++) {
|
||||
if (array_key_exists($i, $order)) {
|
||||
/*
|
||||
* Append the lines of the section to the end of the
|
||||
* ordered text
|
||||
*/
|
||||
foreach ($sections[$order[$i]] as $line) {
|
||||
array_push($orderedText, $line);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->text = $orderedText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a map of sections to lines of a given ini file
|
||||
*
|
||||
* @param array $text The text split up in lines
|
||||
*
|
||||
* @return array $sectionMap A map containing all sections as arrays of lines. The array of section-less
|
||||
* lines will be available using they key '[section-less]' which is no valid
|
||||
* section declaration because it contains brackets.
|
||||
*/
|
||||
private function createSectionMap($text)
|
||||
{
|
||||
$sections = array('[section-less]' => array());
|
||||
$section = '[section-less]';
|
||||
$len = count($text);
|
||||
for ($i = 0; $i < $len; $i++) {
|
||||
if ($this->isSectionDeclaration($text[$i])) {
|
||||
$newSection = $this->getSectionFromDeclaration($this->text[$i]);
|
||||
$sections[$newSection] = array();
|
||||
|
||||
/*
|
||||
* Remove comments 'glued' to the new section from the old
|
||||
* section array and put them into the new section.
|
||||
*/
|
||||
$j = $i - 1;
|
||||
$comments = array();
|
||||
while ($j > 0 && $this->isComment($this->text[$j])) {
|
||||
array_push($comments, array_pop($sections[$section]));
|
||||
$j--;
|
||||
}
|
||||
$comments = array_reverse($comments);
|
||||
foreach ($comments as $comment) {
|
||||
array_push($sections[$newSection], $comment);
|
||||
}
|
||||
|
||||
$section = $newSection;
|
||||
}
|
||||
array_push($sections[$section], $this->text[$i]);
|
||||
}
|
||||
return $sections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the section name from a section declaration
|
||||
*
|
||||
* @param String $declaration The section declaration
|
||||
*/
|
||||
private function getSectionFromDeclaration($declaration)
|
||||
{
|
||||
$tmp = preg_split('/(\[|\]|:)/', $declaration);
|
||||
return trim($tmp[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a section declaration
|
||||
*
|
||||
|
@ -223,7 +345,6 @@ class IniEditor
|
|||
*/
|
||||
private function cleanUpWhitespaces()
|
||||
{
|
||||
|
||||
$i = count($this->text) - 1;
|
||||
for (; $i > 0; $i--) {
|
||||
$line = $this->text[$i];
|
||||
|
@ -233,7 +354,7 @@ class IniEditor
|
|||
/*
|
||||
* Ignore comments that are glued to the section declaration
|
||||
*/
|
||||
while ($i > 0 && preg_match('/^\s*;/', $line) === 1) {
|
||||
while ($i > 0 && $this->isComment($line)) {
|
||||
$i--;
|
||||
$line = $this->text[$i];
|
||||
}
|
||||
|
@ -246,10 +367,10 @@ class IniEditor
|
|||
$line = $this->text[$i];
|
||||
}
|
||||
/*
|
||||
* Add a single whitespace
|
||||
* Refresh section separators
|
||||
*/
|
||||
if ($i !== 0) {
|
||||
$this->insertAtLine($i + 1, '');
|
||||
if ($i !== 0 && $this->sectionSeparators > 0) {
|
||||
$this->insertAtLine($i + 1, str_repeat("\n", $this->sectionSeparators - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -278,7 +399,7 @@ class IniEditor
|
|||
if (strlen($comment) > 0) {
|
||||
$comment = ' ; ' . trim($comment);
|
||||
}
|
||||
$this->text[$lineNr] = str_pad($content, 43) . $comment;
|
||||
$this->text[$lineNr] = str_pad($content, $this->commentIndentation) . $comment;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -320,7 +441,7 @@ class IniEditor
|
|||
*/
|
||||
private function formatKeyValuePair(array $key, $value)
|
||||
{
|
||||
return str_pad($this->formatKey($key), 19) . ' = ' . $this->formatValue($value);
|
||||
return str_pad($this->formatKey($key), $this->valueIndentation) . ' = ' . $this->formatValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -355,7 +476,7 @@ class IniEditor
|
|||
* ignore all comments 'glued' to the next section, to allow section
|
||||
* comments in front of sections
|
||||
*/
|
||||
while ($i > 0 && preg_match('/^\s*;/', $this->text[$i - 1]) === 1) {
|
||||
while ($i > 0 && $this->isComment($this->text[$i - 1])) {
|
||||
$i--;
|
||||
}
|
||||
return $i;
|
||||
|
@ -370,6 +491,14 @@ class IniEditor
|
|||
return $i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given line contains only a comment
|
||||
*/
|
||||
private function isComment($line)
|
||||
{
|
||||
return preg_match('/^\s*;/', $line) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the line contains the property declaration for a key
|
||||
*
|
||||
|
|
|
@ -39,6 +39,31 @@ use \Icinga\Config\IniEditor;
|
|||
*/
|
||||
class PreservingIniWriter extends Zend_Config_Writer_FileAbstract
|
||||
{
|
||||
/**
|
||||
* Stores the options
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* Create a new PreservingIniWriter
|
||||
*
|
||||
* @param array $options Contains the options that should be used for the ConfigWriter
|
||||
* in an associative array. Supports all options of Zend_Config_Writer and additional
|
||||
* options for setting the formatting for the internal IniEditor:
|
||||
* * valueIndentation: The indentation level of the values
|
||||
* * commentIndentation: The indentation level of the comments
|
||||
* * sectionSeparators: The amount of newlines between sections
|
||||
*
|
||||
* @link http://framework.zend.com/apidoc/1.12/files/Config.Writer.html#\Zend_Config_Writer
|
||||
*/
|
||||
public function __construct(array $options = null)
|
||||
{
|
||||
$this->options = $options;
|
||||
parent::__construct($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the Zend_Config into a config file string
|
||||
*
|
||||
|
@ -48,8 +73,9 @@ class PreservingIniWriter extends Zend_Config_Writer_FileAbstract
|
|||
{
|
||||
$oldconfig = new Zend_Config_Ini($this->_filename);
|
||||
$newconfig = $this->_config;
|
||||
$editor = new IniEditor(file_get_contents($this->_filename));
|
||||
$editor = new IniEditor(file_get_contents($this->_filename), $this->options);
|
||||
$this->diffConfigs($oldconfig, $newconfig, $editor);
|
||||
$this->updateSectionOrder($newconfig, $editor);
|
||||
return $editor->getText();
|
||||
}
|
||||
|
||||
|
@ -71,6 +97,23 @@ class PreservingIniWriter extends Zend_Config_Writer_FileAbstract
|
|||
$this->diffPropertyDeletions($oldconfig, $newconfig, $editor, $parents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the order of the sections in the ini file to match
|
||||
* the order of the new config
|
||||
*/
|
||||
private function updateSectionOrder(
|
||||
Zend_Config $newconfig,
|
||||
IniEditor $editor
|
||||
) {
|
||||
$order = array();
|
||||
foreach ($newconfig as $key => $value) {
|
||||
if ($value instanceof Zend_Config) {
|
||||
array_push($order, $key);
|
||||
}
|
||||
}
|
||||
$editor->refreshSectionOrder($order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for created and updated properties and use the editor to create or update these entries
|
||||
*
|
||||
|
|
|
@ -36,6 +36,7 @@ require_once('../../library/Icinga/Config/IniEditor.php');
|
|||
require_once('../../library/Icinga/Config/PreservingIniWriter.php');
|
||||
|
||||
use Icinga\Config\PreservingIniWriter;
|
||||
use Zend_Config;
|
||||
|
||||
class PreservingIniWriterTest extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
|
@ -86,10 +87,10 @@ Prop5="true"
|
|||
PropOne="overwritten"
|
||||
;10
|
||||
';
|
||||
$this->writeToTmp('orig',$ini);
|
||||
$this->writeToTmp('orig', $ini);
|
||||
|
||||
$emptyIni = " ";
|
||||
$this->writeToTmp('empty',$emptyIni);
|
||||
$this->writeToTmp('empty', $emptyIni);
|
||||
|
||||
$editedIni =
|
||||
';1
|
||||
|
@ -114,7 +115,7 @@ prop2="2"
|
|||
[nested : different]
|
||||
prop2="5"
|
||||
';
|
||||
$this->writeToTmp('edited',$editedIni);
|
||||
$this->writeToTmp('edited', $editedIni);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -123,12 +124,12 @@ prop2="5"
|
|||
* @param $name The name of the temporary file
|
||||
* @param $content The content
|
||||
*/
|
||||
private function writeToTmp($name,$content)
|
||||
private function writeToTmp($name, $content)
|
||||
{
|
||||
$this->tmpfiles[$name] =
|
||||
tempnam(dirname(__FILE__) . '/temp',$name);
|
||||
$file = fopen($this->tmpfiles[$name],'w');
|
||||
fwrite($file,$content);
|
||||
tempnam(dirname(__FILE__) . '/temp', $name);
|
||||
$file = fopen($this->tmpfiles[$name], 'w');
|
||||
fwrite($file, $content);
|
||||
fflush($file);
|
||||
fclose($file);
|
||||
}
|
||||
|
@ -151,7 +152,7 @@ prop2="5"
|
|||
{
|
||||
$this->changeConfigAndWriteToFile('orig');
|
||||
$config = new \Zend_Config_Ini(
|
||||
$this->tmpfiles['orig'],null,array('allowModifications' => true)
|
||||
$this->tmpfiles['orig'], null, array('allowModifications' => true)
|
||||
);
|
||||
$this->checkConfigProperties($config);
|
||||
$this->checkConfigComments($this->tmpfiles['orig']);
|
||||
|
@ -164,7 +165,7 @@ prop2="5"
|
|||
{
|
||||
$this->changeConfigAndWriteToFile('empty');
|
||||
$config = new \Zend_Config_Ini(
|
||||
$this->tmpfiles['empty'],null,array('allowModifications' => true)
|
||||
$this->tmpfiles['empty'], null, array('allowModifications' => true)
|
||||
);
|
||||
$this->checkConfigProperties($config);
|
||||
}
|
||||
|
@ -176,12 +177,64 @@ prop2="5"
|
|||
{
|
||||
$original = $this->changeConfigAndWriteToFile('edited');
|
||||
$config = new \Zend_Config_Ini(
|
||||
$this->tmpfiles['edited'],null,array('allowModifications' => true)
|
||||
$this->tmpfiles['edited'], null, array('allowModifications' => true)
|
||||
);
|
||||
$this->checkConfigProperties($config);
|
||||
$this->checkConfigComments($this->tmpfiles['edited']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the order of sections is correctly changed in the config.
|
||||
*/
|
||||
public function testSectionOrderChange()
|
||||
{
|
||||
$original = '
|
||||
;1
|
||||
|
||||
[section2]
|
||||
;3
|
||||
|
||||
;4
|
||||
[section3]
|
||||
;5
|
||||
|
||||
;2
|
||||
[section1]
|
||||
property = "something" ; comment
|
||||
|
||||
';
|
||||
$this->writeToTmp('section-order',$original);
|
||||
$config = new Zend_Config(
|
||||
array(
|
||||
'section1' => array(
|
||||
'property' => 'something'
|
||||
),
|
||||
'section2' => array(),
|
||||
'section3' => array()
|
||||
)
|
||||
);
|
||||
$writer = new PreservingIniWriter(
|
||||
array('config' => $config, 'filename' => $this->tmpfiles['section-order'])
|
||||
);
|
||||
$writer->write();
|
||||
$changed = new \Zend_Config_Ini(
|
||||
$this->tmpfiles['section-order'],
|
||||
null,
|
||||
array('allowModifications' => true)
|
||||
);
|
||||
$this->assertEquals($config->section1->property, $changed->section1->property);
|
||||
|
||||
/*
|
||||
* IniWriter should move the sections, so that comments
|
||||
* are now in the right order
|
||||
*/
|
||||
$this->checkConfigComments(
|
||||
$this->tmpfiles['section-order'],
|
||||
5,
|
||||
'Sections re-ordered correctly'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the test config and write the changes to the temporary
|
||||
* file $tmpFile
|
||||
|
@ -202,9 +255,11 @@ prop2="5"
|
|||
/**
|
||||
* Check if all comments are present
|
||||
*
|
||||
* @param $file
|
||||
* @param String $file The file to check
|
||||
* @param Number $count The amount of comments that should be present
|
||||
* @param String $assertion The assertion message that will be displayed on errors
|
||||
*/
|
||||
private function checkConfigComments($file)
|
||||
private function checkConfigComments($file,$count = 10,$assertion = 'Comment unchanged')
|
||||
{
|
||||
$i = 0;
|
||||
foreach (explode("\n",file_get_contents($file)) as $line) {
|
||||
|
@ -212,11 +267,11 @@ prop2="5"
|
|||
$i++;
|
||||
$this->assertEquals(
|
||||
$i,intval(substr($line,1)),
|
||||
'Comment unchanged'
|
||||
$assertion
|
||||
);
|
||||
}
|
||||
}
|
||||
$this->assertEquals(10,$i,'All comments exist');
|
||||
$this->assertEquals($count, $i, 'All comments exist');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -226,65 +281,65 @@ prop2="5"
|
|||
*/
|
||||
private function checkConfigProperties($config)
|
||||
{
|
||||
$this->assertEquals('val',$config->Trailing2,
|
||||
$this->assertEquals('val', $config->Trailing2,
|
||||
'Section-less property updated.');
|
||||
|
||||
$this->assertNull($config->trailing1,
|
||||
'Section-less property deleted.');
|
||||
|
||||
$this->assertEquals('value',$config->new,
|
||||
$this->assertEquals('value', $config->new,
|
||||
'Section-less property created.');
|
||||
|
||||
$this->assertEquals('0',$config->arr->{0},
|
||||
$this->assertEquals('0', $config->arr->{0},
|
||||
'Value persisted in array');
|
||||
|
||||
$this->assertEquals('update',$config->arr->{2},
|
||||
$this->assertEquals('update', $config->arr->{2},
|
||||
'Value changed in array');
|
||||
|
||||
$this->assertEquals('arrvalue',$config->arr->{4},
|
||||
$this->assertEquals('arrvalue', $config->arr->{4},
|
||||
'Value added to array');
|
||||
|
||||
$this->assertEquals('',$config->parent->propOne,
|
||||
$this->assertEquals('', $config->parent->propOne,
|
||||
'Section property deleted.');
|
||||
|
||||
$this->assertEquals("2",$config->parent->propTwo,
|
||||
$this->assertEquals("2", $config->parent->propTwo,
|
||||
'Section property numerical unchanged.');
|
||||
|
||||
$this->assertEquals('update',$config->parent->propThree,
|
||||
$this->assertEquals('update', $config->parent->propThree,
|
||||
'Section property updated.');
|
||||
|
||||
$this->assertEquals("true",$config->parent->propFour,
|
||||
$this->assertEquals("true", $config->parent->propFour,
|
||||
'Section property boolean unchanged.');
|
||||
|
||||
$this->assertEquals("1",$config->parent->new,
|
||||
$this->assertEquals("1", $config->parent->new,
|
||||
'Section property numerical created.');
|
||||
|
||||
$this->assertNull($config->parent->list->{0},
|
||||
'Section array deleted');
|
||||
|
||||
$this->assertEquals('new',$config->parent->list->{1},
|
||||
$this->assertEquals('new', $config->parent->list->{1},
|
||||
'Section array changed.');
|
||||
|
||||
$this->assertEquals('changed',$config->parent->many->many->nests,
|
||||
$this->assertEquals('changed', $config->parent->many->many->nests,
|
||||
'Change strongly nested value.');
|
||||
|
||||
$this->assertEquals('new',$config->parent->many->many->new,
|
||||
$this->assertEquals('new', $config->parent->many->many->new,
|
||||
'Ccreate strongy nested value.');
|
||||
|
||||
$this->assertEquals('overwritten',$config->child->PropOne,
|
||||
$this->assertEquals('overwritten', $config->child->PropOne,
|
||||
'Overridden inherited property unchanged.');
|
||||
|
||||
$this->assertEquals('somethingNew',$config->child->propTwo,
|
||||
$this->assertEquals('somethingNew', $config->child->propTwo,
|
||||
'Inherited property changed.');
|
||||
|
||||
$this->assertEquals('test',$config->child->create,
|
||||
$this->assertEquals('test', $config->child->create,
|
||||
'Non-inherited property created.');
|
||||
|
||||
$this->assertInstanceOf('Zend_Config',$config->newsection,
|
||||
$this->assertInstanceOf('Zend_Config', $config->newsection,
|
||||
'New section created.');
|
||||
|
||||
$extends = $config->getExtends();
|
||||
$this->assertEquals('child',$extends['newsection'],
|
||||
$this->assertEquals('child', $extends['newsection'],
|
||||
'New inheritance created.');
|
||||
}
|
||||
|
||||
|
@ -320,7 +375,7 @@ prop2="5"
|
|||
$config->newsection = array();
|
||||
$config->newsection->p1 = "prop";
|
||||
$config->newsection->P2 = "prop";
|
||||
$config->setExtend('newsection','child');
|
||||
$config->setExtend('newsection', 'child');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue