Merge branch 'vars' into dev
This commit is contained in:
commit
4c7029ccc0
7 changed files with 367 additions and 27 deletions
|
|
@ -86,10 +86,21 @@ static bool expand_var(char **str, size_t pos, const struct hash_map *vars)
|
|||
size_t r = parse_var_name(*str + pos, &var_name);
|
||||
if (r > 0 && var_name != NULL)
|
||||
{
|
||||
char *value = get_var_or_env(vars, var_name);
|
||||
if (value == NULL)
|
||||
// Undefined variable: expand to empty string
|
||||
value = "";
|
||||
char *value;
|
||||
char rnd_str[10]; // max 5 digits + null terminator
|
||||
if (strcmp(var_name, "RANDOM") == 0)
|
||||
{
|
||||
short rnd = short_random();
|
||||
snprintf(rnd_str, 10, "%d", rnd);
|
||||
value = rnd_str;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = get_var_or_env(vars, var_name);
|
||||
if (value == NULL)
|
||||
// Undefined variable: expand to empty string
|
||||
value = "";
|
||||
}
|
||||
|
||||
char *p = insert_into(*str, value, pos, r);
|
||||
free(var_name);
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ struct hash_map *hash_map_init(size_t size)
|
|||
bool hash_map_insert(struct hash_map *hash_map, const char *key, void *value,
|
||||
bool *updated)
|
||||
{
|
||||
if (hash_map == NULL || hash_map->size == 0 || key == NULL)
|
||||
if (hash_map == NULL || hash_map->size == 0 || key == NULL || value == NULL)
|
||||
return false;
|
||||
|
||||
size_t h = hash(key);
|
||||
|
|
@ -71,6 +71,7 @@ bool hash_map_insert(struct hash_map *hash_map, const char *key, void *value,
|
|||
if (strcmp(iter->key, key) == 0)
|
||||
{
|
||||
// update
|
||||
free(iter->value);
|
||||
iter->value = value;
|
||||
if (updated)
|
||||
*updated = true;
|
||||
|
|
@ -96,16 +97,16 @@ bool hash_map_insert(struct hash_map *hash_map, const char *key, void *value,
|
|||
return true;
|
||||
}
|
||||
|
||||
void hash_map_free(struct hash_map *hash_map)
|
||||
void hash_map_free(struct hash_map **hash_map)
|
||||
{
|
||||
struct pair_list *l;
|
||||
struct pair_list *prev;
|
||||
|
||||
if (hash_map)
|
||||
if (hash_map != NULL && *hash_map != NULL)
|
||||
{
|
||||
for (size_t i = 0; i < hash_map->size; i++)
|
||||
for (size_t i = 0; i < (*hash_map)->size; i++)
|
||||
{
|
||||
l = hash_map->data[i];
|
||||
l = (*hash_map)->data[i];
|
||||
while (l != NULL)
|
||||
{
|
||||
prev = l;
|
||||
|
|
@ -113,8 +114,8 @@ void hash_map_free(struct hash_map *hash_map)
|
|||
destroy_pair_list(&prev);
|
||||
}
|
||||
}
|
||||
free(hash_map->data);
|
||||
free(hash_map);
|
||||
free((*hash_map)->data);
|
||||
free(*hash_map);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,10 +19,23 @@ struct hash_map
|
|||
|
||||
struct hash_map *hash_map_init(size_t size);
|
||||
|
||||
/**
|
||||
* @brief Inserts a key-value pair into the hash map. Key and value are expected
|
||||
* to be on the heap and will be managed by the hash map. It means they are
|
||||
* consumed by this function. If the key already exists, its value is updated
|
||||
* and the key is not consumed so it must be freed by the caller.
|
||||
*
|
||||
* @param hash_map The hash map.
|
||||
* @param key The key to insert.
|
||||
* @param value The value to insert.
|
||||
* @param updated If not NULL, set to true if the key was already present and
|
||||
* updated, false if the key was newly inserted.
|
||||
* @return true on success, false on failure.
|
||||
*/
|
||||
bool hash_map_insert(struct hash_map *hash_map, const char *key, void *value,
|
||||
bool *updated);
|
||||
|
||||
void hash_map_free(struct hash_map *hash_map);
|
||||
void hash_map_free(struct hash_map **hash_map);
|
||||
|
||||
void hash_map_foreach(struct hash_map *hash_map,
|
||||
void (*fn)(const char *, const void *));
|
||||
|
|
|
|||
|
|
@ -2,8 +2,11 @@
|
|||
#include "vars.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../hash_map/hash_map.h"
|
||||
|
||||
|
|
@ -14,6 +17,26 @@ struct hash_map *vars_init(void)
|
|||
return hash_map_init(VARS_INITIAL_SIZE);
|
||||
}
|
||||
|
||||
void vars_default(struct hash_map *vars)
|
||||
{
|
||||
set_var_copy(vars, "?", "0");
|
||||
pid_t pid = getpid();
|
||||
char pid_str[20];
|
||||
snprintf(pid_str, sizeof(pid_str), "%d", pid);
|
||||
set_var_copy(vars, "$", pid_str);
|
||||
}
|
||||
|
||||
short short_random(void)
|
||||
{
|
||||
static bool seeded = false;
|
||||
if (!seeded)
|
||||
{
|
||||
srand((unsigned)time(NULL));
|
||||
seeded = true;
|
||||
}
|
||||
return (short)(rand() & 0x7FFF); // force 16 bits positive
|
||||
}
|
||||
|
||||
char *get_var(const struct hash_map *vars, const char *key)
|
||||
{
|
||||
return (char *)hash_map_get(vars, key);
|
||||
|
|
@ -27,14 +50,21 @@ char *get_var_or_env(const struct hash_map *vars, const char *key)
|
|||
return value;
|
||||
}
|
||||
|
||||
bool set_var(struct hash_map *vars, const char *key, const char *value)
|
||||
bool set_var(struct hash_map *vars, const char *key, const char *value,
|
||||
bool *updated)
|
||||
{
|
||||
if (key == NULL || value == NULL)
|
||||
return false;
|
||||
return hash_map_insert(vars, key, (void *)value, NULL);
|
||||
return hash_map_insert(vars, key, (void *)value, updated);
|
||||
}
|
||||
|
||||
bool set_var_copy(struct hash_map *vars, const char *key, const char *value)
|
||||
{
|
||||
return set_var(vars, strdup(key), strdup(value));
|
||||
char *key_copy = strdup(key);
|
||||
char *value_copy = strdup(value);
|
||||
bool updated;
|
||||
bool res = set_var(vars, key_copy, value_copy, &updated);
|
||||
if (updated || !res)
|
||||
free(key_copy);
|
||||
if (!res)
|
||||
free(value_copy);
|
||||
return res;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,16 @@
|
|||
*/
|
||||
struct hash_map *vars_init(void);
|
||||
|
||||
/**
|
||||
* Set default variables.
|
||||
*/
|
||||
void vars_default(struct hash_map *vars);
|
||||
|
||||
/**
|
||||
* Generate a random short integer (16 bits positive [0-32767]).
|
||||
*/
|
||||
short short_random(void);
|
||||
|
||||
/**
|
||||
* Get the value of a variable, NULL if not found.
|
||||
*/
|
||||
|
|
@ -26,7 +36,8 @@ char *get_var_or_env(const struct hash_map *vars, const char *key);
|
|||
* the hash_map and need to be on the heap. Returns true on success, false on
|
||||
* failure.
|
||||
*/
|
||||
bool set_var(struct hash_map *vars, const char *key, const char *value);
|
||||
bool set_var(struct hash_map *vars, const char *key, const char *value,
|
||||
bool *updated);
|
||||
|
||||
/**
|
||||
* Same as set_var, but makes copies of key and value so you don't have to worry
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <criterion/new/assert.h>
|
||||
#include <criterion/redirect.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../../../src/expansion/expansion.h"
|
||||
#include "../../../src/utils/ast/ast.h"
|
||||
|
|
@ -42,7 +43,7 @@ Test(expand, single_quotes_no_expansion)
|
|||
cr_assert_str_eq((char *)command2->command->data, "echo $VAR",
|
||||
"Variable should not expand inside single quotes");
|
||||
ast_free(&ast);
|
||||
hash_map_free(vars);
|
||||
hash_map_free(&vars);
|
||||
}
|
||||
|
||||
Test(expand, single_dollar)
|
||||
|
|
@ -61,7 +62,7 @@ Test(expand, single_dollar)
|
|||
cr_assert_str_eq((char *)command2->command->data, "echo $ sign",
|
||||
"Variable should not expand inside single quotes");
|
||||
ast_free(&ast);
|
||||
hash_map_free(vars);
|
||||
hash_map_free(&vars);
|
||||
}
|
||||
|
||||
Test(expand, empty_braces_no_expansion)
|
||||
|
|
@ -78,7 +79,7 @@ Test(expand, empty_braces_no_expansion)
|
|||
struct ast_command *command2 = expand(ast_command, vars);
|
||||
cr_assert_null(command2, "Expansion should fail on empty braces");
|
||||
ast_free(&ast);
|
||||
hash_map_free(vars);
|
||||
hash_map_free(&vars);
|
||||
}
|
||||
|
||||
Test(expand, basic_expansion)
|
||||
|
|
@ -97,7 +98,7 @@ Test(expand, basic_expansion)
|
|||
cr_assert_str_eq((char *)command2->command->data, "echo expanded",
|
||||
"Variable should expand correctly");
|
||||
ast_free(&ast);
|
||||
hash_map_free(vars);
|
||||
hash_map_free(&vars);
|
||||
}
|
||||
|
||||
Test(expand, multiple_expansion)
|
||||
|
|
@ -119,7 +120,7 @@ Test(expand, multiple_expansion)
|
|||
"echo expanded values here",
|
||||
"Multiple variables should expand correctly");
|
||||
ast_free(&ast);
|
||||
hash_map_free(vars);
|
||||
hash_map_free(&vars);
|
||||
}
|
||||
|
||||
Test(expand, env_variable)
|
||||
|
|
@ -154,7 +155,7 @@ Test(expand, undefined_variable)
|
|||
cr_assert_str_eq((char *)command2->command->data, "echo ",
|
||||
"Undefined variable should expand to empty string");
|
||||
ast_free(&ast);
|
||||
hash_map_free(vars);
|
||||
hash_map_free(&vars);
|
||||
}
|
||||
|
||||
Test(expand, nested_expansion)
|
||||
|
|
@ -174,7 +175,7 @@ Test(expand, nested_expansion)
|
|||
cr_assert_str_eq((char *)command2->command->data, "echo expanded",
|
||||
"Nested variable should expand correctly");
|
||||
ast_free(&ast);
|
||||
hash_map_free(vars);
|
||||
hash_map_free(&vars);
|
||||
}
|
||||
|
||||
Test(expand, mixed_quotes_expansion)
|
||||
|
|
@ -196,7 +197,7 @@ Test(expand, mixed_quotes_expansion)
|
|||
"Variable in double quotes should expand, while variable "
|
||||
"in single quotes should not");
|
||||
ast_free(&ast);
|
||||
hash_map_free(vars);
|
||||
hash_map_free(&vars);
|
||||
}
|
||||
|
||||
Test(expand, adjacent_variables)
|
||||
|
|
@ -216,5 +217,61 @@ Test(expand, adjacent_variables)
|
|||
cr_assert_str_eq((char *)command2->command->data, "echo helloworld",
|
||||
"Adjacent variables should expand correctly");
|
||||
ast_free(&ast);
|
||||
hash_map_free(vars);
|
||||
hash_map_free(&vars);
|
||||
}
|
||||
|
||||
Test(expand, random)
|
||||
{
|
||||
char str[] = "$RANDOM";
|
||||
char *str_heap = strdup(str);
|
||||
struct list *list = list_append(NULL, str_heap);
|
||||
struct ast *ast = ast_create_command(list);
|
||||
struct ast_command *ast_command = ast_get_command(ast);
|
||||
|
||||
struct ast_command *command2 = expand(ast_command, NULL);
|
||||
cr_assert_not_null(command2, "Expansion returned NULL");
|
||||
int rnd = atoi((char *)command2->command->data);
|
||||
cr_assert(rnd >= 0 && rnd <= 32767,
|
||||
"RANDOM variable should expand to a value between 0 and 32767");
|
||||
ast_free(&ast);
|
||||
}
|
||||
|
||||
Test(expand, pid)
|
||||
{
|
||||
char str[] = "$$";
|
||||
char *str_heap = strdup(str);
|
||||
struct list *list = list_append(NULL, str_heap);
|
||||
struct ast *ast = ast_create_command(list);
|
||||
struct ast_command *ast_command = ast_get_command(ast);
|
||||
|
||||
struct hash_map *vars = vars_init();
|
||||
vars_default(vars);
|
||||
|
||||
struct ast_command *command2 = expand(ast_command, vars);
|
||||
cr_assert_not_null(command2, "Expansion returned NULL");
|
||||
int pid = atoi((char *)command2->command->data);
|
||||
cr_assert(pid == getpid(),
|
||||
"$$ variable should expand to the pid of the process");
|
||||
ast_free(&ast);
|
||||
hash_map_free(&vars);
|
||||
}
|
||||
|
||||
Test(expand, default_last_exit_code)
|
||||
{
|
||||
char str[] = "$?";
|
||||
char *str_heap = strdup(str);
|
||||
struct list *list = list_append(NULL, str_heap);
|
||||
struct ast *ast = ast_create_command(list);
|
||||
struct ast_command *ast_command = ast_get_command(ast);
|
||||
|
||||
struct hash_map *vars = vars_init();
|
||||
vars_default(vars);
|
||||
|
||||
struct ast_command *command2 = expand(ast_command, vars);
|
||||
cr_assert_not_null(command2, "Expansion returned NULL");
|
||||
int code = atoi((char *)command2->command->data);
|
||||
cr_assert(code == 0,
|
||||
"$? variable should expand to the last exit code (default 0)");
|
||||
ast_free(&ast);
|
||||
hash_map_free(&vars);
|
||||
}
|
||||
|
|
|
|||
217
tests/unit/utils/hash_map.c
Normal file
217
tests/unit/utils/hash_map.c
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "../../../src/utils/hash_map/hash_map.h"
|
||||
|
||||
#include <criterion/criterion.h>
|
||||
#include <criterion/new/assert.h>
|
||||
#include <criterion/redirect.h>
|
||||
#include <stdio.h>
|
||||
|
||||
TestSuite(utils_hash_map);
|
||||
|
||||
Test(utils_hash_map, init_free)
|
||||
{
|
||||
struct hash_map *map = hash_map_init(10);
|
||||
cr_expect_not_null(map);
|
||||
cr_expect_eq(map->size, 10);
|
||||
hash_map_free(&map);
|
||||
}
|
||||
|
||||
Test(utils_hash_map, insert_basic)
|
||||
{
|
||||
struct hash_map *map = hash_map_init(10);
|
||||
cr_expect_not_null(map);
|
||||
cr_expect_eq(map->size, 10);
|
||||
|
||||
bool updated = false;
|
||||
bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated);
|
||||
cr_expect_eq(res, true);
|
||||
cr_expect_eq(updated, false);
|
||||
|
||||
hash_map_free(&map);
|
||||
}
|
||||
|
||||
Test(utils_hash_map, insert_multiple)
|
||||
{
|
||||
struct hash_map *map = hash_map_init(10);
|
||||
cr_expect_not_null(map);
|
||||
cr_expect_eq(map->size, 10);
|
||||
|
||||
bool updated = false;
|
||||
bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated);
|
||||
cr_expect_eq(res, true);
|
||||
cr_expect_eq(updated, false);
|
||||
res = hash_map_insert(map, strdup("key2"), strdup("value2"), &updated);
|
||||
cr_expect_eq(res, true);
|
||||
cr_expect_eq(updated, false);
|
||||
|
||||
hash_map_free(&map);
|
||||
}
|
||||
|
||||
Test(utils_hash_map, insert_update)
|
||||
{
|
||||
struct hash_map *map = hash_map_init(10);
|
||||
cr_expect_not_null(map);
|
||||
cr_expect_eq(map->size, 10);
|
||||
|
||||
bool updated = false;
|
||||
bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated);
|
||||
cr_expect_eq(res, true);
|
||||
cr_expect_eq(updated, false);
|
||||
res = hash_map_insert(map, "key1", strdup("value2"), &updated);
|
||||
cr_expect_eq(res, true);
|
||||
cr_expect_eq(updated, true);
|
||||
|
||||
hash_map_free(&map);
|
||||
}
|
||||
|
||||
Test(utils_hash_map, insert_update_multiple)
|
||||
{
|
||||
struct hash_map *map = hash_map_init(10);
|
||||
cr_expect_not_null(map);
|
||||
cr_expect_eq(map->size, 10);
|
||||
|
||||
bool updated = false;
|
||||
bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated);
|
||||
cr_expect_eq(res, true);
|
||||
cr_expect_eq(updated, false);
|
||||
res = hash_map_insert(map, strdup("key2"), strdup("value2"), &updated);
|
||||
cr_expect_eq(res, true);
|
||||
cr_expect_eq(updated, false);
|
||||
res = hash_map_insert(map, "key1", strdup("value2"), &updated);
|
||||
cr_expect_eq(res, true);
|
||||
cr_expect_eq(updated, true);
|
||||
res = hash_map_insert(map, "key1", strdup("value3"), &updated);
|
||||
cr_expect_eq(res, true);
|
||||
cr_expect_eq(updated, true);
|
||||
|
||||
hash_map_free(&map);
|
||||
}
|
||||
|
||||
Test(utils_hash_map, get_basic)
|
||||
{
|
||||
struct hash_map *map = hash_map_init(10);
|
||||
cr_expect_not_null(map);
|
||||
cr_expect_eq(map->size, 10);
|
||||
|
||||
bool updated = false;
|
||||
bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated);
|
||||
cr_expect_eq(res, true);
|
||||
cr_expect_eq(updated, false);
|
||||
|
||||
char *value = (char *)hash_map_get(map, "key1");
|
||||
cr_expect_str_eq(value, "value1");
|
||||
|
||||
hash_map_free(&map);
|
||||
}
|
||||
|
||||
Test(utils_hash_map, get_after_update)
|
||||
{
|
||||
struct hash_map *map = hash_map_init(10);
|
||||
cr_expect_not_null(map);
|
||||
cr_expect_eq(map->size, 10);
|
||||
|
||||
bool updated = false;
|
||||
bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated);
|
||||
cr_expect_eq(res, true);
|
||||
cr_expect_eq(updated, false);
|
||||
|
||||
res = hash_map_insert(map, "key1", strdup("value2"), &updated);
|
||||
cr_expect_eq(res, true);
|
||||
cr_expect_eq(updated, true);
|
||||
|
||||
char *value = (char *)hash_map_get(map, "key1");
|
||||
cr_expect_str_eq(value, "value2");
|
||||
|
||||
hash_map_free(&map);
|
||||
}
|
||||
|
||||
Test(utils_hash_map, get_unknown_key)
|
||||
{
|
||||
struct hash_map *map = hash_map_init(10);
|
||||
cr_expect_not_null(map);
|
||||
cr_expect_eq(map->size, 10);
|
||||
|
||||
char *value = (char *)hash_map_get(map, "unknown_key");
|
||||
cr_expect_null(value);
|
||||
|
||||
hash_map_free(&map);
|
||||
}
|
||||
|
||||
Test(utils_hash_map, delete_key)
|
||||
{
|
||||
struct hash_map *map = hash_map_init(10);
|
||||
cr_expect_not_null(map);
|
||||
cr_expect_eq(map->size, 10);
|
||||
|
||||
bool updated = false;
|
||||
bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated);
|
||||
cr_expect_eq(res, true);
|
||||
cr_expect_eq(updated, false);
|
||||
|
||||
res = hash_map_remove(map, "key1");
|
||||
cr_expect_eq(res, true);
|
||||
|
||||
char *value = (char *)hash_map_get(map, "key1");
|
||||
cr_expect_null(value);
|
||||
|
||||
hash_map_free(&map);
|
||||
}
|
||||
|
||||
Test(utils_hash_map, delete_unknown_key)
|
||||
{
|
||||
struct hash_map *map = hash_map_init(10);
|
||||
cr_expect_not_null(map);
|
||||
cr_expect_eq(map->size, 10);
|
||||
|
||||
bool res = hash_map_remove(map, "unknown_key");
|
||||
cr_expect_eq(res, false);
|
||||
|
||||
hash_map_free(&map);
|
||||
}
|
||||
|
||||
Test(utils_hash_map, free_nonnull_map)
|
||||
{
|
||||
struct hash_map *map = hash_map_init(10);
|
||||
cr_expect_not_null(map);
|
||||
cr_expect_eq(map->size, 10);
|
||||
|
||||
bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), NULL);
|
||||
cr_expect_eq(res, true);
|
||||
res = hash_map_insert(map, strdup("key2"), strdup("value2"), NULL);
|
||||
cr_expect_eq(res, true);
|
||||
|
||||
hash_map_free(&map);
|
||||
}
|
||||
|
||||
Test(utils_hash_map, free_null_map)
|
||||
{
|
||||
hash_map_free(NULL);
|
||||
}
|
||||
|
||||
static size_t count = 0;
|
||||
void foreach_fn(const char *key, const void *value)
|
||||
{
|
||||
printf("Key: %s, Value: %s\n", key, (const char *)value);
|
||||
count++;
|
||||
}
|
||||
|
||||
Test(utils_hash_map, foreach, .init = cr_redirect_stdout)
|
||||
{
|
||||
struct hash_map *map = hash_map_init(10);
|
||||
cr_expect_not_null(map);
|
||||
cr_expect_eq(map->size, 10);
|
||||
|
||||
bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), NULL);
|
||||
cr_expect_eq(res, true);
|
||||
res = hash_map_insert(map, strdup("key2"), strdup("value2"), NULL);
|
||||
cr_expect_eq(res, true);
|
||||
|
||||
count = 0;
|
||||
hash_map_foreach(map, foreach_fn);
|
||||
fflush(stdout);
|
||||
cr_expect_eq(count, 2);
|
||||
cr_expect_stdout_eq_str(
|
||||
"Key: key2, Value: value2\nKey: key1, Value: value1\n");
|
||||
|
||||
hash_map_free(&map);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue