Files
mokoplatform/cli/metadata_read.php
2026-06-11 23:25:41 +00:00

318 lines
11 KiB
PHP

#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: mokoplatform.CLI
* INGROUP: mokoplatform
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
* PATH: /cli/metadata_read.php
* VERSION: 09.26.01
* BRIEF: Read and set metadata fields in .mokogitea/metadata.xml (or manifest.xml)
*/
declare(strict_types=1);
require_once __DIR__ . '/../lib/Enterprise/CliFramework.php';
use MokoEnterprise\CliFramework;
/** Field name → XPath mapping into the metadata XML */
const FIELD_MAP = [
// identity
'name' => 'identity/name',
'display-name' => 'identity/display-name',
'org' => 'identity/org',
'description' => 'identity/description',
'license' => 'identity/license',
'version' => 'identity/version',
// governance
'platform' => 'governance/platform',
'standards-version' => 'governance/standards-version',
'standards-source' => 'governance/standards-source',
// build
'language' => 'build/language',
'package-type' => 'build/package-type',
'entry-point' => 'build/entry-point',
// deploy
'source-dir' => 'deploy/source-dir',
'remote-subdir' => 'deploy/remote-subdir',
'excludes' => 'deploy/excludes',
'dev-host' => 'deploy/dev-host',
'demo-host' => 'deploy/demo-host',
];
class MetadataReadCli extends CliFramework
{
protected function configure(): void
{
$this->setDescription('Read or set metadata fields in .mokogitea/metadata.xml');
$this->addArgument('--path', 'Repository root path', '.');
$this->addArgument('--field', 'Single field name to read', '');
$this->addArgument('--set', 'Set field value (field=value), repeatable', '');
$this->addArgument('--all', 'Print all fields as KEY=VALUE lines', false);
$this->addArgument('--github-output', 'Append all fields to $GITHUB_OUTPUT', false);
$this->addArgument('--json', 'Output all fields as JSON', false);
}
protected function run(): int
{
$path = $this->getArgument('--path');
$field = $this->getArgument('--field');
$setValue = $this->getArgument('--set');
$showAll = $this->getArgument('--all');
$ghOutput = $this->getArgument('--github-output');
$jsonMode = $this->getArgument('--json');
$root = realpath($path) ?: $path;
// -- Locate metadata file --
$metadataFile = $this->findMetadataFile($root);
if ($metadataFile === null) {
$this->log('ERROR', "No metadata file found in {$root}");
return 1;
}
// -- Auto-migrate manifest.xml → metadata.xml --
$metadataFile = $this->migrateIfNeeded($metadataFile, $root);
// -- Set mode --
if ($setValue !== '') {
return $this->handleSet($metadataFile, $setValue);
}
// -- Read mode --
$xml = @simplexml_load_file($metadataFile);
if ($xml === false) {
// Fallback: legacy YAML format (.mokoplatform)
$fields = $this->parseLegacy($metadataFile);
} else {
$fields = $this->parseXml($xml, $metadataFile);
}
$fields = array_filter($fields, fn($v) => $v !== '');
return $this->outputFields($fields, $field, $showAll, $ghOutput, $jsonMode);
}
private function findMetadataFile(string $root): ?string
{
$candidates = [
"{$root}/.mokogitea/metadata.xml",
"{$root}/.mokogitea/manifest.xml",
"{$root}/.mokogitea/.manifest.xml",
"{$root}/.mokogitea/.mokoplatform",
];
foreach ($candidates as $candidate) {
if (file_exists($candidate)) {
return $candidate;
}
}
return null;
}
private function migrateIfNeeded(string $metadataFile, string $root): string
{
$newPath = "{$root}/.mokogitea/metadata.xml";
// Already at the new location
if ($metadataFile === $newPath) {
return $metadataFile;
}
// Legacy file found — migrate
if (str_ends_with($metadataFile, '.mokoplatform')) {
// YAML legacy — can't auto-migrate, just warn
$this->log('WARN', "Legacy .mokoplatform format detected — migrate to metadata.xml manually");
return $metadataFile;
}
// manifest.xml or .manifest.xml → metadata.xml
copy($metadataFile, $newPath);
unlink($metadataFile);
$this->log('INFO', "Migrated " . basename($metadataFile) . " → metadata.xml");
return $newPath;
}
private function parseXml(\SimpleXMLElement $xml, string $filePath): array
{
$fields = [];
foreach (FIELD_MAP as $name => $xpath) {
$parts = explode('/', $xpath);
$node = $xml;
foreach ($parts as $part) {
$node = $node->{$part} ?? null;
if ($node === null) break;
}
if ($name === 'license' && $node !== null) {
// Also extract spdx attribute
$fields['license'] = (string)$node;
$fields['license-spdx'] = (string)($node['spdx'] ?? '');
} else {
$fields[$name] = $node !== null ? (string)$node : '';
}
}
$fields['metadata-file'] = $filePath;
return $fields;
}
private function parseLegacy(string $filePath): array
{
$content = file_get_contents($filePath);
$fields = [];
if (preg_match('/^platform:\s*(.+)/m', $content, $m)) {
$fields['platform'] = trim($m[1], " \t\n\r\"'");
}
if (preg_match('/^standards_version:\s*(.+)/m', $content, $m)) {
$fields['standards-version'] = trim($m[1], " \t\n\r\"'");
}
if (preg_match('/^governed_repo:\s*(.+)/m', $content, $m)) {
$fields['name'] = trim($m[1], " \t\n\r\"'");
}
return $fields;
}
private function handleSet(string $metadataFile, string $setValue): int
{
// Parse field=value pairs (comma-separated or from repeated --set)
$pairs = [];
foreach (explode(',', $setValue) as $pair) {
$pair = trim($pair);
if ($pair === '') continue;
$eq = strpos($pair, '=');
if ($eq === false) {
$this->log('ERROR', "Invalid set format: '{$pair}' — expected field=value");
return 1;
}
$key = trim(substr($pair, 0, $eq));
$val = trim(substr($pair, $eq + 1));
$pairs[$key] = $val;
}
if (empty($pairs)) {
$this->log('ERROR', 'No field=value pairs provided');
return 1;
}
// Validate all fields exist in FIELD_MAP
foreach ($pairs as $key => $val) {
if (!isset(FIELD_MAP[$key])) {
$this->log('ERROR', "Unknown field: '{$key}'");
$this->log('INFO', 'Valid fields: ' . implode(', ', array_keys(FIELD_MAP)));
return 1;
}
}
// Legacy files are read-only
if (str_ends_with($metadataFile, '.mokoplatform')) {
$this->log('ERROR', 'Cannot set fields on legacy .mokoplatform format — migrate to metadata.xml first');
return 1;
}
// Load XML
$xml = @simplexml_load_file($metadataFile);
if ($xml === false) {
$this->log('ERROR', "Failed to parse XML: {$metadataFile}");
return 1;
}
// Set each field
foreach ($pairs as $key => $val) {
$xpath = FIELD_MAP[$key];
$parts = explode('/', $xpath);
$section = $parts[0];
$element = $parts[1];
if (!isset($xml->{$section})) {
$this->log('ERROR', "Section <{$section}> not found in XML — cannot set '{$key}'");
return 1;
}
if (!isset($xml->{$section}->{$element})) {
$this->log('ERROR', "Element <{$element}> not found in <{$section}> — cannot set '{$key}'");
return 1;
}
$old = (string)$xml->{$section}->{$element};
$xml->{$section}->{$element} = $val;
$this->log('INFO', "Set {$key}: '{$old}' → '{$val}'");
}
// Write back with preserved formatting
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadXML($xml->asXML());
$dom->save($metadataFile);
$this->log('INFO', "Updated {$metadataFile}");
return 0;
}
private function outputFields(array $fields, string $field, $showAll, $ghOutput, $jsonMode): int
{
if ($ghOutput) {
$mode = 'github-output';
} elseif ($showAll) {
$mode = 'all';
} elseif ($jsonMode) {
$mode = 'json';
} else {
$mode = 'field';
}
switch ($mode) {
case 'field':
if ($field === '') {
$this->log('ERROR', "Usage: metadata_read.php --path <dir> --field <name>");
$this->log('ERROR', " metadata_read.php --path <dir> --all");
$this->log('ERROR', " metadata_read.php --path <dir> --json");
$this->log('ERROR', " metadata_read.php --path <dir> --set field=value");
return 2;
}
echo ($fields[$field] ?? '') . "\n";
break;
case 'all':
foreach ($fields as $k => $v) {
echo "{$k}={$v}\n";
}
break;
case 'json':
echo json_encode($fields, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
break;
case 'github-output':
$outputFile = getenv('GITHUB_OUTPUT');
if ($outputFile === false || $outputFile === '') {
$this->log('ERROR', 'GITHUB_OUTPUT not set — printing to stdout instead');
foreach ($fields as $k => $v) {
$envKey = str_replace('-', '_', $k);
echo "{$envKey}={$v}\n";
}
} else {
$fh = fopen($outputFile, 'a');
foreach ($fields as $k => $v) {
$envKey = str_replace('-', '_', $k);
fwrite($fh, "{$envKey}={$v}\n");
}
fclose($fh);
$this->log('INFO', "Wrote " . count($fields) . " fields to GITHUB_OUTPUT");
}
break;
}
return 0;
}
}
$app = new MetadataReadCli();
exit($app->execute());