値の配列(同じ名前が複数有り得る) */ private array $values = []; public function __construct(private ?string $file = null) { if ($file !== null && is_file($file)) $this->parse((string)file_get_contents($file)); } public static function fromString(string $text): self { $c = new self(); $c->parse($text); return $c; } private function parse(string $text): void { $section = ''; $lines = preg_split('/\r?\n/', $text); for ($i = 0; $i < count($lines); $i++) { $line = $lines[$i]; $t = ltrim($line); if ($t === '' || $t[0] === '#' || $t[0] === ';') continue; if ($t[0] === '[') { if (!preg_match('/^\[\s*([A-Za-z0-9.-]+)(?:\s+"((?:[^"\\\\]|\\\\.)*)")?\s*\](.*)$/', $t, $m)) throw new GitException("不正な設定の行:{$line}"); $section = strtolower($m[1]); if (isset($m[2]) && $m[2] !== '') $section .= '.'.preg_replace('/\\\\(.)/', '$1', $m[2]); $t = ltrim($m[3]); if ($t === '' || $t[0] === '#' || $t[0] === ';') continue; } if (!preg_match('/^([A-Za-z][A-Za-z0-9-]*)\s*(=\s*(.*))?$/s', $t, $m)) throw new GitException("不正な設定の行:{$line}"); $key = $section.'.'.strtolower($m[1]); if (!isset($m[2]) || $m[2] === '') { $this->values[$key][] = 'true'; continue; } $raw = $m[3]; // 行の最後の「\」で次の行に続く while (preg_match('/(?values[$key][] = self::unquote($raw); } } private static function unquote(string $raw): string { $out = ''; $quoted = false; $len = strlen($raw); $pendingSpace = ''; for ($i = 0; $i < $len; $i++) { $c = $raw[$i]; if (!$quoted && ($c === '#' || $c === ';')) break; if ($c === '"') { $quoted = !$quoted; continue; } if ($c === '\\' && $i + 1 < $len) { $n = $raw[++$i]; $out .= $pendingSpace.match ($n) { 'n' => "\n", 't' => "\t", 'b' => "\x08", default => $n }; $pendingSpace = ''; continue; } if (!$quoted && ($c === ' ' || $c === "\t")) { // 引用符の外の空白は1つにまとめ、最後の空白は消す if ($out !== '' || $pendingSpace !== '') $pendingSpace = ' '; continue; } $out .= $pendingSpace.$c; $pendingSpace = ''; } return $out; } /** 最後の値 */ public function get(string $name, ?string $default = null): ?string { $k = $this->key($name); return isset($this->values[$k]) ? end($this->values[$k]) : $default; } /** 全部の値 */ public function getAll(string $name): array { return $this->values[$this->key($name)] ?? []; } public function getBool(string $name, bool $default = false): bool { $v = $this->get($name); if ($v === null) return $default; return in_array(strtolower($v), ['true', 'yes', 'on', '1'], true); } public function getInt(string $name, int $default = 0): int { $v = $this->get($name); if ($v === null || !preg_match('/^(-?\d+)([kmg]?)$/i', trim($v), $m)) return $default; return (int)$m[1] * match (strtolower($m[2])) { 'k' => 1024, 'm' => 1048576, 'g' => 1073741824, default => 1 }; } public function set(string $name, ?string $value): void { $k = $this->key($name); if ($value === null) unset($this->values[$k]); else $this->values[$k] = [$value]; } /** 全部(名前 => 最後の値) */ public function all(): array { return array_map(fn($v) => end($v), $this->values); } public function toString(): string { $sections = []; foreach ($this->values as $k => $vals) { $p = strrpos($k, '.'); $sections[substr($k, 0, $p)][substr($k, $p + 1)] = $vals; } $out = ''; foreach ($sections as $sec => $keys) { $dot = strpos($sec, '.'); $out .= $dot === false ? "[{$sec}]\n" : '['.substr($sec, 0, $dot).' "'.addcslashes(substr($sec, $dot + 1), '"\\')."\"]\n"; foreach ($keys as $key => $vals) { foreach ($vals as $v) { $q = preg_match('/^\s|\s$|[#;"\\\\\n\t]/', $v) ? '"'.str_replace(['\\', '"', "\n", "\t"], ['\\\\', '\\"', '\\n', '\\t'], $v).'"' : $v; $out .= "\t{$key} = {$q}\n"; } } } return $out; } public function save(?string $file = null): void { $file ??= $this->file; if ($file === null) throw new GitException('設定ファイルが有りません。'); $tmp = $file.'.lock'; if (@file_put_contents($tmp, $this->toString()) === false || !@rename($tmp, $file)) { @unlink($tmp); throw new GitException("設定ファイルに書けません:{$file}"); } } private function key(string $name): string { $first = strpos($name, '.'); $last = strrpos($name, '.'); if ($first === false) throw new GitException("不正な設定の名前:{$name}"); if ($first === $last) return strtolower($name); return strtolower(substr($name, 0, $first)).substr($name, $first, $last - $first).'.'.strtolower(substr($name, $last + 1)); } }