We've come to the conclusion that we need to modify the PATH environment
variable, at least when running on MacOSX or on Windows.
Attached is a little program that defines
void setEnvPath(string const & name, vector<string> const & env);
analogous to the
vector<string> const getEnvPath(string const & name);
that I added the other day.
$ g++ -Iboost -o path path.C
Are people happy for this to go into the repository? I think that expanding
the PATH string to a vector and then collapsing back from a vector are the
safest things to do, given the different syntaxes for paths and for
separators on the different OSes.
The alternative is something like:
void prepend_to_path(string const & path_addition);
but I think that getting that right is non-trivial unfortunately.
Thoughts?
--
Angus
#include <boost/assert.hpp>
#include <boost/tokenizer.hpp>
#include <cstdlib>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#define HAVE_PUTENV 1
/* #undef HAVE_SETENV */
using std::string;
using std::vector;
string const trim(string const & a, char const * p)
{
BOOST_ASSERT(p);
if (a.empty() || !*p)
return a;
string::size_type r = a.find_last_not_of(p);
string::size_type l = a.find_first_not_of(p);
// Is this the minimal test? (lgb)
if (r == string::npos && l == string::npos)
return string();
return a.substr(l, r - l + 1);
}
string const subst(string const & a, char oldchar, char newchar)
{
string tmp(a);
string::iterator lit = tmp.begin();
string::iterator end = tmp.end();
for (; lit != end; ++lit)
if ((*lit) == oldchar)
(*lit) = newchar;
return tmp;
}
string const subst(string const & a,
string const & oldstr, string const & newstr)
{
string lstr(a);
string::size_type i = 0;
string::size_type const olen = oldstr.length();
while ((i = lstr.find(oldstr, i)) != string::npos) {
lstr.replace(i, olen, newstr);
i += newstr.length(); // We need to be sure that we dont
// use the same i over and over again.
}
return lstr;
}
namespace os {
string external_path(string const & p)
{
string dos_path;
#if defined(__CYGWIN__) || defined(__CYGWIN32__)
// Translate from cygwin path syntax to dos path syntax
if (is_absolute_path(p)) {
char dp[PATH_MAX+1];
cygwin_conv_to_full_win32_path(p.c_str(), dp);
dos_path = !dp ? string() : dp;
}
else return p;
#else // regular Win32
dos_path = p;
#endif
// No backslashes in LaTeX files
dos_path = subst(dos_path,'\\','/');
return dos_path;
}
string internal_path(string const & p)
{
#if defined(__CYGWIN__) || defined(__CYGWIN32__)
char posix_path[PATH_MAX+1];
posix_path[0] = '\0';
cygwin_conv_to_posix_path(p.c_str(), posix_path);
return posix_path;
#else
return subst(p,"\\","/");
#endif
}
} // namespace os
string const getEnv(string const & envname)
{
// f.ex. what about error checking?
char const * const ch = getenv(envname.c_str());
return !ch ? string() : ch;
}
vector<string> const getEnvPath(string const & name)
{
typedef boost::char_separator<char> Separator;
typedef boost::tokenizer<Separator> Tokenizer;
#if defined (__EMX__) || defined (_WIN32)
Separator const separator(";");
#else
Separator const separator(":");
#endif
string const env_var = getEnv(name);
Tokenizer const tokens(env_var, separator);
Tokenizer::const_iterator it = tokens.begin();
Tokenizer::const_iterator const end = tokens.end();
std::vector<string> vars;
for (; it != end; ++it) {
string const path = os::internal_path(*it);
vars.push_back(trim(path, "\""));
}
return vars;
}
bool PutEnv(string const & envstr)
{
// CHECK Look at and fix this.
// f.ex. what about error checking?
#if HAVE_PUTENV
// this leaks, but what can we do about it?
// Is doing a getenv() and a free() of the older value
// a good idea? (JMarc)
// Actually we don't have to leak...calling putenv like this
// should be enough: ... and this is obviously not enough if putenv
// does not make a copy of the string. It is also not very wise to
// put a string on the free store. If we have to leak we should do it
// like this:
char * leaker = new char[envstr.length() + 1];
envstr.copy(leaker, envstr.length());
leaker[envstr.length()] = '\0';
int const retval = ::putenv(leaker);
// If putenv does not make a copy of the char const * this
// is very dangerous. OTOH if it does take a copy this is the
// best solution.
// The only implementation of putenv that I have seen does not
// allocate memory. _And_ after testing the putenv in glibc it
// seems that we need to make a copy of the string contents.
// I will enable the above.
//int retval = lyx::putenv(envstr.c_str());
#else
#ifdef HAVE_SETENV
string varname;
string const str = envstr.split(varname,'=');
int const retval = ::setenv(varname.c_str(), str.c_str(), true);
#else
// No environment setting function. Can this happen?
int const retval = 1; //return an error condition.
#endif
#endif
return retval == 0;
}
void setEnvPath(string const & name, vector<string> const & env)
{
#if defined (__EMX__) || defined (_WIN32)
char const separator(';');
#else
char const separator(':');
#endif
std::ostringstream ss;
vector<string>::const_iterator it = env.begin();
vector<string>::const_iterator const end = env.end();
for (; it != end; ++it) {
if (ss.tellp() > 0)
ss << separator;
ss << os::external_path(*it);
}
PutEnv(name + "=" + ss.str());
}
void print(std::ostream & os, vector<string> vec)
{
vector<string>::const_iterator it = vec.begin();
vector<string>::const_iterator const end = vec.end();
for (; it != end; ++it)
os << *it << '\n';
}
int main()
{
string const path_str = getEnv("PATH");
std::cout << path_str << "\n\n";
vector<string> path = getEnvPath("PATH");
print(std::cout, path);
std::cout << std::endl;
path.insert(path.begin(), "C:/Angus");
print(std::cout, path);
std::cout << std::endl;
setEnvPath("PATH", path);
string const path_str2 = getEnv("PATH");
std::cout << path_str2 << "\n\n";
return 0;
}