From bff073e2fbae905f9aa94073f5cafcdee7d6fe8a Mon Sep 17 00:00:00 2001 From: AstroAir Date: Thu, 7 Nov 2024 00:10:54 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20MimeTypes=20=E7=B1=BB?= =?UTF-8?q?=E4=BB=A5=E6=94=AF=E6=8C=81=20MIME=20=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E5=A4=84=E7=90=86=EF=BC=8C=E9=87=8D=E6=9E=84=20LCG=20=E7=B1=BB?= =?UTF-8?q?=E4=BB=A5=E5=86=85=E8=81=94=E6=9C=80=E5=B0=8F=E5=92=8C=E6=9C=80?= =?UTF-8?q?=E5=A4=A7=E5=80=BC=E5=87=BD=E6=95=B0=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=20XML=20=E5=A4=B4=E6=96=87=E4=BB=B6=E5=8C=85=E5=90=AB=EF=BC=8C?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=8F=92=E4=BB=B6=E7=AE=A1=E7=90=86=E5=99=A8?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E5=BC=82=E5=B8=B8=E5=A4=84=E7=90=86=EF=BC=8C?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=B7=AE=E5=BC=82=E6=AF=94=E8=BE=83=E5=BA=93?= =?UTF-8?q?=E5=8F=8A=E5=85=B6=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/atom.utils/pymodule.cpp | 471 ++++++++++++++++++++++++++++++++ modules/atom.web/pymodule.cpp | 14 +- pysrc/app/plugin_manager.py | 4 +- src/atom/utils/difflib.cpp | 311 +++++++++++++++++++++ src/atom/utils/difflib.hpp | 55 ++++ src/atom/utils/lcg.cpp | 7 - src/atom/utils/lcg.hpp | 6 +- src/atom/utils/print.hpp | 170 ++++++++---- src/atom/utils/xml.hpp | 5 + src/atom/web/minetype.cpp | 184 +++++++++++++ src/atom/web/minetype.hpp | 28 ++ tests/atom/utils/difflib.cpp | 92 +++++++ 12 files changed, 1286 insertions(+), 61 deletions(-) create mode 100644 modules/atom.utils/pymodule.cpp create mode 100644 src/atom/utils/difflib.cpp create mode 100644 src/atom/utils/difflib.hpp create mode 100644 src/atom/web/minetype.cpp create mode 100644 src/atom/web/minetype.hpp create mode 100644 tests/atom/utils/difflib.cpp diff --git a/modules/atom.utils/pymodule.cpp b/modules/atom.utils/pymodule.cpp new file mode 100644 index 00000000..5e0d820a --- /dev/null +++ b/modules/atom.utils/pymodule.cpp @@ -0,0 +1,471 @@ +#include +#include +#include + +#include "atom/utils/aes.hpp" +#include "atom/utils/argsview.hpp" +#include "atom/utils/bit.hpp" +#include "atom/utils/difflib.hpp" +#include "atom/utils/error_stack.hpp" +#include "atom/utils/lcg.hpp" +#include "atom/utils/qdatetime.hpp" +#include "atom/utils/qprocess.hpp" +#include "atom/utils/qtimer.hpp" +#include "atom/utils/qtimezone.hpp" +#include "atom/utils/random.hpp" +#include "atom/utils/time.hpp" +#include "atom/utils/uuid.hpp" +#include "atom/utils/xml.hpp" + +namespace py = pybind11; +using namespace atom::utils; + +template +void bind_random(py::module &m, const std::string &name) { + using RandomType = Random; + py::class_(m, name.c_str()) + .def(py::init(), + py::arg("min"), py::arg("max")) + .def(py::init(), + py::arg("seed"), py::arg("params")) + .def("seed", &RandomType::seed, + py::arg("value") = std::random_device{}()) + .def("__call__", py::overload_cast<>(&RandomType::operator())) + //.def("__call__", + // py::overload_cast( + // &RandomType::operator(), py::const_)) + .def("generate", &RandomType::template generate::iterator>) + .def("vector", &RandomType::vector) + .def("param", &RandomType::param) + .def("engine", &RandomType::engine, + py::return_value_policy::reference_internal) + .def("distribution", &RandomType::distribution, + py::return_value_policy::reference_internal); +} + +PYBIND11_MODULE(diff, m) { + m.def("encryptAES", &encryptAES, py::arg("plaintext"), py::arg("key"), + py::arg("iv"), py::arg("tag"), + "Encrypts the input plaintext using the AES algorithm."); + m.def("decryptAES", &decryptAES, py::arg("ciphertext"), py::arg("key"), + py::arg("iv"), py::arg("tag"), + "Decrypts the input ciphertext using the AES algorithm."); + m.def("compress", &compress, py::arg("data"), + "Compresses the input data using the Zlib library."); + m.def("decompress", &decompress, py::arg("data"), + "Decompresses the input data using the Zlib library."); + m.def("calculateSha256", &calculateSha256, py::arg("filename"), + "Calculates the SHA-256 hash of a file."); + m.def("calculateSha224", &calculateSha224, py::arg("data"), + "Calculates the SHA-224 hash of a string."); + m.def("calculateSha384", &calculateSha384, py::arg("data"), + "Calculates the SHA-384 hash of a string."); + m.def("calculateSha512", &calculateSha512, py::arg("data"), + "Calculates the SHA-512 hash of a string."); + + py::class_(m, "ArgumentParser") + .def(py::init<>()) + .def(py::init()) + .def("set_description", &atom::utils::ArgumentParser::setDescription) + .def("set_epilog", &atom::utils::ArgumentParser::setEpilog) + .def("add_argument", &atom::utils::ArgumentParser::addArgument, + py::arg("name"), + py::arg("type") = atom::utils::ArgumentParser::ArgType::AUTO, + py::arg("required") = false, py::arg("default_value") = std::any(), + py::arg("help") = "", + py::arg("aliases") = std::vector(), + py::arg("is_positional") = false, + py::arg("nargs") = atom::utils::ArgumentParser::Nargs()) + .def("add_flag", &atom::utils::ArgumentParser::addFlag, py::arg("name"), + py::arg("help") = "", + py::arg("aliases") = std::vector()) + .def("add_subcommand", &atom::utils::ArgumentParser::addSubcommand) + .def("add_mutually_exclusive_group", + &atom::utils::ArgumentParser::addMutuallyExclusiveGroup) + .def("add_argument_from_file", + &atom::utils::ArgumentParser::addArgumentFromFile) + .def("set_file_delimiter", + &atom::utils::ArgumentParser::setFileDelimiter) + .def("parse", &atom::utils::ArgumentParser::parse) + .def("get_flag", &atom::utils::ArgumentParser::getFlag) + .def("get_subcommand_parser", + &atom::utils::ArgumentParser::getSubcommandParser) + .def("print_help", &atom::utils::ArgumentParser::printHelp); + + py::enum_(m, "ArgType") + .value("STRING", atom::utils::ArgumentParser::ArgType::STRING) + .value("INTEGER", atom::utils::ArgumentParser::ArgType::INTEGER) + .value("UNSIGNED_INTEGER", + atom::utils::ArgumentParser::ArgType::UNSIGNED_INTEGER) + .value("LONG", atom::utils::ArgumentParser::ArgType::LONG) + .value("UNSIGNED_LONG", + atom::utils::ArgumentParser::ArgType::UNSIGNED_LONG) + .value("FLOAT", atom::utils::ArgumentParser::ArgType::FLOAT) + .value("DOUBLE", atom::utils::ArgumentParser::ArgType::DOUBLE) + .value("BOOLEAN", atom::utils::ArgumentParser::ArgType::BOOLEAN) + .value("FILEPATH", atom::utils::ArgumentParser::ArgType::FILEPATH) + .value("AUTO", atom::utils::ArgumentParser::ArgType::AUTO) + .export_values(); + + py::enum_(m, "NargsType") + .value("NONE", atom::utils::ArgumentParser::NargsType::NONE) + .value("OPTIONAL", atom::utils::ArgumentParser::NargsType::OPTIONAL) + .value("ZERO_OR_MORE", + atom::utils::ArgumentParser::NargsType::ZERO_OR_MORE) + .value("ONE_OR_MORE", + atom::utils::ArgumentParser::NargsType::ONE_OR_MORE) + .value("CONSTANT", atom::utils::ArgumentParser::NargsType::CONSTANT) + .export_values(); + + py::class_(m, "Nargs") + .def(py::init<>()) + .def(py::init(), + py::arg("type"), py::arg("count") = 1) + .def_readwrite("type", &atom::utils::ArgumentParser::Nargs::type) + .def_readwrite("count", &atom::utils::ArgumentParser::Nargs::count); + + m.def("create_mask", &createMask, py::arg("bits"), + "Creates a bitmask with the specified number of bits set to 1."); + m.def("count_bytes", &countBytes, py::arg("value"), + "Counts the number of set bits (1s) in the given value."); + m.def("reverse_bits", &reverseBits, py::arg("value"), + "Reverses the bits in the given value."); + m.def("rotate_left", &rotateLeft, py::arg("value"), + py::arg("shift"), + "Performs a left rotation on the bits of the given value."); + m.def("rotate_right", &rotateRight, py::arg("value"), + py::arg("shift"), + "Performs a right rotation on the bits of the given value."); + m.def("merge_masks", &mergeMasks, py::arg("mask1"), + py::arg("mask2"), "Merges two bitmasks into one."); + m.def("split_mask", &splitMask, py::arg("mask"), + py::arg("position"), "Splits a bitmask into two parts."); + + py::class_(m, "SequenceMatcher") + .def(py::init()) + .def("set_seqs", &SequenceMatcher::setSeqs) + .def("ratio", &SequenceMatcher::ratio) + .def("get_matching_blocks", &SequenceMatcher::getMatchingBlocks) + .def("get_opcodes", &SequenceMatcher::getOpcodes); + + py::class_(m, "Differ") + .def_static("compare", &Differ::compare) + .def_static("unified_diff", &Differ::unifiedDiff); + + py::class_(m, "HtmlDiff") + .def_static("make_file", &HtmlDiff::makeFile) + .def_static("make_table", &HtmlDiff::makeTable); + + m.def("get_close_matches", &getCloseMatches); + + py::class_(m, "ErrorInfo") + .def(py::init<>()) + .def_readwrite("errorMessage", &atom::error::ErrorInfo::errorMessage) + .def_readwrite("moduleName", &atom::error::ErrorInfo::moduleName) + .def_readwrite("functionName", &atom::error::ErrorInfo::functionName) + .def_readwrite("line", &atom::error::ErrorInfo::line) + .def_readwrite("fileName", &atom::error::ErrorInfo::fileName) + .def_readwrite("timestamp", &atom::error::ErrorInfo::timestamp) + .def_readwrite("uuid", &atom::error::ErrorInfo::uuid) + .def("__repr__", [](const atom::error::ErrorInfo &e) { + return ""; + }); + + py::class_>(m, "ErrorStack") + .def(py::init<>()) + .def_static("create_shared", &atom::error::ErrorStack::createShared) + .def_static("create_unique", &atom::error::ErrorStack::createUnique) + .def("insert_error", &atom::error::ErrorStack::insertError) + .def("set_filtered_modules", + &atom::error::ErrorStack::setFilteredModules) + .def("clear_filtered_modules", + &atom::error::ErrorStack::clearFilteredModules) + .def("print_filtered_error_stack", + &atom::error::ErrorStack::printFilteredErrorStack) + .def("get_filtered_errors_by_module", + &atom::error::ErrorStack::getFilteredErrorsByModule) + .def("get_compressed_errors", + &atom::error::ErrorStack::getCompressedErrors); + + py::class_(m, "LCG") + .def(py::init(), + py::arg("seed") = static_cast( + std::chrono::steady_clock::now().time_since_epoch().count())) + .def("next", &LCG::next, + "Generates the next random number in the sequence.") + .def("seed", &LCG::seed, py::arg("new_seed"), + "Seeds the generator with a new seed value.") + .def("save_state", &LCG::saveState, py::arg("filename"), + "Saves the current state of the generator to a file.") + .def("load_state", &LCG::loadState, py::arg("filename"), + "Loads the state of the generator from a file.") + .def("next_int", &LCG::nextInt, py::arg("min") = 0, + py::arg("max") = std::numeric_limits::max(), + "Generates a random integer within a specified range.") + .def("next_double", &LCG::nextDouble, py::arg("min") = 0.0, + py::arg("max") = 1.0, + "Generates a random double within a specified range.") + .def("next_bernoulli", &LCG::nextBernoulli, + py::arg("probability") = 0.5, + "Generates a random boolean value based on a specified " + "probability.") + .def("next_gaussian", &LCG::nextGaussian, py::arg("mean") = 0.0, + py::arg("stddev") = 1.0, + "Generates a random number following a Gaussian (normal) " + "distribution.") + .def("next_poisson", &LCG::nextPoisson, py::arg("lambda") = 1.0, + "Generates a random number following a Poisson distribution.") + .def("next_exponential", &LCG::nextExponential, py::arg("lambda") = 1.0, + "Generates a random number following an Exponential distribution.") + .def("next_geometric", &LCG::nextGeometric, + py::arg("probability") = 0.5, + "Generates a random number following a Geometric distribution.") + .def("next_gamma", &LCG::nextGamma, py::arg("shape"), + py::arg("scale") = 1.0, + "Generates a random number following a Gamma distribution.") + .def("next_beta", &LCG::nextBeta, py::arg("alpha"), py::arg("beta"), + "Generates a random number following a Beta distribution.") + .def("next_chi_squared", &LCG::nextChiSquared, + py::arg("degrees_of_freedom"), + "Generates a random number following a Chi-Squared distribution.") + .def("next_hypergeometric", &LCG::nextHypergeometric, py::arg("total"), + py::arg("success"), py::arg("draws"), + "Generates a random number following a Hypergeometric " + "distribution.") + .def("next_discrete", &LCG::nextDiscrete, py::arg("weights"), + "Generates a random index based on a discrete distribution.") + .def("next_multinomial", &LCG::nextMultinomial, py::arg("trials"), + py::arg("probabilities"), "Generates a multinomial distribution.") + .def("shuffle", &LCG::shuffle, py::arg("data"), + "Shuffles a vector of data.") + .def("sample", &LCG::sample, py::arg("data"), + py::arg("sample_size"), "Samples a subset of data from a vector.") + .def_static("min", &LCG::min, + "Returns the minimum value that can be generated.") + .def_static("max", &LCG::max, + "Returns the maximum value that can be generated."); + + py::class_(m, "QDateTime") + .def(py::init<>(), "Default constructor for QDateTime.") + .def( + py::init(), + py::arg("dateTimeString"), py::arg("format"), + "Constructs a QDateTime object from a date-time string and format.") + .def_static("currentDateTime", + py::overload_cast<>(&QDateTime::currentDateTime), + "Returns the current date and time.") + .def_static( + "fromString", + py::overload_cast( + &QDateTime::fromString), + py::arg("dateTimeString"), py::arg("format"), + "Constructs a QDateTime object from a date-time string and format.") + .def("toString", + py::overload_cast(&QDateTime::toString, + py::const_), + py::arg("format"), + "Converts the QDateTime object to a string in the specified " + "format.") + .def("toTimeT", &QDateTime::toTimeT, + "Converts the QDateTime object to a std::time_t value.") + .def("isValid", &QDateTime::isValid, + "Checks if the QDateTime object is valid.") + .def("addDays", &QDateTime::addDays, py::arg("days"), + "Adds a number of days to the QDateTime object.") + .def("addSecs", &QDateTime::addSecs, py::arg("seconds"), + "Adds a number of seconds to the QDateTime object.") + .def("daysTo", &QDateTime::daysTo, py::arg("other"), + "Computes the number of days between the current QDateTime object " + "and another QDateTime object.") + .def("secsTo", &QDateTime::secsTo, py::arg("other"), + "Computes the number of seconds between the current QDateTime " + "object and another QDateTime object.") + .def(py::self < py::self) + .def(py::self <= py::self) + .def(py::self > py::self) + .def(py::self >= py::self) + .def(py::self == py::self) + .def(py::self != py::self); + + py::class_(m, "QProcess") + .def(py::init<>(), "Default constructor for QProcess.") + .def("set_working_directory", &QProcess::setWorkingDirectory, + py::arg("dir"), "Sets the working directory for the process.") + .def("set_environment", &QProcess::setEnvironment, py::arg("env"), + "Sets the environment variables for the process.") + .def( + "start", &QProcess::start, py::arg("program"), py::arg("args"), + "Starts the external process with the given program and arguments.") + .def("wait_for_started", &QProcess::waitForStarted, + py::arg("timeoutMs") = -1, "Waits for the process to start.") + .def("wait_for_finished", &QProcess::waitForFinished, + py::arg("timeoutMs") = -1, "Waits for the process to finish.") + .def("is_running", &QProcess::isRunning, + "Checks if the process is currently running.") + .def("write", &QProcess::write, py::arg("data"), + "Writes data to the process's standard input.") + .def("read_all_standard_output", &QProcess::readAllStandardOutput, + "Reads all available data from the process's standard output.") + .def("read_all_standard_error", &QProcess::readAllStandardError, + "Reads all available data from the process's standard error.") + .def("terminate", &QProcess::terminate, "Terminates the process."); + + py::class_(m, "ElapsedTimer") + .def(py::init<>(), "Default constructor.") + .def("start", &ElapsedTimer::start, "Start or restart the timer.") + .def("invalidate", &ElapsedTimer::invalidate, "Invalidate the timer.") + .def("is_valid", &ElapsedTimer::isValid, + "Check if the timer has been started and is valid.") + .def("elapsed_ns", &ElapsedTimer::elapsedNs, + "Get elapsed time in nanoseconds.") + .def("elapsed_us", &ElapsedTimer::elapsedUs, + "Get elapsed time in microseconds.") + .def("elapsed_ms", &ElapsedTimer::elapsedMs, + "Get elapsed time in milliseconds.") + .def("elapsed_sec", &ElapsedTimer::elapsedSec, + "Get elapsed time in seconds.") + .def("elapsed_min", &ElapsedTimer::elapsedMin, + "Get elapsed time in minutes.") + .def("elapsed_hrs", &ElapsedTimer::elapsedHrs, + "Get elapsed time in hours.") + .def("elapsed", &ElapsedTimer::elapsed, + "Get elapsed time in milliseconds (same as elapsedMs).") + .def("has_expired", &ElapsedTimer::hasExpired, py::arg("ms"), + "Check if a specified duration (in milliseconds) has passed.") + .def("remaining_time_ms", &ElapsedTimer::remainingTimeMs, py::arg("ms"), + "Get the remaining time until the specified duration (in " + "milliseconds) has passed.") + .def_static( + "current_time_ms", &ElapsedTimer::currentTimeMs, + "Get the current absolute time in milliseconds since epoch.") + .def(py::self < py::self) + .def(py::self > py::self) + .def(py::self <= py::self) + .def(py::self >= py::self) + .def(py::self == py::self) + .def(py::self != py::self); + + py::class_(m, "ElapsedTimer") + .def(py::init<>(), "Default constructor.") + .def("start", &ElapsedTimer::start, "Start or restart the timer.") + .def("invalidate", &ElapsedTimer::invalidate, "Invalidate the timer.") + .def("is_valid", &ElapsedTimer::isValid, + "Check if the timer has been started and is valid.") + .def("elapsed_ns", &ElapsedTimer::elapsedNs, + "Get elapsed time in nanoseconds.") + .def("elapsed_us", &ElapsedTimer::elapsedUs, + "Get elapsed time in microseconds.") + .def("elapsed_ms", &ElapsedTimer::elapsedMs, + "Get elapsed time in milliseconds.") + .def("elapsed_sec", &ElapsedTimer::elapsedSec, + "Get elapsed time in seconds.") + .def("elapsed_min", &ElapsedTimer::elapsedMin, + "Get elapsed time in minutes.") + .def("elapsed_hrs", &ElapsedTimer::elapsedHrs, + "Get elapsed time in hours.") + .def("elapsed", &ElapsedTimer::elapsed, + "Get elapsed time in milliseconds (same as elapsedMs).") + .def("has_expired", &ElapsedTimer::hasExpired, py::arg("ms"), + "Check if a specified duration (in milliseconds) has passed.") + .def("remaining_time_ms", &ElapsedTimer::remainingTimeMs, py::arg("ms"), + "Get the remaining time until the specified duration (in " + "milliseconds) has passed.") + .def_static( + "current_time_ms", &ElapsedTimer::currentTimeMs, + "Get the current absolute time in milliseconds since epoch.") + .def(py::self < py::self) + .def(py::self > py::self) + .def(py::self <= py::self) + .def(py::self >= py::self) + .def(py::self == py::self) + .def(py::self != py::self); + + bind_random>(m, + "RandomInt"); + bind_random>( + m, "RandomDouble"); + + m.def("get_timestamp_string", &getTimestampString, + "Retrieves the current timestamp as a formatted string."); + m.def("convert_to_china_time", &convertToChinaTime, py::arg("utcTimeStr"), + "Converts a UTC time string to China Standard Time (CST, UTC+8)."); + m.def("get_china_timestamp_string", &getChinaTimestampString, + "Retrieves the current China Standard Time (CST) as a formatted " + "timestamp string."); + m.def("timestamp_to_string", &timeStampToString, py::arg("timestamp"), + "Converts a timestamp to a formatted string."); + m.def("to_string", &toString, py::arg("tm"), py::arg("format"), + "Converts a `tm` structure to a formatted string."); + m.def("get_utc_time", &getUtcTime, + "Retrieves the current UTC time as a formatted string."); + m.def("timestamp_to_time", ×tampToTime, py::arg("timestamp"), + "Converts a timestamp to a `tm` structure."); + + py::class_(m, "UUID") + .def(py::init<>(), "Constructs a new UUID with a random value.") + .def(py::init &>(), py::arg("data"), + "Constructs a UUID from a given 16-byte array.") + .def("to_string", &UUID::toString, + "Converts the UUID to a string representation.") + .def_static("from_string", &UUID::fromString, py::arg("str"), + "Creates a UUID from a string representation.") + .def("get_data", &UUID::getData, + "Retrieves the underlying data of the UUID.") + .def("version", &UUID::version, "Gets the version of the UUID.") + .def("variant", &UUID::variant, "Gets the variant of the UUID.") + .def_static( + "generate_v3", &UUID::generateV3, py::arg("namespace_uuid"), + py::arg("name"), + "Generates a version 3 UUID using the MD5 hashing algorithm.") + .def_static( + "generate_v5", &UUID::generateV5, py::arg("namespace_uuid"), + py::arg("name"), + "Generates a version 5 UUID using the SHA-1 hashing algorithm.") + .def_static("generate_v1", &UUID::generateV1, + "Generates a version 1, time-based UUID.") + .def_static("generate_v4", &UUID::generateV4, + "Generates a version 4, random UUID.") + .def(py::self == py::self) + .def(py::self != py::self) + .def(py::self < py::self) + .def(py::self > py::self) + .def(py::self <= py::self) + .def(py::self >= py::self) + .def("__str__", &UUID::toString); + + m.def("generate_unique_uuid", &generateUniqueUUID, + "Generates a unique UUID and returns it as a string."); + + py::class_(m, "XMLReader") + .def(py::init()) + .def("get_child_element_names", + &atom::utils::XMLReader::getChildElementNames) + .def("get_element_text", &atom::utils::XMLReader::getElementText) + .def("get_attribute_value", &atom::utils::XMLReader::getAttributeValue) + .def("get_root_element_names", + &atom::utils::XMLReader::getRootElementNames) + .def("has_child_element", &atom::utils::XMLReader::hasChildElement) + .def("get_child_element_text", + &atom::utils::XMLReader::getChildElementText) + .def("get_child_element_attribute_value", + &atom::utils::XMLReader::getChildElementAttributeValue) + .def("get_value_by_path", &atom::utils::XMLReader::getValueByPath) + .def("get_attribute_value_by_path", + &atom::utils::XMLReader::getAttributeValueByPath) + .def("has_child_element_by_path", + &atom::utils::XMLReader::hasChildElementByPath) + .def("get_child_element_text_by_path", + &atom::utils::XMLReader::getChildElementTextByPath) + .def("get_child_element_attribute_value_by_path", + &atom::utils::XMLReader::getChildElementAttributeValueByPath) + .def("save_to_file", &atom::utils::XMLReader::saveToFile); +} \ No newline at end of file diff --git a/modules/atom.web/pymodule.cpp b/modules/atom.web/pymodule.cpp index 6c2a201b..37ad00ac 100644 --- a/modules/atom.web/pymodule.cpp +++ b/modules/atom.web/pymodule.cpp @@ -5,6 +5,7 @@ #include "atom/web/curl.hpp" #include "atom/web/downloader.hpp" #include "atom/web/httpparser.hpp" +#include "atom/web/minetype.hpp" #include "atom/web/time.hpp" #include "atom/web/utils.hpp" @@ -201,6 +202,17 @@ PYBIND11_MODULE(web, m) { .def("clear_headers", &HttpHeaderParser::clearHeaders, "Clear all the parsed headers"); + py::class_(m, "MimeTypes") + .def(py::init&, bool>(), + py::arg("knownFiles"), py::arg("lenient") = false) + .def("read_json", &MimeTypes::readJson) + .def("guess_type", &MimeTypes::guessType) + .def("guess_all_extensions", &MimeTypes::guessAllExtensions) + .def("guess_extension", &MimeTypes::guessExtension) + .def("add_type", &MimeTypes::addType) + .def("list_all_types", &MimeTypes::listAllTypes) + .def("guess_type_by_content", &MimeTypes::guessTypeByContent); + py::class_(m, "TimeManager") .def(py::init<>()) .def("get_system_time", &TimeManager::getSystemTime, @@ -246,4 +258,4 @@ PYBIND11_MODULE(web, m) { m.def("sort_addr_info", &sortAddrInfo, "Sort address information by family", py::arg("addr_info")); #endif -} +} \ No newline at end of file diff --git a/pysrc/app/plugin_manager.py b/pysrc/app/plugin_manager.py index 2eeca5e3..46299138 100644 --- a/pysrc/app/plugin_manager.py +++ b/pysrc/app/plugin_manager.py @@ -145,8 +145,8 @@ def get_plugin_info(plugin_name: str) -> Dict: """ if plugin_name not in loaded_plugins: logger.error("Plugin {} not found.", plugin_name) - raise HTTPException(status_code=404, detail=f"Plugin { - plugin_name} not found") + raise HTTPException( + status_code=404, detail=f"Plugin {plugin_name} not found") plugin = loaded_plugins[plugin_name] info = { diff --git a/src/atom/utils/difflib.cpp b/src/atom/utils/difflib.cpp new file mode 100644 index 00000000..787738c4 --- /dev/null +++ b/src/atom/utils/difflib.cpp @@ -0,0 +1,311 @@ +#include "difflib.hpp" + +#include +#include +#include +#include + +namespace atom::utils { +static auto joinLines(const std::vector& lines) -> std::string { + std::string joined; + for (const auto& line : lines) { + joined += line + "\n"; + } + return joined; +} + +class SequenceMatcher::Impl { +public: + Impl(std::string str1, std::string str2) + : seq1_(std::move(str1)), seq2_(std::move(str2)) { + computeMatchingBlocks(); + } + + void setSeqs(const std::string& str1, const std::string& str2) { + seq1_ = str1; + seq2_ = str2; + computeMatchingBlocks(); + } + + [[nodiscard]] auto ratio() const -> double { + double matches = sumMatchingBlocks(); + return 2.0 * matches / (seq1_.size() + seq2_.size()); + } + + [[nodiscard]] auto getMatchingBlocks() const + -> std::vector> { + return matching_blocks; + } + + [[nodiscard]] auto getOpcodes() const + -> std::vector> { + std::vector> opcodes; + int aStart = 0; + int bStart = 0; + + for (const auto& block : matching_blocks) { + int aIndex = std::get<0>(block); + int bIndex = std::get<1>(block); + int size = std::get<2>(block); + + if (size > 0) { + if (aStart < aIndex || bStart < bIndex) { + if (aStart < aIndex && bStart < bIndex) { + opcodes.emplace_back("replace", aStart, aIndex, bStart, + bIndex); + } else if (aStart < aIndex) { + opcodes.emplace_back("delete", aStart, aIndex, bStart, + bStart); + } else { + opcodes.emplace_back("insert", aStart, aStart, bStart, + bIndex); + } + } + opcodes.emplace_back("equal", aIndex, aIndex + size, bIndex, + bIndex + size); + aStart = aIndex + size; + bStart = bIndex + size; + } + } + return opcodes; + } + +private: + std::string seq1_; + std::string seq2_; + std::vector> matching_blocks; + + void computeMatchingBlocks() { + std::unordered_map> seq2_index_map; + for (size_t j = 0; j < seq2_.size(); ++j) { + seq2_index_map[seq2_[j]].push_back(j); + } + + for (size_t i = 0; i < seq1_.size(); ++i) { + auto it = seq2_index_map.find(seq1_[i]); + if (it != seq2_index_map.end()) { + for (size_t j : it->second) { + size_t matchLength = 0; + while (i + matchLength < seq1_.size() && + j + matchLength < seq2_.size() && + seq1_[i + matchLength] == seq2_[j + matchLength]) { + ++matchLength; + } + if (matchLength > 0) { + matching_blocks.emplace_back(i, j, matchLength); + } + } + } + } + matching_blocks.emplace_back(seq1_.size(), seq2_.size(), 0); + std::sort(matching_blocks.begin(), matching_blocks.end(), + [](const std::tuple& a, + const std::tuple& b) { + if (std::get<0>(a) != std::get<0>(b)) { + return std::get<0>(a) < std::get<0>(b); + } + return std::get<1>(a) < std::get<1>(b); + }); + } + + [[nodiscard]] auto sumMatchingBlocks() const -> double { + double matches = 0; + for (const auto& block : matching_blocks) { + matches += std::get<2>(block); + } + return matches; + } +}; + +SequenceMatcher::SequenceMatcher(const std::string& str1, + const std::string& str2) + : pimpl_(new Impl(str1, str2)) {} +SequenceMatcher::~SequenceMatcher() = default; + +void SequenceMatcher::setSeqs(const std::string& str1, + const std::string& str2) { + pimpl_->setSeqs(str1, str2); +} + +auto SequenceMatcher::ratio() const -> double { return pimpl_->ratio(); } + +auto SequenceMatcher::getMatchingBlocks() const + -> std::vector> { + return pimpl_->getMatchingBlocks(); +} + +auto SequenceMatcher::getOpcodes() const + -> std::vector> { + return pimpl_->getOpcodes(); +} + +auto Differ::compare(const std::vector& vec1, + const std::vector& vec2) + -> std::vector { + std::vector result; + SequenceMatcher matcher("", ""); + + size_t i = 0, j = 0; + while (i < vec1.size() || j < vec2.size()) { + if (i < vec1.size() && j < vec2.size() && vec1[i] == vec2[j]) { + result.push_back(" " + vec1[i]); + ++i; + ++j; + } else if (j == vec2.size() || + (i < vec1.size() && (j == 0 || vec1[i] != vec2[j - 1]))) { + result.push_back("- " + vec1[i]); + ++i; + } else { + result.push_back("+ " + vec2[j]); + ++j; + } + } + return result; +} + +auto Differ::unifiedDiff(const std::vector& vec1, + const std::vector& vec2, + const std::string& label1, const std::string& label2, + int context) -> std::vector { + std::vector diff; + SequenceMatcher matcher("", ""); + matcher.setSeqs(joinLines(vec1), joinLines(vec2)); + auto opcodes = matcher.getOpcodes(); + + diff.push_back("--- " + label1); + diff.push_back("+++ " + label2); + + int start_a = 0, start_b = 0; + int end_a = 0, end_b = 0; + std::vector chunk; + for (const auto& opcode : opcodes) { + std::string tag = std::get<0>(opcode); + int i1 = std::get<1>(opcode); + int i2 = std::get<2>(opcode); + int j1 = std::get<3>(opcode); + int j2 = std::get<4>(opcode); + + if (tag == "equal") { + if (i2 - i1 > 2 * context) { + chunk.push_back("@@ -" + std::to_string(start_a + 1) + "," + + std::to_string(end_a - start_a) + " +" + + std::to_string(start_b + 1) + "," + + std::to_string(end_b - start_b) + " @@"); + for (int k = start_a; + k < + std::min(start_a + context, static_cast(vec1.size())); + ++k) { + chunk.push_back(" " + vec1[k]); + } + diff.insert(diff.end(), chunk.begin(), chunk.end()); + chunk.clear(); + start_a = i2 - context; + start_b = j2 - context; + } else { + for (int k = i1; k < i2; ++k) { + if (k < vec1.size()) { + chunk.push_back(" " + vec1[k]); + } + } + } + end_a = i2; + end_b = j2; + } else { + if (chunk.empty()) { + chunk.push_back("@@ -" + std::to_string(start_a + 1) + "," + + std::to_string(end_a - start_a) + " +" + + std::to_string(start_b + 1) + "," + + std::to_string(end_b - start_b) + " @@"); + } + if (tag == "replace") { + for (int k = i1; k < i2; ++k) { + if (k < vec1.size()) { + chunk.push_back("- " + vec1[k]); + } + } + for (int k = j1; k < j2; ++k) { + if (k < vec2.size()) { + chunk.push_back("+ " + vec2[k]); + } + } + } else if (tag == "delete") { + for (int k = i1; k < i2; ++k) { + if (k < vec1.size()) { + chunk.push_back("- " + vec1[k]); + } + } + } else if (tag == "insert") { + for (int k = j1; k < j2; ++k) { + if (k < vec2.size()) { + chunk.push_back("+ " + vec2[k]); + } + } + } + end_a = i2; + end_b = j2; + } + } + if (!chunk.empty()) { + diff.insert(diff.end(), chunk.begin(), chunk.end()); + } + return diff; +} + +auto HtmlDiff::makeFile(const std::vector& fromlines, + const std::vector& tolines, + const std::string& fromdesc, + const std::string& todesc) -> std::string { + std::ostringstream os; + os << "\nDiff\n\n"; + os << "

Differences

\n"; + + os << "\n\n"; + + auto diffs = Differ::compare(fromlines, tolines); + for (const auto& line : diffs) { + os << "\n"; + } + os << "
" << fromdesc << "" << todesc + << "
" << line << "
\n\n"; + return os.str(); +} + +auto HtmlDiff::makeTable(const std::vector& fromlines, + const std::vector& tolines, + const std::string& fromdesc, + const std::string& todesc) -> std::string { + std::ostringstream os; + os << "\n\n"; + + auto diffs = Differ::compare(fromlines, tolines); + for (const auto& line : diffs) { + os << "\n"; + } + os << "
" << fromdesc << "" << todesc + << "
" << line << "
\n"; + return os.str(); +} + +auto getCloseMatches(const std::string& word, + const std::vector& possibilities, int n, + double cutoff) -> std::vector { + std::vector> scores; + for (const auto& possibility : possibilities) { + SequenceMatcher matcher(word, possibility); + double score = matcher.ratio(); + if (score >= cutoff) { + scores.emplace_back(score, possibility); + } + } + std::sort(scores.begin(), scores.end(), + [](const std::pair& a, + const std::pair& b) { + return a.first > b.first; + }); + std::vector matches; + for (int i = 0; i < std::min(n, static_cast(scores.size())); ++i) { + matches.push_back(scores[i].second); + } + return matches; +} +} // namespace atom::utils diff --git a/src/atom/utils/difflib.hpp b/src/atom/utils/difflib.hpp new file mode 100644 index 00000000..db98e1a7 --- /dev/null +++ b/src/atom/utils/difflib.hpp @@ -0,0 +1,55 @@ +#ifndef ATOM_UTILS_DIFFLIB_HPP +#define ATOM_UTILS_DIFFLIB_HPP + +#include +#include +#include + +namespace atom::utils { +class SequenceMatcher { +public: + SequenceMatcher(const std::string& str1, const std::string& str2); + ~SequenceMatcher(); + + void setSeqs(const std::string& str1, const std::string& str2); + [[nodiscard]] auto ratio() const -> double; + [[nodiscard]] auto getMatchingBlocks() const + -> std::vector>; + [[nodiscard]] auto getOpcodes() const + -> std::vector>; + +private: + class Impl; + std::unique_ptr pimpl_; +}; + +class Differ { +public: + static auto compare(const std::vector& vec1, + const std::vector& vec2) + -> std::vector; + static auto unifiedDiff(const std::vector& vec1, + const std::vector& vec2, + const std::string& label1 = "a", + const std::string& label2 = "b", + int context = 3) -> std::vector; +}; + +class HtmlDiff { +public: + static auto makeFile(const std::vector& fromlines, + const std::vector& tolines, + const std::string& fromdesc = "", + const std::string& todesc = "") -> std::string; + static auto makeTable(const std::vector& fromlines, + const std::vector& tolines, + const std::string& fromdesc = "", + const std::string& todesc = "") -> std::string; +}; + +auto getCloseMatches(const std::string& word, + const std::vector& possibilities, int n = 3, + double cutoff = 0.6) -> std::vector; +} // namespace atom::utils + +#endif // ATOM_UTILS_DIFFLIB_HPP diff --git a/src/atom/utils/lcg.cpp b/src/atom/utils/lcg.cpp index 14ae06d8..3bd5a511 100644 --- a/src/atom/utils/lcg.cpp +++ b/src/atom/utils/lcg.cpp @@ -271,11 +271,4 @@ auto LCG::nextMultinomial(int trials, const std::vector& probabilities) trials, probabilities.size()); return counts; } - -constexpr auto LCG::min() -> result_type { return 0; } - -constexpr auto LCG::max() -> result_type { - return std::numeric_limits::max(); -} - } // namespace atom::utils diff --git a/src/atom/utils/lcg.hpp b/src/atom/utils/lcg.hpp index 810b2ae9..464607e6 100644 --- a/src/atom/utils/lcg.hpp +++ b/src/atom/utils/lcg.hpp @@ -186,13 +186,15 @@ class LCG { * @brief Returns the minimum value that can be generated. * @return The minimum value. */ - static constexpr auto min() -> result_type; + static constexpr auto min() -> result_type { return 0; } /** * @brief Returns the maximum value that can be generated. * @return The maximum value. */ - static constexpr auto max() -> result_type; + static constexpr auto max() -> result_type { + return std::numeric_limits::max(); + } private: result_type current_; ///< The current state of the generator. diff --git a/src/atom/utils/print.hpp b/src/atom/utils/print.hpp index bd469691..ec9d0921 100644 --- a/src/atom/utils/print.hpp +++ b/src/atom/utils/print.hpp @@ -4,19 +4,24 @@ #include #include #include +#include #include +#include #include #include #include #include #include #include +#include #include #include #include #include +#include #include #include +#include #include #include "atom/utils/time.hpp" @@ -35,7 +40,8 @@ constexpr int BUFFER3_SIZE = 4096; constexpr int THREAD_ID_WIDTH = 16; template -void log(Stream& stream, LogLevel level, std::string_view fmt, Args&&... args) { +inline void log(Stream& stream, LogLevel level, std::string_view fmt, + Args&&... args) { std::string levelStr; switch (level) { case LogLevel::DEBUG: @@ -64,33 +70,38 @@ void log(Stream& stream, LogLevel level, std::string_view fmt, Args&&... args) { stream << "[" << atom::utils::getChinaTimestampString() << "] [" << levelStr << "] [" << idHexStr << "] " - << std::vformat(fmt, std::make_format_args(args...)) << std::endl; + << std::vformat(fmt, + std::make_format_args(std::forward(args)...)) + << std::endl; } template -void printToStream(Stream& stream, std::string_view fmt, Args&&... args) { - stream << std::vformat(fmt, std::make_format_args(args...)); +inline void printToStream(Stream& stream, std::string_view fmt, + Args&&... args) { + stream << std::vformat(fmt, + std::make_format_args(std::forward(args)...)); } template -void print(std::string_view fmt, Args&&... args) { +inline void print(std::string_view fmt, Args&&... args) { printToStream(std::cout, fmt, std::forward(args)...); } template -void printlnToStream(Stream& stream, std::string_view fmt, Args&&... args) { +inline void printlnToStream(Stream& stream, std::string_view fmt, + Args&&... args) { printToStream(stream, fmt, std::forward(args)...); stream << std::endl; } template -void println(std::string_view fmt, Args&&... args) { +inline void println(std::string_view fmt, Args&&... args) { printlnToStream(std::cout, fmt, std::forward(args)...); } template -void printToFile(const std::string& fileName, std::string_view fmt, - Args&&... args) { +inline void printToFile(const std::string& fileName, std::string_view fmt, + Args&&... args) { std::ofstream file(fileName, std::ios::app); if (file.is_open()) { printToStream(file, fmt, std::forward(args)...); @@ -111,9 +122,10 @@ enum class Color { }; template -void printColored(Color color, std::string_view fmt, Args&&... args) { +inline void printColored(Color color, std::string_view fmt, Args&&... args) { std::cout << "\033[" << static_cast(color) << "m" - << std::vformat(fmt, std::make_format_args(args...)) + << std::vformat( + fmt, std::make_format_args(std::forward(args)...)) << "\033[0m"; // 恢复默认颜色 } @@ -126,7 +138,7 @@ class Timer { void reset() { startTime = std::chrono::high_resolution_clock::now(); } - [[nodiscard]] auto elapsed() const -> double { + [[nodiscard]] inline auto elapsed() const -> double { auto endTime = std::chrono::high_resolution_clock::now(); return std::chrono::duration(endTime - startTime).count(); } @@ -135,25 +147,25 @@ class Timer { class CodeBlock { private: int indentLevel = 0; - const int spacesPerIndent = 4; + static constexpr int spacesPerIndent = 4; public: - void increaseIndent() { ++indentLevel; } - void decreaseIndent() { + constexpr void increaseIndent() { ++indentLevel; } + constexpr void decreaseIndent() { if (indentLevel > 0) { --indentLevel; } } template - void print(std::string_view fmt, Args&&... args) { + inline void print(std::string_view fmt, Args&&... args) const { std::cout << std::string( static_cast(indentLevel) * spacesPerIndent, ' '); atom::utils::print(fmt, std::forward(args)...); } template - void println(std::string_view fmt, Args&&... args) { + inline void println(std::string_view fmt, Args&&... args) const { std::cout << std::string( static_cast(indentLevel) * spacesPerIndent, ' '); atom::utils::println(fmt, std::forward(args)...); @@ -169,20 +181,22 @@ enum class TextStyle { }; template -void printStyled(TextStyle style, std::string_view fmt, Args&&... args) { +inline void printStyled(TextStyle style, std::string_view fmt, Args&&... args) { std::cout << "\033[" << static_cast(style) << "m" - << std::vformat(fmt, std::make_format_args(args...)) << "\033[0m"; + << std::vformat( + fmt, std::make_format_args(std::forward(args)...)) + << "\033[0m"; } class MathStats { public: template - static auto mean(const Container& data) -> double { + [[nodiscard]] static inline auto mean(const Container& data) -> double { return std::accumulate(data.begin(), data.end(), 0.0) / data.size(); } template - static auto median(Container data) -> double { + [[nodiscard]] static inline auto median(Container data) -> double { std::sort(data.begin(), data.end()); if (data.size() % 2 == 0) { return (data[data.size() / 2 - 1] + data[data.size() / 2]) / 2.0; @@ -192,7 +206,8 @@ class MathStats { } template - static auto standardDeviation(const Container& data) -> double { + [[nodiscard]] static inline auto standardDeviation(const Container& data) + -> double { double meanValue = mean(data); double variance = std::accumulate(data.begin(), data.end(), 0.0, @@ -204,21 +219,21 @@ class MathStats { return std::sqrt(variance); } }; -\ + class MemoryTracker { private: - std::map allocations; + std::unordered_map allocations; public: - void allocate(const std::string& identifier, size_t size) { + inline void allocate(const std::string& identifier, size_t size) { allocations[identifier] = size; } - void deallocate(const std::string& identifier) { + inline void deallocate(const std::string& identifier) { allocations.erase(identifier); } - void printUsage() { + inline void printUsage() const { size_t total = 0; for (const auto& [identifier, size] : allocations) { println("{}: {} bytes", identifier, size); @@ -236,26 +251,33 @@ class FormatLiteral { : fmt_str_(format) {} template - auto operator()(Args&&... args) const -> std::string { - return std::vformat(fmt_str_, std::make_format_args(args...)); + [[nodiscard]] inline auto operator()(Args&&... args) const -> std::string { + return std::vformat(fmt_str_, + std::make_format_args(std::forward(args)...)); } }; constexpr auto operator""_fmt(const char* str, std::size_t len) { return FormatLiteral(std::string_view(str, len)); } + } // namespace atom::utils #if __cplusplus >= 202302L +namespace std { + template -struct std::formatter< - T, std::enable_if_t< - std::is_same_v> || - std::is_same_v> || - std::is_same_v> || - std::is_same_v>, - char>> : std::formatter { - auto format(const T& container, format_context& ctx) const { +struct formatter< + T, + enable_if_t> || + is_same_v> || + is_same_v> || + is_same_v> || + is_same_v> || + is_same_v>, + char>> : formatter { + auto format(const T& container, + format_context& ctx) const -> decltype(ctx.out()) { auto out = ctx.out(); *out++ = '['; bool first = true; @@ -272,9 +294,10 @@ struct std::formatter< } }; -template -struct std::formatter> : std::formatter { - auto format(const std::map& m, format_context& ctx) const { +template +struct formatter> : formatter { + auto format(const std::map& m, + format_context& ctx) const -> decltype(ctx.out()) { auto out = ctx.out(); *out++ = '{'; bool first = true; @@ -291,10 +314,10 @@ struct std::formatter> : std::formatter { } }; -template -struct std::formatter> - : std::formatter { - auto format(const std::unordered_map& m, format_context& ctx) const { +template +struct formatter> : formatter { + auto format(const std::unordered_map& m, + format_context& ctx) const -> decltype(ctx.out()) { auto out = ctx.out(); *out++ = '{'; bool first = true; @@ -312,8 +335,9 @@ struct std::formatter> }; template -struct std::formatter> : std::formatter { - auto format(const std::array& arr, format_context& ctx) const { +struct formatter> : formatter { + auto format(const std::array& arr, + format_context& ctx) const -> decltype(ctx.out()) { auto out = ctx.out(); *out++ = '['; for (std::size_t i = 0; i < N; ++i) { @@ -329,8 +353,9 @@ struct std::formatter> : std::formatter { }; template -struct std::formatter> : std::formatter { - auto format(const std::pair& p, format_context& ctx) const { +struct formatter> : formatter { + auto format(const std::pair& p, + format_context& ctx) const -> decltype(ctx.out()) { auto out = ctx.out(); *out++ = '('; out = std::format_to(out, "{}", p.first); @@ -341,6 +366,53 @@ struct std::formatter> : std::formatter { return out; } }; -#endif +template +struct formatter> : formatter { + auto format(const std::tuple& tup, + format_context& ctx) const -> decltype(ctx.out()) { + auto out = ctx.out(); + *out++ = '('; + std::apply( + [&](const Ts&... args) { + std::size_t n = 0; + ((void)((n++ > 0 ? (out = std::format_to(out, ", {}", args)) + : (out = std::format_to(out, "{}", args))), + 0), + ...); + }, + tup); + *out++ = ')'; + return out; + } +}; + +template +struct formatter> : formatter { + auto format(const std::variant& var, + format_context& ctx) const -> decltype(ctx.out()) { + return std::visit( + [&ctx](const auto& val) -> decltype(ctx.out()) { + return std::format_to(ctx.out(), "{}", val); + }, + var); + } +}; + +template +struct formatter> : formatter { + auto format(const std::optional& opt, + format_context& ctx) const -> decltype(ctx.out()) { + auto out = ctx.out(); + if (opt.has_value()) { + return std::format_to(out, "Optional({})", opt.value()); + } else { + return std::format_to(out, "Optional()"); + } + } +}; + +} // namespace std #endif + +#endif \ No newline at end of file diff --git a/src/atom/utils/xml.hpp b/src/atom/utils/xml.hpp index bcb92318..801486fb 100644 --- a/src/atom/utils/xml.hpp +++ b/src/atom/utils/xml.hpp @@ -15,7 +15,12 @@ Description: A XML reader class using tinyxml2. #ifndef ATOM_UTILS_XML_HPP #define ATOM_UTILS_XML_HPP +#if __has_include() #include +#elif __has_include() +#include +#endif + #include #include diff --git a/src/atom/web/minetype.cpp b/src/atom/web/minetype.cpp new file mode 100644 index 00000000..fa0c2a53 --- /dev/null +++ b/src/atom/web/minetype.cpp @@ -0,0 +1,184 @@ +#include "minetype.hpp" +#include +#include +#include +#include +#include +#include +#include + +#include "atom/log/loguru.hpp" +#include "atom/type/json.hpp" + +using json = nlohmann::json; + +class MimeTypes::Impl { +public: + Impl(const std::vector& knownFiles, bool lenient) + : lenient_(lenient) { + for (const auto& file : knownFiles) { + read(file); + } + } + + void readJson(const std::string& jsonFile) { + std::ifstream file(jsonFile); + if (!file) { + LOG_F(WARNING, "Could not open JSON file {}", jsonFile); + return; + } + + json jsonData; + file >> jsonData; + + std::unique_lock lock(mutex_); + for (const auto& [mimeType, extensions] : jsonData.items()) { + for (const auto& ext : extensions) { + addType(mimeType, ext.get()); + } + } + } + + auto guessType(const std::string& url) + -> std::pair, std::optional> { + std::filesystem::path path(url); + std::string extension = path.extension().string(); + return getMimeType(extension); + } + + auto guessAllExtensions(const std::string& mimeType) + -> std::vector { + std::shared_lock lock(mutex_); + auto iter = reverseMap_.find(mimeType); + if (iter != reverseMap_.end()) { + return iter->second; + } + return {}; + } + + auto guessExtension(const std::string& mimeType) + -> std::optional { + auto extensions = guessAllExtensions(mimeType); + return extensions.empty() ? std::nullopt + : std::make_optional(extensions[0]); + } + + void addType(const std::string& mimeType, const std::string& extension) { + std::unique_lock lock(mutex_); + typesMap_[extension] = mimeType; + reverseMap_[mimeType].emplace_back(extension); + } + + void listAllTypes() const { + std::shared_lock lock(mutex_); + for (const auto& [ext, type] : typesMap_) { + LOG_F(INFO, "Extension: {} -> MIME Type: {}", ext, type); + } + } + + auto guessTypeByContent(const std::string& filePath) + -> std::optional { + std::ifstream file(filePath, std::ios::binary); + if (!file) { + LOG_F(WARNING, "Could not open file {}", filePath); + return std::nullopt; + } + + std::array buffer; + file.read(buffer.data(), buffer.size()); + + if (buffer[0] == '\xFF' && buffer[1] == '\xD8') { + return "image/jpeg"; + } + if (buffer[0] == '\x89' && buffer[1] == 'P' && buffer[2] == 'N' && + buffer[3] == 'G') { + return "image/png"; + } + if (buffer[0] == 'G' && buffer[1] == 'I' && buffer[2] == 'F') { + return "image/gif"; + } + if (buffer[0] == 'P' && buffer[1] == 'K') { + return "application/zip"; + } + + return std::nullopt; + } + +private: + mutable std::shared_mutex mutex_; + std::unordered_map typesMap_; + std::unordered_map> reverseMap_; + bool lenient_; + + void read(const std::string& file) { + std::ifstream fileStream(file); + if (!fileStream) { + LOG_F(WARNING, "Could not open file {}", file); + return; + } + + std::string line; + while (std::getline(fileStream, line)) { + if (line.empty() || line[0] == '#') { + continue; + } + std::istringstream iss(line); + std::string mimeType; + if (iss >> mimeType) { + std::string ext; + while (iss >> ext) { + addType(mimeType, ext); + } + } + } + } + + auto getMimeType(const std::string& extension) + -> std::pair, std::optional> { + std::shared_lock lock(mutex_); + auto iter = typesMap_.find(extension); + if (iter != typesMap_.end()) { + return {iter->second, std::nullopt}; + } + if (lenient_) { + return {"application/octet-stream", std::nullopt}; + } + return {std::nullopt, std::nullopt}; + } +}; + +MimeTypes::MimeTypes(const std::vector& knownFiles, bool lenient) + : pImpl(std::make_unique(knownFiles, lenient)) {} + +MimeTypes::~MimeTypes() = default; + +void MimeTypes::readJson(const std::string& jsonFile) { + pImpl->readJson(jsonFile); +} + +auto MimeTypes::guessType(const std::string& url) + -> std::pair, std::optional> { + return pImpl->guessType(url); +} + +auto MimeTypes::guessAllExtensions(const std::string& mimeType) + -> std::vector { + return pImpl->guessAllExtensions(mimeType); +} + +auto MimeTypes::guessExtension(const std::string& mimeType) + -> std::optional { + return pImpl->guessExtension(mimeType); +} + +void MimeTypes::addType(const std::string& mimeType, + const std::string& extension) { + pImpl->addType(mimeType, extension); +} + +void MimeTypes::listAllTypes() const { pImpl->listAllTypes(); } + +auto MimeTypes::guessTypeByContent(const std::string& filePath) + -> std::optional { + return pImpl->guessTypeByContent(filePath); +} \ No newline at end of file diff --git a/src/atom/web/minetype.hpp b/src/atom/web/minetype.hpp new file mode 100644 index 00000000..7f25d319 --- /dev/null +++ b/src/atom/web/minetype.hpp @@ -0,0 +1,28 @@ +#ifndef MIMETYPES_H +#define MIMETYPES_H + +#include +#include +#include +#include + +class MimeTypes { +public: + MimeTypes(const std::vector& knownFiles, bool lenient = false); + ~MimeTypes(); + + void readJson(const std::string& jsonFile); + std::pair, std::optional> guessType( + const std::string& url); + std::vector guessAllExtensions(const std::string& mimeType); + std::optional guessExtension(const std::string& mimeType); + void addType(const std::string& mimeType, const std::string& extension); + void listAllTypes() const; + std::optional guessTypeByContent(const std::string& filePath); + +private: + class Impl; + std::unique_ptr pImpl; +}; + +#endif // MIMETYPES_H \ No newline at end of file diff --git a/tests/atom/utils/difflib.cpp b/tests/atom/utils/difflib.cpp new file mode 100644 index 00000000..84f4f77f --- /dev/null +++ b/tests/atom/utils/difflib.cpp @@ -0,0 +1,92 @@ +#ifndef ATOM_UTILS_TEST_DIFFLIB_HPP +#define ATOM_UTILS_TEST_DIFFLIB_HPP + +#include + +#include "atom/utils/difflib.hpp" + +using namespace atom::utils; + +TEST(SequenceMatcherTest, Ratio) { + SequenceMatcher matcher("hello", "hallo"); + EXPECT_NEAR(matcher.ratio(), 0.8, 0.01); +} + +TEST(SequenceMatcherTest, SetSeqs) { + SequenceMatcher matcher("hello", "world"); + matcher.setSeqs("hello", "hallo"); + EXPECT_NEAR(matcher.ratio(), 0.8, 0.01); +} + +TEST(SequenceMatcherTest, GetMatchingBlocks) { + SequenceMatcher matcher("hello", "hallo"); + auto blocks = matcher.getMatchingBlocks(); + ASSERT_EQ(blocks.size(), 3); + EXPECT_EQ(blocks[0], std::make_tuple(0, 0, 1)); + EXPECT_EQ(blocks[1], std::make_tuple(2, 2, 3)); + EXPECT_EQ(blocks[2], std::make_tuple(5, 5, 0)); +} + +TEST(SequenceMatcherTest, GetOpcodes) { + SequenceMatcher matcher("hello", "hallo"); + auto opcodes = matcher.getOpcodes(); + ASSERT_EQ(opcodes.size(), 3); + EXPECT_EQ(opcodes[0], std::make_tuple("equal", 0, 1, 0, 1)); + EXPECT_EQ(opcodes[1], std::make_tuple("replace", 1, 2, 1, 2)); + EXPECT_EQ(opcodes[2], std::make_tuple("equal", 2, 5, 2, 5)); +} + +TEST(DifferTest, Compare) { + std::vector vec1 = {"line1", "line2", "line3"}; + std::vector vec2 = {"line1", "lineX", "line3"}; + auto result = Differ::compare(vec1, vec2); + ASSERT_EQ(result.size(), 3); + EXPECT_EQ(result[0], " line1"); + EXPECT_EQ(result[1], "- line2"); + EXPECT_EQ(result[2], "+ lineX"); +} + +TEST(DifferTest, UnifiedDiff) { + std::vector vec1 = {"line1", "line2", "line3"}; + std::vector vec2 = {"line1", "lineX", "line3"}; + auto result = Differ::unifiedDiff(vec1, vec2); + ASSERT_EQ(result.size(), 6); + EXPECT_EQ(result[0], "--- a"); + EXPECT_EQ(result[1], "+++ b"); + EXPECT_EQ(result[2], "@@ -1,3 +1,3 @@"); + EXPECT_EQ(result[3], " line1"); + EXPECT_EQ(result[4], "-line2"); + EXPECT_EQ(result[5], "+lineX"); +} + +TEST(HtmlDiffTest, MakeFile) { + std::vector fromlines = {"line1", "line2", "line3"}; + std::vector tolines = {"line1", "lineX", "line3"}; + auto result = HtmlDiff::makeFile(fromlines, tolines); + EXPECT_NE(result.find(""), std::string::npos); + EXPECT_NE(result.find("

Differences

"), std::string::npos); + EXPECT_NE(result.find(" line1"), std::string::npos); + EXPECT_NE(result.find("- line2"), std::string::npos); + EXPECT_NE(result.find("+ lineX"), std::string::npos); +} + +TEST(HtmlDiffTest, MakeTable) { + std::vector fromlines = {"line1", "line2", "line3"}; + std::vector tolines = {"line1", "lineX", "line3"}; + auto result = HtmlDiff::makeTable(fromlines, tolines); + EXPECT_NE(result.find(" line1"), std::string::npos); + EXPECT_NE(result.find("- line2"), std::string::npos); + EXPECT_NE(result.find("+ lineX"), std::string::npos); +} + +TEST(GetCloseMatchesTest, Basic) { + std::vector possibilities = {"hello", "hallo", "hullo"}; + auto matches = getCloseMatches("hello", possibilities); + ASSERT_EQ(matches.size(), 3); + EXPECT_EQ(matches[0], "hello"); + EXPECT_EQ(matches[1], "hallo"); + EXPECT_EQ(matches[2], "hullo"); +} + +#endif // ATOM_UTILS_TEST_DIFFLIB_HPP \ No newline at end of file