diff --git a/extras/ConfigSchema/StringHashAdapter.php b/extras/ConfigSchema/StringHashAdapter.php index ca607b7c..ca0437e0 100644 --- a/extras/ConfigSchema/StringHashAdapter.php +++ b/extras/ConfigSchema/StringHashAdapter.php @@ -13,6 +13,10 @@ class ConfigSchema_StringHashAdapter */ public function adapt($hash, $schema) { + if (! $hash instanceof ConfigSchema_StringHash) { + $hash = new ConfigSchema_StringHash($hash); + } + if (!isset($hash['ID'])) { trigger_error('Missing key ID in string hash'); return; @@ -23,6 +27,7 @@ class ConfigSchema_StringHashAdapter // This will cause problems if we decide to support nested // namespaces, but for now it's ok. $schema->addNamespace($hash['ID'], $hash['DESCRIPTION']); + $this->_findUnused($hash); return; } @@ -36,8 +41,43 @@ class ConfigSchema_StringHashAdapter $schema->add($ns, $directive, $default, $type, $description); } + if (isset($hash['VALUE-ALIASES'])) { + $raw_value_aliases = $hash['VALUE-ALIASES']; + $value_aliases = eval("return array($raw_value_aliases);"); + $schema->addValueAliases($ns, $directive, $value_aliases); + } + if (isset($hash['ALLOWED'])) { + $raw_allowed = $hash['ALLOWED']; + $allowed = eval("return array($raw_allowed);"); + $schema->addAllowedValues($ns, $directive, $allowed); + } + + if (isset($hash['ALIASES'])) { + $raw_aliases = $hash['ALIASES']; + $aliases = preg_split('/\s*,\s*/', $raw_aliases); + foreach ($aliases as $alias) { + list($new_ns, $new_directive) = explode('.', $alias, 2); + $schema->addAlias($ns, $directive, $new_ns, $new_directive); + } + } + + $this->_findUnused($hash); } + /** + * Triggers errors for any unused keys passed in the hash; such keys + * may indicate typos, missing values, etc. + * @param $hash Instance of ConfigSchema_StringHash to check. + */ + protected function _findUnused($hash) { + $accessed = $hash->getAccessed(); + foreach ($hash as $k => $v) { + if (!isset($accessed[$k])) { + trigger_error("String hash key '$k' not used by adapter", E_USER_NOTICE); + } + } + } + } diff --git a/tests/ConfigSchema/StringHashAdapterTest.php b/tests/ConfigSchema/StringHashAdapterTest.php index d29bc347..c1b41b9f 100644 --- a/tests/ConfigSchema/StringHashAdapterTest.php +++ b/tests/ConfigSchema/StringHashAdapterTest.php @@ -9,8 +9,11 @@ class ConfigSchema_StringHashAdapterTest extends UnitTestCase function assertAdapt($input, $calls = array()) { $schema = new HTMLPurifier_ConfigSchemaMock(); - foreach ($calls as $func => $params) { - $schema->expectOnce($func, $params); + $called = array(); + foreach ($calls as $signature) { + list($func, $params) = $signature; + if (!isset($called[$func])) $called[$func] = 0; + $schema->expectAt($called[$func]++, $func, $params); } $adapter = new ConfigSchema_StringHashAdapter(); $adapter->adapt($input, $schema); @@ -25,10 +28,10 @@ class ConfigSchema_StringHashAdapterTest extends UnitTestCase 'DESCRIPTION' => "Description of default.\n", ), array( - 'add' => array( + array('add', array( 'Namespace', 'Directive', 'defaultbar', 'string', "Description of default.\n" - ) + )), ) ); } @@ -37,19 +40,98 @@ class ConfigSchema_StringHashAdapterTest extends UnitTestCase $this->assertAdapt( array( 'ID' => 'Namespace', - 'DESCRIPTION' => 'Description of namespace' + 'DESCRIPTION' => 'Description of namespace', ), array( - 'addNamespace' => array('Namespace', 'Description of namespace'), + array('addNamespace', array('Namespace', 'Description of namespace')), ) ); } - function testMissingId() { + function testValueAliases() { + $this->assertAdapt( + array( + 'ID' => 'Ns.Dir', + 'VALUE-ALIASES' => " + 'milk' => 'dairy', + 'cheese' => 'dairy', + ", + ), + array( + array('addValueAliases', array('Ns', 'Dir', array('milk' => 'dairy', 'cheese' => 'dairy'))), + ) + ); + } + + function testAllowedValues() { + $this->assertAdapt( + array( + 'ID' => 'Ns.Dir', + 'ALLOWED' => "'val1', 'val2'", + ), + array( + array('addAllowedValues', array('Ns', 'Dir', array('val1', 'val2'))), + ) + ); + } + + function testAlias() { + $this->assertAdapt( + array( + 'ID' => 'Ns.Dir', + 'ALIASES' => "Ns.Dir2, Ns2.Dir", + ), + array( + array('addAlias', array('Ns', 'Dir', 'Ns', 'Dir2')), + array('addAlias', array('Ns', 'Dir', 'Ns2', 'Dir')), + ) + ); + } + + function testCombo() { + $this->assertAdapt( + array( + 'ID' => 'Ns.Dir', + 'DEFAULT' => "'default' . 'bar'", + 'TYPE' => 'string', + 'DESCRIPTION' => "Description of default.\n", + 'VALUE-ALIASES' => " + 'milk' => 'dairy', + 'cheese' => 'dairy', + ", + 'ALLOWED' => "'val1', 'val2'", + 'ALIASES' => "Ns.Dir2, Ns2.Dir", + ), + array( + array('add', array( + 'Ns', 'Dir', 'defaultbar', 'string', + "Description of default.\n" + )), + array('addValueAliases', array('Ns', 'Dir', array('milk' => 'dairy', 'cheese' => 'dairy'))), + array('addAllowedValues', array('Ns', 'Dir', array('val1', 'val2'))), + array('addAlias', array('Ns', 'Dir', 'Ns', 'Dir2')), + array('addAlias', array('Ns', 'Dir', 'Ns2', 'Dir')), + ) + ); + } + + function testMissingIdError() { $this->expectError('Missing key ID in string hash'); $this->assertAdapt(array()); } - + function testExtraError() { + $this->expectError("String hash key 'FOOBAR' not used by adapter"); + $this->assertAdapt( + array( + 'ID' => 'Namespace', + 'DESCRIPTION' => 'Description of namespace', + 'FOOBAR' => 'Extra stuff', + ), + array( + array('addNamespace', array('Namespace', 'Description of namespace')), + ) + ); + } } diff --git a/tests/ConfigSchema/StringHashParser/Simple.txt b/tests/ConfigSchema/StringHashParser/Simple.txt index fc3c4f0f..cc91d41b 100644 --- a/tests/ConfigSchema/StringHashParser/Simple.txt +++ b/tests/ConfigSchema/StringHashParser/Simple.txt @@ -1,5 +1,8 @@ Namespace.Directive TYPE: string +CHAIN-ME: 2 --DESCRIPTION-- Multiline stuff +--FOR-WHO-- +Single multiline diff --git a/tests/ConfigSchema/StringHashParserTest.php b/tests/ConfigSchema/StringHashParserTest.php index d6e95b86..6e0c0014 100644 --- a/tests/ConfigSchema/StringHashParserTest.php +++ b/tests/ConfigSchema/StringHashParserTest.php @@ -27,7 +27,9 @@ class ConfigSchema_StringHashParserTest extends UnitTestCase $this->assertParse('Simple.txt', array( 'ID' => 'Namespace.Directive', 'TYPE' => 'string', - 'DESCRIPTION' => "Multiline\nstuff\n" + 'CHAIN-ME' => '2', + 'DESCRIPTION' => "Multiline\nstuff\n", + 'FOR-WHO' => "Single multiline\n", )); }