From edc7b398692e828e7ca802953d2e055040bf050b Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Fri, 3 Oct 2025 15:50:23 +0300 Subject: [PATCH] Fixes --- .../src/Command/TransmogrifyCommand.php | 6 +- .../src/Lib/Traits/TypeMapperTrait.php | 86 ++--- .../src/Lib/Util/CommanLinePrinter.php | 184 ---------- .../src/Lib/Util/CommandLinePrinter.php | 329 +++++++++--------- 4 files changed, 202 insertions(+), 403 deletions(-) delete mode 100644 app/plugins/Transmogrify/src/Lib/Util/CommanLinePrinter.php diff --git a/app/plugins/Transmogrify/src/Command/TransmogrifyCommand.php b/app/plugins/Transmogrify/src/Command/TransmogrifyCommand.php index 17aecffe4..591f855b0 100644 --- a/app/plugins/Transmogrify/src/Command/TransmogrifyCommand.php +++ b/app/plugins/Transmogrify/src/Command/TransmogrifyCommand.php @@ -48,7 +48,7 @@ use Transmogrify\Lib\Traits\ManageDefaultsTrait; use Transmogrify\Lib\Traits\RowTransformationTrait; use Transmogrify\Lib\Traits\TypeMapperTrait; -use Transmogrify\Lib\Util\CommanLinePrinter; +use Transmogrify\Lib\Util\CommandLinePrinter; use Transmogrify\Lib\Util\DbInfoPrinter; use Transmogrify\Lib\Util\RawSqlQueries; @@ -282,7 +282,7 @@ public function execute(Arguments $args, ConsoleIo $io): int }; $stmt = $this->inconn->executeQuery($insql); - $progress = new CommanLinePrinter($io, 'green', 50, true); + $progress = new CommandLinePrinter($io, 'green', 50, true); $progress->start($count); $tally = 0; $warns = 0; @@ -357,7 +357,7 @@ public function execute(Arguments $args, ConsoleIo $io): int $io->error(sprintf('Errors: %d', $err)); // Step 11: Execute any post-processing hooks for the table - if ($modeltableEmpty && !$notSelected) { + if ($modeltableEmpty && $notSelected) { $this->runPostTableHook($t); } diff --git a/app/plugins/Transmogrify/src/Lib/Traits/TypeMapperTrait.php b/app/plugins/Transmogrify/src/Lib/Traits/TypeMapperTrait.php index 4c87e65ec..333c45895 100644 --- a/app/plugins/Transmogrify/src/Lib/Traits/TypeMapperTrait.php +++ b/app/plugins/Transmogrify/src/Lib/Traits/TypeMapperTrait.php @@ -202,54 +202,56 @@ protected function mapOrgIdentitycoPersonId(array $row): ?int $oid = (int)$row['id']; - if(!isset($this->cache['org_identities']['co_people'])) { + if(empty($this->cache['org_identities']['co_people'])) { $this->cache['org_identities']['co_people'] = []; - } - - // Guard 1: If we already have this org identity mapped, return immediately - if (isset($this->cache['org_identities']['co_people'][$oid])) { - return $this->cache['org_identities']['co_people'][$oid]; - } - - // Build cache on first use - $this->io->info('Populating org identity map...'); - - $tableName = 'cm_co_org_identity_links'; - $changelogFK = 'co_org_identity_link_id'; - $qualifiedTableName = $this->inconn->qualifyTableName($tableName); - - // Only fetch current rows (historical/deleted rows are filtered out) - $mapsql = RawSqlQueries::buildSelectAll( - qualifiedTableName: $qualifiedTableName, - onlyCurrent: true, - changelogFK: $changelogFK - ); - $stmt = $this->inconn->executeQuery($mapsql); - - while ($r = $stmt->fetchAssociative()) { - if (!empty($r['org_identity_id'])) { - $rOid = (int)$r['org_identity_id']; - $cop = isset($r['co_person_id']) ? (int)$r['co_person_id'] : null; - - if ($cop === null) { - // Defensive: skip rows without a co_person_id - $this->io->warning('Org Identity ' . $rOid . ' has no mapped CO Person ID, skipping'); - continue; - } - - if (isset($this->cache['org_identities']['co_people'][$rOid])) { - // Unexpected duplicate "current" mapping, keep the first and warn - $this->io->warning('Found duplicate current CO Person mapping for Org Identity ' . $rOid . ', skipping'); - continue; + // Build cache on first use + $this->io->info('Populating org identity map...'); + + $tableName = 'cm_co_org_identity_links'; + $changelogFK = 'co_org_identity_link_id'; + $qualifiedTableName = $this->inconn->qualifyTableName($tableName); + + // Only fetch current rows (historical/deleted rows are filtered out) + $mapsql = RawSqlQueries::buildSelectAll( + qualifiedTableName: $qualifiedTableName, + onlyCurrent: true, + changelogFK: $changelogFK + ); + + $stmt = $this->inconn->executeQuery($mapsql); + + while ($r = $stmt->fetchAssociative()) { + if (!empty($r['org_identity_id'])) { + $rOid = (int)$r['org_identity_id']; + $rOrevision = (int)$r['revision']; + $cop = isset($r['co_person_id']) ? (int)$r['co_person_id'] : null; + + if ($cop === null) { + $this->io->warning('Org Identity ' . $rOid . ' has no mapped CO Person ID, skipping'); + continue; + } + + if (isset($this->cache['org_identities']['co_people'][$rOid][$rOrevision])) { + // If for some reason we already have a record, it's probably due to + // improper unpooling from a legacy deployment. We'll accept only the + // first record and throw warnings on the others. + $this->io->verbose('Found duplicate current CO Person mapping for Org Identity ' . $rOid . ', skipping'); + continue; + } + + $this->cache['org_identities']['co_people'][$rOid][$rOrevision] = $cop; } - - $this->cache['org_identities']['co_people'][$rOid] = $cop; } } - // Return the now-cached mapping (or null if not present) - return $this->cache['org_identities']['co_people'][$oid] ?? null; + if (!empty($this->cache['org_identities']['co_people'][$oid])) { + // Return the record with the highest revision number + $rev = max(array_keys($this->cache['org_identities']['co_people'][ $oid ])); + return $this->cache['org_identities']['co_people'][$oid][$rev]; + } + + return null; } /** diff --git a/app/plugins/Transmogrify/src/Lib/Util/CommanLinePrinter.php b/app/plugins/Transmogrify/src/Lib/Util/CommanLinePrinter.php deleted file mode 100644 index 0df0a8fb8..000000000 --- a/app/plugins/Transmogrify/src/Lib/Util/CommanLinePrinter.php +++ /dev/null @@ -1,184 +0,0 @@ -io = $io; - $this->barColor = in_array($barColor, ['blue', 'green'], true) ? $barColor : 'blue'; - $this->barWidth = max(10, $barWidth); - $this->useColors = $useColors; - } - - /** Initialize the two-area layout and draw the 0% bar */ - public function start(int $total): void - { - $this->total = max(0, $total); - $this->current = 0; - $this->messageLines = 0; - - // Draw initial progress bar line (without trailing newline) - $this->rawWrite("\r" . $this->formatBar(0)); - - // Save cursor position at the end of the progress bar line - $this->rawWrite("\033[s"); - - // Move cursor back down ONLY if messages exist - if ($this->messageLines > 0) { - $this->rawWrite("\033[" . $this->messageLines . "B\r"); - } - } - - /** Update the progress bar (0..total). Call as work advances. */ - public function update(int $current): void - { - $this->current = min(max(0, $current), $this->total); - - // Restore to saved position (progress bar line), redraw, save again - $this->rawWrite("\033[u"); // restore saved cursor (bar line) - $this->rawWrite("\r" . $this->formatBar($this->current)); - $this->rawWrite("\033[s"); // re-save position at end of bar - - // Move cursor back down to where messages continue - if ($this->messageLines > 0) { - $this->rawWrite("\033[" . $this->messageLines . "B\r"); - } else { - // exactly one line below the bar is the first message line - $this->rawWrite("\033[1B\r"); - } - } - - /** Finish: force bar to 100% and add a newline separating it from any further output */ - public function finish(): void - { - $this->update($this->total); - // Move cursor to end of messages (already there), and ensure a newline after the last message block - $this->rawWrite(PHP_EOL); - } - - // Convenience wrappers for messages under the bar - public function info(string $message): void { $this->message($message, 'info'); } - public function warn(string $message): void { $this->message($message, 'warn'); } - public function error(string $message): void { $this->message($message, 'error'); } - public function debug(string $message): void { $this->message($message, 'debug'); } - - /** Print a message under the bar. Handles multi-line strings. */ - public function message(string $message, string $level = 'info'): void - { - $lines = $this->colorizeLevel($level, $message); - // Ensure the message ends with a newline so we can count line advances - if ($lines === '' || substr($lines, -1) !== "\n") { - $lines .= "\n"; - } - - // If this is the first message, create the message area by moving to the next line - if ($this->messageLines === 0) { - $this->rawWrite(PHP_EOL); - } - - $this->rawWrite($lines); - - // Count how many terminal lines were added based on newlines seen. - // Note: This doesn’t account for soft-wrapped lines; keep messages reasonably short. - $this->messageLines += substr_count($lines, "\n"); - } - - private function colorizeLevel(string $level, string $message): string - { - $level = strtolower($level); - $prefix = ''; - $color = null; - switch ($level) { - case 'warn': - case 'warning': - $prefix = '[WARN] '; - $color = 'yellow'; - break; - case 'error': - $prefix = '[ERROR] '; - $color = 'red'; - break; - case 'debug': - $prefix = '[DEBUG] '; - $color = 'cyan'; - break; - default: - $prefix = '[INFO] '; - $color = null; // default terminal color - } - - $text = $prefix . $message; - if ($this->useColors && $color) { - return $this->wrapColor($text, $color); - } - return $text; - } - - private function formatBar(int $current): string - { - $total = max(1, $this->total); - $pct = (int) floor(($current / $total) * 100); - - $width = $this->barWidth; - $filled = (int) floor(($pct / 100) * $width); - $remaining = max(0, $width - $filled); - - $filledStr = str_repeat('=', max(0, $filled - 1)) . ($filled > 0 ? '>' : ''); - $emptyStr = str_repeat('.', $remaining); - - $bar = sprintf('[%s%s] %3d%% (%d/%d)', $filledStr, $emptyStr, $pct, $current, $this->total); - - // Clear the line to the right to avoid remnants on shorter redraws - $bar .= "\033[K"; - - if ($this->useColors) { - $color = $this->barColor === 'green' ? 'green' : 'blue'; - $bar = $this->wrapColor($bar, $color); - } - return $bar; - } - - private function wrapColor(string $text, string $color): string - { - $map = [ - 'red' => '0;31', - 'green' => '0;32', - 'yellow' => '0;33', - 'blue' => '0;34', - 'magenta'=> '0;35', - 'cyan' => '0;36', - ]; - $code = $map[$color] ?? null; - if (!$code) { return $text; } - return "\033[{$code}m{$text}\033[0m"; - } - - private function rawWrite(string $str): void - { - if ($this->io) { - // ConsoleIo::out() defaults to a trailing newline; we want raw text - $this->io->out($str, 0); - } else { - // Fallback to STDOUT - echo $str; - } - } -} diff --git a/app/plugins/Transmogrify/src/Lib/Util/CommandLinePrinter.php b/app/plugins/Transmogrify/src/Lib/Util/CommandLinePrinter.php index 91f58f3fd..41352174b 100644 --- a/app/plugins/Transmogrify/src/Lib/Util/CommandLinePrinter.php +++ b/app/plugins/Transmogrify/src/Lib/Util/CommandLinePrinter.php @@ -5,199 +5,180 @@ use Cake\Console\ConsoleIo; /** - * CommandLinePrinter (Dual-area progress + message logger) + * DualAreaProgress * - * Singleton providing a progress bar fixed on the top line while printing - * messages (info/warn/error/debug) underneath. Uses ANSI escape codes only. + * Keeps a progress bar on the top line and logs messages beneath it. + * Works with plain STDOUT or Cake's ConsoleIo. Uses ANSI escape codes. */ class CommandLinePrinter { - public static ?CommandLinePrinter $instance = null; - - private ?ConsoleIo $io; - private int $total = 0; - private int $current = 0; - private int $messageLines = 0; // number of newline-terminated lines printed under the bar - private string $barColor; // 'blue' or 'green' - private int $barWidth; - private bool $useColors; - - /** - * Private constructor to enforce singleton usage. Use initialize(). - */ - private function __construct(?ConsoleIo $io = null, string $barColor = 'blue', int $barWidth = 50, bool $useColors = true) - { - $this->io = $io; - $this->barColor = in_array($barColor, ['blue', 'green'], true) ? $barColor : 'blue'; - $this->barWidth = max(10, $barWidth); - $this->useColors = $useColors; + private ?ConsoleIo $io; + private int $total = 0; + private int $current = 0; + private int $messageLines = 0; // how many newline-terminated lines printed under the bar + private string $barColor; // 'blue' or 'green' + private int $barWidth; + private bool $useColors; + + public function __construct(?ConsoleIo $io = null, string $barColor = 'blue', int $barWidth = 50, bool $useColors = true) + { + $this->io = $io; + $this->barColor = in_array($barColor, ['blue', 'green'], true) ? $barColor : 'blue'; + $this->barWidth = max(10, $barWidth); + $this->useColors = $useColors; + } + + /** Initialize the two-area layout and draw the 0% bar */ + public function start(int $total): void + { + $this->total = max(0, $total); + $this->current = 0; + $this->messageLines = 0; + + // Draw initial progress bar line (without trailing newline) + $this->rawWrite("\r" . $this->formatBar(0)); + + // Save cursor position at the end of the progress bar line + $this->rawWrite("\033[s"); + + // Move cursor back down ONLY if messages exist + if ($this->messageLines > 0) { + $this->rawWrite("\033[" . $this->messageLines . "B\r"); } - - /** - * Initialize or reinitialize the singleton and start the progress display. - */ - public static function initialize(?ConsoleIo $io, int $total, string $barColor = 'blue', int $barWidth = 50, bool $useColors = true): self - { - self::$instance = new self($io, $barColor, $barWidth, $useColors); - self::$instance->start($total); - return self::$instance; - } - - /** - * Get the current singleton instance. If not initialized, creates a default one. - */ - public static function get(): self - { - if (!self::$instance) { - self::$instance = new self(null, 'blue', 50, true); - } - return self::$instance; - } - - /** Initialize the two-area layout and draw the 0% bar */ - public function start(int $total): void - { - $this->total = max(0, $total); - $this->current = 0; - $this->messageLines = 0; - - // Draw initial progress bar line (without trailing newline) - $this->rawWrite("\r" . $this->formatBar(0)); - - // Save cursor position at the end of the progress bar line - $this->rawWrite("\033[s"); - - // Move to the line below where messages begin - $this->rawWrite(PHP_EOL); + } + + /** Update the progress bar (0..total). Call as work advances. */ + public function update(int $current): void + { + $this->current = min(max(0, $current), $this->total); + + // Restore to saved position (progress bar line), redraw, save again + $this->rawWrite("\033[u"); // restore saved cursor (bar line) + $this->rawWrite("\r" . $this->formatBar($this->current)); + $this->rawWrite("\033[s"); // re-save position at end of bar + + // Move cursor back down to where messages continue + if ($this->messageLines > 0) { + $this->rawWrite("\033[" . $this->messageLines . "B\r"); + } else { + // exactly one line below the bar is the first message line + $this->rawWrite("\033[1B\r"); } - - /** Update the progress bar (0..total). Call as work advances. */ - public function update(int $current): void - { - $this->current = min(max(0, $current), $this->total); - - // Restore to saved position (progress bar line), redraw, save again - $this->rawWrite("\033[u"); // restore saved cursor (bar line) - $this->rawWrite("\r" . $this->formatBar($this->current)); - $this->rawWrite("\033[s"); // re-save position at end of bar - - // Move cursor back down to where messages continue - if ($this->messageLines > 0) { - $this->rawWrite("\033[" . $this->messageLines . "B\r"); - } else { - // exactly one line below the bar is the first message line - $this->rawWrite("\033[1B\r"); - } + } + + /** Finish: force bar to 100% and add a newline separating it from any further output */ + public function finish(): void + { + $this->update($this->total); + // Move cursor to end of messages (already there), and ensure a newline after the last message block + $this->rawWrite(PHP_EOL); + } + + // Convenience wrappers for messages under the bar + public function info(string $message): void { $this->message($message, 'info'); } + public function warn(string $message): void { $this->message($message, 'warn'); } + public function error(string $message): void { $this->message($message, 'error'); } + public function debug(string $message): void { $this->message($message, 'debug'); } + + /** Print a message under the bar. Handles multi-line strings. */ + public function message(string $message, string $level = 'info'): void + { + $lines = $this->colorizeLevel($level, $message); + // Ensure the message ends with a newline so we can count line advances + if ($lines === '' || substr($lines, -1) !== "\n") { + $lines .= "\n"; } - /** Finish: force bar to 100% and add a newline separating it from any further output */ - public function finish(): void - { - $this->update($this->total); - // Move cursor to end of messages (already there), and ensure a newline after the last message block - $this->rawWrite(PHP_EOL); + // If this is the first message, create the message area by moving to the next line + if ($this->messageLines === 0) { + $this->rawWrite(PHP_EOL); } - // Convenience wrappers for messages under the bar - public function info(string $message): void { $this->message($message, 'info'); } - public function warn(string $message): void { $this->message($message, 'warn'); } - public function error(string $message): void { $this->message($message, 'error'); } - public function debug(string $message): void { $this->message($message, 'debug'); } - - /** Print a message under the bar. Handles multi-line strings. */ - public function message(string $message, string $level = 'info'): void - { - $lines = $this->colorizeLevel($level, $message); - // Ensure the message ends with a newline so we can count line advances - if ($lines === '' || substr($lines, -1) !== "\n") { - $lines .= "\n"; - } - - $this->rawWrite($lines); - - // Count how many terminal lines were added based on newlines seen. - // Note: This doesn’t account for soft-wrapped lines; keep messages reasonably short. - $this->messageLines += substr_count($lines, "\n"); + $this->rawWrite($lines); + + // Count how many terminal lines were added based on newlines seen. + // Note: This doesn’t account for soft-wrapped lines; keep messages reasonably short. + $this->messageLines += substr_count($lines, "\n"); + } + + private function colorizeLevel(string $level, string $message): string + { + $level = strtolower($level); + $prefix = ''; + $color = null; + switch ($level) { + case 'warn': + case 'warning': + $prefix = '[WARN] '; + $color = 'yellow'; + break; + case 'error': + $prefix = '[ERROR] '; + $color = 'red'; + break; + case 'debug': + $prefix = '[DEBUG] '; + $color = 'cyan'; + break; + default: + $prefix = '[INFO] '; + $color = null; // default terminal color } - private function colorizeLevel(string $level, string $message): string - { - $level = strtolower($level); - $prefix = ''; - $color = null; - switch ($level) { - case 'warn': - case 'warning': - $prefix = '[WARN] '; - $color = 'yellow'; - break; - case 'error': - $prefix = '[ERROR] '; - $color = 'red'; - break; - case 'debug': - $prefix = '[DEBUG] '; - $color = 'cyan'; - break; - default: - $prefix = '[INFO] '; - $color = null; // default terminal color - } - - $text = $prefix . $message; - if ($this->useColors && $color) { - return $this->wrapColor($text, $color); - } - return $text; + $text = $prefix . $message; + if ($this->useColors && $color) { + return $this->wrapColor($text, $color); } + return $text; + } - private function formatBar(int $current): string - { - $total = max(1, $this->total); - $pct = (int) floor(($current / $total) * 100); + private function formatBar(int $current): string + { + $total = max(1, $this->total); + $pct = (int) floor(($current / $total) * 100); - $width = $this->barWidth; - $filled = (int) floor(($pct / 100) * $width); - $remaining = max(0, $width - $filled); + $width = $this->barWidth; + $filled = (int) floor(($pct / 100) * $width); + $remaining = max(0, $width - $filled); - $filledStr = str_repeat('=', max(0, $filled - 1)) . ($filled > 0 ? '>' : ''); - $emptyStr = str_repeat('.', $remaining); + $filledStr = str_repeat('=', max(0, $filled - 1)) . ($filled > 0 ? '>' : ''); + $emptyStr = str_repeat('.', $remaining); - $bar = sprintf('[%s%s] %3d%% (%d/%d)', $filledStr, $emptyStr, $pct, $current, $this->total); + $bar = sprintf('[%s%s] %3d%% (%d/%d)', $filledStr, $emptyStr, $pct, $current, $this->total); - // Clear the line to the right to avoid remnants on shorter redraws - $bar .= "\033[K"; + // Clear the line to the right to avoid remnants on shorter redraws + $bar .= "\033[K"; - if ($this->useColors) { - $color = $this->barColor === 'green' ? 'green' : 'blue'; - $bar = $this->wrapColor($bar, $color); - } - return $bar; + if ($this->useColors) { + $color = $this->barColor === 'green' ? 'green' : 'blue'; + $bar = $this->wrapColor($bar, $color); } - - private function wrapColor(string $text, string $color): string - { - $map = [ - 'red' => '0;31', - 'green' => '0;32', - 'yellow' => '0;33', - 'blue' => '0;34', - 'magenta'=> '0;35', - 'cyan' => '0;36', - ]; - $code = $map[$color] ?? null; - if (!$code) { return $text; } - return "\033[{$code}m{$text}\033[0m"; - } - - private function rawWrite(string $str): void - { - if ($this->io) { - // ConsoleIo::out() defaults to a trailing newline; we want raw text - $this->io->out($str, 0); - } else { - // Fallback to STDOUT - echo $str; - } + return $bar; + } + + private function wrapColor(string $text, string $color): string + { + $map = [ + 'red' => '0;31', + 'green' => '0;32', + 'yellow' => '0;33', + 'blue' => '0;34', + 'magenta'=> '0;35', + 'cyan' => '0;36', + ]; + $code = $map[$color] ?? null; + if (!$code) { return $text; } + return "\033[{$code}m{$text}\033[0m"; + } + + private function rawWrite(string $str): void + { + if ($this->io) { + // ConsoleIo::out() defaults to a trailing newline; we want raw text + $this->io->out($str, 0); + } else { + // Fallback to STDOUT + echo $str; } + } }