Download Reference Manual
The Developer's Library for D
About Wiki Forums Source Search Contact

Getting environment variables

Moderators: larsivi kris

Posted: 02/11/07 12:29:43

What is the tango way?

Author Message

Posted: 02/11/07 13:00:34

I believe we don't have a "Tango solution" for this as of yet. For now you will have to use getenv in tango.stdc.stdlib.

Posted: 02/11/07 16:34:39 -- Modified: 02/11/07 16:36:01 by
Deewiant

I posted the following code at digitalmars.D.announce in June 2006 (EDIT: well, the version (Tango) bit was added only about a week ago ;-)):

version (Windows) {
	version (Tango) import tango.text.convert.Utf : toUtf8;
	else            import std.utf : toUtf8 = toUTF8;

	pragma (lib, "kernel32");

	extern (Windows) {
		void* GetEnvironmentStringsW();
		bool  FreeEnvironmentStringsW(wchar**);
	}

	char[][char[]] environment() {
		wchar** env = cast(wchar**)GetEnvironmentStringsW();
		scope (exit) FreeEnvironmentStringsW(env);

		char[][char[]] arr;

		wchar[] key   = new wchar[20],
			value = new wchar[40];

		for (wchar* str = cast(wchar*)env; *str; ++str) {
			size_t k = 0, v = 0;

			while (*str != '=') {
				key[k++] = *str++;

				if (k == key.length)
					key.length = 2 * key.length;
			}

			++str;

			while (*str) {
				value[v++] = *str++;

				if (v == value.length)
					value.length = 2 * value.length;
			}

			arr[toUtf8(key[0..k])] = toUtf8(value[0..v]);
		}

		return arr;
	}
} else {
	pragma (msg, "If this is not a Posix-compliant platform, trying to access environment\n"
	             "variables might cause an access violation and thereby a crash."
	);

	extern (C) extern char** environ;

	char[][char[]] environment() {
		char[][char[]] arr;

		char[] key   = new char[20],
		       value = new char[40];

		for (char** p = environ; *p; ++p) {
			size_t k = 0, v = 0;

			char* str = *p;

			while (*str != '=') {
				key[k++] = *str++;

				if (k == key.length)
					key.length = 2 * key.length;
			}

			++str;

			while (*str) {
				value[v++] = *str++;

				if (v == value.length)
					value.length = 2 * value.length;
			}

			arr[key[0..k]] = value[0..v];
		}

		return arr;
	}
}

Like I said in my post, I'm not good enough with pointers to use mostly the same code for both versions, so I wrote two completely separate functions. Somebody more experienced or more patient (I wrote this very quickly with the intent to "just get it to work") might be able to unify them.

Setting environment variables - something that should, IMHO, also be in Tango - I haven't written code for.

Posted: 02/11/07 22:50:16

Nice! May we add this to Tango?

Posted: 02/12/07 11:11:35

Sure, feel free to do whatever you want with it (like maybe replacing the extern declarations with the relevant imports).

Posted: 02/12/07 14:41:35

Thanks for the replies. Two more functions that might be useful. One sets a var, the other gets a var by name without copying the whole env into an AA. Tested with phobos on linux, but should work with tango (no time to install tango there)

import tango.text.convert.Utf;
import tango.sys.Common;

version (Windows)
{
	pragma (lib, "kernel32.lib");
	extern (Windows)
	{
		int SetEnvironmentVariableW(wchar*, wchar*);
		uint GetEnvironmentVariableW(wchar*, wchar*, uint);
		const int ERROR_ENVVAR_NOT_FOUND = 203;
	}       
}
else
	import tango.stdc.posix.stdlib;

// This one might go into tango.sys.Common
class SysException : Exception
{
	int code;

	this(char[] msg, int errorCode = 0)
	{
		super(msg);
		code = errorCode;
	}

	this()
	{
		this(SysError.lastMsg, SysError.lastCode);
	}
        
	this(int errorCode)
	{
		this(SysError.lookup(errorCode), errorCode);
	}
}

// Undefines the variable, if value is null or empty string
void setEnv(char[] variable, char[] value = null)
{
	version (Windows)
	{
		wchar* var, val;
                 
		var = (toUtf16(variable) ~ "\0").ptr;           
                
		if (value.length > 0)
			val = (toUtf16(value) ~ "\0").ptr;
                
		if (!SetEnvironmentVariableW(var, val))
			throw new SysException();
	}
	else
	{
		int result;
                
		if (value.length == 0)
			unsetenv((variable ~ '\0').ptr);
		else
			result = setenv((variable ~ '\0').ptr, (value ~ '\0').ptr, 1);
                
		if (result != 0)
			throw new SysException();
	}
}

// Returns null if the variable does not exist
char[] getEnv(char[] variable)
{
	version	(Windows)
	{
		wchar[]	var	= toUtf16(variable)	~ "\0";

		uint size =	GetEnvironmentVariableW(var.ptr, cast(wchar*)null, 0);
		if (size ==	0)
		{
			if (SysError.lastCode == ERROR_ENVVAR_NOT_FOUND)
			{
				return null;
			}
			else
				throw new SysException();
		}

		wchar[]	buffer = new wchar[size];
		size = GetEnvironmentVariableW(var.ptr,	buffer.ptr,	size);
		if (size ==	0)
			throw new SysException();
		
		return toUtf8(buffer[0..size]);
	}
	else
	{
		char* ptr =	getenv(variable.ptr);
				
		if (ptr	is null)
			return null;

		return ptr[0..strlen(ptr)].dup;
	}
}

unittest
{
	const char[] VAR = "TESTENVVAR";
	const char[] VAL1 =	"VAL1";
	const char[] VAL2 =	"VAL2";

	assert(getEnv(VAR) is null);

	setEnv(VAR,	VAL1);
	assert(getEnv(VAR) == VAL1);
		
	setEnv(VAR,	VAL2);
	assert(getEnv(VAR) == VAL2);

	setEnv(VAR,	null);
	assert(getEnv(VAR) is null);

	setEnv(VAR,	VAL1);
	setEnv(VAR,	"");
		
	assert(getEnv(VAR) is null);
}

Posted: 02/12/07 14:49:14

A correction: the result of the call the unsetenv should be assigned to 'result' like 'result = unsetenv'