Skip to content

Commit

Permalink
Add new functions for manipulating the environment
Browse files Browse the repository at this point in the history
This adds functions for using collections with the environment.  It
allows for conveniently retreiving and setting the environment variables
via a map, rather than individually.

This also adds a clearenv and printenv for completeness.

Signed-off-by: Michael Carroll <mjcarroll@intrinsic.ai>
  • Loading branch information
mjcarroll committed Nov 1, 2023
1 parent 4d1e541 commit acd5fbd
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 2 deletions.
44 changes: 43 additions & 1 deletion include/gz/utils/Environment.hh
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <gz/utils/Export.hh>

#include <string>
#include <unordered_map>

namespace gz
{
Expand Down Expand Up @@ -66,7 +67,48 @@ bool GZ_UTILS_VISIBLE setenv(
/// \return True if the variable was unset or false otherwise.
bool GZ_UTILS_VISIBLE unsetenv(const std::string &_name);

}
/// \brief Unset all environment variables
///
/// Note: This function is not thread-safe and should not be called
/// concurrently with `env` or `setenv`
///
/// \return True if the environment was unset or false otherwise.
bool GZ_UTILS_VISIBLE clearenv();

/// \brief Type alias for a collection of environment variables
using EnvironmentMap = std::unordered_map<std::string, std::string>;

/// \brief Retrieve all current environment variables
///
/// Note: This function is not thread-safe and should not be called
/// concurrently with `setenv` or `unsetenv`
///
/// \return A collection of current environment variables
EnvironmentMap GZ_UTILS_VISIBLE env();

/// \brief Set the environment variable '_name'.
///
/// Note: On Windows setting an empty string (_value=="")
/// is the equivalent of unsetting the variable.
//
/// Note: This function is not thread-safe and should not be called
/// concurrently with `env` or `unsetenv`
///
/// \param[in] _vars Collection of environment variables to set
/// \return True if all variables were set or false otherwise.
bool GZ_UTILS_VISIBLE setenv(const EnvironmentMap &_vars);

/// \brief Print the entire current environment to a string
///
/// This prints each variable in the form KEY=VALUE\n
///
/// Note: This function is not thread-safe and should not be called
/// concurrently with `setenv` or `unsetenv`
///
/// \return A string containing all environment variables
std::string printenv();

} // namespace GZ_UTILS_VERSION_NAMESPACE
} // namespace utils
} // namespace gz

Expand Down
70 changes: 70 additions & 0 deletions src/Environment.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
#include <cstdlib>
#include <string>

extern char ** environ;

namespace gz
{
namespace utils
Expand Down Expand Up @@ -98,6 +100,74 @@ bool unsetenv(const std::string &_name)
#endif
return true;
}

/////////////////////////////////////////////////
bool clearenv()
{
::clearenv();
return true;
/*
bool success = true;
for (const auto &[key, value] : env())
{
success &= unsetenv(key);
}
return success;
*/
}

/////////////////////////////////////////////////
EnvironmentMap env()
{
// Portable method for reading environment variables
// Ref: https://stackoverflow.com/a/71483564/460065
char **currentEnv {nullptr};
#if defined(WIN) && (_MSC_VER >= 1900)
currentEnv = *__p_environ();
#else
currentEnv = environ;
#endif

// In the case that clearenv() was just called
// currentEnv will be nullptr
if (currentEnv == nullptr)
return {};

EnvironmentMap ret;
for (; *currentEnv; ++currentEnv)
{
std::string var(*currentEnv);
auto key = var.substr(0, var.find('='));
var.erase(0, var.find('=') + 1);
ret[key] = var;
}
return ret;
}

/////////////////////////////////////////////////
bool setenv(const EnvironmentMap &_vars)
{
bool success = true;
for (const auto &[key, value] : _vars)
{
success &= setenv(key, value);
}
return success;
}

/////////////////////////////////////////////////
std::string printenv()
{
std::string ret;
for (const auto &[key, value] : env())
{
ret.append(key);
ret.append("=");
ret.append(value);
ret.append("\n");
}
return ret;
}
} // namespace GZ_UTILS_VERSION_NAMESPACE
} // namespace utils
} // namespace gz
43 changes: 42 additions & 1 deletion src/Environment_TEST.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ using namespace gz;
/////////////////////////////////////////////////
TEST(Environment, emptyENV)
{
gz::utils::clearenv();

std::string var;
EXPECT_FALSE(utils::env("!!SHOULD_NOT_EXIST!!", var));
EXPECT_TRUE(var.empty());
Expand All @@ -34,6 +36,8 @@ TEST(Environment, emptyENV)
/////////////////////////////////////////////////
TEST(Environment, envSet)
{
gz::utils::clearenv();

const auto key = "GZ_ENV_SET";
ASSERT_TRUE(utils::setenv(key, "VALUE"));

Expand Down Expand Up @@ -67,6 +71,8 @@ TEST(Environment, envSet)
/////////////////////////////////////////////////
TEST(Environment, envUnset)
{
gz::utils::clearenv();

const auto key = "GZ_ENV_UNSET";
ASSERT_TRUE(utils::unsetenv(key));

Expand Down Expand Up @@ -94,8 +100,10 @@ TEST(Environment, envUnset)
}

/////////////////////////////////////////////////
TEST(Util_TEST, envSetEmpty)
TEST(Environment, envSetEmpty)
{
gz::utils::clearenv();

const auto key = "GZ_ENV_SET_EMPTY";

ASSERT_TRUE(utils::setenv(key, ""));
Expand Down Expand Up @@ -133,3 +141,36 @@ TEST(Util_TEST, envSetEmpty)
}
ASSERT_TRUE(utils::unsetenv(key));
}

/////////////////////////////////////////////////
TEST(Environment, envGetCollection)
{
gz::utils::clearenv();
auto currentEnv = gz::utils::env();
EXPECT_EQ(currentEnv.size(), 0);

ASSERT_TRUE(gz::utils::setenv("GZ_FOO_KEY", "GZ_FOO_VAL"));
ASSERT_TRUE(gz::utils::setenv("GZ_BAR_KEY", "GZ_BAR_VAL"));
ASSERT_TRUE(gz::utils::setenv("GZ_BAZ_KEY", "GZ_BAZ_VAL"));

currentEnv = gz::utils::env();
EXPECT_EQ(currentEnv.size(), 3);

EXPECT_EQ(currentEnv["GZ_FOO_KEY"], "GZ_FOO_VAL");
EXPECT_EQ(currentEnv["GZ_BAR_KEY"], "GZ_BAR_VAL");
EXPECT_EQ(currentEnv["GZ_BAZ_KEY"], "GZ_BAZ_VAL");
}

/////////////////////////////////////////////////
TEST(Environment, printenv)
{
gz::utils::clearenv();
EXPECT_EQ(gz::utils::printenv(), "");

ASSERT_TRUE(gz::utils::setenv("GZ_FOO_KEY", "GZ_FOO_VAL"));
ASSERT_TRUE(gz::utils::setenv("GZ_BAR_KEY", "GZ_BAR_VAL"));
ASSERT_TRUE(gz::utils::setenv("GZ_BAZ_KEY", "GZ_BAZ_VAL"));

EXPECT_EQ(gz::utils::printenv(),
"GZ_BAZ_KEY=GZ_BAZ_VAL\nGZ_BAR_KEY=GZ_BAR_VAL\nGZ_FOO_KEY=GZ_FOO_VAL\n");
}

0 comments on commit acd5fbd

Please sign in to comment.