From f9358a457f861184548ca2130930ea331a22b8e0 Mon Sep 17 00:00:00 2001 From: Allan CORNET Date: Sat, 1 Nov 2025 10:21:28 +0100 Subject: [PATCH] feat(os_functions): loadenv builtin --- CHANGELOG.md | 6 +- THIRDPARTY.md | 8 + modules/os_functions/CMakeLists.txt | 4 + .../builtin/c/nlsOs_functions_builtin.vcxproj | 13 +- .../c/nlsOs_functions_builtin.vcxproj.filters | 6 + modules/os_functions/builtin/cpp/Gateway.cpp | 3 + .../builtin/cpp/loadenvBuiltin.cpp | 76 +++++++ .../builtin/include/loadenvBuiltin.hpp | 21 ++ .../os_functions/help/en_US/xml/loadenv.xml | 64 ++++++ .../os_functions/help/fr_FR/xml/loadenv.xml | 64 ++++++ modules/os_functions/module.iss | 1 + .../src/c/nlsOs_functions.vcxproj | 2 + .../src/c/nlsOs_functions.vcxproj.filters | 6 + .../os_functions/src/cpp/LoadEnvironment.cpp | 83 +++++++ .../src/include/LoadEnvironment.hpp | 24 ++ .../src/include/cppenv/EnvManager.hpp | 213 ++++++++++++++++++ .../src/include/cppenv/IEnvManager.hpp | 21 ++ .../src/include/cppenv/cppenv.hpp | 43 ++++ modules/os_functions/tests/sample.env | 14 ++ modules/os_functions/tests/test_loadenv.m | 38 ++++ 20 files changed, 704 insertions(+), 6 deletions(-) create mode 100644 modules/os_functions/builtin/cpp/loadenvBuiltin.cpp create mode 100644 modules/os_functions/builtin/include/loadenvBuiltin.hpp create mode 100644 modules/os_functions/help/en_US/xml/loadenv.xml create mode 100644 modules/os_functions/help/fr_FR/xml/loadenv.xml create mode 100644 modules/os_functions/src/cpp/LoadEnvironment.cpp create mode 100644 modules/os_functions/src/include/LoadEnvironment.hpp create mode 100644 modules/os_functions/src/include/cppenv/EnvManager.hpp create mode 100644 modules/os_functions/src/include/cppenv/IEnvManager.hpp create mode 100644 modules/os_functions/src/include/cppenv/cppenv.hpp create mode 100644 modules/os_functions/tests/sample.env create mode 100644 modules/os_functions/tests/test_loadenv.m diff --git a/CHANGELOG.md b/CHANGELOG.md index fb2899fe31..729a714705 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,14 +49,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `fwrite`: return also bytes count as second output argument. +- `loadenv`: Load environment variables defined in .env or regular text files. + - `help`: Help for functions in Command Window. - macOS Tahoe 26: CI setup and platform support. -- Python 3.14 support. - - Fedora 43 support. +- Python 3.14 support. + ### Changed - Help framework reworked for improved performance and usability: diff --git a/THIRDPARTY.md b/THIRDPARTY.md index 63f51a2a53..b8ed01ff87 100644 --- a/THIRDPARTY.md +++ b/THIRDPARTY.md @@ -445,3 +445,11 @@ Beautiful and accessible math in all browsers > > (Apache 2.0) + +- cppenv + +cppenv is a header-only C++ library that makes it easy to manage .env files in your projects. + +> + +> (MIT) diff --git a/modules/os_functions/CMakeLists.txt b/modules/os_functions/CMakeLists.txt index 785aeea2bc..7199281136 100644 --- a/modules/os_functions/CMakeLists.txt +++ b/modules/os_functions/CMakeLists.txt @@ -56,6 +56,8 @@ target_include_directories( PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/include ${CMAKE_CURRENT_SOURCE_DIR}/../interpreter/src/include ${CMAKE_CURRENT_SOURCE_DIR}/../types/src/include + ${CMAKE_CURRENT_SOURCE_DIR}/../error_manager/src/include + ${CMAKE_CURRENT_SOURCE_DIR}/../characters_encoding/src/include ${CMAKE_CURRENT_SOURCE_DIR}/../stream_manager/src/include ${CMAKE_CURRENT_SOURCE_DIR}/../i18n/src/include) # ============================================================================== @@ -66,6 +68,7 @@ target_link_libraries( nlsError_manager nlsI18n nlsInterpreter + nlsCharacters_encoding nlsTypes) # ============================================================================== include(GNUInstallDirs) @@ -100,6 +103,7 @@ install( DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests DESTINATION "${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/modules/${module_name}" FILES_MATCHING + PATTERN "*.env" PATTERN "*.m") install( DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/help diff --git a/modules/os_functions/builtin/c/nlsOs_functions_builtin.vcxproj b/modules/os_functions/builtin/c/nlsOs_functions_builtin.vcxproj index 8079649bea..c9ee084682 100644 --- a/modules/os_functions/builtin/c/nlsOs_functions_builtin.vcxproj +++ b/modules/os_functions/builtin/c/nlsOs_functions_builtin.vcxproj @@ -95,7 +95,7 @@ Disabled WIN32;_DEBUG;_WINDOWS;_USRDLL;NLSOS_FUNCTIONS_BUILTIN_EXPORTS;%(PreprocessorDefinitions) true - $(SolutionDir)modules/os_functions/builtin/include;$(SolutionDir)modules/os_functions/src/include;$(SolutionDir)modules/commons/src/include;$(SolutionDir)modules/interpreter/src/include;$(SolutionDir)modules/types/src/include;$(SolutionDir)modules/stream_manager/src/include;$(SolutionDir)modules/overload/src/include;$(SolutionDir)modules/error_manager/src/include;$(SolutionDir)modules/i18n/src/include;$(SolutionDir)../NelSon-thirdparty-$(PlatformName)/Boost + $(SolutionDir)modules/os_functions/builtin/include;$(SolutionDir)modules/os_functions/src/include;$(SolutionDir)modules/commons/src/include;$(SolutionDir)modules/interpreter/src/include;$(SolutionDir)modules/characters_encoding/src/include;$(SolutionDir)modules/types/src/include;$(SolutionDir)modules/stream_manager/src/include;$(SolutionDir)modules/overload/src/include;$(SolutionDir)modules/error_manager/src/include;$(SolutionDir)modules/i18n/src/include;$(SolutionDir)../NelSon-thirdparty-$(PlatformName)/Boost true 4190 true @@ -116,7 +116,7 @@ Disabled WIN32;_DEBUG;_WINDOWS;_USRDLL;NLSOS_FUNCTIONS_BUILTIN_EXPORTS;%(PreprocessorDefinitions) true - $(SolutionDir)modules/os_functions/builtin/include;$(SolutionDir)modules/os_functions/src/include;$(SolutionDir)modules/commons/src/include;$(SolutionDir)modules/interpreter/src/include;$(SolutionDir)modules/types/src/include;$(SolutionDir)modules/stream_manager/src/include;$(SolutionDir)modules/overload/src/include;$(SolutionDir)modules/error_manager/src/include;$(SolutionDir)modules/i18n/src/include;$(SolutionDir)../NelSon-thirdparty-$(PlatformName)/Boost + $(SolutionDir)modules/os_functions/builtin/include;$(SolutionDir)modules/os_functions/src/include;$(SolutionDir)modules/commons/src/include;$(SolutionDir)modules/interpreter/src/include;$(SolutionDir)modules/characters_encoding/src/include;$(SolutionDir)modules/types/src/include;$(SolutionDir)modules/stream_manager/src/include;$(SolutionDir)modules/overload/src/include;$(SolutionDir)modules/error_manager/src/include;$(SolutionDir)modules/i18n/src/include;$(SolutionDir)../NelSon-thirdparty-$(PlatformName)/Boost true 4190 true @@ -139,7 +139,7 @@ true WIN32;NDEBUG;_WINDOWS;_USRDLL;NLSOS_FUNCTIONS_BUILTIN_EXPORTS;%(PreprocessorDefinitions) true - $(SolutionDir)modules/os_functions/builtin/include;$(SolutionDir)modules/os_functions/src/include;$(SolutionDir)modules/commons/src/include;$(SolutionDir)modules/interpreter/src/include;$(SolutionDir)modules/types/src/include;$(SolutionDir)modules/stream_manager/src/include;$(SolutionDir)modules/overload/src/include;$(SolutionDir)modules/error_manager/src/include;$(SolutionDir)modules/i18n/src/include;$(SolutionDir)../NelSon-thirdparty-$(PlatformName)/Boost + $(SolutionDir)modules/os_functions/builtin/include;$(SolutionDir)modules/os_functions/src/include;$(SolutionDir)modules/commons/src/include;$(SolutionDir)modules/interpreter/src/include;$(SolutionDir)modules/characters_encoding/src/include;$(SolutionDir)modules/types/src/include;$(SolutionDir)modules/stream_manager/src/include;$(SolutionDir)modules/overload/src/include;$(SolutionDir)modules/error_manager/src/include;$(SolutionDir)modules/i18n/src/include;$(SolutionDir)../NelSon-thirdparty-$(PlatformName)/Boost true 4190 true @@ -164,7 +164,7 @@ true WIN32;NDEBUG;_WINDOWS;_USRDLL;NLSOS_FUNCTIONS_BUILTIN_EXPORTS;%(PreprocessorDefinitions) true - $(SolutionDir)modules/os_functions/builtin/include;$(SolutionDir)modules/os_functions/src/include;$(SolutionDir)modules/commons/src/include;$(SolutionDir)modules/interpreter/src/include;$(SolutionDir)modules/types/src/include;$(SolutionDir)modules/stream_manager/src/include;$(SolutionDir)modules/overload/src/include;$(SolutionDir)modules/error_manager/src/include;$(SolutionDir)modules/i18n/src/include;$(SolutionDir)../NelSon-thirdparty-$(PlatformName)/Boost + $(SolutionDir)modules/os_functions/builtin/include;$(SolutionDir)modules/os_functions/src/include;$(SolutionDir)modules/commons/src/include;$(SolutionDir)modules/interpreter/src/include;$(SolutionDir)modules/characters_encoding/src/include;$(SolutionDir)modules/types/src/include;$(SolutionDir)modules/stream_manager/src/include;$(SolutionDir)modules/overload/src/include;$(SolutionDir)modules/error_manager/src/include;$(SolutionDir)modules/i18n/src/include;$(SolutionDir)../NelSon-thirdparty-$(PlatformName)/Boost true 4190 true @@ -189,6 +189,7 @@ + @@ -198,6 +199,9 @@ + + {a75dc538-eefa-4873-888b-a034a7ee72c5} + {d0ef0ceb-a3bd-44ab-91b3-f33db1870f8d} @@ -225,6 +229,7 @@ + diff --git a/modules/os_functions/builtin/c/nlsOs_functions_builtin.vcxproj.filters b/modules/os_functions/builtin/c/nlsOs_functions_builtin.vcxproj.filters index fb6166db2e..11846ad5bb 100644 --- a/modules/os_functions/builtin/c/nlsOs_functions_builtin.vcxproj.filters +++ b/modules/os_functions/builtin/c/nlsOs_functions_builtin.vcxproj.filters @@ -60,6 +60,9 @@ Source Files + + Source Files + @@ -107,6 +110,9 @@ Header Files + + Header Files + diff --git a/modules/os_functions/builtin/cpp/Gateway.cpp b/modules/os_functions/builtin/cpp/Gateway.cpp index 1db3b54087..a1a8cdee04 100644 --- a/modules/os_functions/builtin/cpp/Gateway.cpp +++ b/modules/os_functions/builtin/cpp/Gateway.cpp @@ -16,6 +16,7 @@ #include "isunixBuiltin.hpp" #include "searchenvBuiltin.hpp" #include "setenvBuiltin.hpp" +#include "loadenvBuiltin.hpp" #include "systemBuiltin.hpp" #include "winopenBuiltin.hpp" #include "winqueryregBuiltin.hpp" @@ -33,6 +34,8 @@ static const nlsGateway gateway[] = { CPP_BUILTIN_WITH_EVALUATOR }, { "unix", (ptrBuiltin)Nelson::OsFunctionsGateway::systemBuiltin, 2, 1, CPP_BUILTIN_WITH_EVALUATOR }, + { "loadenv", (ptrBuiltin)Nelson::OsFunctionsGateway::loadenvBuiltin, -1, 1, + CPP_BUILTIN_WITH_EVALUATOR }, { "getenv", (ptrBuiltin)Nelson::OsFunctionsGateway::getenvBuiltin, 1, 1 }, { "setenv", (ptrBuiltin)Nelson::OsFunctionsGateway::setenvBuiltin, 1, 2 }, { "searchenv", (ptrBuiltin)Nelson::OsFunctionsGateway::searchenvBuiltin, 1, 2 }, diff --git a/modules/os_functions/builtin/cpp/loadenvBuiltin.cpp b/modules/os_functions/builtin/cpp/loadenvBuiltin.cpp new file mode 100644 index 0000000000..c16fa3724c --- /dev/null +++ b/modules/os_functions/builtin/cpp/loadenvBuiltin.cpp @@ -0,0 +1,76 @@ +//============================================================================= +// Copyright (c) 2016-present Allan CORNET (Nelson) +//============================================================================= +// This file is part of Nelson. +//============================================================================= +// LICENCE_BLOCK_BEGIN +// SPDX-License-Identifier: LGPL-3.0-or-later +// LICENCE_BLOCK_END +//============================================================================= +#include "loadenvBuiltin.hpp" +#include "InputOutputArgumentsCheckers.hpp" +#include "LoadEnvironment.hpp" +#include "Error.hpp" +#include "i18n.hpp" +#include "characters_encoding.hpp" +#include +#include +//============================================================================= +using namespace Nelson; +//============================================================================= +ArrayOfVector +Nelson::OsFunctionsGateway::loadenvBuiltin(Evaluator* eval, int nLhs, const ArrayOfVector& argIn) +{ + ArrayOfVector retval; + nargincheck(argIn, 1, 1); + nargoutcheck(nLhs, 0, 1); + + std::wstring filename = argIn[0].getContentAsWideString(); + bool applyToEnv = (nLhs == 0); + + std::wstring errorMessage; + std::vector> result; + try { + result = LoadEnvironment(filename, applyToEnv, errorMessage); + } catch (const std::exception& ex) { + errorMessage = utf8_to_wstring(std::string("Exception: ")) + utf8_to_wstring(ex.what()); + } catch (...) { + if (errorMessage.empty()) + errorMessage = _W("Unknown error while loading environment"); + } + + if (!errorMessage.empty()) { + Error(errorMessage); + return retval; + } + + if (nLhs == 1) { + wstringVector keys; + wstringVector values; + keys.reserve(result.size()); + values.reserve(result.size()); + for (const auto& kv : result) { + keys.push_back(kv.first); + values.push_back(kv.second); + } + + FunctionDef* funcDef = nullptr; + if (!eval) { + Error(_W("Evaluator is not initialized.")); + } + Context* context = eval->getContext(); + if (!context) { + Error(_W("Context is not initialized.")); + } + if (context->lookupFunction("dictionary", funcDef)) { + ArrayOfVector args; + args << ArrayOf::stringArrayConstructor(keys, Dimensions(keys.size(), 1)); + args << ArrayOf::stringArrayConstructor(values, Dimensions(values.size(), 1)); + return funcDef->evaluateFunction(eval, args, 1); + } else { + Error(_W("Function 'dictionary' not found in the current context.")); + } + } + return retval; +} +//============================================================================= diff --git a/modules/os_functions/builtin/include/loadenvBuiltin.hpp b/modules/os_functions/builtin/include/loadenvBuiltin.hpp new file mode 100644 index 0000000000..4f4ba9c92a --- /dev/null +++ b/modules/os_functions/builtin/include/loadenvBuiltin.hpp @@ -0,0 +1,21 @@ +//============================================================================= +// Copyright (c) 2016-present Allan CORNET (Nelson) +//============================================================================= +// This file is part of Nelson. +//============================================================================= +// LICENCE_BLOCK_BEGIN +// SPDX-License-Identifier: LGPL-3.0-or-later +// LICENCE_BLOCK_END +//============================================================================= +#pragma once +//============================================================================= +#include "ArrayOf.hpp" +#include "Evaluator.hpp" +//============================================================================= +namespace Nelson::OsFunctionsGateway { +//============================================================================= +ArrayOfVector +loadenvBuiltin(Evaluator* eval, int nLhs, const ArrayOfVector& argIn); +//============================================================================= +} // namespace Nelson +//============================================================================= diff --git a/modules/os_functions/help/en_US/xml/loadenv.xml b/modules/os_functions/help/en_US/xml/loadenv.xml new file mode 100644 index 0000000000..cf6b08a341 --- /dev/null +++ b/modules/os_functions/help/en_US/xml/loadenv.xml @@ -0,0 +1,64 @@ + + + SAME AS NELSON SOFTWARE + en_US + loadenv + Load environment variables defined in .env or regular text files. + + loadenv(filename) + D = loadenv(filename) + + + + filename + string scalar, character vector: environment filename. + + + + + s + dictionary: the environment variables and their values. + + + +

loadenv(filename) loads environment variables from a .env or plain text file by parsing one key-value pair per line and sets them as environment variables in the Nelson environment.

+

D = loadenv(filename) returns a dictionary containing the parsed key-value pairs. When an output argument is specified, loadenv does not modify the Nelson environment.

+
+ + + + + nelson + + + + + + + setenv + + + getenv + + + + + 1.15.0 + initial version + + + + Allan CORNET + +
diff --git a/modules/os_functions/help/fr_FR/xml/loadenv.xml b/modules/os_functions/help/fr_FR/xml/loadenv.xml new file mode 100644 index 0000000000..11c37b02b8 --- /dev/null +++ b/modules/os_functions/help/fr_FR/xml/loadenv.xml @@ -0,0 +1,64 @@ + + + SAME AS NELSON SOFTWARE + fr_FR + loadenv + Charger les variables d'environnement définies dans des fichiers .env ou des fichiers texte ordinaires. + + loadenv(filename) + D = loadenv(filename) + + + + filename + chaine de caractères: nom du fichier d'environnement. + + + + + s + dictionnaire: les variables d'environnement et leurs valeurs. + + + +

loadenv(filename) charge les variables d'environnement à partir d'un fichier .env ou texte brut en analysant une paire clé-valeur par ligne et les définit comme variables d'environnement dans l'environnement Nelson.

+

D = loadenv(filename) renvoie un dictionnaire contenant les paires clé-valeur analysées. Lorsqu'un argument de sortie est spécifié, loadenv ne modifie pas l'environnement Nelson.

+
+ + + + + nelson + + + + + + + setenv + + + getenv + + + + + 1.15.0 + initial version + + + + Allan CORNET + +
diff --git a/modules/os_functions/module.iss b/modules/os_functions/module.iss index ab28eee3e2..4243ce1b5d 100644 --- a/modules/os_functions/module.iss +++ b/modules/os_functions/module.iss @@ -21,4 +21,5 @@ Source: {#RootPath}modules\{#MODULE_NAME}\functions\*.m; DestDir: {app}\modules\ Source: {#RootPath}modules\{#MODULE_NAME}\help\*.nhz; DestDir: {app}\modules\{#MODULE_NAME}\help\; Flags: recursesubdirs;Components: {#COMPONENT_HELP_FILES}; ;============================================================================== Source: {#RootPath}modules\{#MODULE_NAME}\tests\*.m; DestDir: {app}\modules\{#MODULE_NAME}\tests\; Flags: recursesubdirs;Components: {#COMPONENT_TESTS_MANAGER} and {#COMPONENT_UNIT_TESTS}; +Source: {#RootPath}modules\{#MODULE_NAME}\tests\*.env; DestDir: {app}\modules\{#MODULE_NAME}\tests\; Flags: recursesubdirs;Components: {#COMPONENT_TESTS_MANAGER} and {#COMPONENT_UNIT_TESTS}; ;============================================================================== diff --git a/modules/os_functions/src/c/nlsOs_functions.vcxproj b/modules/os_functions/src/c/nlsOs_functions.vcxproj index 06a2b06077..20e1922f4d 100644 --- a/modules/os_functions/src/c/nlsOs_functions.vcxproj +++ b/modules/os_functions/src/c/nlsOs_functions.vcxproj @@ -182,6 +182,7 @@ + @@ -203,6 +204,7 @@ +
diff --git a/modules/os_functions/src/c/nlsOs_functions.vcxproj.filters b/modules/os_functions/src/c/nlsOs_functions.vcxproj.filters index 9bae1cf16e..4d9ecbe1a9 100644 --- a/modules/os_functions/src/c/nlsOs_functions.vcxproj.filters +++ b/modules/os_functions/src/c/nlsOs_functions.vcxproj.filters @@ -54,6 +54,9 @@ Source Files + + Source Files + @@ -95,6 +98,9 @@ Header Files + + Header Files + diff --git a/modules/os_functions/src/cpp/LoadEnvironment.cpp b/modules/os_functions/src/cpp/LoadEnvironment.cpp new file mode 100644 index 0000000000..70b682d717 --- /dev/null +++ b/modules/os_functions/src/cpp/LoadEnvironment.cpp @@ -0,0 +1,83 @@ +//============================================================================= +// Copyright (c) 2016-present Allan CORNET (Nelson) +//============================================================================= +// This file is part of Nelson. +//============================================================================= +// LICENCE_BLOCK_BEGIN +// SPDX-License-Identifier: LGPL-3.0-or-later +// LICENCE_BLOCK_END +//============================================================================= +#include +#include +#include +#include +#include +#include "LoadEnvironment.hpp" +#include "characters_encoding.hpp" +#include "i18n.hpp" +#include "SetVariableEnvironment.hpp" +//============================================================================= +namespace Nelson { +//============================================================================= +std::vector> +LoadEnvironment(const std::wstring& filename, bool applyToEnv, std::wstring& errorMessage) +{ + std::vector> result; + try { + cppenv::EnvManager envManager; + bool loaded = envManager.load_from_file(filename); + if (!loaded) { + errorMessage = _W("Failed to load environment file: ") + filename; + return {}; + } + + std::vector keys = envManager.names(); + result.reserve(keys.size()); + + auto trim_and_unquote = [](std::wstring s) -> std::wstring { + // trim whitespace + const std::wstring ws = L" \t\r\n"; + auto start = s.find_first_not_of(ws); + if (start == std::wstring::npos) + return L""; + auto end = s.find_last_not_of(ws); + s = s.substr(start, end - start + 1); + // remove matching surrounding single or double quotes + if (s.size() >= 2) { + if ((s.front() == L'\'' && s.back() == L'\'') + || (s.front() == L'"' && s.back() == L'"')) { + s = s.substr(1, s.size() - 2); + } + } + return s; + }; + + for (const auto& key : keys) { + auto valueOpt = envManager.get_value_as(key); + if (!valueOpt.has_value()) + continue; + std::wstring wkey = utf8_to_wstring(key); + std::wstring val = utf8_to_wstring(valueOpt.value()); + val = trim_and_unquote(val); + result.emplace_back(std::move(wkey), std::move(val)); + } + + if (applyToEnv) { + for (const auto& kv : result) { + SetVariableEnvironmentW(kv.first, kv.second); + } + } + } catch (const std::exception& ex) { + errorMessage + = _W("Exception while loading environment: ") + utf8_to_wstring(std::string(ex.what())); + return {}; + } catch (...) { + errorMessage = _W("Unknown error while loading environment file: ") + filename; + return {}; + } + + return result; +} +//============================================================================= +} // namespace Nelson +//============================================================================= diff --git a/modules/os_functions/src/include/LoadEnvironment.hpp b/modules/os_functions/src/include/LoadEnvironment.hpp new file mode 100644 index 0000000000..29a32b976f --- /dev/null +++ b/modules/os_functions/src/include/LoadEnvironment.hpp @@ -0,0 +1,24 @@ +//============================================================================= +// Copyright (c) 2016-present Allan CORNET (Nelson) +//============================================================================= +// This file is part of Nelson. +//============================================================================= +// LICENCE_BLOCK_BEGIN +// SPDX-License-Identifier: LGPL-3.0-or-later +// LICENCE_BLOCK_END +//============================================================================= +#pragma once +//============================================================================= +#include "nlsOs_functions_exports.h" +#include +#include +#include +//============================================================================= +namespace Nelson { +//============================================================================= +NLSOS_FUNCTIONS_IMPEXP +std::vector> +LoadEnvironment(const std::wstring& filename, bool applyToEnv, std::wstring& errorMessage); +//============================================================================= +} +//============================================================================= diff --git a/modules/os_functions/src/include/cppenv/EnvManager.hpp b/modules/os_functions/src/include/cppenv/EnvManager.hpp new file mode 100644 index 0000000000..8c98bcb85f --- /dev/null +++ b/modules/os_functions/src/include/cppenv/EnvManager.hpp @@ -0,0 +1,213 @@ +#pragma once + +#include "./IEnvManager.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // added + +namespace cppenv { + +class EnvManager : public IEnvManager +{ +public: + bool + load_from_file(const std::filesystem::path& filename) override + { +#ifdef _MSC_VER + std::ifstream file(filename.wstring(), std::ios::in | std::ios::binary); +#else + std::ifstream file(filename.string()); +#endif + + if (!file.is_open()) { + return false; + } + + std::string line; + + while (std::getline(file, line)) { + + line = trim(line); + + if (line.empty() || line[0] == '#') + continue; + + size_t equal_pos = line.find('='); + + if (equal_pos != std::string::npos) { + + std::string key = trim(line.substr(0, equal_pos)); + + std::string value = trim(line.substr(equal_pos + 1)); + + value = remove_inline_comment(value); + value = strip_quotes(value); + + // update existing key or append to preserve order + auto it = std::find_if(env_vars.begin(), env_vars.end(), + [&](const auto& kv) { return kv.first == key; }); + if (it != env_vars.end()) { + it->second = value; + } else { + env_vars.emplace_back(key, value); + } + } + } + + file.close(); + + return true; + } + + std::optional + get_value(const std::string& key) const override + { + + auto it = std::find_if( + env_vars.cbegin(), env_vars.cend(), [&](const auto& kv) { return kv.first == key; }); + + if (it != env_vars.cend()) { + + return it->second; + } + + return std::nullopt; // Returns an empty value if the key does not exist + } + + std::vector + names() const + { + std::vector keys; + keys.reserve(env_vars.size()); + + for (const auto& kv : env_vars) { + keys.push_back(kv.first); + } + + return keys; + } + + std::optional + operator[](const std::string& key) const + { + return get_value(key); + } + + template + std::optional + operator[](const char* key) const + { + return get_value_as(std::string(key)); + } + + /* + template, int>::type = 0> + std::optional operator[](const std::string& key) const { + return get_value_as(key); + }*/ + + template + std::optional + get_value_as(const std::string& key) const + { + + auto it = std::find_if( + env_vars.cbegin(), env_vars.cend(), [&](const auto& kv) { return kv.first == key; }); + + if (it == env_vars.cend()) { + return std::nullopt; + } + + std::istringstream iss(it->second); + T value; + + if constexpr (std::is_same::value) { + + std::string lower_val = it->second; + + std::transform(lower_val.begin(), lower_val.end(), lower_val.begin(), ::tolower); + + if (lower_val == "true" || lower_val == "1") { + return true; + } + + if (lower_val == "false" || lower_val == "0") { + return false; + } + + return std::nullopt; + + } else if constexpr (std::is_same::value) { + + return it->second; + + } else { + + iss >> value; + + if (iss.fail() || !iss.eof()) { + return std::nullopt; + } + + return value; + } + } + +private: + std::vector> env_vars; + + static std::string + trim(const std::string& str) + { + + size_t start = str.find_first_not_of(" \t\r\n"); + size_t end = str.find_last_not_of(" \t\r\n"); + + return (start == std::string::npos || end == std::string::npos) + ? "" + : str.substr(start, end - start + 1); + } + + static std::string + remove_inline_comment(const std::string& value) + { + + bool in_quotes = false; + std::string cleaned; + + for (size_t i = 0; i < value.size(); ++i) { + char c = value[i]; + + if (c == '"') + in_quotes = !in_quotes; + + if (!in_quotes && c == '#') + break; + + cleaned += c; + } + + return trim(cleaned); + } + + static std::string + strip_quotes(const std::string& value) + { + + if (value.size() >= 2 && (value.front() == '"' && value.back() == '"')) { + return value.substr(1, value.size() - 2); + } + + return value; + } +}; + +} diff --git a/modules/os_functions/src/include/cppenv/IEnvManager.hpp b/modules/os_functions/src/include/cppenv/IEnvManager.hpp new file mode 100644 index 0000000000..0971871b62 --- /dev/null +++ b/modules/os_functions/src/include/cppenv/IEnvManager.hpp @@ -0,0 +1,21 @@ +#pragma once +#include +#include +#include + +namespace cppenv { + +class IEnvManager +{ +public: + virtual ~IEnvManager() = default; + + virtual bool + load_from_file(const std::filesystem::path& filename) + = 0; + + virtual std::optional + get_value(const std::string& key) const = 0; +}; + +} diff --git a/modules/os_functions/src/include/cppenv/cppenv.hpp b/modules/os_functions/src/include/cppenv/cppenv.hpp new file mode 100644 index 0000000000..b20343f6b6 --- /dev/null +++ b/modules/os_functions/src/include/cppenv/cppenv.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include "./IEnvManager.hpp" +#include "./EnvManager.hpp" + +namespace cppenv::type { + +using OptString = std::optional; +using OptInt = std::optional; +using OptFloat = std::optional; +using OptDouble = std::optional; +using OptUInt = std::optional; +using OptSize_t = std::optional; +using OptChar = std::optional; +using OptBool = std::optional; + +} + +namespace cppenv::convert_type { + +bool +string_to_bool(const std::optional& opt_str, bool default_value = false) +{ + + if (!opt_str.has_value()) { + return default_value; + } + + std::string str = *opt_str; + std::transform( + str.begin(), str.end(), str.begin(), [](unsigned char c) { return std::tolower(c); }); + + if (str == "true" || str == "1") { + return true; + } + if (str == "false" || str == "0") { + return false; + } + + throw std::invalid_argument("Invalid boolean string : " + *opt_str); +} + +} diff --git a/modules/os_functions/tests/sample.env b/modules/os_functions/tests/sample.env new file mode 100644 index 0000000000..fb03064f8a --- /dev/null +++ b/modules/os_functions/tests/sample.env @@ -0,0 +1,14 @@ +Key1='Value1' +Key2='Value2' +AWS_ACCESS_KEY_ID='your-AWS_ACCESS_KEY_ID-goes-here' +AWS_SECRET_ACCESS_KEY='your-AWS_SECRET_ACCESS_KEY-goes-here' +MW_WASB_SAS_TOKEN='your-MW_WASB_SAS_TOKEN-goes-here' +username='your-username-goes-here' +password='your-password-goes-here' +DB_NAME='MySQL_DB' +DB_USER='user_name' +DB_PASSWORD='pass_word' +DB_DOMAIN='abc123.mysqldb.com' +DB_PORT='1234' +TEMPORARY_DOWNLOAD='C:/Users/username/Local/Temp/my_download' +会意; 會意='OK' diff --git a/modules/os_functions/tests/test_loadenv.m b/modules/os_functions/tests/test_loadenv.m new file mode 100644 index 0000000000..b201cfa94c --- /dev/null +++ b/modules/os_functions/tests/test_loadenv.m @@ -0,0 +1,38 @@ +%============================================================================= +% Copyright (c) 2016-present Allan CORNET (Nelson) +%============================================================================= +% This file is part of Nelson. +%============================================================================= +% LICENCE_BLOCK_BEGIN +% SPDX-License-Identifier: LGPL-3.0-or-later +% LICENCE_BLOCK_END +%============================================================================= +assert_isequal(nargin('loadenv'), 1); +assert_isequal(nargout('loadenv'), -1); +%============================================================================= +env_file = [modulepath('os_functions', 'tests'), '/sample.env']; +D = loadenv(env_file); +%============================================================================= +assert_isequal(class(D), 'dictionary'); +REF = ["Key1"; + "Key2"; + "AWS_ACCESS_KEY_ID"; + "AWS_SECRET_ACCESS_KEY"; + "MW_WASB_SAS_TOKEN"; + "username"; + "password"; + "DB_NAME"; + "DB_USER"; + "DB_PASSWORD"; + "DB_DOMAIN"; + "DB_PORT"; + "TEMPORARY_DOWNLOAD"; + "会意; 會意"]; +assert_isequal(D.keys, REF); +%============================================================================= +assert_isequal(getenv('Key1'), ''); +loadenv(env_file); +assert_isequal(getenv('Key1'), 'Value1'); +%============================================================================= +assert_isequal(getenv('会意; 會意'), 'OK'); +%============================================================================= \ No newline at end of file