From d80cdd9712a2d0debb7ae4590d26ccdc8c626313 Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Thu, 5 Mar 2026 18:55:27 +0000 Subject: [PATCH] chore: add TextFileCheckDirtyOutput to FileUtils --- src/Utils/Utils/FileUtils.cpp | 105 +++++++++++ src/Utils/Utils/FileUtils.h | 32 ++++ .../Generating/CodeGenerator.cpp | 163 +++--------------- .../Generating/CodeGenerator.h | 13 +- 4 files changed, 161 insertions(+), 152 deletions(-) diff --git a/src/Utils/Utils/FileUtils.cpp b/src/Utils/Utils/FileUtils.cpp index da7db444..f369a26d 100644 --- a/src/Utils/Utils/FileUtils.cpp +++ b/src/Utils/Utils/FileUtils.cpp @@ -1,5 +1,6 @@ #include "FileUtils.h" +#include #include namespace fs = std::filesystem; @@ -65,4 +66,108 @@ namespace utils return true; } + + TextFileCheckDirtyOutput::TextFileCheckDirtyOutput(fs::path path) + : m_path(std::move(path)), + m_open(false), + m_has_existing_file(false) + { + } + + TextFileCheckDirtyOutput::~TextFileCheckDirtyOutput() + { + Close(); + } + + bool TextFileCheckDirtyOutput::Open() + { + if (m_open) + return false; + + auto parentFolder(m_path); + parentFolder.remove_filename(); + create_directories(parentFolder); + + m_has_existing_file = fs::is_regular_file(m_path); + + if (!m_has_existing_file) + { + m_file_stream = std::ofstream(m_path, std::fstream::out | std::fstream::binary); + if (!m_file_stream.is_open()) + return false; + } + + m_open = true; + return true; + } + + std::ostream& TextFileCheckDirtyOutput::Stream() + { + if (!m_has_existing_file) + return m_file_stream; + + return m_memory; + } + + TextFileCheckDirtyResult TextFileCheckDirtyOutput::Close() + { + if (!m_open) + return TextFileCheckDirtyResult::FAILURE; + + m_open = false; + + if (m_has_existing_file) + { + const auto renderedContent = std::move(m_memory).str(); + m_memory = std::ostringstream(); + + if (!FileIsDirty(renderedContent)) + return TextFileCheckDirtyResult::OUTPUT_WAS_UP_TO_DATE; + + std::ofstream stream(m_path, std::fstream::out | std::fstream::binary); + if (!stream.is_open()) + return TextFileCheckDirtyResult::FAILURE; + + stream.write(renderedContent.data(), renderedContent.size()); + stream.close(); + } + + return TextFileCheckDirtyResult::OUTPUT_WRITTEN; + } + + [[nodiscard]] bool TextFileCheckDirtyOutput::FileIsDirty(const std::string& renderedContent) const + { + const auto fileSize = static_cast(fs::file_size(m_path)); + const size_t contentSize = renderedContent.size(); + if (fileSize != contentSize) + return true; + + std::ifstream oldFileStream(m_path, std::fstream::in | std::fstream::binary); + if (!oldFileStream.is_open()) + return true; + + char buffer[4096]; + size_t currentContentOffset = 0; + while (currentContentOffset < contentSize) + { + const auto expectedReadCount = std::min(sizeof(buffer), contentSize - currentContentOffset); + oldFileStream.read(buffer, expectedReadCount); + if (oldFileStream.gcount() != expectedReadCount) + { + oldFileStream.close(); + return true; + } + + if (memcmp(buffer, renderedContent.data() + currentContentOffset, expectedReadCount) != 0) + { + oldFileStream.close(); + return true; + } + + currentContentOffset += expectedReadCount; + } + + oldFileStream.close(); + return false; + } } // namespace utils diff --git a/src/Utils/Utils/FileUtils.h b/src/Utils/Utils/FileUtils.h index 803b9ecd..6b5aa647 100644 --- a/src/Utils/Utils/FileUtils.h +++ b/src/Utils/Utils/FileUtils.h @@ -21,4 +21,36 @@ namespace utils * \return \c true if the user input was valid and could be processed successfully, otherwise \c false. */ bool ParsePathsString(const std::string& pathsString, std::set& output); + + enum class TextFileCheckDirtyResult : std::uint8_t + { + OUTPUT_WRITTEN, + OUTPUT_WAS_UP_TO_DATE, + FAILURE + }; + + class TextFileCheckDirtyOutput final + { + public: + explicit TextFileCheckDirtyOutput(std::filesystem::path path); + ~TextFileCheckDirtyOutput(); + TextFileCheckDirtyOutput(const TextFileCheckDirtyOutput& other) = delete; + TextFileCheckDirtyOutput(TextFileCheckDirtyOutput&& other) noexcept = default; + TextFileCheckDirtyOutput& operator=(const TextFileCheckDirtyOutput& other) = delete; + TextFileCheckDirtyOutput& operator=(TextFileCheckDirtyOutput&& other) noexcept = default; + + bool Open(); + std::ostream& Stream(); + TextFileCheckDirtyResult Close(); + + private: + [[nodiscard]] bool FileIsDirty(const std::string& renderedContent) const; + + std::filesystem::path m_path; + + bool m_open; + bool m_has_existing_file; + std::ofstream m_file_stream; + std::ostringstream m_memory; + }; } // namespace utils diff --git a/src/ZoneCodeGeneratorLib/Generating/CodeGenerator.cpp b/src/ZoneCodeGeneratorLib/Generating/CodeGenerator.cpp index 679c525e..c41877bc 100644 --- a/src/ZoneCodeGeneratorLib/Generating/CodeGenerator.cpp +++ b/src/ZoneCodeGeneratorLib/Generating/CodeGenerator.cpp @@ -5,136 +5,15 @@ #include "Templates/ZoneLoadTemplate.h" #include "Templates/ZoneMarkTemplate.h" #include "Templates/ZoneWriteTemplate.h" +#include "Utils/FileUtils.h" #include "Utils/Logging/Log.h" #include "Utils/StringUtils.h" -#include #include #include -#include -#include namespace fs = std::filesystem; -namespace -{ - class CodeGeneratorOutput final - { - public: - explicit CodeGeneratorOutput(fs::path path) - : m_path(std::move(path)), - m_open(false), - m_has_existing_file(false) - { - } - - ~CodeGeneratorOutput() - { - Close(); - } - - bool Open() - { - if (m_open) - return false; - - auto parentFolder(m_path); - parentFolder.remove_filename(); - create_directories(parentFolder); - - m_has_existing_file = fs::is_regular_file(m_path); - - if (!m_has_existing_file) - { - m_file_stream = std::ofstream(m_path, std::fstream::out | std::fstream::binary); - if (!m_file_stream.is_open()) - return false; - } - - m_open = true; - return true; - } - - std::ostream& Stream() - { - if (!m_has_existing_file) - return m_file_stream; - - return m_memory; - } - - CodeGeneratorOutputResult Close() - { - if (!m_open) - return CodeGeneratorOutputResult::FAILURE; - - m_open = false; - - if (m_has_existing_file) - { - const auto renderedContent = std::move(m_memory).str(); - m_memory = std::ostringstream(); - - if (!FileIsDirty(renderedContent)) - return CodeGeneratorOutputResult::OUTPUT_WAS_UP_TO_DATE; - - std::ofstream stream(m_path, std::fstream::out | std::fstream::binary); - if (!stream.is_open()) - return CodeGeneratorOutputResult::FAILURE; - - stream.write(renderedContent.data(), renderedContent.size()); - stream.close(); - } - - return CodeGeneratorOutputResult::OUTPUT_WRITTEN; - } - - private: - [[nodiscard]] bool FileIsDirty(const std::string& renderedContent) const - { - const auto fileSize = static_cast(fs::file_size(m_path)); - const size_t contentSize = renderedContent.size(); - if (fileSize != contentSize) - return true; - - std::ifstream oldFileStream(m_path, std::fstream::in | std::fstream::binary); - if (!oldFileStream.is_open()) - return true; - - char buffer[4096]; - size_t currentContentOffset = 0; - while (currentContentOffset < contentSize) - { - const auto expectedReadCount = std::min(sizeof(buffer), contentSize - currentContentOffset); - oldFileStream.read(buffer, expectedReadCount); - if (oldFileStream.gcount() != expectedReadCount) - { - oldFileStream.close(); - return true; - } - - if (memcmp(buffer, renderedContent.data() + currentContentOffset, expectedReadCount) != 0) - { - oldFileStream.close(); - return true; - } - - currentContentOffset += expectedReadCount; - } - - oldFileStream.close(); - return false; - } - - fs::path m_path; - - bool m_open; - bool m_has_existing_file; - std::ofstream m_file_stream; - std::ostringstream m_memory; - }; -} // namespace - CodeGenerator::CodeGenerator(const ZoneCodeGeneratorArguments* args) : m_args(args) { @@ -149,7 +28,7 @@ void CodeGenerator::SetupTemplates() m_template_mapping["assetstructtests"] = std::make_unique(); } -CodeGeneratorOutputResult CodeGenerator::GenerateCodeOncePerTemplate(const OncePerTemplateRenderingContext& context, ICodeTemplate* codeTemplate) const +utils::TextFileCheckDirtyResult CodeGenerator::GenerateCodeOncePerTemplate(const OncePerTemplateRenderingContext& context, ICodeTemplate* codeTemplate) const { bool wroteAtLeastOneFile = false; for (const auto& codeFile : codeTemplate->GetFilesToRenderOncePerTemplate(context)) @@ -157,30 +36,30 @@ CodeGeneratorOutputResult CodeGenerator::GenerateCodeOncePerTemplate(const OnceP fs::path outputPath(m_args->m_output_directory); outputPath.append(codeFile.m_file_name); - CodeGeneratorOutput out(outputPath); + utils::TextFileCheckDirtyOutput out(outputPath); if (!out.Open()) { con::error("Failed to open file '{}'", outputPath.string()); - return CodeGeneratorOutputResult::FAILURE; + return utils::TextFileCheckDirtyResult::FAILURE; } codeTemplate->RenderOncePerTemplateFile(out.Stream(), codeFile.m_tag, context); const auto fileResult = out.Close(); - if (fileResult == CodeGeneratorOutputResult::FAILURE) + if (fileResult == utils::TextFileCheckDirtyResult::FAILURE) { con::error("Failed to write file '{}'", outputPath.string()); - return CodeGeneratorOutputResult::FAILURE; + return utils::TextFileCheckDirtyResult::FAILURE; } - if (fileResult == CodeGeneratorOutputResult::OUTPUT_WRITTEN) + if (fileResult == utils::TextFileCheckDirtyResult::OUTPUT_WRITTEN) wroteAtLeastOneFile = true; } - return wroteAtLeastOneFile ? CodeGeneratorOutputResult::OUTPUT_WRITTEN : CodeGeneratorOutputResult::OUTPUT_WAS_UP_TO_DATE; + return wroteAtLeastOneFile ? utils::TextFileCheckDirtyResult::OUTPUT_WRITTEN : utils::TextFileCheckDirtyResult::OUTPUT_WAS_UP_TO_DATE; } -CodeGeneratorOutputResult CodeGenerator::GenerateCodeOncePerAsset(const OncePerAssetRenderingContext& context, ICodeTemplate* codeTemplate) const +utils::TextFileCheckDirtyResult CodeGenerator::GenerateCodeOncePerAsset(const OncePerAssetRenderingContext& context, ICodeTemplate* codeTemplate) const { bool wroteAtLeastOneFile = false; for (const auto& codeFile : codeTemplate->GetFilesToRenderOncePerAsset(context)) @@ -188,27 +67,27 @@ CodeGeneratorOutputResult CodeGenerator::GenerateCodeOncePerAsset(const OncePerA fs::path outputPath(m_args->m_output_directory); outputPath.append(codeFile.m_file_name); - CodeGeneratorOutput out(outputPath); + utils::TextFileCheckDirtyOutput out(outputPath); if (!out.Open()) { con::error("Failed to open file '{}'", outputPath.string()); - return CodeGeneratorOutputResult::FAILURE; + return utils::TextFileCheckDirtyResult::FAILURE; } codeTemplate->RenderOncePerAssetFile(out.Stream(), codeFile.m_tag, context); const auto fileResult = out.Close(); - if (fileResult == CodeGeneratorOutputResult::FAILURE) + if (fileResult == utils::TextFileCheckDirtyResult::FAILURE) { con::error("Failed to write file '{}'", outputPath.string()); - return CodeGeneratorOutputResult::FAILURE; + return utils::TextFileCheckDirtyResult::FAILURE; } - if (fileResult == CodeGeneratorOutputResult::OUTPUT_WRITTEN) + if (fileResult == utils::TextFileCheckDirtyResult::OUTPUT_WRITTEN) wroteAtLeastOneFile = true; } - return wroteAtLeastOneFile ? CodeGeneratorOutputResult::OUTPUT_WRITTEN : CodeGeneratorOutputResult::OUTPUT_WAS_UP_TO_DATE; + return wroteAtLeastOneFile ? utils::TextFileCheckDirtyResult::OUTPUT_WRITTEN : utils::TextFileCheckDirtyResult::OUTPUT_WAS_UP_TO_DATE; } bool CodeGenerator::GetAssetWithName(const IDataRepository* repository, const std::string& name, StructureInformation*& asset) @@ -267,13 +146,13 @@ bool CodeGenerator::GenerateCode(const IDataRepository* repository) const auto result = GenerateCodeOncePerAsset(*context, foundTemplate->second.get()); switch (result) { - case CodeGeneratorOutputResult::OUTPUT_WRITTEN: + case utils::TextFileCheckDirtyResult::OUTPUT_WRITTEN: con::info("Successfully generated code for asset '{}' with preset '{}'", asset->m_definition->GetFullName(), foundTemplate->first); break; - case CodeGeneratorOutputResult::OUTPUT_WAS_UP_TO_DATE: + case utils::TextFileCheckDirtyResult::OUTPUT_WAS_UP_TO_DATE: con::info("Code was up to date for asset '{}' with preset '{}'", asset->m_definition->GetFullName(), foundTemplate->first); break; - case CodeGeneratorOutputResult::FAILURE: + case utils::TextFileCheckDirtyResult::FAILURE: con::error("Failed to generate code for asset '{}' with preset '{}'", asset->m_definition->GetFullName(), foundTemplate->first); return false; } @@ -284,13 +163,13 @@ bool CodeGenerator::GenerateCode(const IDataRepository* repository) const auto result = GenerateCodeOncePerTemplate(*context, foundTemplate->second.get()); switch (result) { - case CodeGeneratorOutputResult::OUTPUT_WRITTEN: + case utils::TextFileCheckDirtyResult::OUTPUT_WRITTEN: con::info("Successfully generated code with preset '{}'", foundTemplate->first); break; - case CodeGeneratorOutputResult::OUTPUT_WAS_UP_TO_DATE: + case utils::TextFileCheckDirtyResult::OUTPUT_WAS_UP_TO_DATE: con::info("Code was up to date for preset '{}'", foundTemplate->first); break; - case CodeGeneratorOutputResult::FAILURE: + case utils::TextFileCheckDirtyResult::FAILURE: con::error("Failed to generate code with preset '{}'", foundTemplate->first); return false; } diff --git a/src/ZoneCodeGeneratorLib/Generating/CodeGenerator.h b/src/ZoneCodeGeneratorLib/Generating/CodeGenerator.h index 565b60d9..8fca5eae 100644 --- a/src/ZoneCodeGeneratorLib/Generating/CodeGenerator.h +++ b/src/ZoneCodeGeneratorLib/Generating/CodeGenerator.h @@ -1,20 +1,13 @@ #pragma once #include "ICodeTemplate.h" +#include "Utils/FileUtils.h" #include "ZoneCodeGeneratorArguments.h" -#include #include #include #include -enum class CodeGeneratorOutputResult : std::uint8_t -{ - OUTPUT_WRITTEN, - OUTPUT_WAS_UP_TO_DATE, - FAILURE -}; - class CodeGenerator { public: @@ -25,8 +18,8 @@ public: private: void SetupTemplates(); - CodeGeneratorOutputResult GenerateCodeOncePerTemplate(const OncePerTemplateRenderingContext& context, ICodeTemplate* codeTemplate) const; - CodeGeneratorOutputResult GenerateCodeOncePerAsset(const OncePerAssetRenderingContext& context, ICodeTemplate* codeTemplate) const; + utils::TextFileCheckDirtyResult GenerateCodeOncePerTemplate(const OncePerTemplateRenderingContext& context, ICodeTemplate* codeTemplate) const; + utils::TextFileCheckDirtyResult GenerateCodeOncePerAsset(const OncePerAssetRenderingContext& context, ICodeTemplate* codeTemplate) const; static bool GetAssetWithName(const IDataRepository* repository, const std::string& name, StructureInformation*& asset);