feat: add Rust env namespace
This commit is contained in:
@ -15,6 +15,7 @@ PRIVATE
|
||||
yycc/string/op.cpp
|
||||
yycc/patch/fopen.cpp
|
||||
yycc/rust/panic.cpp
|
||||
yycc/rust/env.cpp
|
||||
yycc/windows/com.cpp
|
||||
#yycc/windows/dialog.cpp
|
||||
yycc/windows/winfct.cpp
|
||||
@ -53,6 +54,7 @@ FILES
|
||||
yycc/rust/panic.hpp
|
||||
yycc/rust/option.hpp
|
||||
yycc/rust/result.hpp
|
||||
yycc/rust/env.hpp
|
||||
yycc/windows/import_guard_head.hpp
|
||||
yycc/windows/import_guard_tail.hpp
|
||||
yycc/windows/com.hpp
|
||||
|
159
src/yycc/rust/env.cpp
Normal file
159
src/yycc/rust/env.cpp
Normal file
@ -0,0 +1,159 @@
|
||||
#include "env.hpp"
|
||||
#include "../macro/os_detector.hpp"
|
||||
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
#include "../encoding/windows.hpp"
|
||||
#include "../num/safe_op.hpp"
|
||||
#include "../num/safe_cast.hpp"
|
||||
#include <Windows.h>
|
||||
#include <winbase.h>
|
||||
#else
|
||||
#include "../string/reinterpret.hpp"
|
||||
#include <cstdlib>
|
||||
#include <cerrno>
|
||||
#include <stdexcept>
|
||||
#endif
|
||||
|
||||
#define SAFECAST ::yycc::num::safe_cast
|
||||
#define SAFEOP ::yycc::num::safe_op
|
||||
#define ENC ::yycc::encoding::windows
|
||||
#define REINTERPRET ::yycc::string::reinterpret
|
||||
|
||||
namespace yycc::rust::env {
|
||||
|
||||
EnvResult<std::u8string> get_var(const std::u8string_view &name) {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-getenvironmentvariablew
|
||||
// Convert to wchar
|
||||
auto wname = ENC::to_wchar(name);
|
||||
if (!wname.has_value()) {
|
||||
return std::unexpected(EnvError::BadEncoding);
|
||||
}
|
||||
|
||||
// Prepare a variable with proper init size for holding value.
|
||||
std::wstring wvalue;
|
||||
wvalue.resize(256);
|
||||
|
||||
// Start to fetch value
|
||||
while (true) {
|
||||
// YYC MARK:
|
||||
// Due to the shitty design of GetEnvironmentVariableW,
|
||||
// the size passed to this function must include NULL terminal.
|
||||
// So we forcely use checked add and sub for this bad behavior.
|
||||
auto fct_size = SAFEOP::checked_add<size_t>(wvalue.size(), 1);
|
||||
if (!fct_size.has_value()) return std::unexpected(EnvError::BadArithmetic);
|
||||
auto rv = ::GetEnvironmentVariableW(wname.value().c_str(), wvalue.data(), fct_size.value());
|
||||
|
||||
// Check the return value
|
||||
if (rv == 0) {
|
||||
// Function failed. Extract error reason.
|
||||
auto ec = GetLastError();
|
||||
if (ec == ERROR_ENVVAR_NOT_FOUND) return std::unexpected(EnvError::NoSuchName);
|
||||
else return std::unexpected(EnvError::BadCall);
|
||||
} else {
|
||||
// Function okey. Check the size.
|
||||
// Fetch function expected size.
|
||||
auto rv_size = SAFECAST::try_to<size_t>(rv);
|
||||
if (!rv_size.has_value()) return std::unexpected(EnvError::BadArithmetic);
|
||||
auto exp_size = SAFEOP::checked_sub<size_t>(rv_size.value(), 1);
|
||||
if (!exp_size.has_value()) return std::unexpected(EnvError::BadArithmetic);
|
||||
|
||||
// YYC MARK:
|
||||
// According to Microsoft, the return value of this function is just a bullshit.
|
||||
// If "wvalue" is big enough, the range of return value is [0, wvalue.size()],
|
||||
// indicating the size of final string, excluding NULL terminal.
|
||||
// otherwise, the range of return value is [wvalue.size() + 1, +inf),
|
||||
// indicating the required size of buffer including NULL terminal.
|
||||
// So we must treat this return value carefully.
|
||||
if (exp_size.value() <= wvalue.size()) {
|
||||
// Okey, shrink to the real size of value and break.
|
||||
wvalue.resize(rv_size.value());
|
||||
break;
|
||||
} else {
|
||||
// We need resize it and try again.
|
||||
wvalue.resize(exp_size.value());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert back to UTF8 string and return.
|
||||
return ENC::to_utf8(wvalue).transform_error([](auto err) { return EnvError::BadEncoding; });
|
||||
#else
|
||||
// String view is not NULL-terminal-guaranted,
|
||||
// so we solve this when casting its type.
|
||||
auto ordinary_name = REINTERPRET::as_ordinary(name);
|
||||
|
||||
// Fetch variable
|
||||
auto finder = std::getenv(ordinary_name.c_str());
|
||||
if (finder == nullptr) return std::unexpected(EnvError::NoSuchName);
|
||||
else return REINTERPRET::as_utf8(finder);
|
||||
#endif
|
||||
}
|
||||
|
||||
EnvResult<void> set_var(const std::u8string_view &name, const std::u8string_view &value) {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-setenvironmentvariablew
|
||||
|
||||
// Convert to wchar
|
||||
auto wname = ENC::to_wchar(name);
|
||||
auto wvalue = ENC::to_wchar(value);
|
||||
if (!(wname.has_value() && wvalue.has_value())) {
|
||||
return std::unexpected(EnvError::BadEncoding);
|
||||
}
|
||||
|
||||
// Delete variable and check result.
|
||||
auto rv = ::SetEnvironmentVariableW(wname.value().c_str(), wvalue.value().c_str());
|
||||
if (!rv) {
|
||||
return std::unexpected(EnvError::BadCall);
|
||||
}
|
||||
|
||||
return {};
|
||||
#else
|
||||
// Reference: https://pubs.opengroup.org/onlinepubs/9699919799/functions/setenv.html
|
||||
|
||||
// Set variable
|
||||
auto ordinary_name = REINTERPRET::as_ordinary(name);
|
||||
auto ordinary_value = REINTERPRET::as_ordinary(value);
|
||||
auto rv = setenv(ordinary_name.c_str(), ordinary_value.c_str(), 1);
|
||||
if (rv == 0) return {};
|
||||
|
||||
// Check error type
|
||||
if (errno == EINVAL) return std::unexpected(EnvError::BadName);
|
||||
else if (errno == ENOMEM) return std::unexpected(EnvError::NoMemory);
|
||||
else throw std::runtime_error("impossible errno");
|
||||
#endif
|
||||
}
|
||||
|
||||
EnvResult<void> del_var(const std::u8string_view &name) {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-setenvironmentvariablew
|
||||
|
||||
// Convert to wchar
|
||||
auto wname = ENC::to_wchar(name);
|
||||
if (!wname.has_value()) {
|
||||
return std::unexpected(EnvError::BadEncoding);
|
||||
}
|
||||
|
||||
// Delete variable and check result.
|
||||
auto rv = ::SetEnvironmentVariableW(wname.value().c_str(), NULL);
|
||||
if (!rv) {
|
||||
return std::unexpected(EnvError::BadCall);
|
||||
}
|
||||
|
||||
return {};
|
||||
#else
|
||||
// Reference: https://pubs.opengroup.org/onlinepubs/9699919799/functions/unsetenv.html
|
||||
|
||||
// Delete variable
|
||||
auto ordinary_name = REINTERPRET::as_ordinary(name);
|
||||
auto rv = unsetenv(ordinary_name.c_str());
|
||||
if (rv == 0) return {};
|
||||
|
||||
// Check error type
|
||||
if (errno == EINVAL) return std::unexpected(EnvError::BadName);
|
||||
else throw std::runtime_error("impossible errno");
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace yycc::rust::env
|
62
src/yycc/rust/env.hpp
Normal file
62
src/yycc/rust/env.hpp
Normal file
@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <expected>
|
||||
|
||||
/**
|
||||
* @brief The namespace providing environment variable operations.
|
||||
* @details
|
||||
* When I programming with Rust, I was astonished that
|
||||
* Rust standard library have so much robust environment variable operations.
|
||||
* Oppositly, C++ STL still lake in this even in today.
|
||||
*
|
||||
* The functions manipulating environment variable is different in different OS.
|
||||
* I create this namespace inspired from Rust standard library
|
||||
* to glue all these things up and make a uniform interface.
|
||||
*/
|
||||
namespace yycc::rust::env {
|
||||
|
||||
/// @brief The error occurs in this module.
|
||||
enum class EnvError {
|
||||
NoSuchName, ///< The variable with given name is not presented.
|
||||
BadEncoding, ///< Error when performing encoding convertion.
|
||||
BadArithmetic, ///< Error when performing arithmetic operations.
|
||||
BadCall, ///< Error occurs when calling backend functions.
|
||||
BadName, ///< Given name is ill-formated (empty string or has "=" character).
|
||||
NoMemory, ///< No enough memory to finish this operation.
|
||||
};
|
||||
|
||||
/// @brief The result type in this module.
|
||||
template<typename T>
|
||||
using EnvResult = std::expected<T, EnvError>;
|
||||
|
||||
/**
|
||||
* @brief Get the value of given environment variable name.
|
||||
* @param[in] name The name of environment variable
|
||||
* @return Gotten value, or error occurs.
|
||||
*/
|
||||
EnvResult<std::u8string> get_var(const std::u8string_view& name);
|
||||
|
||||
/**
|
||||
* @brief Set the value of given environment variable name.
|
||||
* @details
|
||||
* If there is no such name variable presented in environment,
|
||||
* a new variable will be created,
|
||||
* otherwise, new value will overwrite old value.
|
||||
* @param[in] name The name of environment variable
|
||||
* @param[in] value The value to be written into.
|
||||
* @return Nothing or error occurs.
|
||||
*/
|
||||
EnvResult<void> set_var(const std::u8string_view& name, const std::u8string_view& value);
|
||||
|
||||
/**
|
||||
* @brief Delete environment variable with given name.
|
||||
* @details
|
||||
* If given variable is not presented in environment,
|
||||
* this function will NOT return error.
|
||||
* @param[in] name The name of environment variable
|
||||
* @return Nothing, or error occurs.
|
||||
*/
|
||||
EnvResult<void> del_var(const std::u8string_view& name);
|
||||
|
||||
} // namespace yycc::rust::env
|
@ -12,6 +12,7 @@ PRIVATE
|
||||
yycc/constraint/builder.cpp
|
||||
yycc/patch/ptr_pad.cpp
|
||||
yycc/patch/fopen.cpp
|
||||
yycc/rust/env.cpp
|
||||
yycc/string/op.cpp
|
||||
yycc/string/reinterpret.cpp
|
||||
yycc/num/parse.cpp
|
||||
|
45
testbench/yycc/rust/env.cpp
Normal file
45
testbench/yycc/rust/env.cpp
Normal file
@ -0,0 +1,45 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/rust/env.hpp>
|
||||
|
||||
#define ENV ::yycc::rust::env
|
||||
|
||||
namespace yycctest::rust::env {
|
||||
|
||||
constexpr char8_t VAR_NAME[] = u8"HOMER";
|
||||
constexpr char8_t VAR_VALUE[] = u8"doh";
|
||||
|
||||
TEST(RustEnv, All) {
|
||||
// Write a new variable should okey
|
||||
{
|
||||
auto rv = ENV::set_var(VAR_NAME, VAR_VALUE);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
}
|
||||
|
||||
// After writing, we can fetch it and check its value.
|
||||
{
|
||||
auto rv = ENV::get_var(VAR_NAME);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), VAR_VALUE);
|
||||
}
|
||||
|
||||
// The we can delete it.
|
||||
{
|
||||
auto rv = ENV::del_var(VAR_NAME);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
}
|
||||
|
||||
// Delete inexisting variable also should be okey
|
||||
{
|
||||
auto rv = ENV::del_var(VAR_NAME);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
}
|
||||
|
||||
// After deleting, we can not fetch it anymore.
|
||||
{
|
||||
auto rv = ENV::get_var(VAR_NAME);
|
||||
ASSERT_FALSE(rv.has_value());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user