From 04899cb0f7165251333f8eae0a44f23b94bbb8d4 Mon Sep 17 00:00:00 2001 From: boykopovar Date: Tue, 13 Oct 2026 12:50:32 +0300 Subject: [PATCH] fix(relinker): align console progress columns --- core/relinker/cli/include/ConsoleProgress.hpp | 19 ++++++++++++++++--- core/relinker/main.cpp | 11 ++++++++++- .../relinker/tests/test_executables.py | 2 +- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/core/relinker/cli/include/ConsoleProgress.hpp b/core/relinker/cli/include/ConsoleProgress.hpp index 8f328471b..a05bf6779 100644 --- a/core/relinker/cli/include/ConsoleProgress.hpp +++ b/core/relinker/cli/include/ConsoleProgress.hpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include namespace Cli { @@ -13,6 +15,12 @@ class ConsoleProgress final : public Domain::IProgress { public: explicit ConsoleProgress(std::ostream& stream) : stream(stream) {} + void IncludeFile(std::string_view path) { + const auto separator = path.find_last_of("/\\"); + const auto name = path.substr(separator == std::string_view::npos ? 0 : separator + 1); + nameWidth = std::max(nameWidth, textWidth(name)); + } + void Report(const Domain::ProgressEvent& event) override { if (event.Completed > event.Total) throw std::logic_error("Invalid progress counters"); const char* label = nullptr; @@ -51,9 +59,9 @@ public: if (!changed && !complete && now - lastUpdate < std::chrono::milliseconds(80)) return; if (changed || messages) Finish(); std::ostringstream line; - line << label; - if (!name.empty()) line << " | " << name; - line << " | " << percent << '%'; + line << std::left << std::setw(22) << label; + line << " | " << name << std::string(nameWidth > textWidth(name) ? nameWidth - textWidth(name) : 0, ' '); + line << " | " << std::right << std::setw(3) << percent << '%'; if (event.Stage == Domain::ProgressStage::Modules) line << " (" << event.Completed << '/' << event.Total << ')'; const auto text = line.str(); stream << '\r' << text; @@ -84,6 +92,11 @@ public: } private: + static std::size_t textWidth(std::string_view text) { + return static_cast(std::count_if(text.begin(), text.end(), [](unsigned char character) { return (character & 0xc0) != 0x80; })); + } + + std::size_t nameWidth = 0; std::ostream& stream; Domain::ProgressStage lastStage = Domain::ProgressStage::Write; std::string lastInput; diff --git a/core/relinker/main.cpp b/core/relinker/main.cpp index e90fea9a1..556c4743f 100644 --- a/core/relinker/main.cpp +++ b/core/relinker/main.cpp @@ -33,7 +33,7 @@ struct ConvertedExecutable { std::string Diagnostics; }; -ConvertedExecutable Convert(const Cli::Args& args, const std::string& inputPath, const std::string& absPath, const bool mainExecutable, Domain::IProgress& progress) { +ConvertedExecutable Convert(const Cli::Args& args, const std::string& inputPath, const std::string& absPath, const bool mainExecutable, Cli::ConsoleProgress& progress) { Io::FileReader fileReader; Relinker::ConversionInput input; input.Executable = fileReader.Read(inputPath); @@ -59,6 +59,9 @@ ConvertedExecutable Convert(const Cli::Args& args, const std::string& inputPath, options.WindowsGui = args.windowsGui; options.WindowsDiagnostics = args.windowsDiagnostics && mainExecutable; options.ExcludedModules = args.excludedSceModules; + progress.IncludeFile(Io::Utf8Path(input.InputPath)); + progress.IncludeFile(Io::Utf8Path(input.OutputPath)); + for (const auto& module : input.Modules) progress.IncludeFile(Io::Utf8Path(module.Path)); auto converted = Relinker::Conversion(progress).Convert(std::move(input), options); return {inputPath, absPath, std::move(converted.Analysis), std::move(converted.Modules), std::move(converted.Executable), std::move(converted.WindowsDiagnostics)}; } @@ -121,6 +124,12 @@ int Run(const int argc, char* argv[]) { extraExecutables.push_back({executable, relative, input, output}); } + progress.IncludeFile(args.inputPath); + progress.IncludeFile(absPath); + for (const auto& extra : extraExecutables) { + progress.IncludeFile(Io::Utf8Path(extra.Input)); + progress.IncludeFile(Io::Utf8Path(extra.Output)); + } std::vector executables; executables.push_back(Convert(args, args.inputPath, absPath, true, progress)); for (const auto& [executable, relative, input, output] : extraExecutables) { diff --git a/core/relinker/relinker/tests/test_executables.py b/core/relinker/relinker/tests/test_executables.py index bc8fcb260..b975342de 100644 --- a/core/relinker/relinker/tests/test_executables.py +++ b/core/relinker/relinker/tests/test_executables.py @@ -36,7 +36,7 @@ def main(): assert secondary.read_bytes().startswith(b"MZ" if windows else b"\x7fELF"), secondary assert "Executable: picker/picker.bin\n" in result.stdout, result.stdout assert f"Executable file: {secondary}\n" in result.stdout, result.stdout - assert f"app{suffix}\npicker.picker.bin{suffix}\nlibs/\n" in result.stdout, result.stdout + assert f" |-- app{suffix}\n |-- picker.picker.bin{suffix}\n |-- libs/\n" in result.stdout, result.stdout assert (output.parent / "picker.picker.bin.registry.json").is_file() assert sorted(path.name for path in output.parent.rglob("*.guest.prx")) == ["bundled.prx.guest.prx"] if not windows: