Skip to content

Commit

Permalink
Add new functions for manipulating the environment (#114)
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 <[email protected]>
  • Loading branch information
mjcarroll authored Nov 7, 2023
1 parent 36172b7 commit 8529918
Show file tree
Hide file tree
Showing 3 changed files with 180 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
/// NOLINTNEXTLINE - This is incorrectly parsed as a global variable
std::string GZ_UTILS_VISIBLE printenv();
} // namespace GZ_UTILS_VERSION_NAMESPACE
} // namespace utils
} // namespace gz

Expand Down
94 changes: 94 additions & 0 deletions src/Environment.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,19 @@

#include <gz/utils/Environment.hh>

#include <algorithm>
#include <cstdlib>
#include <string>
#include <vector>

#ifdef _WIN32
#include <windows.h>
#include <processenv.h>
#endif

#ifndef _WIN32
extern char ** environ;
#endif

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

/////////////////////////////////////////////////
bool clearenv()
{
bool success = true;
#if __linux__
if (0 != ::clearenv())
{
success = false;
}
#else
// Windows and macOS don't have clearenv
// so iterate and clear one-by-one
for (const auto &[key, value] : env())
{
success &= unsetenv(key);
}
#endif
return success;

}

/////////////////////////////////////////////////
EnvironmentMap env()
{
EnvironmentMap ret;

// Helper function to split KEY=VAL
auto split = [](const std::string &_inp)
{
return std::make_pair(
_inp.substr(0, _inp.find('=')),
_inp.substr(_inp.find('=') + 1));
};

char **currentEnv = nullptr;
#ifdef _WIN32
currentEnv = *__p__environ();
#else
currentEnv = environ;
#endif
// In the case that clearenv() was just called
// currentEnv will be nullptr
if (currentEnv == nullptr)
return {};

for (; *currentEnv; ++currentEnv)
{
ret.emplace(split(*currentEnv));
}
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;
// Variables are in an unordered_map as we generally don't
// care, but for printing sort for consistent display
auto currentEnv = env();
auto sorted = std::vector<std::pair<std::string, std::string>>(
currentEnv.begin(), currentEnv.end());
std::sort(sorted.begin(), sorted.end());
for (const auto &[key, value] : sorted)
{
ret.append(key);
ret.append("=");
ret.append(value);
ret.append("\n");
}
return ret;
}
} // namespace GZ_UTILS_VERSION_NAMESPACE
} // namespace utils
} // namespace gz
44 changes: 43 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,37 @@ 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"));

// Always returned in sorted order
EXPECT_EQ(gz::utils::printenv(),
"GZ_BAR_KEY=GZ_BAR_VAL\nGZ_BAZ_KEY=GZ_BAZ_VAL\nGZ_FOO_KEY=GZ_FOO_VAL\n");
}

0 comments on commit 8529918

Please sign in to comment.