diff --git a/README.md b/README.md index f618c96..7dc4d65 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ |-|-| | [![PyPI version](https://badge.fury.io/py/retranslator.svg)](https://badge.fury.io/py/retranslator) | __Python__ | | [![nimble](https://raw.githubusercontent.com/yglukhov/nimble-tag/master/nimble.png)](https://nimble.directory/pkg/retranslator) | __Nim__ | +| [![CMake](https://img.shields.io/badge/CMake-3.16+-blue.svg)](https://cmake.org/) | __C++__ | # [RegularExpressions.Transformer](https://github.com/linksplatform/RegularExpressions.Transformer) @@ -25,6 +26,8 @@ NuGet package: [Platform.RegularExpressions.Transformer](https://www.nuget.org/p Python version: [retranslator](https://github.com/linksplatform/RegularExpressions.Transformer/tree/master/python) +C++ version: [cpp](https://github.com/linksplatform/RegularExpressions.Transformer/tree/master/cpp) + ## [Documentation](https://linksplatform.github.io/RegularExpressions.Transformer) [PDF file](https://linksplatform.github.io/RegularExpressions.Transformer/csharp/Platform.RegularExpressions.Transformer.pdf) with code for e-readers. diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt new file mode 100644 index 0000000..0daab27 --- /dev/null +++ b/cpp/CMakeLists.txt @@ -0,0 +1,99 @@ +cmake_minimum_required(VERSION 3.16) + +project(RegularExpressionsTransformer + VERSION 1.0.0 + DESCRIPTION "Platform Regular Expressions Transformer C++ Library" + LANGUAGES CXX) + +# Set C++ standard +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# Add compiler-specific options +if(MSVC) + add_compile_options(/W4 /permissive-) +else() + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# Define the library target +add_library(RegularExpressionsTransformer + src/TextSteppedTransformer.cpp + src/FileTransformer.cpp +) + +# Set target properties +set_target_properties(RegularExpressionsTransformer PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} + PUBLIC_HEADER "include/ISubstitutionRule.h;include/SubstitutionRule.h;include/ITransformer.h;include/ITextTransformer.h;include/TextSteppedTransformer.h;include/TextTransformer.h;include/IFileTransformer.h;include/FileTransformer.h" +) + +# Include directories +target_include_directories(RegularExpressionsTransformer + PUBLIC + $ + $ + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src +) + +# Find and link required libraries +find_package(Threads REQUIRED) +target_link_libraries(RegularExpressionsTransformer + PUBLIC + Threads::Threads +) + +# Enable testing +enable_testing() + +# Add tests subdirectory if it exists +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/tests) + add_subdirectory(tests) +endif() + +# Add examples subdirectory if it exists +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/examples) + add_subdirectory(examples) +endif() + +# Install targets +include(GNUInstallDirs) + +install(TARGETS RegularExpressionsTransformer + EXPORT RegularExpressionsTransformerTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/Platform/RegularExpressions/Transformer +) + +# Install the export set +install(EXPORT RegularExpressionsTransformerTargets + FILE RegularExpressionsTransformerTargets.cmake + NAMESPACE Platform::RegularExpressions:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/RegularExpressionsTransformer +) + +# Create config files +include(CMakePackageConfigHelpers) + +configure_package_config_file( + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/RegularExpressionsTransformerConfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/RegularExpressionsTransformerConfig.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/RegularExpressionsTransformer +) + +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/RegularExpressionsTransformerConfigVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY AnyNewerVersion +) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/RegularExpressionsTransformerConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/RegularExpressionsTransformerConfigVersion.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/RegularExpressionsTransformer +) \ No newline at end of file diff --git a/cpp/README.md b/cpp/README.md new file mode 100644 index 0000000..6c67bba --- /dev/null +++ b/cpp/README.md @@ -0,0 +1,139 @@ +# RegularExpressions.Transformer C++ + +C++ implementation of Platform.RegularExpressions.Transformer library. + +## Features + +- **Text Transformation**: Apply sequences of regular expression substitutions to text +- **File Transformation**: Transform files and directories using regex rules +- **Rule-based Processing**: Configure multiple substitution rules with repeat counts +- **Cross-platform**: Uses standard C++17 and CMake for portability + +## Requirements + +- C++17 compatible compiler +- CMake 3.16 or later +- Google Test (automatically downloaded if not found) + +## Building + +```bash +mkdir build +cd build +cmake .. +make +``` + +## Running Tests + +```bash +# From build directory +ctest +# or +./tests/RegularExpressionsTransformerTests +``` + +## Running Examples + +```bash +# From build directory +./examples/BasicExample +./examples/FileExample +``` + +## Usage + +### Basic Text Transformation + +```cpp +#include "TextTransformer.h" +#include "SubstitutionRule.h" + +using namespace Platform::RegularExpressions::Transformer; + +// Create substitution rules +auto rule1 = std::make_shared("hello", "hi"); +auto rule2 = std::make_shared("world", "universe"); + +std::vector> rules = {rule1, rule2}; + +// Create transformer +TextTransformer transformer(rules); + +// Transform text +std::string result = transformer.transform("hello world"); +// Result: "hi universe" +``` + +### File Transformation + +```cpp +#include "FileTransformer.h" +#include "TextTransformer.h" +#include "SubstitutionRule.h" + +using namespace Platform::RegularExpressions::Transformer; + +// Create text transformer with rules +auto rule = std::make_shared(R"(\d+)", "[NUMBER]"); +std::vector> rules = {rule}; +auto textTransformer = std::make_shared(rules); + +// Create file transformer +FileTransformer fileTransformer(textTransformer, ".txt", ".transformed"); + +// Transform file or directory +fileTransformer.transform("input.txt", "output.transformed"); +fileTransformer.transform("source_folder/", "target_folder/"); +``` + +### Advanced Rules + +```cpp +// Rule with maximum repeat count +auto rule = std::make_shared("a+", "X", 3); + +// Rule with custom regex options +auto complexRule = std::make_shared( + R"((?i)hello)", // Case-insensitive pattern + "Hi", + 0, // No repeat limit + std::regex_constants::ECMAScript | std::regex_constants::icase +); + +// Rule with path pattern for file filtering +rule->setPathPattern(R"(.*\.cpp$)"); // Only process .cpp files +``` + +## Architecture + +- **ISubstitutionRule**: Interface for substitution rules +- **SubstitutionRule**: Implementation of substitution rules with regex patterns +- **ITransformer**: Base interface for transformers +- **ITextTransformer**: Interface for text transformation +- **TextTransformer**: Main text transformation implementation +- **TextSteppedTransformer**: Step-by-step text transformation for debugging +- **IFileTransformer**: Interface for file transformation +- **FileTransformer**: File and directory transformation implementation + +## Installation + +### Using CMake + +```cmake +find_package(RegularExpressionsTransformer REQUIRED) +target_link_libraries(your_target Platform::RegularExpressions::RegularExpressionsTransformer) +``` + +### Manual Installation + +```bash +mkdir build && cd build +cmake .. +make +sudo make install +``` + +## License + +This project is licensed under the MIT License - see the LICENSE file for details. \ No newline at end of file diff --git a/cpp/cmake/RegularExpressionsTransformerConfig.cmake.in b/cpp/cmake/RegularExpressionsTransformerConfig.cmake.in new file mode 100644 index 0000000..66dac93 --- /dev/null +++ b/cpp/cmake/RegularExpressionsTransformerConfig.cmake.in @@ -0,0 +1,8 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(Threads) + +include("${CMAKE_CURRENT_LIST_DIR}/RegularExpressionsTransformerTargets.cmake") + +check_required_components(RegularExpressionsTransformer) \ No newline at end of file diff --git a/cpp/examples/CMakeLists.txt b/cpp/examples/CMakeLists.txt new file mode 100644 index 0000000..9a0750e --- /dev/null +++ b/cpp/examples/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.16) + +# Basic example +add_executable(BasicExample + basic_example.cpp +) + +target_link_libraries(BasicExample + RegularExpressionsTransformer +) + +# File transformation example +add_executable(FileExample + file_example.cpp +) + +target_link_libraries(FileExample + RegularExpressionsTransformer +) \ No newline at end of file diff --git a/cpp/examples/basic_example.cpp b/cpp/examples/basic_example.cpp new file mode 100644 index 0000000..7f503b7 --- /dev/null +++ b/cpp/examples/basic_example.cpp @@ -0,0 +1,39 @@ +#include "../include/TextTransformer.h" +#include "../include/SubstitutionRule.h" +#include +#include + +using namespace Platform::RegularExpressions::Transformer; + +int main() { + try { + // Create substitution rules + auto rule1 = std::make_shared("hello", "hi"); + auto rule2 = std::make_shared("world", "universe"); + auto rule3 = std::make_shared(R"(\d+)", "NUMBER"); + + // Create a vector of rules + std::vector> rules = {rule1, rule2, rule3}; + + // Create the transformer + TextTransformer transformer(rules); + + // Test text + std::string sourceText = "hello world! I have 123 apples and 456 oranges."; + + std::cout << "Source text: " << sourceText << std::endl; + + // Transform the text + std::string result = transformer.transform(sourceText); + + std::cout << "Transformed text: " << result << std::endl; + + // Expected output: "hi universe! I have NUMBER apples and NUMBER oranges." + + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } + + return 0; +} \ No newline at end of file diff --git a/cpp/examples/file_example.cpp b/cpp/examples/file_example.cpp new file mode 100644 index 0000000..01a41d3 --- /dev/null +++ b/cpp/examples/file_example.cpp @@ -0,0 +1,67 @@ +#include "../include/FileTransformer.h" +#include "../include/TextTransformer.h" +#include "../include/SubstitutionRule.h" +#include +#include +#include +#include + +using namespace Platform::RegularExpressions::Transformer; + +int main() { + try { + // Create a temporary directory for testing + std::string tempDir = "temp_example"; + std::filesystem::create_directories(tempDir); + + // Create a source file + std::string sourceFile = tempDir + "/source.txt"; + std::ofstream file(sourceFile); + file << "Hello World!\n"; + file << "This is a test file with some numbers: 123, 456, 789.\n"; + file << "We will transform this content.\n"; + file.close(); + + // Create substitution rules + auto rule1 = std::make_shared("Hello", "Hi"); + auto rule2 = std::make_shared("World", "Universe"); + auto rule3 = std::make_shared(R"(\b\d+\b)", "[NUMBER]"); + + std::vector> rules = {rule1, rule2, rule3}; + + // Create transformers + auto textTransformer = std::make_shared(rules); + FileTransformer fileTransformer(textTransformer, ".txt", ".transformed"); + + // Transform the file + std::string targetFile = tempDir + "/source.transformed"; + fileTransformer.transform(sourceFile, targetFile); + + // Read and display the results + std::cout << "Original file content:" << std::endl; + std::ifstream original(sourceFile); + std::string line; + while (std::getline(original, line)) { + std::cout << " " << line << std::endl; + } + original.close(); + + std::cout << std::endl << "Transformed file content:" << std::endl; + std::ifstream transformed(targetFile); + while (std::getline(transformed, line)) { + std::cout << " " << line << std::endl; + } + transformed.close(); + + // Clean up + std::filesystem::remove_all(tempDir); + + std::cout << std::endl << "Example completed successfully!" << std::endl; + + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } + + return 0; +} \ No newline at end of file diff --git a/cpp/include/FileTransformer.h b/cpp/include/FileTransformer.h new file mode 100644 index 0000000..e36b5cf --- /dev/null +++ b/cpp/include/FileTransformer.h @@ -0,0 +1,116 @@ +#pragma once + +#include "IFileTransformer.h" +#include "ITextTransformer.h" +#include +#include + +namespace Platform::RegularExpressions::Transformer { + +/** + * @brief File transformer implementation. + */ +class FileTransformer : public IFileTransformer { +private: + std::shared_ptr textTransformer_; + std::string sourceFileExtension_; + std::string targetFileExtension_; + +public: + /** + * @brief Constructs a FileTransformer. + * @param textTransformer The text transformer to use. + * @param sourceFileExtension The source file extension to process. + * @param targetFileExtension The target file extension to generate. + */ + FileTransformer(std::shared_ptr textTransformer, + const std::string& sourceFileExtension, + const std::string& targetFileExtension) + : textTransformer_(textTransformer) + , sourceFileExtension_(sourceFileExtension) + , targetFileExtension_(targetFileExtension) {} + + // ITransformer interface implementation + const std::vector>& getRules() const override { + return textTransformer_->getRules(); + } + + // IFileTransformer interface implementation + const std::string& getSourceFileExtension() const override { + return sourceFileExtension_; + } + + const std::string& getTargetFileExtension() const override { + return targetFileExtension_; + } + + void transform(const std::string& sourcePath, const std::string& targetPath) override; + +protected: + /** + * @brief Transforms a single file. + * @param sourcePath The source file path. + * @param targetPath The target file path. + */ + virtual void transformFile(const std::string& sourcePath, const std::string& targetPath); + + /** + * @brief Transforms a folder recursively. + * @param sourcePath The source folder path. + * @param targetPath The target folder path. + */ + virtual void transformFolder(const std::string& sourcePath, const std::string& targetPath); + +private: + /** + * @brief Gets the target file name based on source path and target directory. + * @param sourcePath The source file path. + * @param targetDirectory The target directory. + * @return The target file path. + */ + std::string getTargetFileName(const std::string& sourcePath, const std::string& targetDirectory) const; + + /** + * @brief Checks if a file extension matches the expected extension. + * @param filePath The file path to check. + * @param extension The expected extension. + * @return true if the extension matches. + */ + static bool fileExtensionMatches(const std::string& filePath, const std::string& extension); + + /** + * @brief Counts files recursively with the specified extension. + * @param path The directory path. + * @param extension The file extension to count. + * @return The number of files found. + */ + static long countFilesRecursively(const std::string& path, const std::string& extension); + + /** + * @brief Checks if a path is a directory. + * @param path The path to check. + * @return true if the path is a directory. + */ + static bool isDirectory(const std::string& path); + + /** + * @brief Checks if a path looks like a directory path (ends with separator). + * @param path The path to check. + * @return true if it looks like a directory path. + */ + static bool looksLikeDirectoryPath(const std::string& path); + + /** + * @brief Ensures that a directory exists, creating it if necessary. + * @param path The directory path. + */ + static void ensureDirectoryExists(const std::string& path); + + /** + * @brief Ensures that the parent directory of a file exists. + * @param filePath The file path. + */ + static void ensureParentDirectoryExists(const std::string& filePath); +}; + +} // namespace Platform::RegularExpressions::Transformer \ No newline at end of file diff --git a/cpp/include/IFileTransformer.h b/cpp/include/IFileTransformer.h new file mode 100644 index 0000000..7451e5a --- /dev/null +++ b/cpp/include/IFileTransformer.h @@ -0,0 +1,35 @@ +#pragma once + +#include "ITransformer.h" +#include + +namespace Platform::RegularExpressions::Transformer { + +/** + * @brief Defines the file transformer interface. + */ +class IFileTransformer : public ITransformer { +public: + virtual ~IFileTransformer() = default; + + /** + * @brief Gets the source file extension. + * @return The source file extension. + */ + virtual const std::string& getSourceFileExtension() const = 0; + + /** + * @brief Gets the target file extension. + * @return The target file extension. + */ + virtual const std::string& getTargetFileExtension() const = 0; + + /** + * @brief Transforms files from source path to target path. + * @param sourcePath The source file or directory path. + * @param targetPath The target file or directory path. + */ + virtual void transform(const std::string& sourcePath, const std::string& targetPath) = 0; +}; + +} // namespace Platform::RegularExpressions::Transformer \ No newline at end of file diff --git a/cpp/include/ISubstitutionRule.h b/cpp/include/ISubstitutionRule.h new file mode 100644 index 0000000..31bfa22 --- /dev/null +++ b/cpp/include/ISubstitutionRule.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +namespace Platform::RegularExpressions::Transformer { + +/** + * @brief Defines the substitution rule interface. + */ +class ISubstitutionRule { +public: + virtual ~ISubstitutionRule() = default; + + /** + * @brief Gets the match pattern. + * @return The regex match pattern. + */ + virtual const std::regex& getMatchPattern() const = 0; + + /** + * @brief Gets the substitution pattern. + * @return The substitution pattern string. + */ + virtual const std::string& getSubstitutionPattern() const = 0; + + /** + * @brief Gets the maximum repeat count. + * @return The maximum repeat count. + */ + virtual int getMaximumRepeatCount() const = 0; + + /** + * @brief Gets the path pattern. + * @return The regex path pattern (optional). + */ + virtual const std::regex* getPathPattern() const = 0; +}; + +} // namespace Platform::RegularExpressions::Transformer \ No newline at end of file diff --git a/cpp/include/ITextTransformer.h b/cpp/include/ITextTransformer.h new file mode 100644 index 0000000..1ac725a --- /dev/null +++ b/cpp/include/ITextTransformer.h @@ -0,0 +1,23 @@ +#pragma once + +#include "ITransformer.h" +#include + +namespace Platform::RegularExpressions::Transformer { + +/** + * @brief Defines the text transformer interface. + */ +class ITextTransformer : public ITransformer { +public: + virtual ~ITextTransformer() = default; + + /** + * @brief Transforms the source text using the configured rules. + * @param sourceText The text to transform. + * @return The transformed text. + */ + virtual std::string transform(const std::string& sourceText) = 0; +}; + +} // namespace Platform::RegularExpressions::Transformer \ No newline at end of file diff --git a/cpp/include/ITransformer.h b/cpp/include/ITransformer.h new file mode 100644 index 0000000..3ea821e --- /dev/null +++ b/cpp/include/ITransformer.h @@ -0,0 +1,23 @@ +#pragma once + +#include "ISubstitutionRule.h" +#include +#include + +namespace Platform::RegularExpressions::Transformer { + +/** + * @brief Defines the base transformer interface. + */ +class ITransformer { +public: + virtual ~ITransformer() = default; + + /** + * @brief Gets the list of substitution rules. + * @return Reference to the rules vector. + */ + virtual const std::vector>& getRules() const = 0; +}; + +} // namespace Platform::RegularExpressions::Transformer \ No newline at end of file diff --git a/cpp/include/SubstitutionRule.h b/cpp/include/SubstitutionRule.h new file mode 100644 index 0000000..a0b9d35 --- /dev/null +++ b/cpp/include/SubstitutionRule.h @@ -0,0 +1,169 @@ +#pragma once + +#include "ISubstitutionRule.h" +#include +#include +#include +#include +#include +#include + +namespace Platform::RegularExpressions::Transformer { + +/** + * @brief Represents a substitution rule implementation. + */ +class SubstitutionRule : public ISubstitutionRule { +public: + static constexpr std::regex_constants::syntax_option_type DEFAULT_REGEX_OPTIONS = + std::regex_constants::ECMAScript | std::regex_constants::multiline; + +private: + std::regex matchPattern_; + std::string substitutionPattern_; + std::unique_ptr pathPattern_; + int maximumRepeatCount_; + +public: + /** + * @brief Constructs a SubstitutionRule with regex objects. + * @param matchPattern The regex pattern to match. + * @param substitutionPattern The substitution string. + * @param maximumRepeatCount Maximum number of repetitions (0 = no limit). + */ + SubstitutionRule(const std::regex& matchPattern, + const std::string& substitutionPattern, + int maximumRepeatCount = 0) + : matchPattern_(matchPattern) + , substitutionPattern_(substitutionPattern) + , pathPattern_(nullptr) + , maximumRepeatCount_(maximumRepeatCount) {} + + /** + * @brief Constructs a SubstitutionRule with string patterns. + * @param matchPatternStr The regex pattern string to match. + * @param substitutionPattern The substitution string. + * @param maximumRepeatCount Maximum number of repetitions (0 = no limit). + * @param regexOptions Regex compilation options. + */ + SubstitutionRule(const std::string& matchPatternStr, + const std::string& substitutionPattern, + int maximumRepeatCount = 0, + std::regex_constants::syntax_option_type regexOptions = DEFAULT_REGEX_OPTIONS) + : matchPattern_(matchPatternStr, regexOptions) + , substitutionPattern_(substitutionPattern) + , pathPattern_(nullptr) + , maximumRepeatCount_(maximumRepeatCount) {} + + /** + * @brief Copy constructor. + */ + SubstitutionRule(const SubstitutionRule& other) + : matchPattern_(other.matchPattern_) + , substitutionPattern_(other.substitutionPattern_) + , pathPattern_(other.pathPattern_ ? std::make_unique(*other.pathPattern_) : nullptr) + , maximumRepeatCount_(other.maximumRepeatCount_) {} + + /** + * @brief Copy assignment operator. + */ + SubstitutionRule& operator=(const SubstitutionRule& other) { + if (this != &other) { + matchPattern_ = other.matchPattern_; + substitutionPattern_ = other.substitutionPattern_; + pathPattern_ = other.pathPattern_ ? std::make_unique(*other.pathPattern_) : nullptr; + maximumRepeatCount_ = other.maximumRepeatCount_; + } + return *this; + } + + /** + * @brief Move constructor. + */ + SubstitutionRule(SubstitutionRule&& other) noexcept + : matchPattern_(std::move(other.matchPattern_)) + , substitutionPattern_(std::move(other.substitutionPattern_)) + , pathPattern_(std::move(other.pathPattern_)) + , maximumRepeatCount_(other.maximumRepeatCount_) {} + + /** + * @brief Move assignment operator. + */ + SubstitutionRule& operator=(SubstitutionRule&& other) noexcept { + if (this != &other) { + matchPattern_ = std::move(other.matchPattern_); + substitutionPattern_ = std::move(other.substitutionPattern_); + pathPattern_ = std::move(other.pathPattern_); + maximumRepeatCount_ = other.maximumRepeatCount_; + } + return *this; + } + + // ISubstitutionRule interface implementation + const std::regex& getMatchPattern() const override { + return matchPattern_; + } + + const std::string& getSubstitutionPattern() const override { + return substitutionPattern_; + } + + int getMaximumRepeatCount() const override { + return maximumRepeatCount_; + } + + const std::regex* getPathPattern() const override { + return pathPattern_.get(); + } + + /** + * @brief Sets the path pattern. + * @param pathPattern The path pattern regex. + */ + void setPathPattern(const std::regex& pathPattern) { + pathPattern_ = std::make_unique(pathPattern); + } + + /** + * @brief Sets the path pattern from string. + * @param pathPatternStr The path pattern string. + * @param regexOptions Regex compilation options. + */ + void setPathPattern(const std::string& pathPatternStr, + std::regex_constants::syntax_option_type regexOptions = DEFAULT_REGEX_OPTIONS) { + pathPattern_ = std::make_unique(pathPatternStr, regexOptions); + } + + /** + * @brief Sets the maximum repeat count. + * @param count The maximum repeat count. + */ + void setMaximumRepeatCount(int count) { + maximumRepeatCount_ = count; + } + + /** + * @brief Returns string representation of the rule. + * @return String representation. + */ + std::string toString() const { + std::ostringstream ss; + ss << "\"" << "pattern" << "\" -> \"" << substitutionPattern_ << "\""; + + if (pathPattern_) { + ss << " on files matching path pattern"; + } + + if (maximumRepeatCount_ > 0) { + if (maximumRepeatCount_ == std::numeric_limits::max()) { + ss << " repeated forever"; + } else { + ss << " repeated up to " << maximumRepeatCount_ << " times"; + } + } + + return ss.str(); + } +}; + +} // namespace Platform::RegularExpressions::Transformer \ No newline at end of file diff --git a/cpp/include/TextSteppedTransformer.h b/cpp/include/TextSteppedTransformer.h new file mode 100644 index 0000000..722e7a1 --- /dev/null +++ b/cpp/include/TextSteppedTransformer.h @@ -0,0 +1,136 @@ +#pragma once + +#include "ITransformer.h" +#include +#include +#include + +namespace Platform::RegularExpressions::Transformer { + +/** + * @brief A stepped transformer that processes rules one by one. + */ +class TextSteppedTransformer : public ITransformer { +private: + std::vector> rules_; + std::string text_; + int current_; + +public: + /** + * @brief Default constructor. + */ + TextSteppedTransformer() + : current_(-1) {} + + /** + * @brief Constructs with rules. + * @param rules The substitution rules. + */ + explicit TextSteppedTransformer(const std::vector>& rules) + : rules_(rules), current_(-1) {} + + /** + * @brief Constructs with rules and text. + * @param rules The substitution rules. + * @param text The text to transform. + */ + TextSteppedTransformer(const std::vector>& rules, + const std::string& text) + : rules_(rules), text_(text), current_(-1) {} + + /** + * @brief Constructs with rules, text, and current position. + * @param rules The substitution rules. + * @param text The text to transform. + * @param current The current rule index. + */ + TextSteppedTransformer(const std::vector>& rules, + const std::string& text, + int current) + : rules_(rules), text_(text), current_(current) {} + + // ITransformer interface implementation + const std::vector>& getRules() const override { + return rules_; + } + + /** + * @brief Gets the current text. + * @return The current text being transformed. + */ + const std::string& getText() const { + return text_; + } + + /** + * @brief Gets the current rule index. + * @return The current rule index. + */ + int getCurrent() const { + return current_; + } + + /** + * @brief Sets the text. + * @param text The text to set. + */ + void setText(const std::string& text) { + text_ = text; + } + + /** + * @brief Sets the current rule index. + * @param current The current rule index. + */ + void setCurrent(int current) { + current_ = current; + } + + /** + * @brief Resets the transformer with new parameters. + * @param rules The substitution rules. + * @param text The text to transform. + * @param current The current rule index. + */ + void reset(const std::vector>& rules, + const std::string& text, + int current) { + rules_ = rules; + text_ = text; + current_ = current; + } + + /** + * @brief Resets the transformer with rules and text. + * @param rules The substitution rules. + * @param text The text to transform. + */ + void reset(const std::vector>& rules, + const std::string& text) { + reset(rules, text, -1); + } + + /** + * @brief Resets with new text. + * @param text The text to transform. + */ + void reset(const std::string& text) { + reset(rules_, text, -1); + } + + /** + * @brief Resets the transformer to initial state. + */ + void reset() { + reset({}, "", -1); + } + + /** + * @brief Processes the next rule. + * @return true if a rule was processed, false if no more rules. + */ + bool next(); +}; + +} // namespace Platform::RegularExpressions::Transformer \ No newline at end of file diff --git a/cpp/include/TextTransformer.h b/cpp/include/TextTransformer.h new file mode 100644 index 0000000..0b26587 --- /dev/null +++ b/cpp/include/TextTransformer.h @@ -0,0 +1,41 @@ +#pragma once + +#include "ITextTransformer.h" +#include "TextSteppedTransformer.h" + +namespace Platform::RegularExpressions::Transformer { + +/** + * @brief Main text transformer implementation. + */ +class TextTransformer : public ITextTransformer { +private: + std::vector> rules_; + +public: + /** + * @brief Constructs with substitution rules. + * @param substitutionRules The list of substitution rules. + */ + explicit TextTransformer(const std::vector>& substitutionRules) + : rules_(substitutionRules) {} + + // ITransformer interface implementation + const std::vector>& getRules() const override { + return rules_; + } + + // ITextTransformer interface implementation + std::string transform(const std::string& sourceText) override { + TextSteppedTransformer baseTransformer(rules_); + baseTransformer.reset(sourceText); + + while (baseTransformer.next()) { + // Continue processing until all rules are applied + } + + return baseTransformer.getText(); + } +}; + +} // namespace Platform::RegularExpressions::Transformer \ No newline at end of file diff --git a/cpp/src/FileTransformer.cpp b/cpp/src/FileTransformer.cpp new file mode 100644 index 0000000..1614d30 --- /dev/null +++ b/cpp/src/FileTransformer.cpp @@ -0,0 +1,183 @@ +#include "../include/FileTransformer.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Platform::RegularExpressions::Transformer { + +void FileTransformer::transform(const std::string& sourcePath, const std::string& targetPath) { + std::filesystem::path defaultPath = std::filesystem::current_path(); + + std::string actualSourcePath = sourcePath.empty() ? defaultPath.string() : sourcePath; + std::string actualTargetPath = targetPath.empty() ? defaultPath.string() : targetPath; + + bool sourceIsDirectory = isDirectory(actualSourcePath) || looksLikeDirectoryPath(actualSourcePath); + bool targetIsDirectory = isDirectory(actualTargetPath) || looksLikeDirectoryPath(actualTargetPath); + + if (sourceIsDirectory && targetIsDirectory) { + // Folder -> Folder + if (!std::filesystem::exists(actualSourcePath)) { + return; + } + transformFolder(actualSourcePath, actualTargetPath); + } else if (!(sourceIsDirectory || targetIsDirectory)) { + // File -> File + if (!std::filesystem::exists(actualSourcePath)) { + throw std::runtime_error("Source file does not exist: " + actualSourcePath); + } + ensureParentDirectoryExists(actualTargetPath); + transformFile(actualSourcePath, actualTargetPath); + } else if (targetIsDirectory) { + // File -> Folder + if (!std::filesystem::exists(actualSourcePath)) { + throw std::runtime_error("Source file does not exist: " + actualSourcePath); + } + ensureDirectoryExists(actualTargetPath); + transformFile(actualSourcePath, getTargetFileName(actualSourcePath, actualTargetPath)); + } else { + // Folder -> File + throw std::runtime_error("Cannot transform folder to file"); + } +} + +void FileTransformer::transformFile(const std::string& sourcePath, const std::string& targetPath) { + // Check if target file is newer than source and application + if (std::filesystem::exists(targetPath)) { + auto targetTime = std::filesystem::last_write_time(targetPath); + auto sourceTime = std::filesystem::last_write_time(sourcePath); + + if (sourceTime < targetTime) { + return; // Skip if target is newer + } + } + + // Read source file + std::ifstream sourceFile(sourcePath); + if (!sourceFile) { + throw std::runtime_error("Cannot open source file: " + sourcePath); + } + + std::stringstream buffer; + buffer << sourceFile.rdbuf(); + std::string sourceText = buffer.str(); + sourceFile.close(); + + // Transform text + std::string targetText = textTransformer_->transform(sourceText); + + // Write target file + std::ofstream targetFile(targetPath); + if (!targetFile) { + throw std::runtime_error("Cannot create target file: " + targetPath); + } + + targetFile << targetText; + targetFile.close(); +} + +void FileTransformer::transformFolder(const std::string& sourcePath, const std::string& targetPath) { + if (countFilesRecursively(sourcePath, sourceFileExtension_) == 0) { + return; + } + + ensureDirectoryExists(targetPath); + + // Process subdirectories + for (const auto& entry : std::filesystem::directory_iterator(sourcePath)) { + if (entry.is_directory()) { + std::filesystem::path relativePath = std::filesystem::relative(entry.path(), sourcePath); + std::filesystem::path newTargetPath = std::filesystem::path(targetPath) / relativePath; + transformFolder(entry.path().string(), newTargetPath.string()); + } + } + + // Process files in parallel + std::vector> futures; + + for (const auto& entry : std::filesystem::directory_iterator(sourcePath)) { + if (entry.is_regular_file()) { + std::string filePath = entry.path().string(); + if (fileExtensionMatches(filePath, sourceFileExtension_)) { + futures.push_back(std::async(std::launch::async, [this, filePath, targetPath]() { + transformFile(filePath, getTargetFileName(filePath, targetPath)); + })); + } + } + } + + // Wait for all tasks to complete + for (auto& future : futures) { + future.wait(); + } +} + +std::string FileTransformer::getTargetFileName(const std::string& sourcePath, const std::string& targetDirectory) const { + std::filesystem::path source(sourcePath); + std::filesystem::path target(targetDirectory); + + std::filesystem::path filename = source.filename(); + filename.replace_extension(targetFileExtension_); + + return (target / filename).string(); +} + +bool FileTransformer::fileExtensionMatches(const std::string& filePath, const std::string& extension) { + std::filesystem::path path(filePath); + std::string fileExt = path.extension().string(); + + // Case-insensitive comparison + std::string lowerFileExt = fileExt; + std::string lowerExpectedExt = extension; + + std::transform(lowerFileExt.begin(), lowerFileExt.end(), lowerFileExt.begin(), ::tolower); + std::transform(lowerExpectedExt.begin(), lowerExpectedExt.end(), lowerExpectedExt.begin(), ::tolower); + + return lowerFileExt == lowerExpectedExt; +} + +long FileTransformer::countFilesRecursively(const std::string& path, const std::string& extension) { + long count = 0; + + try { + for (const auto& entry : std::filesystem::recursive_directory_iterator(path)) { + if (entry.is_regular_file() && fileExtensionMatches(entry.path().string(), extension)) { + count++; + } + } + } catch (const std::filesystem::filesystem_error&) { + // Directory might not exist or be accessible + return 0; + } + + return count; +} + +bool FileTransformer::isDirectory(const std::string& path) { + return std::filesystem::exists(path) && std::filesystem::is_directory(path); +} + +bool FileTransformer::looksLikeDirectoryPath(const std::string& path) { + return path.back() == '/' || path.back() == '\\'; +} + +void FileTransformer::ensureDirectoryExists(const std::string& path) { + if (!std::filesystem::exists(path)) { + std::filesystem::create_directories(path); + } +} + +void FileTransformer::ensureParentDirectoryExists(const std::string& filePath) { + std::filesystem::path path(filePath); + std::filesystem::path parentPath = path.parent_path(); + + if (!parentPath.empty() && !std::filesystem::exists(parentPath)) { + std::filesystem::create_directories(parentPath); + } +} + +} // namespace Platform::RegularExpressions::Transformer \ No newline at end of file diff --git a/cpp/src/TextSteppedTransformer.cpp b/cpp/src/TextSteppedTransformer.cpp new file mode 100644 index 0000000..b6a0a86 --- /dev/null +++ b/cpp/src/TextSteppedTransformer.cpp @@ -0,0 +1,33 @@ +#include "../include/TextSteppedTransformer.h" +#include + +namespace Platform::RegularExpressions::Transformer { + +bool TextSteppedTransformer::next() { + int current = current_ + 1; + + if (current >= static_cast(rules_.size())) { + return false; + } + + auto rule = rules_[current]; + const auto& matchPattern = rule->getMatchPattern(); + const auto& substitutionPattern = rule->getSubstitutionPattern(); + int maximumRepeatCount = rule->getMaximumRepeatCount(); + + int replaceCount = 0; + std::string text = text_; + + do { + text = std::regex_replace(text, matchPattern, substitutionPattern); + replaceCount++; + } while ((maximumRepeatCount == std::numeric_limits::max() || + replaceCount <= maximumRepeatCount) && + std::regex_search(text, matchPattern)); + + text_ = text; + current_ = current; + return true; +} + +} // namespace Platform::RegularExpressions::Transformer \ No newline at end of file diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt new file mode 100644 index 0000000..a371235 --- /dev/null +++ b/cpp/tests/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.16) + +# Find Google Test +find_package(GTest) + +if(NOT GTest_FOUND) + # If GTest is not found, try to build it from source + include(FetchContent) + FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG release-1.12.1 + ) + FetchContent_MakeAvailable(googletest) + + # For compatibility with older CMake versions + if(NOT TARGET GTest::GTest) + add_library(GTest::GTest ALIAS gtest) + add_library(GTest::Main ALIAS gtest_main) + endif() +endif() + +# Create test executable +add_executable(RegularExpressionsTransformerTests + SubstitutionRuleTests.cpp + TextTransformerTests.cpp + FileTransformerTests.cpp +) + +target_link_libraries(RegularExpressionsTransformerTests + RegularExpressionsTransformer + GTest::GTest + GTest::Main +) + +# Add the test to CTest +add_test(NAME RegularExpressionsTransformerTests + COMMAND RegularExpressionsTransformerTests) \ No newline at end of file diff --git a/cpp/tests/FileTransformerTests.cpp b/cpp/tests/FileTransformerTests.cpp new file mode 100644 index 0000000..327db56 --- /dev/null +++ b/cpp/tests/FileTransformerTests.cpp @@ -0,0 +1,151 @@ +#include +#include "../include/FileTransformer.h" +#include "../include/TextTransformer.h" +#include "../include/SubstitutionRule.h" +#include +#include +#include + +using namespace Platform::RegularExpressions::Transformer; + +class FileTransformerTests : public ::testing::Test { +protected: + std::string tempDir; + + void SetUp() override { + tempDir = std::filesystem::temp_directory_path() / "transformer_tests"; + std::filesystem::create_directories(tempDir); + } + + void TearDown() override { + if (std::filesystem::exists(tempDir)) { + std::filesystem::remove_all(tempDir); + } + } + + void createTestFile(const std::string& path, const std::string& content) { + std::ofstream file(path); + file << content; + file.close(); + } + + std::string readTestFile(const std::string& path) { + std::ifstream file(path); + std::string content((std::istreambuf_iterator(file)), + std::istreambuf_iterator()); + return content; + } +}; + +TEST_F(FileTransformerTests, TransformSingleFile) { + // Create transformer + auto rule = std::make_shared("hello", "hi"); + std::vector> rules = {rule}; + auto textTransformer = std::make_shared(rules); + + FileTransformer fileTransformer(textTransformer, ".txt", ".out"); + + // Create test file + std::string sourcePath = tempDir + "/test.txt"; + std::string targetPath = tempDir + "/test.out"; + createTestFile(sourcePath, "hello world"); + + // Transform + fileTransformer.transform(sourcePath, targetPath); + + // Verify result + EXPECT_TRUE(std::filesystem::exists(targetPath)); + EXPECT_EQ("hi world", readTestFile(targetPath)); +} + +TEST_F(FileTransformerTests, TransformFileToDirectory) { + // Create transformer + auto rule = std::make_shared("test", "demo"); + std::vector> rules = {rule}; + auto textTransformer = std::make_shared(rules); + + FileTransformer fileTransformer(textTransformer, ".txt", ".out"); + + // Create test file and target directory + std::string sourcePath = tempDir + "/source.txt"; + std::string targetDir = tempDir + "/output/"; + std::filesystem::create_directories(targetDir); + createTestFile(sourcePath, "test content"); + + // Transform + fileTransformer.transform(sourcePath, targetDir); + + // Verify result + std::string expectedTarget = targetDir + "source.out"; + EXPECT_TRUE(std::filesystem::exists(expectedTarget)); + EXPECT_EQ("demo content", readTestFile(expectedTarget)); +} + +TEST_F(FileTransformerTests, TransformDirectory) { + // Create transformer + auto rule = std::make_shared("old", "new"); + std::vector> rules = {rule}; + auto textTransformer = std::make_shared(rules); + + FileTransformer fileTransformer(textTransformer, ".txt", ".out"); + + // Create source directory with files + std::string sourceDir = tempDir + "/source/"; + std::string targetDir = tempDir + "/target/"; + std::filesystem::create_directories(sourceDir); + + createTestFile(sourceDir + "file1.txt", "old content 1"); + createTestFile(sourceDir + "file2.txt", "old content 2"); + createTestFile(sourceDir + "ignore.log", "old content 3"); // Should be ignored + + // Transform + fileTransformer.transform(sourceDir, targetDir); + + // Verify results + EXPECT_TRUE(std::filesystem::exists(targetDir + "file1.out")); + EXPECT_TRUE(std::filesystem::exists(targetDir + "file2.out")); + EXPECT_FALSE(std::filesystem::exists(targetDir + "ignore.log")); + + EXPECT_EQ("new content 1", readTestFile(targetDir + "file1.out")); + EXPECT_EQ("new content 2", readTestFile(targetDir + "file2.out")); +} + +TEST_F(FileTransformerTests, NonExistentSourceFile) { + auto rule = std::make_shared("a", "b"); + std::vector> rules = {rule}; + auto textTransformer = std::make_shared(rules); + + FileTransformer fileTransformer(textTransformer, ".txt", ".out"); + + std::string sourcePath = tempDir + "/nonexistent.txt"; + std::string targetPath = tempDir + "/output.out"; + + EXPECT_THROW(fileTransformer.transform(sourcePath, targetPath), std::runtime_error); +} + +TEST_F(FileTransformerTests, SkipIfTargetNewer) { + // Create transformer + auto rule = std::make_shared("old", "new"); + std::vector> rules = {rule}; + auto textTransformer = std::make_shared(rules); + + FileTransformer fileTransformer(textTransformer, ".txt", ".out"); + + // Create source file + std::string sourcePath = tempDir + "/source.txt"; + std::string targetPath = tempDir + "/target.out"; + createTestFile(sourcePath, "old content"); + + // Create target file with newer timestamp + createTestFile(targetPath, "existing content"); + + // Make target newer by modifying its timestamp + auto now = std::filesystem::file_time_type::clock::now(); + std::filesystem::last_write_time(targetPath, now + std::chrono::seconds(1)); + + // Transform (should skip) + fileTransformer.transform(sourcePath, targetPath); + + // Verify target wasn't changed + EXPECT_EQ("existing content", readTestFile(targetPath)); +} \ No newline at end of file diff --git a/cpp/tests/SubstitutionRuleTests.cpp b/cpp/tests/SubstitutionRuleTests.cpp new file mode 100644 index 0000000..5b6e15f --- /dev/null +++ b/cpp/tests/SubstitutionRuleTests.cpp @@ -0,0 +1,87 @@ +#include +#include "../include/SubstitutionRule.h" + +using namespace Platform::RegularExpressions::Transformer; + +class SubstitutionRuleTests : public ::testing::Test { +protected: + void SetUp() override { + // Set up any common test data here + } +}; + +TEST_F(SubstitutionRuleTests, ConstructorWithStringPattern) { + std::string pattern = R"(^\s*?\#pragma[\sa-zA-Z0-9\/]+$)"; + std::string substitution = ""; + + SubstitutionRule rule(pattern, substitution); + + EXPECT_EQ(substitution, rule.getSubstitutionPattern()); + EXPECT_EQ(0, rule.getMaximumRepeatCount()); + EXPECT_EQ(nullptr, rule.getPathPattern()); +} + +TEST_F(SubstitutionRuleTests, ConstructorWithRegexPattern) { + std::regex pattern(R"(\d+)"); + std::string substitution = "number"; + int maxRepeat = 5; + + SubstitutionRule rule(pattern, substitution, maxRepeat); + + EXPECT_EQ(substitution, rule.getSubstitutionPattern()); + EXPECT_EQ(maxRepeat, rule.getMaximumRepeatCount()); + EXPECT_EQ(nullptr, rule.getPathPattern()); +} + +TEST_F(SubstitutionRuleTests, CopyConstructor) { + std::string pattern = R"([a-z]+)"; + std::string substitution = "word"; + int maxRepeat = 3; + + SubstitutionRule original(pattern, substitution, maxRepeat); + original.setPathPattern(R"(.*\.txt$)"); + + SubstitutionRule copy(original); + + EXPECT_EQ(original.getSubstitutionPattern(), copy.getSubstitutionPattern()); + EXPECT_EQ(original.getMaximumRepeatCount(), copy.getMaximumRepeatCount()); + EXPECT_NE(nullptr, copy.getPathPattern()); +} + +TEST_F(SubstitutionRuleTests, MoveConstructor) { + std::string pattern = R"([A-Z]+)"; + std::string substitution = "WORD"; + + SubstitutionRule original(pattern, substitution); + SubstitutionRule moved(std::move(original)); + + EXPECT_EQ(substitution, moved.getSubstitutionPattern()); + EXPECT_EQ(0, moved.getMaximumRepeatCount()); +} + +TEST_F(SubstitutionRuleTests, SetPathPattern) { + SubstitutionRule rule("test", "replacement"); + std::string pathPattern = R"(.*\.cpp$)"; + + rule.setPathPattern(pathPattern); + + EXPECT_NE(nullptr, rule.getPathPattern()); +} + +TEST_F(SubstitutionRuleTests, SetMaximumRepeatCount) { + SubstitutionRule rule("test", "replacement"); + int newCount = 10; + + rule.setMaximumRepeatCount(newCount); + + EXPECT_EQ(newCount, rule.getMaximumRepeatCount()); +} + +TEST_F(SubstitutionRuleTests, ToStringBasic) { + SubstitutionRule rule("test", "replacement"); + + std::string result = rule.toString(); + + EXPECT_TRUE(result.find("test") != std::string::npos); + EXPECT_TRUE(result.find("replacement") != std::string::npos); +} \ No newline at end of file diff --git a/cpp/tests/TextTransformerTests.cpp b/cpp/tests/TextTransformerTests.cpp new file mode 100644 index 0000000..1580581 --- /dev/null +++ b/cpp/tests/TextTransformerTests.cpp @@ -0,0 +1,86 @@ +#include +#include "../include/TextTransformer.h" +#include "../include/SubstitutionRule.h" +#include + +using namespace Platform::RegularExpressions::Transformer; + +class TextTransformerTests : public ::testing::Test { +protected: + void SetUp() override { + // Set up any common test data here + } +}; + +TEST_F(TextTransformerTests, BasicTransformation) { + auto rule1 = std::make_shared("a", "b"); + auto rule2 = std::make_shared("b", "c"); + + std::vector> rules = {rule1, rule2}; + TextTransformer transformer(rules); + + std::string sourceText = "aaaa"; + std::string result = transformer.transform(sourceText); + + EXPECT_EQ("cccc", result); +} + +TEST_F(TextTransformerTests, NoTransformationNeeded) { + auto rule = std::make_shared("x", "y"); + + std::vector> rules = {rule}; + TextTransformer transformer(rules); + + std::string sourceText = "aaaa"; + std::string result = transformer.transform(sourceText); + + EXPECT_EQ("aaaa", result); +} + +TEST_F(TextTransformerTests, PartialTransformation) { + auto rule1 = std::make_shared("a", "b"); + auto rule2 = std::make_shared("x", "y"); // This won't match + auto rule3 = std::make_shared("b", "c"); + + std::vector> rules = {rule1, rule2, rule3}; + TextTransformer transformer(rules); + + std::string sourceText = "aaaa"; + std::string result = transformer.transform(sourceText); + + EXPECT_EQ("cccc", result); +} + +TEST_F(TextTransformerTests, ComplexPattern) { + auto rule = std::make_shared(R"(\d+)", "NUMBER"); + + std::vector> rules = {rule}; + TextTransformer transformer(rules); + + std::string sourceText = "I have 123 apples and 456 oranges"; + std::string result = transformer.transform(sourceText); + + EXPECT_EQ("I have NUMBER apples and NUMBER oranges", result); +} + +TEST_F(TextTransformerTests, EmptyRules) { + std::vector> rules; + TextTransformer transformer(rules); + + std::string sourceText = "test text"; + std::string result = transformer.transform(sourceText); + + EXPECT_EQ("test text", result); +} + +TEST_F(TextTransformerTests, EmptyInput) { + auto rule = std::make_shared("a", "b"); + + std::vector> rules = {rule}; + TextTransformer transformer(rules); + + std::string sourceText = ""; + std::string result = transformer.transform(sourceText); + + EXPECT_EQ("", result); +} \ No newline at end of file