Imported Syokuhou into Index.

This commit is contained in:
flash 2024-10-04 22:28:27 +00:00
parent eb0f65c5ef
commit 28d65263be
18 changed files with 1924 additions and 1 deletions

View file

@ -1 +1 @@
0.2410.42042 0.2410.42227

192
src/Config/Config.php Normal file
View file

@ -0,0 +1,192 @@
<?php
// Config.php
// Created: 2023-10-20
// Updated: 2024-10-04
namespace Index\Config;
use InvalidArgumentException;
use RuntimeException;
/**
* Provides a common interface for configuration providers.
*/
interface Config {
/**
* Creates a scoped configuration instance that prepends a prefix to all names.
*
* @param non-empty-array<string> ...$prefix Parts of the desired.
* @return Config A scoped configuration instance.
*/
public function scopeTo(string ...$prefix): Config;
/**
* Gets the separator character used for scoping.
*
* @return string Separator character.
*/
public function getSeparator(): string;
/**
* Checks if configurations contains given names.
* An empty $names list will always return true.
*
* @param string|string[] $names Name or names to check for.
* @return bool Whether all given names are present or not.
*/
public function hasValues(string|array $names): bool;
/**
* Removes values with given names from the configuration, if writable.
*
* @param string|string[] $names Name or names to remove.
* @throws RuntimeException If the configuration is read only.
*/
public function removeValues(string|array $names): void;
/**
* Lists all value informations from this configuration.
*
* @param int $range Amount of items to take, 0 for all. Must be a positive integer.
* @param int $offset Amount of items to skip. Must be a positive integer. Has no effect if $range is 0.
* @throws InvalidArgumentException If $range or $offset are negative.
* @return ConfigValueInfo[] Configuration value infos.
*/
public function getAllValueInfos(int $range = 0, int $offset = 0): array;
/**
* Gets value informations for one or more items.
*
* @param string|string[] $names Name or names to retrieve.
* @return ConfigValueInfo[] Array with value informations.
*/
public function getValueInfos(string|array $names): array;
/**
* Gets value information for a single item.
*
* @param string $name Name or names to retrieve.
* @return ?ConfigValueInfo Value information, or null if not present.
*/
public function getValueInfo(string $name): ?ConfigValueInfo;
/**
* Gets multiple values of varying types at once.
*
* The format of a $specs entry can be one of the following:
* If the entry is a string:
* - The name of a value: 'value_name'
* - The name of a value followed by a requested type, separated by a colon: 'value_name:s', 'value_name:i', 'value_name:a'
* If the entry is an array, all except [0] are optional:
* - [0] follows to same format as the above described string
* - [1] is the default value to fall back on, MUST be the same type as the one specified in [0].
* - [2] is an alternative key for the output array.
*
* Available types are:
* :s - string
* :a - array
* :i - integer
* :b - boolean
* :f - float
* :d - float
*
* @param array<string|string[]> $specs Specification of what items to grab.
* @throws InvalidArgumentException If $specs is malformed.
* @return array<string, mixed> An associative array containing the retrieved values.
*/
public function getValues(array $specs): array;
/**
* Gets a single string value.
*
* @param string $name Name of the value to fetch.
* @param string $default Default value to fall back on if the value is not present.
* @return string Configuration value for $name.
*/
public function getString(string $name, string $default = ''): string;
/**
* Gets a single integer value.
*
* @param string $name Name of the value to fetch.
* @param int $default Default value to fall back on if the value is not present.
* @return int Configuration value for $name.
*/
public function getInteger(string $name, int $default = 0): int;
/**
* Gets a single floating point value.
*
* @param string $name Name of the value to fetch.
* @param float $default Default value to fall back on if the value is not present.
* @return float Configuration value for $name.
*/
public function getFloat(string $name, float $default = 0): float;
/**
* Gets a single boolean value.
*
* @param string $name Name of the value to fetch.
* @param bool $default Default value to fall back on if the value is not present.
* @return bool Configuration value for $name.
*/
public function getBoolean(string $name, bool $default = false): bool;
/**
* Gets an array value.
*
* @param string $name Name of the value to fetch.
* @param mixed[] $default Default value to fall back on if the value is not present.
* @return mixed[] Configuration value for $name.
*/
public function getArray(string $name, array $default = []): array;
/**
* Sets multiple values at once using an associative array.
*
* @param array<string, mixed> $values Values to save.
* @throws InvalidArgumentException If $values is malformed.
* @throws RuntimeException If the configuration is read only.
*/
public function setValues(array $values): void;
/**
* Sets a single string value.
*
* @param string $name Name of the value to save.
* @param string $value Value to save.
*/
public function setString(string $name, string $value): void;
/**
* Sets a single integer value.
*
* @param string $name Name of the value to save.
* @param int $value Value to save.
*/
public function setInteger(string $name, int $value): void;
/**
* Sets a single floating point value.
*
* @param string $name Name of the value to save.
* @param float $value Value to save.
*/
public function setFloat(string $name, float $value): void;
/**
* Sets a single boolean value.
*
* @param string $name Name of the value to save.
* @param bool $value Value to save.
*/
public function setBoolean(string $name, bool $value): void;
/**
* Sets an array value.
*
* @param string $name Name of the value to save.
* @param mixed[] $value Value to save.
*/
public function setArray(string $name, array $value): void;
}

View file

@ -0,0 +1,110 @@
<?php
// ConfigValueInfo.php
// Created: 2023-10-20
// Updated: 2024-10-04
namespace Index\Config;
use Stringable;
use UnexpectedValueException;
/**
* Provides a common interface for configuration values.
*/
interface ConfigValueInfo extends Stringable {
/**
* Gets the name of this configuration value.
*
* @return string Configuration value name.
*/
public function getName(): string;
/**
* Gets the type of this configuration value.
*
* @return string Configuration value type name.
*/
public function getType(): string;
/**
* Checks whether the value is a string.
*
* @return bool True if the value is a string.
*/
public function isString(): bool;
/**
* Checks whether the value is an integer.
*
* @return bool True if the value is an integer.
*/
public function isInteger(): bool;
/**
* Checks whether the value is a floating point number.
*
* @return bool True if the value is a floating point number.
*/
public function isFloat(): bool;
/**
* Checks whether the value is a boolean.
*
* @return bool True if the value is a boolean.
*/
public function isBoolean(): bool;
/**
* Checks whether the value is an array.
*
* @return bool True if the value is an array.
*/
public function isArray(): bool;
/**
* Gets the raw value without any type validation.
*
* @return mixed The configuration value.
*/
public function getValue(): mixed;
/**
* Ensures the value is a string and returns the value.
*
* @throws UnexpectedValueException If the value is not a string.
* @return string String configuration value.
*/
public function getString(): string;
/**
* Ensures the value is an integer and returns the value.
*
* @throws UnexpectedValueException If the value is not an integer.
* @return int Integer configuration value.
*/
public function getInteger(): int;
/**
* Ensures the value is a floating point number and returns the value.
*
* @throws UnexpectedValueException If the value is not a floating point number.
* @return float Floating point number configuration value.
*/
public function getFloat(): float;
/**
* Ensures the value is a boolean and returns the value.
*
* @throws UnexpectedValueException If the value is not a boolean.
* @return bool Boolean configuration value.
*/
public function getBoolean(): bool;
/**
* Ensures the value is an array and returns the value.
*
* @throws UnexpectedValueException If the value is not an array.
* @return mixed[] Array configuration value.
*/
public function getArray(): array;
}

290
src/Config/Db/DbConfig.php Normal file
View file

@ -0,0 +1,290 @@
<?php
// DbConfig.php
// Created: 2023-10-20
// Updated: 2024-10-04
namespace Index\Config\Db;
use InvalidArgumentException;
use Index\Config\{Config,GetValueInfoTrait,GetValuesTrait,MutableConfigTrait,ScopedConfig};
use Index\Data\{DbConnection,DbStatementCache,DbTools};
/**
* Provides a configuration based on a {@see DbConnection} instance.
*
* @todo provide table name in constructor
* @todo scan for vendor specific queries and generalise them
* @todo getValues() parsing should probably be done external so it can be reused
*/
class DbConfig implements Config {
use MutableConfigTrait, GetValueInfoTrait, GetValuesTrait;
private DbStatementCache $cache;
/** @var array<string, DbConfigValueInfo> */
private array $values = [];
/** @var string[] */
private array $extraFieldNames;
/** @var mixed[] */
private array $extraFieldValues;
/**
* @param DbConnection $dbConn
* @param string $tableName
* @param string $nameField
* @param string $valueField
* @param array<string, mixed> $extraFields
*/
public function __construct(
DbConnection $dbConn,
private string $tableName,
private string $nameField = 'config_name',
private string $valueField = 'config_value',
array $extraFields = [],
) {
$this->cache = new DbStatementCache($dbConn);
$this->extraFieldNames = array_keys($extraFields);
$this->extraFieldValues = array_values($extraFields);
}
public static function validateName(string $name): bool {
$parts = explode('.', $name);
foreach($parts as $part) {
if($part === '' || trim($part) !== $part)
return false;
if(preg_match('#^([a-z][a-zA-Z0-9_]+)$#', $part) !== 1)
return false;
}
return true;
}
/**
* Resets value cache.
*/
public function reset(): void {
$this->values = [];
}
/**
* Unloads specifics items from the local cache.
*
* @param string|string[] $names Names of values to unload.
*/
public function unload(string|array $names): void {
if(empty($names))
return;
if(is_string($names))
$names = [$names];
foreach($names as $name)
unset($this->values[$name]);
}
public function getSeparator(): string {
return '.';
}
public function scopeTo(string ...$prefix): Config {
return new ScopedConfig($this, $prefix);
}
public function hasValues(string|array $names): bool {
if(empty($names))
return true;
if(is_string($names))
$names = [$names];
$cachedNames = array_keys($this->values);
$names = array_diff($names, $cachedNames);
if(!empty($names)) {
// array_diff preserves keys, the for() later would fuck up without it
$names = array_values($names);
$nameCount = count($names);
$query = sprintf(
'SELECT COUNT(*) FROM %s WHERE %s IN (%s)',
$this->tableName, $this->nameField,
DbTools::prepareListString($nameCount)
);
foreach($this->extraFieldNames as $extraFieldName)
$query .= sprintf(' AND %s = ?', $extraFieldName);
$stmt = $this->cache->get($query);
$args = 0;
foreach($names as $name)
$stmt->addParameter(++$args, $name);
foreach($this->extraFieldValues as $extraFieldValue)
$stmt->addParameter(++$args, $extraFieldValue);
$stmt->execute();
$result = $stmt->getResult();
if($result->next())
return $result->getInteger(0) >= $nameCount;
}
return true;
}
public function removeValues(string|array $names): void {
if(empty($names))
return;
if(is_string($names))
$names = [$names];
foreach($names as $name)
unset($this->values[$name]);
$nameCount = count($names);
$query = sprintf(
'DELETE FROM %s WHERE %s IN (%s)',
$this->tableName, $this->nameField,
DbTools::prepareListString($nameCount)
);
foreach($this->extraFieldNames as $extraFieldName)
$query .= sprintf(' AND %s = ?', $extraFieldName);
$stmt = $this->cache->get($query);
$args = 0;
foreach($names as $name)
$stmt->addParameter(++$args, $name);
foreach($this->extraFieldValues as $extraFieldValue)
$stmt->addParameter(++$args, $extraFieldValue);
$stmt->execute();
}
public function getAllValueInfos(int $range = 0, int $offset = 0): array {
$this->reset();
$infos = [];
$hasRange = $range !== 0;
$query = sprintf('SELECT %s, %s FROM %s', $this->nameField, $this->valueField, $this->tableName);
foreach($this->extraFieldNames as $i => $extraFieldName)
$query .= sprintf(' %s %s = ?', $i < 1 ? 'WHERE' : 'AND', $extraFieldName);
if($hasRange) {
if($range < 0)
throw new InvalidArgumentException('$range must be a positive integer');
if($offset < 0)
throw new InvalidArgumentException('$offset must be greater than zero if a range is specified');
$query .= ' LIMIT ? OFFSET ?';
}
$stmt = $this->cache->get($query);
$args = 0;
foreach($this->extraFieldValues as $extraFieldValue)
$stmt->addParameter(++$args, $extraFieldValue);
if($hasRange) {
$stmt->addParameter(++$args, $range);
$stmt->addParameter(++$args, $offset);
}
$stmt->execute();
$result = $stmt->getResult();
while($result->next()) {
$name = $result->getString(0);
$infos[] = $this->values[$name] = new DbConfigValueInfo($result);
}
return $infos;
}
public function getValueInfos(string|array $names): array {
if(empty($names))
return [];
if(is_string($names))
$names = [$names];
$infos = [];
$skip = [];
foreach($names as $name)
if(array_key_exists($name, $this->values)) {
$infos[] = $this->values[$name];
$skip[] = $name;
}
$names = array_diff($names, $skip);
if(!empty($names)) {
// array_diff preserves keys, the for() later would fuck up without it
$names = array_values($names);
$nameCount = count($names);
$query = sprintf(
'SELECT %s, %s FROM %s WHERE %s IN (%s)',
$this->nameField, $this->valueField, $this->tableName, $this->nameField,
DbTools::prepareListString($nameCount)
);
foreach($this->extraFieldNames as $extraFieldName)
$query .= sprintf(' AND %s = ?', $extraFieldName);
$stmt = $this->cache->get($query);
$args = 0;
foreach($names as $name)
$stmt->addParameter(++$args, $name);
foreach($this->extraFieldValues as $extraFieldValue)
$stmt->addParameter(++$args, $extraFieldValue);
$stmt->execute();
$result = $stmt->getResult();
while($result->next()) {
$name = $result->getString(0);
$infos[] = $this->values[$name] = new DbConfigValueInfo($result);
}
}
return $infos;
}
public function setValues(array $values): void {
if(empty($values))
return;
$fields = [$this->nameField, $this->valueField];
if(count($this->extraFieldNames) > 0)
$fields = array_merge($fields, $this->extraFieldNames);
$fieldCount = count($fields);
$fields = implode(', ', $fields);
$stmt = $this->cache->get(sprintf(
'INSERT INTO %s (%s) VALUES (%s)',
$this->tableName, $fields,
DbTools::prepareListString($fieldCount)
));
foreach($values as $name => $value) {
if(!self::validateName($name))
throw new InvalidArgumentException('invalid name encountered in $values');
if(is_array($value)) {
foreach($value as $entry)
if(!is_scalar($entry))
throw new InvalidArgumentException('an array value in $values contains a non-scalar type');
} elseif(!is_scalar($value))
throw new InvalidArgumentException('invalid value type encountered in $values');
$this->removeValues($name);
$args = 0;
$stmt->addParameter(++$args, $name);
$stmt->addParameter(++$args, serialize($value));
foreach($this->extraFieldValues as $extraFieldValue)
$stmt->addParameter(++$args, $extraFieldValue);
$stmt->execute();
}
}
}

View file

@ -0,0 +1,94 @@
<?php
// DbConfigValueInfo.php
// Created: 2023-10-20
// Updated: 2024-10-04
namespace Index\Config\Db;
use UnexpectedValueException;
use Index\Config\ConfigValueInfo;
use Index\Data\DbResult;
/**
* Provides information about a databased configuration value.
*/
class DbConfigValueInfo implements ConfigValueInfo {
private string $name;
private string $value;
/**
* @param DbResult $result Database result for this config value.
*/
public function __construct(DbResult $result) {
$this->name = $result->getString(0);
$this->value = $result->getString(1);
}
public function getName(): string {
return $this->name;
}
public function getType(): string {
return match($this->value[0]) {
'b' => 'bool',
'a' => 'array',
'd' => 'float',
'i' => 'int',
's' => 'string',
default => 'unknown',
};
}
public function isBoolean(): bool { return $this->value[0] === 'b'; }
public function isArray(): bool { return $this->value[0] === 'a'; }
public function isFloat(): bool { return $this->value[0] === 'd'; }
public function isInteger(): bool { return $this->value[0] === 'i'; }
public function isString(): bool { return $this->value[0] === 's'; }
public function getValue(): mixed {
return unserialize($this->value);
}
public function getString(): string {
$value = $this->getValue();
if(!is_string($value))
throw new UnexpectedValueException('Value is not a string.');
return $value;
}
public function getInteger(): int {
$value = $this->getValue();
if(!is_int($value))
throw new UnexpectedValueException('Value is not an integer.');
return $value;
}
public function getFloat(): float {
$value = $this->getValue();
if(!is_float($value))
throw new UnexpectedValueException('Value is not a floating point number.');
return $value;
}
public function getBoolean(): bool {
$value = $this->getValue();
if(!is_bool($value))
throw new UnexpectedValueException('Value is not a boolean.');
return $value;
}
public function getArray(): array {
$value = $this->getValue();
if(!is_array($value))
throw new UnexpectedValueException('Value is not an array.');
return $value;
}
public function __toString(): string {
$value = $this->getValue();
if(is_array($value))
return implode(', ', $value);
return (string)$value; // @phpstan-ignore-line dude trust me
}
}

144
src/Config/Fs/FsConfig.php Normal file
View file

@ -0,0 +1,144 @@
<?php
// FsConfig.php
// Created: 2023-10-20
// Updated: 2024-10-04
namespace Index\Config\Fs;
use InvalidArgumentException;
use RuntimeException;
use Index\Config\{Config,GetValueInfoTrait,GetValuesTrait,ImmutableConfigTrait,ScopedConfig};
/**
* Provides a configuration in string based format.
*/
class FsConfig implements Config {
use ImmutableConfigTrait, GetValueInfoTrait, GetValuesTrait;
/**
* @param array<string, FsConfigValueInfo> $values
*/
public function __construct(private array $values) {}
public function getSeparator(): string {
return ':';
}
public function scopeTo(string ...$prefix): Config {
return new ScopedConfig($this, $prefix);
}
public function hasValues(string|array $names): bool {
if(is_string($names))
return array_key_exists($names, $this->values);
foreach($names as $name)
if(!array_key_exists($name, $this->values))
return false;
return true;
}
public function getAllValueInfos(int $range = 0, int $offset = 0): array {
if($range === 0)
return array_values($this->values);
if($range < 0)
throw new InvalidArgumentException('$range must be a positive integer');
if($offset < 0)
throw new InvalidArgumentException('$offset must be greater than zero if a range is specified');
return array_slice($this->values, $offset, $range);
}
public function getValueInfos(string|array $names): array {
if(is_string($names))
return array_key_exists($names, $this->values) ? [$this->values[$names]] : [];
$infos = [];
foreach($names as $name)
if(array_key_exists($name, $this->values))
$infos[] = $this->values[$name];
return $infos;
}
/**
* Creates an instance of FsConfig from an array of lines.
*
* @param string[] $lines Config lines.
* @return FsConfig
*/
public static function fromLines(array $lines): self {
$values = [];
foreach($lines as $line) {
$line = trim($line);
if($line === '' || $line[0] === '#' || $line[0] === ';')
continue;
$info = new FsConfigValueInfo(...explode(' ', $line, 2));
$values[$info->getName()] = $info;
}
return new FsConfig($values);
}
/**
* Creates an instance of FsConfig from a string.
*
* @param string $lines Config lines.
* @param non-empty-string $newLine Line separator character.
* @return FsConfig
*/
public static function fromString(string $lines, string $newLine = "\n"): self {
return self::fromLines(explode($newLine, $lines));
}
/**
* Creates an instance of FsConfig from a file.
*
* @param string $path Config file path.
* @throws InvalidArgumentException If $path does not exist or could not be opened.
* @return FsConfig
*/
public static function fromFile(string $path): self {
if(!is_file($path))
throw new InvalidArgumentException('$path does not exist');
$handle = fopen($path, 'rb');
if($handle === false)
throw new RuntimeException('could not open a file handle from $path');
try {
return self::fromStream($handle);
} finally {
fclose($handle);
}
}
/**
* Creates an instance of FsConfig from a readable stream.
*
* @param resource $stream Config file stream.
* @throws InvalidArgumentException If $stream is not a .
* @return FsConfig
*/
public static function fromStream(mixed $stream): self {
if(!is_resource($stream))
throw new InvalidArgumentException('$stream must be a resource');
$values = [];
while(($line = fgets($stream)) !== false) {
$line = trim($line);
if($line === '' || $line[0] === '#' || $line[0] === ';')
continue;
$info = new FsConfigValueInfo(...explode(' ', $line, 2));
$values[$info->getName()] = $info;
}
return new FsConfig($values);
}
}

View file

@ -0,0 +1,83 @@
<?php
// FsConfigValueInfo.php
// Created: 2023-10-20
// Updated: 2024-10-04
namespace Index\Config\Fs;
use Index\Config\ConfigValueInfo;
/**
* Value info for file configs.
*/
class FsConfigValueInfo implements ConfigValueInfo {
private string $name;
private string $value;
/**
* @param string $name Name of the config value.
* @param string $value Value of the config value.
*/
public function __construct(string $name, string $value = '') {
$this->name = $name;
$this->value = trim($value);
}
public function getName(): string {
return $this->name;
}
public function getType(): string {
// SharpChat config format is just all strings and casts on demand
return 'string';
}
public function isString(): bool {
return true;
}
public function isInteger(): bool {
return true;
}
public function isFloat(): bool {
return true;
}
public function isBoolean(): bool {
return true;
}
public function isArray(): bool {
return true;
}
public function getValue(): mixed {
return $this->value;
}
public function getString(): string {
return $this->value;
}
public function getInteger(): int {
return (int)$this->value;
}
public function getFloat(): float {
return (float)$this->value;
}
public function getBoolean(): bool {
return $this->value !== '0'
&& strcasecmp($this->value, 'false') !== 0;
}
public function getArray(): array {
return explode(' ', $this->value);
}
public function __toString(): string {
return $this->value;
}
}

View file

@ -0,0 +1,41 @@
<?php
// GetValueInfoTrait.php
// Created: 2023-10-20
// Updated: 2024-10-04
namespace Index\Config;
/**
* Provides implementations for things that are essentially macros for {@see Config::getValueInfos}.
*/
trait GetValueInfoTrait {
public function getValueInfo(string $name): ?ConfigValueInfo {
$infos = $this->getValueInfos($name);
return empty($infos) ? null : $infos[0];
}
public function getString(string $name, string $default = ''): string {
$valueInfo = $this->getValueInfo($name);
return $valueInfo?->isString() ? $valueInfo->getString() : $default;
}
public function getInteger(string $name, int $default = 0): int {
$valueInfo = $this->getValueInfo($name);
return $valueInfo?->isInteger() ? $valueInfo->getInteger() : $default;
}
public function getFloat(string $name, float $default = 0): float {
$valueInfo = $this->getValueInfo($name);
return $valueInfo?->isFloat() ? $valueInfo->getFloat() : $default;
}
public function getBoolean(string $name, bool $default = false): bool {
$valueInfo = $this->getValueInfo($name);
return $valueInfo?->isBoolean() ? $valueInfo->getBoolean() : $default;
}
public function getArray(string $name, array $default = []): array {
$valueInfo = $this->getValueInfo($name);
return $valueInfo?->isArray() ? $valueInfo->getArray() : $default;
}
}

View file

@ -0,0 +1,97 @@
<?php
// GetValuesTrait.php
// Created: 2023-10-20
// Updated: 2024-10-04
namespace Index\Config;
use InvalidArgumentException;
/**
* Provides implementation for {@see Config::getValues} based on {@see Config::getValueInfos}.
*/
trait GetValuesTrait {
/**
* Format described in {@see Config::getValues}.
*
* @param array<string|string[]> $specs
* @throws InvalidArgumentException If $specs contains an invalid entry.
* @return array<string, mixed>
*/
public function getValues(array $specs): array {
$names = [];
$evald = [];
foreach($specs as $key => $spec) {
if(is_string($spec)) {
$name = $spec;
$default = null;
$alias = null;
} elseif(is_array($spec) && !empty($spec)) {
$name = $spec[0];
$default = $spec[1] ?? null;
$alias = $spec[2] ?? null;
} else
throw new InvalidArgumentException('$specs array contains an invalid entry');
$nameLength = strlen($name);
if($nameLength > 3 && ($colon = strrpos($name, ':')) === $nameLength - 2) {
$type = substr($name, $colon + 1, 1);
$name = substr($name, 0, $colon);
} else $type = '';
$names[] = $name;
$evald[$key] = [
'name' => $name,
'type' => $type,
'default' => $default,
'alias' => $alias,
];
}
$infos = $this->getValueInfos($names);
$results = [];
foreach($evald as $spec) {
foreach($infos as $infoTest)
if($infoTest->getName() === $spec['name']) {
$info = $infoTest;
break;
}
$resultName = $spec['alias'] ?? $spec['name'];
if(!isset($info)) {
$defaultValue = $spec['default'] ?? null;
if($spec['type'] !== '')
settype($defaultValue, match($spec['type']) {
's' => 'string',
'a' => 'array',
'i' => 'int',
'b' => 'bool',
'f' => 'float',
'd' => 'double',
default => throw new InvalidArgumentException(sprintf('invalid type letter encountered: "%s"', $spec['type'])),
});
$results[$resultName] = $defaultValue;
continue;
}
$results[$resultName] = match($spec['type']) {
's' => $info->getString(),
'a' => $info->getArray(),
'i' => $info->getInteger(),
'b' => $info->getBoolean(),
'f' => $info->getFloat(),
'd' => $info->getFloat(),
'' => $info->getValue(),
default => throw new InvalidArgumentException('unknown type encountered in $specs'),
};
unset($info);
}
return $results;
}
}

View file

@ -0,0 +1,41 @@
<?php
// ImmutableConfigTrait.php
// Created: 2023-10-20
// Updated: 2024-10-04
namespace Index\Config;
use RuntimeException;
/**
* Intercepts mutable methods required to be implemented by {@see Config} and returns exceptions.
*/
trait ImmutableConfigTrait {
public function removeValues(string|array $names): void {
throw new RuntimeException('this configuration is read only');
}
public function setValues(array $values): void {
throw new RuntimeException('this configuration is read only');
}
public function setString(string $name, string $value): void {
throw new RuntimeException('this configuration is read only');
}
public function setInteger(string $name, int $value): void {
throw new RuntimeException('this configuration is read only');
}
public function setFloat(string $name, float $value): void {
throw new RuntimeException('this configuration is read only');
}
public function setBoolean(string $name, bool $value): void {
throw new RuntimeException('this configuration is read only');
}
public function setArray(string $name, array $value): void {
throw new RuntimeException('this configuration is read only');
}
}

View file

@ -0,0 +1,31 @@
<?php
// MutableConfigTrait.php
// Created: 2023-10-20
// Updated: 2024-10-04
namespace Index\Config;
/**
* Defines set aliases so you don't have to.
*/
trait MutableConfigTrait {
public function setString(string $name, string $value): void {
$this->setValues([$name => $value]);
}
public function setInteger(string $name, int $value): void {
$this->setValues([$name => $value]);
}
public function setFloat(string $name, float $value): void {
$this->setValues([$name => $value]);
}
public function setBoolean(string $name, bool $value): void {
$this->setValues([$name => $value]);
}
public function setArray(string $name, array $value): void {
$this->setValues([$name => $value]);
}
}

View file

@ -0,0 +1,69 @@
<?php
// NullConfig.php
// Created: 2023-10-20
// Updated: 2024-10-04
namespace Index\Config\Null;
use Index\Config\{Config,ConfigValueInfo,GetValuesTrait};
/**
* Provides a black hole configuration that will always return the default values.
*/
class NullConfig implements Config {
use GetValuesTrait;
public function __construct() {}
public function getSeparator(): string {
return "\0";
}
public function scopeTo(string ...$prefix): Config {
return $this;
}
public function hasValues(string|array $names): bool {
return is_array($names) && empty($names);
}
public function getAllValueInfos(int $range = 0, int $offset = 0): array {
return [];
}
public function getValueInfos(string|array $names): array {
return [];
}
public function getValueInfo(string $name): ?ConfigValueInfo {
return null;
}
public function getString(string $name, string $default = ''): string {
return $default;
}
public function getInteger(string $name, int $default = 0): int {
return $default;
}
public function getFloat(string $name, float $default = 0): float {
return $default;
}
public function getBoolean(string $name, bool $default = false): bool {
return $default;
}
public function getArray(string $name, array $default = []): array {
return $default;
}
public function removeValues(string|array $names): void {}
public function setValues(array $values): void {}
public function setString(string $name, string $value): void {}
public function setInteger(string $name, int $value): void {}
public function setFloat(string $name, float $value): void {}
public function setBoolean(string $name, bool $value): void {}
public function setArray(string $name, array $value): void {}
}

156
src/Config/ScopedConfig.php Normal file
View file

@ -0,0 +1,156 @@
<?php
// ScopedConfig.php
// Created: 2023-10-20
// Updated: 2024-10-04
namespace Index\Config;
use InvalidArgumentException;
/**
* Provides a scoped configuration instead.
*/
class ScopedConfig implements Config {
private Config $config;
private string $prefix;
/** @var non-empty-array<string> */
private array $prefixRaw;
private int $prefixLength;
/** @param string[] $prefixRaw */
public function __construct(Config $config, array $prefixRaw) {
if(empty($prefixRaw))
throw new InvalidArgumentException('$prefix may not be empty');
$scopeChar = $config->getSeparator();
$prefix = implode($scopeChar, $prefixRaw) . $scopeChar;
$this->config = $config;
$this->prefix = $prefix;
$this->prefixRaw = $prefixRaw;
$this->prefixLength = strlen($prefix);
}
/**
* @param string|string[] $names
* @return string[]
*/
private function prefixNames(string|array $names): array {
if(is_string($names))
return [$this->prefix . $names];
foreach($names as $key => $name)
$names[$key] = $this->prefix . $name;
return $names;
}
private function prefixName(string $name): string {
return $this->prefix . $name;
}
public function scopeTo(string ...$prefix): Config {
return $this->config->scopeTo(...array_merge($this->prefixRaw, $prefix));
}
public function getSeparator(): string {
return $this->config->getSeparator();
}
public function hasValues(string|array $names): bool {
return $this->config->hasValues($this->prefixNames($names));
}
public function removeValues(string|array $names): void {
$this->config->removeValues($this->prefixNames($names));
}
public function getAllValueInfos(int $range = 0, int $offset = 0): array {
$infos = $this->config->getAllValueInfos($range, $offset);
foreach($infos as $key => $info)
$infos[$key] = new ScopedConfigValueInfo($info, $this->prefixLength);
return $infos;
}
public function getValueInfos(string|array $names): array {
$infos = $this->config->getValueInfos($this->prefixNames($names));
foreach($infos as $key => $info)
$infos[$key] = new ScopedConfigValueInfo($info, $this->prefixLength);
return $infos;
}
public function getValueInfo(string $name): ?ConfigValueInfo {
$info = $this->config->getValueInfo($this->prefixName($name));
if($info !== null)
$info = new ScopedConfigValueInfo($info, $this->prefixLength);
return $info;
}
public function getValues(array $specs): array {
foreach($specs as $key => $spec) {
if(is_string($spec))
$specs[$key] = $this->prefixName($spec);
elseif(is_array($spec) && !empty($spec))
$specs[$key][0] = $this->prefixName($spec[0]);
else
throw new InvalidArgumentException('$specs array contains an invalid entry');
}
$results = [];
foreach($this->config->getValues($specs) as $name => $result)
// prefix removal should probably be done with a whitelist of sorts
$results[str_starts_with($name, $this->prefix) ? substr($name, $this->prefixLength) : $name] = $result;
return $results;
}
public function getString(string $name, string $default = ''): string {
return $this->config->getString($this->prefixName($name), $default);
}
public function getInteger(string $name, int $default = 0): int {
return $this->config->getInteger($this->prefixName($name), $default);
}
public function getFloat(string $name, float $default = 0): float {
return $this->config->getFloat($this->prefixName($name), $default);
}
public function getBoolean(string $name, bool $default = false): bool {
return $this->config->getBoolean($this->prefixName($name), $default);
}
public function getArray(string $name, array $default = []): array {
return $this->config->getArray($this->prefixName($name), $default);
}
public function setValues(array $values): void {
if(empty($values))
return;
$prefixed = [];
foreach($values as $name => $value)
$prefixed[$this->prefixName($name)] = $value;
$this->config->setValues($values);
}
public function setString(string $name, string $value): void {
$this->config->setString($this->prefixName($name), $value);
}
public function setInteger(string $name, int $value): void {
$this->config->setInteger($this->prefixName($name), $value);
}
public function setFloat(string $name, float $value): void {
$this->config->setFloat($this->prefixName($name), $value);
}
public function setBoolean(string $name, bool $value): void {
$this->config->setBoolean($this->prefixName($name), $value);
}
public function setArray(string $name, array $value): void {
$this->config->setArray($this->prefixName($name), $value);
}
}

View file

@ -0,0 +1,85 @@
<?php
// ScopedConfigValueInfo.php
// Created: 2023-10-20
// Updated: 2024-10-04
namespace Index\Config;
/**
* Provides information about a scoped configuration value.
*/
class ScopedConfigValueInfo implements ConfigValueInfo {
/**
* @param ConfigValueInfo $info Base config value info instance.
* @param int $prefixLength Length of the prefix.
*/
public function __construct(
private ConfigValueInfo $info,
private int $prefixLength
) {}
public function getName(): string {
return substr($this->info->getName(), $this->prefixLength);
}
/**
* Gets the real name of the configuration value, without removing the prefix.
*
* @return string Unprefixed configuration value.
*/
public function getRealName(): string {
return $this->info->getName();
}
public function getType(): string {
return $this->info->getType();
}
public function isString(): bool {
return $this->info->isString();
}
public function isInteger(): bool {
return $this->info->isInteger();
}
public function isFloat(): bool {
return $this->info->isFloat();
}
public function isBoolean(): bool {
return $this->info->isBoolean();
}
public function isArray(): bool {
return $this->info->isArray();
}
public function getValue(): mixed {
return $this->info->getValue();
}
public function getString(): string {
return $this->info->getString();
}
public function getInteger(): int {
return $this->info->getInteger();
}
public function getFloat(): float {
return $this->info->getFloat();
}
public function getBoolean(): bool {
return $this->info->getBoolean();
}
public function getArray(): array {
return $this->info->getArray();
}
public function __toString(): string {
return (string)$this->info;
}
}

182
tests/DbConfigTest.php Normal file
View file

@ -0,0 +1,182 @@
<?php
// DbConfigTest.php
// Created: 2023-10-20
// Updated: 2024-10-04
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use Index\Config\{GetValueInfoTrait,GetValuesTrait,MutableConfigTrait};
use Index\Config\Db\{DbConfig,DbConfigValueInfo};
use Index\Data\{DbConnection,DbTools};
#[CoversClass(DbConfig::class)]
#[CoversClass(DbConfigValueInfo::class)]
#[CoversClass(MutableConfigTrait::class)]
#[CoversClass(GetValueInfoTrait::class)]
#[CoversClass(GetValuesTrait::class)]
final class DbConfigTest extends TestCase {
private DbConnection $dbConn;
private DbConfig $config;
private const VALUES = [
'private.allow_password_reset' => 'b:1;',
'private.enable' => 'b:0;',
'private.msg' => 's:71:"Things are happening. Check back later for something new... eventually.";',
'private.perm.cat' => 's:4:"user";',
'private.perm.val' => 'i:1;',
'site.desc' => 's:38:"The internet\'s last convenience store.";',
'site.ext_logo' => 's:51:"https://static.flash.moe/images/flashii-logo-v3.png";',
'site.name' => 's:5:"Edgii";',
'site.social.bsky' => 's:36:"https://bsky.app/profile/flashii.net";',
'site.url' => 's:18:"https://edgii.net/";',
'test.array' => 'a:5:{i:0;i:1234;i:1;d:56.789;i:2;s:6:"Mewow!";i:3;b:1;i:4;s:4:"jeff";}',
'test.bool' => 'b:1;',
'test.float' => 'd:9876.4321;',
'test.int' => 'i:243230;',
];
private const USER_VALUES = [
'mobile.left_handed' => ['b:0;', 'b:1;'],
'profile.allow_indexing' => ['b:1;', 'b:0;'],
];
protected function setUp(): void {
$this->dbConn = DbTools::create('sqlite::memory:');
$this->dbConn->execute('CREATE TABLE skh_config (config_name TEXT NOT NULL COLLATE NOCASE, config_value BLOB NOT NULL, PRIMARY KEY (config_name))');
$this->dbConn->execute('CREATE TABLE skh_user_settings (user_id INTEGER NOT NULL, setting_name TEXT NOT NULL COLLATE NOCASE, setting_value BLOB NOT NULL, PRIMARY KEY (user_id, setting_name))');
$stmt = $this->dbConn->prepare('INSERT INTO skh_config (config_name, config_value) VALUES (?, ?)');
foreach(self::VALUES as $name => $value) {
$stmt->addParameter(1, $name);
$stmt->addParameter(2, $value);
$stmt->execute();
}
$stmt = $this->dbConn->prepare('INSERT INTO skh_user_settings (user_id, setting_name, setting_value) VALUES (?, ?, ?)');
for($i = 1; $i <= 10; ++$i)
foreach(self::USER_VALUES as $name => $value) {
$stmt->addParameter(1, $i);
$stmt->addParameter(2, $name);
$stmt->addParameter(3, $value[$i % 2]);
$stmt->execute();
}
$this->config = new DbConfig($this->dbConn, 'skh_config');
}
public function testScoping(): void {
$this->assertEquals('user', $this->config->getString('private.perm.cat'));
$this->assertEquals('Edgii', $this->config->getString('site.name'));
$scoped = $this->config->scopeTo('private', 'perm');
$this->assertEquals('user', $scoped->getString('cat'));
}
public function testHasValues(): void {
// hasValues should always return true when the list is empty
$this->assertTrue($this->config->hasValues([]));
$this->assertFalse($this->config->hasValues('meow'));
$this->assertTrue($this->config->hasValues('site.desc'));
$this->assertTrue($this->config->hasValues(['site.ext_logo', 'site.url']));
$this->assertFalse($this->config->hasValues(['site.ext_logo', 'site.url', 'site.gun']));
}
public function testGetAllValueInfos(): void {
$all = $this->config->getAllValueInfos();
$expected = array_keys(self::VALUES);
$values = [];
foreach($all as $info)
$values[] = $info->getName();
$this->assertEquals($expected, $values);
$subset = $this->config->getAllValueInfos(2, 3);
$expected = [
'private.perm.cat',
'private.perm.val',
];
$values = [];
foreach($subset as $info)
$values[] = $info->getName();
$this->assertEquals($expected, $values);
}
public function testGetValues(): void {
$this->assertNull($this->config->getValueInfo('doesnotexist'));
$scoped = $this->config->scopeTo('private', 'perm');
$expected = ['private.perm.cat' => 'user', 'private.perm.val' => 1];
$values = [];
$valueInfos = $scoped->getValueInfos(['cat', 'val', 'poop']);
foreach($valueInfos as $valueInfo)
$values[$valueInfo->getRealName()] = $valueInfo->getValue();
$this->assertEquals($expected, $values);
$scoped = $this->config->scopeTo('site')->scopeTo('social');
$expected = [
'bsky' => 'https://bsky.app/profile/flashii.net',
'bsky_show' => true,
'twitter' => '',
'twitterShow' => false,
];
$values = $scoped->getValues([
'bsky',
['bsky_show:b', true],
'twitter:s',
['twitter_show:b', false, 'twitterShow'],
]);
$this->assertEquals($expected, $values);
$this->assertEquals('', $this->config->getString('none.string'));
$this->assertEquals('test', $this->config->getString('none.string', 'test'));
$this->assertEquals('https://edgii.net/', $this->config->getString('site.url'));
$this->assertEquals(0, $this->config->getInteger('site.url'));
$this->assertEquals(0, $this->config->getInteger('none.int'));
$this->assertEquals(10, $this->config->getInteger('none.int', 10));
$this->assertEquals(243230, $this->config->getInteger('test.int'));
$this->assertEquals('', $this->config->getString('test.int'));
$this->assertEquals(0, $this->config->getFloat('none.float'));
$this->assertEquals(0.1, $this->config->getFloat('none.float', 0.1));
$this->assertEquals(9876.4321, $this->config->getFloat('test.float'));
$this->assertEmpty($this->config->getArray('test.float'));
$this->assertEquals(false, $this->config->getBoolean('none.bool'));
$this->assertEquals(true, $this->config->getBoolean('none.bool', true));
$this->assertEquals(true, $this->config->getBoolean('test.bool'));
$this->assertEquals(false, $this->config->getBoolean('private.msg'));
$this->assertEquals(0, $this->config->getFloat('test.bool'));
$this->assertEmpty($this->config->getArray('none.array'));
$this->assertEquals(['de', 'het', 'een'], $this->config->getArray('none.array', ['de', 'het', 'een']));
$this->assertEquals([1234, 56.789, 'Mewow!', true, 'jeff'], $this->config->getArray('test.array'));
$this->assertEquals(false, $this->config->getBoolean('test.array'));
}
public function testNameValidation(): void {
$this->assertTrue(DbConfig::validateName('th1s.iS.vAL1d'));
$this->assertFalse(DbConfig::validateName(''));
$this->assertFalse(DbConfig::validateName('this..is.not.valid'));
$this->assertFalse(DbConfig::validateName('this..is.not.valid'));
$this->assertFalse(DbConfig::validateName('First.may.Not.be.uppercase'));
}
public function testUserSettings(): void {
for($i = 1; $i <= 10; ++$i) {
$config = new DbConfig($this->dbConn, 'skh_user_settings', 'setting_name', 'setting_value', ['user_id' => $i]);
foreach(self::USER_VALUES as $name => $value)
$this->assertEquals(unserialize($value[$i % 2]), $config->getBoolean($name));
}
}
}

191
tests/FsConfigTest.php Normal file
View file

@ -0,0 +1,191 @@
<?php
// FsConfigTest.php
// Created: 2023-10-20
// Updated: 2024-10-04
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use Index\Config\{GetValueInfoTrait,GetValuesTrait,ImmutableConfigTrait};
use Index\Config\Fs\{FsConfig,FsConfigValueInfo};
#[CoversClass(FsConfig::class)]
#[CoversClass(FsConfigValueInfo::class)]
#[CoversClass(ImmutableConfigTrait::class)]
#[CoversClass(GetValueInfoTrait::class)]
#[CoversClass(GetValuesTrait::class)]
final class FsConfigTest extends TestCase {
public function testImmutableRemove(): void {
$this->expectException(\RuntimeException::class);
FsConfig::fromString('test value')->removeValues('test');
}
public function testImmutableSetString(): void {
$this->expectException(\RuntimeException::class);
FsConfig::fromString('test value')->setString('test', 'the');
}
public function testImmutableSetInteger(): void {
$this->expectException(\RuntimeException::class);
FsConfig::fromString('test 1234')->setInteger('test', 5678);
}
public function testImmutableSetFloat(): void {
$this->expectException(\RuntimeException::class);
FsConfig::fromString('test 56.78')->setFloat('test', 12.34);
}
public function testImmutableSetBoolean(): void {
$this->expectException(\RuntimeException::class);
FsConfig::fromString('test true')->setBoolean('test', false);
}
public function testImmutableSetArray(): void {
$this->expectException(\RuntimeException::class);
FsConfig::fromString('test words words words')->setArray('test', ['meow', 'meow', 'meow']);
}
public function testImmutableSetValues(): void {
$this->expectException(\RuntimeException::class);
FsConfig::fromString('')->setValues([
'stringval' => 'the',
'intval' => 1234,
'floatval' => 56.78,
'boolval' => true,
'arrval' => ['meow'],
]);
}
public function testScoping(): void {
$config = FsConfig::fromLines([
'test Inaccessible',
'scoped:test Accessible',
]);
$this->assertEquals('Inaccessible', $config->getString('test'));
$this->assertEquals('Accessible', $config->getString('scoped:test'));
$scoped = $config->scopeTo('scoped');
$this->assertEquals('Accessible', $scoped->getString('test'));
}
public function testHasValues(): void {
$config = FsConfig::fromLines([
'test 123',
'scoped:test true',
'scoped:meow meow',
]);
// hasValues should always return true when the list is empty
$this->assertTrue($config->hasValues([]));
$this->assertFalse($config->hasValues('meow'));
$this->assertTrue($config->hasValues('test'));
$this->assertFalse($config->hasValues(['test', 'meow']));
$this->assertTrue($config->hasValues(['scoped:test', 'scoped:meow']));
}
public function testGetAllValueInfos(): void {
$config = FsConfig::fromFile(__DIR__ . '/sharpchat.cfg');
$all = $config->getAllValueInfos();
$expected = [
'chat:port',
'chat:msgMaxLength',
'chat:floodKickLength',
'chat:channels',
'chat:channels:lounge:name',
'chat:channels:lounge:autoJoin',
'chat:channels:prog:name',
'chat:channels:games:name',
'chat:channels:splat:name',
'chat:channels:passwd:name',
'chat:channels:passwd:password',
'chat:channels:staff:name',
'chat:channels:staff:minRank',
'msz:secret',
'msz:url',
'mariadb:host',
'mariadb:user',
'mariadb:pass',
'mariadb:db',
];
$values = [];
foreach($all as $info)
$values[] = $info->getName();
$this->assertEquals($expected, $values);
$subset = $config->getAllValueInfos(3, 6);
$expected = [
'chat:channels:prog:name',
'chat:channels:games:name',
'chat:channels:splat:name',
];
$values = [];
foreach($subset as $info)
$values[] = $info->getName();
$this->assertEquals($expected, $values);
}
public function testGetValues(): void {
$config = FsConfig::fromFile(__DIR__ . '/sharpchat.cfg');
$this->assertNull($config->getValueInfo('doesnotexist'));
$scoped = $config->scopeTo('chat')->scopeTo('channels', 'passwd');
$expected = ['chat:channels:passwd:name' => 'Password', 'chat:channels:passwd:password' => 'meow'];
$values = [];
$valueInfos = $scoped->getValueInfos(['name', 'password', 'minRank']);
foreach($valueInfos as $valueInfo)
$values[$valueInfo->getRealName()] = $valueInfo->getValue();
$this->assertEquals($expected, $values);
$scoped = $config->scopeTo('chat', 'channels', 'lounge');
$expected = [
'name' => 'Lounge',
'auto_join' => true,
'minRank' => 0,
];
$values = $scoped->getValues([
'name',
['autoJoin:b', false, 'auto_join'],
'minRank:i',
]);
$this->assertEquals($expected, $values);
$this->assertEquals('', $config->getString('msz:url2'));
$this->assertEquals('test', $config->getString('msz:url2', 'test'));
$this->assertEquals('https://flashii.net/_sockchat', $config->getString('msz:url'));
$this->assertEquals(0, $config->getInteger('chat:connMaxCount'));
$this->assertEquals(10, $config->getInteger('chat:connMaxCount', 10));
$this->assertEquals(30, $config->getInteger('chat:floodKickLength'));
$this->assertEquals('30', $config->getString('chat:floodKickLength'));
$this->assertEquals(0, $config->getFloat('boat'));
$this->assertEquals(0.1, $config->getFloat('boat', 0.1));
$this->assertEquals(192.168, $config->getFloat('mariadb:host'));
$this->assertEquals('192.168.0.123', $config->getString('mariadb:host'));
$this->assertEquals(false, $config->getBoolean('nonexist'));
$this->assertEquals(true, $config->getBoolean('nonexist', true));
$this->assertEquals(true, $config->getBoolean('chat:channels:lounge:autoJoin'));
$this->assertEquals('true', $config->getString('chat:channels:lounge:autoJoin'));
$this->assertEquals(true, $config->getBoolean('mariadb:db'));
$this->assertEmpty($config->getArray('nonexist'));
$this->assertEquals(['de', 'het', 'een'], $config->getArray('nonexist', ['de', 'het', 'een']));
$this->assertEquals(['lounge', 'prog', 'games', 'splat', 'passwd', 'staff'], $config->getArray('chat:channels'));
$this->assertEquals('lounge prog games splat passwd staff', $config->getString('chat:channels'));
$this->assertEquals(['fake', 'secret', 'meow'], $config->getArray('msz:secret'));
$this->assertEquals('fake secret meow', $config->getString('msz:secret'));
}
}

81
tests/NullConfigTest.php Normal file
View file

@ -0,0 +1,81 @@
<?php
// NullConfigTest.php
// Created: 2023-10-20
// Updated: 2024-10-04
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use Index\Config\GetValuesTrait;
use Index\Config\Null\NullConfig;
#[CoversClass(NullConfig::class)]
#[CoversClass(GetValuesTrait::class)]
final class NullConfigTest extends TestCase {
public function testNullConfig(): void {
$config = new NullConfig;
// no-ops but run anyway to ensure no screaming
$config->removeValues('test');
$config->setString('stringval', 'the');
$config->setInteger('intval', 1234);
$config->setFloat('floatval', 56.78);
$config->setBoolean('boolval', true);
$config->setArray('arrval', ['meow']);
$config->setValues([
'stringval' => 'the',
'intval' => 1234,
'floatval' => 56.78,
'boolval' => true,
'arrval' => ['meow'],
]);
// NullConfig currently returns itself when scoping
// might change this depending on whether the scope prefix will be exposed or not
$scoped = $config->scopeTo('scoped');
$this->assertEquals($config, $scoped);
// hasValues should always return true when the list is empty
$this->assertTrue($config->hasValues([]));
$this->assertFalse($config->hasValues('anything'));
$this->assertFalse($config->hasValues(['manything1', 'manything2']));
$this->assertEmpty($config->getAllValueInfos());
$this->assertEmpty($config->getValueInfos('the'));
$expected = [
'test_no_type' => null,
'test_yes_type' => false,
'test_no_type_yes_default' => 1234,
'test_yes_type_yes_default' => 56.78,
'aliased' => null,
];
$values = $config->getValues([
'test_no_type',
'test_yes_type:b',
['test_no_type_yes_default', 1234],
['test_yes_type_yes_default:d', 56.78],
['test_no_default_yes_alias', null, 'aliased'],
]);
$this->assertEqualsCanonicalizing($expected, $values);
$this->assertNull($config->getValueInfo('value'));
$this->assertEquals('', $config->getString('string'));
$this->assertEquals('default', $config->getString('string', 'default'));
$this->assertEquals(0, $config->getInteger('int'));
$this->assertEquals(960, $config->getInteger('int', 960));
$this->assertEquals(0, $config->getFloat('float'));
$this->assertEquals(67.7, $config->getFloat('float', 67.7));
$this->assertFalse($config->getBoolean('bool'));
$this->assertTrue($config->getBoolean('bool', true));
$this->assertEmpty($config->getArray('arr'));
$this->assertEqualsCanonicalizing(['de', 'het', 'een'], $config->getArray('the', ['de', 'het', 'een']));
}
}

36
tests/sharpchat.cfg Normal file
View file

@ -0,0 +1,36 @@
# and ; can be used at the start of a line for comments.
# General Configuration
chat:port 6770
chat:msgMaxLength 5000
;chat:connMaxCount 5
chat:floodKickLength 30
# Channels
chat:channels lounge prog games splat passwd staff
# Lounge channel settings
chat:channels:lounge:name Lounge
chat:channels:lounge:autoJoin true
chat:channels:prog:name Programming
chat:channels:games:name Games
chat:channels:splat:name Splatoon
# Passworded channel
chat:channels:passwd:name Password
chat:channels:passwd:password meow
# Staff channel settings
chat:channels:staff:name Staff
chat:channels:staff:minRank 5
# Misuzu integration settings
msz:secret fake secret meow
msz:url https://flashii.net/_sockchat
# MariaDB configuration
mariadb:host 192.168.0.123
mariadb:user chat
mariadb:pass nyaa
mariadb:db chat