diff --git a/CHANGELOG.md b/CHANGELOG.md
index d1c430d..6b1bb73 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,15 +14,13 @@ The scope of what is covered by the version number excludes:
- create a release branch
- update the changelog below
-- update version and copyright-years in `./LICENSE.md` and `./src/[module-name]/init.lua` (in doc-comments
- header, and in module constants)
+- update version and copyright-years in `./LICENSE.md` and `./src/time.c` (in module constants)
- create a new rockspec and update the version inside the new rockspec:
- `cp [module-name]-scm-1.rockspec ./rockspecs/[module-name]-X.Y.Z-1.rockspec`
-- test: run `make test` and `make lint`
-- clean and render the docs: run `make clean` and `make docs`
-- commit the changes as `release X.Y.Z`
+ `cp luasystem-scm-0.rockspec ./rockspecs/luasystem-X.Y.Z-1.rockspec`
+- clean and render the docs: run `ldoc .`
+- commit the changes as `Release vX.Y.Z`
- push the commit, and create a release PR
-- after merging tag the release commit with `X.Y.Z`
+- after merging tag the release commit with `vX.Y.Z`
- upload to LuaRocks:
`luarocks upload ./rockspecs/[module-name]-X.Y.Z-1.rockspec --api-key=ABCDEFGH`
- test the newly created rock:
@@ -32,9 +30,20 @@ The scope of what is covered by the version number excludes:
### Version X.Y.Z, unreleased
-- a fix
-- a change
+- Change: `gettime` is deprecated. Use `unixtime` instead.
+- Feat: `unixtime` returns the time since the Unix epoch.
+- Feat: `windowstime` returns the time since the Windows epoch.
+- Feat: `time` returns the system time.
+- Feat: `setenv` added to set environment variables.
+- Feat: `getenvs` added to list environment variables.
+- Feat: `random` added to return high-quality random bytes
-### Version 0.1.0, released 01-Jan-2022
+### Version 0.2.1, released 02-Oct-2016
+
+### Version 0.2.0, released 08-May-2016
+
+### Version 0.1.1, released 10-Apr-2016
+
+### Version 0.1.0, released 11-Feb-2016
- initial release
diff --git a/luasystem-scm-0.rockspec b/luasystem-scm-0.rockspec
index 74e301d..be206c5 100644
--- a/luasystem-scm-0.rockspec
+++ b/luasystem-scm-0.rockspec
@@ -11,7 +11,7 @@ version = package_version.."-"..rockspec_revision
source = {
url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git",
branch = (package_version == "scm") and "master" or nil,
- tag = (package_version ~= "scm") and package_version or nil,
+ tag = (package_version ~= "scm") and "v"..package_version or nil,
}
description = {
@@ -45,7 +45,7 @@ local function make_platform(plat)
return {
modules = {
['system.core'] = {
- sources = { 'src/core.c', 'src/compat.c', 'src/time.c', },
+ sources = { 'src/core.c', 'src/compat.c', 'src/time.c', 'src/environment.c', 'src/random.c' },
defines = defines[plat],
libraries = libraries[plat],
},
diff --git a/rockspecs/luasystem-0.2.1-1.rockspec b/rockspecs/luasystem-0.2.1-1.rockspec
index 7d8b9b0..e14118d 100644
--- a/rockspecs/luasystem-0.2.1-1.rockspec
+++ b/rockspecs/luasystem-0.2.1-1.rockspec
@@ -11,7 +11,7 @@ version = package_version.."-"..rockspec_revision
source = {
url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git",
branch = (package_version == "scm") and "master" or nil,
- tag = (package_version ~= "scm") and package_version or nil,
+ tag = (package_version ~= "scm") and "v"..package_version or nil,
}
description = {
diff --git a/spec/01-time_spec.lua b/spec/01-time_spec.lua
new file mode 100644
index 0000000..49fdd18
--- /dev/null
+++ b/spec/01-time_spec.lua
@@ -0,0 +1,77 @@
+local system = require 'system.core'
+
+describe('Test time functions', function()
+
+ -- returns the new second, on the new second
+ local function wait_for_second_rollover()
+ local start_time = math.floor(os.time())
+ local end_time = math.floor(os.time())
+ while end_time == start_time do
+ end_time = math.floor(os.time())
+ end
+ return end_time
+ end
+
+
+ describe("time()", function()
+
+ it('returns current time', function()
+ wait_for_second_rollover()
+ local lua_time = wait_for_second_rollover()
+ local aaa_time = system.time()
+ assert.is.near(lua_time, aaa_time, 0.01)
+
+ wait_for_second_rollover()
+ assert.is.near(1, system.time() - aaa_time, 0.01)
+ end)
+
+ end)
+
+
+
+ describe("monotime()", function()
+
+ it('returns monotonically increasing time', function()
+ local starttime = system.monotime()
+ local endtime = system.monotime()
+ local delta = endtime - starttime
+ assert.is_true(starttime > 0)
+ assert.is_true(delta >= 0)
+ assert.is_true(system.monotime() - endtime >= 0)
+ end)
+
+ end)
+
+
+
+ describe("sleep()", function()
+
+ it("should sleep for the specified time", function()
+ local start_time = wait_for_second_rollover()
+ system.sleep(1)
+ local end_time = os.time()
+ local elapsed_time = end_time - start_time
+ assert.is.near(elapsed_time, 1, 0.01)
+ end)
+
+
+ it("should sleep for the specified time; fractional", function()
+ local start_time = system.time()
+ system.sleep(0.5)
+ local end_time = system.time()
+ local elapsed_time = end_time - start_time
+ assert.is.near(0.5, elapsed_time, 0.01)
+ end)
+
+
+ it("should return immediately for a non-positive sleep time", function()
+ local start_time = wait_for_second_rollover()
+ system.sleep(-1)
+ local end_time = os.time()
+ local elapsed_time = end_time - start_time
+ assert.is.near(elapsed_time, 0, 0.001)
+ end)
+
+ end)
+
+end)
diff --git a/spec/02-random_spec.lua b/spec/02-random_spec.lua
new file mode 100644
index 0000000..ca6645d
--- /dev/null
+++ b/spec/02-random_spec.lua
@@ -0,0 +1,47 @@
+local system = require("system")
+
+describe("Random:", function()
+
+ describe("random()", function()
+
+ it("should return random bytes for a valid number of bytes", function()
+ local num_bytes = system.MAX_RANDOM_BUFFER_SIZE
+ local result, err_msg = system.random(num_bytes)
+ assert.is_nil(err_msg)
+ assert.is.string(result)
+ assert.is_equal(num_bytes, #result)
+ end)
+
+
+ it("should return an error message for an invalid number of bytes", function()
+ local num_bytes = 0
+ local result, err_msg = system.random(num_bytes)
+ assert.is.falsy(result)
+ assert.are.equal("invalid number of bytes, must be between 1 and 1024", err_msg)
+ end)
+
+
+ it("should return an error message for exceeding the maximum buffer size", function()
+ local num_bytes = system.MAX_RANDOM_BUFFER_SIZE + 1
+ local result, err_msg = system.random(num_bytes)
+ assert.is.falsy(result)
+ assert.are.equal("invalid number of bytes, must be between 1 and 1024", err_msg)
+ end)
+
+
+ it("should not return duplicate results", function()
+ local num_bytes = 10
+ local result1, err_msg = system.random(num_bytes)
+ assert.is_nil(err_msg)
+ assert.is.string(result1)
+
+ local result2, err_msg = system.random(num_bytes)
+ assert.is_nil(err_msg)
+ assert.is.string(result2)
+
+ assert.is_not.equal(result1, result2)
+ end)
+
+ end)
+
+end)
diff --git a/spec/03-environment_spec.lua b/spec/03-environment_spec.lua
new file mode 100644
index 0000000..dc7fe2a
--- /dev/null
+++ b/spec/03-environment_spec.lua
@@ -0,0 +1,76 @@
+-- Import the library that contains the environment-related functions
+local system = require("system")
+
+describe("Environment Variables:", function()
+
+ describe("setenv()", function()
+
+ it("should set an environment variable", function()
+ assert.is_true(system.setenv("TEST_VAR", "test_value"))
+ assert.is_equal("test_value", os.getenv("TEST_VAR"))
+ end)
+
+
+ it("should set an empty environment variable value", function()
+ assert.is_true(system.setenv("TEST_VAR", ""))
+ assert.is_equal("", os.getenv("TEST_VAR"))
+ end)
+
+
+ it("should set an empty environment variable", function()
+ assert.is_true(system.setenv("TEST_VAR", ""))
+ assert.is_equal("", os.getenv("TEST_VAR"))
+ end)
+
+
+ it("should error on input bad type", function()
+ assert.has_error(function()
+ system.setenv("TEST_VAR", {})
+ end)
+ assert.has_error(function()
+ system.setenv({}, "test_value")
+ end)
+ end)
+
+
+ it("should return success on deleting a variable that doesn't exist", function()
+ if os.getenv("TEST_VAR") ~= nil then
+ -- clear if it was already set
+ assert.is_true(system.setenv("TEST_VAR", nil))
+ end
+
+ assert.is_true(system.setenv("TEST_VAR", nil)) -- clear again shouldn't fail
+ end)
+
+ end)
+
+
+
+ describe("getenvs()", function()
+
+ it("should list environment variables", function()
+ assert.is_true(system.setenv("TEST_VAR1", nil))
+ assert.is_true(system.setenv("TEST_VAR2", nil))
+ assert.is_true(system.setenv("TEST_VAR3", nil))
+ local envVars1 = system.getenvs()
+ assert.is_true(system.setenv("TEST_VAR1", "test_value1"))
+ assert.is_true(system.setenv("TEST_VAR2", "test_value2"))
+ assert.is_true(system.setenv("TEST_VAR3", "test_value3"))
+ local envVars2 = system.getenvs()
+ assert.is_true(system.setenv("TEST_VAR1", nil))
+ assert.is_true(system.setenv("TEST_VAR2", nil))
+ assert.is_true(system.setenv("TEST_VAR3", nil))
+
+ for k,v in pairs(envVars1) do
+ envVars2[k] = nil
+ end
+ assert.are.same({
+ TEST_VAR1 = "test_value1",
+ TEST_VAR2 = "test_value2",
+ TEST_VAR3 = "test_value3",
+ }, envVars2)
+ end)
+
+ end)
+
+end)
diff --git a/spec/time_spec.lua b/spec/time_spec.lua
deleted file mode 100644
index a017cfe..0000000
--- a/spec/time_spec.lua
+++ /dev/null
@@ -1,31 +0,0 @@
-local system = require 'system.core'
-
-describe('Test time functions', function()
- it('gettime returns current time', function()
- local starttime = system.gettime()
- local expected = os.time()
- local endtime = system.gettime()
- local delta = endtime - starttime
- local avg = starttime + delta/2
- assert.is_true(expected >= math.floor(starttime))
- assert.is_true(expected <= math.ceil(endtime))
- assert.is_near(expected, avg, 1 + delta)
- end)
-
- it('monottime returns monotonically increasing time', function()
- local starttime = system.monotime()
- local endtime = system.monotime()
- local delta = endtime - starttime
- assert.is_true(starttime > 0)
- assert.is_true(delta >= 0)
- assert.is_true(system.monotime() - endtime >= 0)
- end)
-
- it('sleep will wait for specified amount of time', function()
- local starttime = system.gettime()
- local starttick = system.monotime()
- system.sleep(0.5)
- assert.is_near(0.5, system.gettime() - starttime, 0.15)
- assert.is_near(0.5, system.monotime() - starttick, 0.15)
- end)
-end)
diff --git a/src/core.c b/src/core.c
index 6778402..c3a3305 100644
--- a/src/core.c
+++ b/src/core.c
@@ -13,6 +13,8 @@
#endif
void time_open(lua_State *L);
+void environment_open(lua_State *L);
+void random_open(lua_State *L);
/*-------------------------------------------------------------------------
* Initializes all library modules.
@@ -23,5 +25,7 @@ LUAEXPORT int luaopen_system_core(lua_State *L) {
lua_pushstring(L, LUASYSTEM_VERSION);
lua_rawset(L, -3);
time_open(L);
+ random_open(L);
+ environment_open(L);
return 1;
}
diff --git a/src/environment.c b/src/environment.c
new file mode 100644
index 0000000..8a39c34
--- /dev/null
+++ b/src/environment.c
@@ -0,0 +1,124 @@
+/// @submodule system
+#include
+#include
+#include "compat.h"
+#include
+#include
+
+
+/***
+Returns a table with all environment variables.
+@function getenvs
+@treturn table table with all environment variables and their values
+*/
+static int lua_list_environment_variables(lua_State* L) {
+ lua_newtable(L);
+
+#ifdef _WIN32
+ char* envStrings = GetEnvironmentStrings();
+ char* envString = envStrings;
+
+ if (envStrings == NULL) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ while (*envString != '\0') {
+ const char* envVar = envString;
+
+ // Split the environment variable into key and value
+ char* equals = strchr(envVar, '=');
+ if (equals != NULL) {
+ lua_pushlstring(L, envVar, equals - envVar); // Push the key
+ lua_pushstring(L, equals + 1); // Push the value
+ lua_settable(L, -3); // Set the key-value pair in the table
+ }
+
+ envString += strlen(envString) + 1;
+ }
+
+ FreeEnvironmentStrings(envStrings);
+#else
+ extern char** environ;
+
+ if (environ != NULL) {
+ for (char** envVar = environ; *envVar != NULL; envVar++) {
+ const char* envVarStr = *envVar;
+
+ // Split the environment variable into key and value
+ char* equals = strchr(envVarStr, '=');
+ if (equals != NULL) {
+ lua_pushlstring(L, envVarStr, equals - envVarStr); // Push the key
+ lua_pushstring(L, equals + 1); // Push the value
+ lua_settable(L, -3); // Set the key-value pair in the table
+ }
+ }
+ }
+#endif
+
+ return 1;
+}
+
+
+/***
+Sets an environment variable.
+@function setenv
+@tparam string name name of the environment variable
+@tparam[opt] string value value of the environment variable, if nil the variable will be deleted
+@treturn boolean success
+*/
+static int lua_set_environment_variable(lua_State* L) {
+ const char* variableName = luaL_checkstring(L, 1);
+ const char* variableValue = luaL_optstring(L, 2, NULL);
+
+#ifdef _WIN32
+ if (variableValue == NULL) {
+ // If the value is nil, delete the environment variable
+ if (SetEnvironmentVariable(variableName, NULL)) {
+ lua_pushboolean(L, 1);
+ } else {
+ lua_pushboolean(L, 0);
+ }
+ } else {
+ // Set the environment variable with the provided value
+ if (SetEnvironmentVariable(variableName, variableValue)) {
+ lua_pushboolean(L, 1);
+ } else {
+ lua_pushboolean(L, 0);
+ }
+ }
+#else
+ if (variableValue == NULL) {
+ // If the value is nil, delete the environment variable
+ if (unsetenv(variableName) == 0) {
+ lua_pushboolean(L, 1);
+ } else {
+ lua_pushboolean(L, 0);
+ }
+ } else {
+ // Set the environment variable with the provided value
+ if (setenv(variableName, variableValue, 1) == 0) {
+ lua_pushboolean(L, 1);
+ } else {
+ lua_pushboolean(L, 0);
+ }
+ }
+#endif
+
+ return 1;
+}
+
+
+
+static luaL_Reg func[] = {
+ { "setenv", lua_set_environment_variable },
+ { "getenvs", lua_list_environment_variables },
+ { NULL, NULL }
+};
+
+/*-------------------------------------------------------------------------
+ * Initializes module
+ *-------------------------------------------------------------------------*/
+void environment_open(lua_State *L) {
+ luaL_setfuncs(L, func, 0);
+}
diff --git a/src/random.c b/src/random.c
new file mode 100644
index 0000000..d3e1816
--- /dev/null
+++ b/src/random.c
@@ -0,0 +1,94 @@
+/// @submodule system
+#include
+#include
+#include "compat.h"
+#include
+// #include
+// #include
+// #include
+#include
+
+
+// Maximum buffer size for random bytes
+#define MAX_RANDOM_BUFFER_SIZE 1024
+
+
+/***
+Generate random bytes.
+This uses `getrandom()` on Linux, `CryptGenRandom()` on Windows, and `/dev/urandom` on macOS.
+@function random
+@tparam[opt=1] int length number of bytes to get, must be less than or equal to `MAX_RANDOM_BUFFER_SIZE` (1024)
+@treturn[1] string string of random bytes
+@treturn[2] nil
+@treturn[2] string error message
+*/
+static int lua_get_random_bytes(lua_State* L) {
+ int num_bytes = luaL_optinteger(L, 1, 1); // Number of bytes, default to 1 if not provided
+
+ if (num_bytes <= 0 || num_bytes > MAX_RANDOM_BUFFER_SIZE) {
+ lua_pushnil(L);
+ lua_pushfstring(L, "invalid number of bytes, must be between 1 and %d", MAX_RANDOM_BUFFER_SIZE);
+ return 2;
+ }
+
+ unsigned char buffer[MAX_RANDOM_BUFFER_SIZE];
+ ssize_t n;
+
+#ifdef _WIN32
+ if (CryptGenRandom(NULL, num_bytes, buffer) == 0) {
+ DWORD error = GetLastError();
+ lua_pushnil(L);
+ lua_pushfstring(L, "failed to get random data: %lu", error);
+ return 2;
+ }
+#else
+#ifndef __APPLE__
+ // Neither Apple nor Windows
+ n = getrandom(buffer, num_bytes, 0);
+
+ if (n < 0) {
+ lua_pushnil(L);
+ lua_pushstring(L, "failed to get random data");
+ return 2;
+ }
+#else
+ // macOS uses /dev/urandom for non-blocking
+ int fd = open("/dev/urandom", O_RDONLY);
+ if (fd < 0) {
+ lua_pushnil(L);
+ lua_pushstring(L, "failed opening /dev/urandom");
+ return 2;
+ }
+ n = read(fd, buffer, num_bytes);
+ close(fd);
+ if (n < 0) {
+ lua_pushnil(L);
+ lua_pushstring(L, "failed reading /dev/urandom");
+ return 2;
+}
+
+#endif
+
+#endif
+
+ lua_pushlstring(L, (const char*)buffer, num_bytes);
+ return 1;
+}
+
+
+
+static luaL_Reg func[] = {
+ { "random", lua_get_random_bytes },
+ { NULL, NULL }
+};
+
+
+
+/*-------------------------------------------------------------------------
+ * Initializes module
+ *-------------------------------------------------------------------------*/
+void random_open(lua_State *L) {
+ luaL_setfuncs(L, func, 0);
+ lua_pushinteger(L, MAX_RANDOM_BUFFER_SIZE);
+ lua_setfield(L, -2, "MAX_RANDOM_BUFFER_SIZE");
+}
diff --git a/src/time.c b/src/time.c
index bdceb1c..1cd81be 100644
--- a/src/time.c
+++ b/src/time.c
@@ -136,7 +136,7 @@ static int time_lua_monotime(lua_State *L)
Sleep without a busy loop.
This function will sleep, without doing a busy-loop and wasting CPU cycles.
@function sleep
-@tparam number seconds to sleep (fractional).
+@tparam number seconds seconds to sleep (fractional).
@return nothing
*/
#ifdef _WIN32