// retoor <retoor@molodetz.nl>
#include "tool.h"
#include "r_config.h"
#include "r_diff.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <glob.h>
#include <errno.h>
#include <time.h>
#include <limits.h>
static char *expand_home_directory(const char *path);
static const char *get_file_type(struct stat *st);
static void format_time(time_t raw_time, char *buffer, size_t size);
static struct json_object *read_file_get_description(void);
static char *read_file_execute(tool_t *self, struct json_object *args);
static void read_file_print_action(const char *name, struct json_object *args);
static struct json_object *write_file_get_description(void);
static char *write_file_execute(tool_t *self, struct json_object *args);
static void write_file_print_action(const char *name, struct json_object *args);
static struct json_object *directory_glob_get_description(void);
static char *directory_glob_execute(tool_t *self, struct json_object *args);
static void directory_glob_print_action(const char *name, struct json_object *args);
static struct json_object *mkdir_get_description(void);
static char *mkdir_execute(tool_t *self, struct json_object *args);
static void mkdir_print_action(const char *name, struct json_object *args);
static struct json_object *chdir_get_description(void);
static char *chdir_execute(tool_t *self, struct json_object *args);
static void chdir_print_action(const char *name, struct json_object *args);
static struct json_object *getpwd_get_description(void);
static char *getpwd_execute(tool_t *self, struct json_object *args);
static void getpwd_print_action(const char *name, struct json_object *args);
static const tool_vtable_t read_file_vtable = {
.get_description = read_file_get_description,
.execute = read_file_execute,
.print_action = read_file_print_action
};
static const tool_vtable_t write_file_vtable = {
.get_description = write_file_get_description,
.execute = write_file_execute,
.print_action = write_file_print_action
};
static const tool_vtable_t directory_glob_vtable = {
.get_description = directory_glob_get_description,
.execute = directory_glob_execute,
.print_action = directory_glob_print_action
};
static const tool_vtable_t mkdir_vtable = {
.get_description = mkdir_get_description,
.execute = mkdir_execute,
.print_action = mkdir_print_action
};
static const tool_vtable_t chdir_vtable = {
.get_description = chdir_get_description,
.execute = chdir_execute,
.print_action = chdir_print_action
};
static const tool_vtable_t getpwd_vtable = {
.get_description = getpwd_get_description,
.execute = getpwd_execute,
.print_action = getpwd_print_action
};
static tool_t read_file_tool = { .vtable = &read_file_vtable, .name = "read_file" };
static tool_t write_file_tool = { .vtable = &write_file_vtable, .name = "write_file" };
static tool_t directory_glob_tool = { .vtable = &directory_glob_vtable, .name = "directory_glob" };
static tool_t mkdir_tool = { .vtable = &mkdir_vtable, .name = "mkdir" };
static tool_t chdir_tool = { .vtable = &chdir_vtable, .name = "chdir" };
static tool_t getpwd_tool = { .vtable = &getpwd_vtable, .name = "getpwd" };
tool_t *tool_read_file_create(void) { return &read_file_tool; }
tool_t *tool_write_file_create(void) { return &write_file_tool; }
tool_t *tool_directory_glob_create(void) { return &directory_glob_tool; }
tool_t *tool_mkdir_create(void) { return &mkdir_tool; }
tool_t *tool_chdir_create(void) { return &chdir_tool; }
tool_t *tool_getpwd_create(void) { return &getpwd_tool; }
static char *expand_home_directory(const char *path) {
if (!path) return NULL;
if (path[0] != '~') return strdup(path);
const char *home_dir = getenv("HOME");
if (!home_dir) home_dir = getenv("USERPROFILE");
if (!home_dir) return strdup(path);
size_t home_len = strlen(home_dir);
size_t path_len = strlen(path);
char *expanded = malloc(home_len + path_len);
if (!expanded) return NULL;
strcpy(expanded, home_dir);
strcat(expanded, path + 1);
return expanded;
}
static const char *get_file_type(struct stat *st) {
if (S_ISREG(st->st_mode)) return "file";
if (S_ISDIR(st->st_mode)) return "directory";
return "other";
}
static void format_time(time_t raw_time, char *buffer, size_t size) {
struct tm *time_info = localtime(&raw_time);
strftime(buffer, size, "%Y-%m-%d %H:%M:%S", time_info);
}
static char *make_dir_recursive(const char *path) {
char temp[4096];
char *p = NULL;
size_t len = strlen(path);
if (len >= sizeof(temp)) return strdup("Path too long!");
memcpy(temp, path, len + 1);
if (temp[len - 1] == '/') temp[len - 1] = '\0';
for (p = temp + 1; *p; p++) {
if (*p == '/') {
*p = '\0';
if (mkdir(temp, 0777) != 0 && errno != EEXIST) {
return strdup("Failed to create directory!");
}
*p = '/';
}
}
if (mkdir(temp, 0777) != 0 && errno != EEXIST) {
return strdup("Failed to create directory!");
}
return strdup("Directory successfully created.");
}
static void ensure_parent_directory_exists(const char *path_including_file_name) {
if (!path_including_file_name) return;
char *path = strdup(path_including_file_name);
if (!path) return;
char *last_slash = strrchr(path, '/');
if (last_slash) {
*last_slash = '\0';
char *result = make_dir_recursive(path);
free(result);
}
free(path);
}
static void read_file_print_action(const char *name, struct json_object *args) {
(void)name;
if (!args) return;
struct json_object *path;
if (json_object_object_get_ex(args, "path", &path)) {
fprintf(stderr, " -> Reading file: %s\n", json_object_get_string(path));
}
}
static char *read_file_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *path_obj;
if (!json_object_object_get_ex(args, "path", &path_obj)) {
return strdup("Error: missing 'path' argument");
}
char *expanded_path = expand_home_directory(json_object_get_string(path_obj));
if (!expanded_path) return strdup("Failed to expand path!");
FILE *fp = fopen(expanded_path, "r");
free(expanded_path);
if (!fp) return strdup("Failed to open file for reading!");
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
if (size < 0) {
fclose(fp);
return strdup("Failed to determine file size!");
}
rewind(fp);
char *content = malloc((size_t)size + 1);
if (!content) {
fclose(fp);
return strdup("Memory allocation failed!");
}
size_t read_size = fread(content, 1, (size_t)size, fp);
content[read_size] = '\0';
fclose(fp);
return content;
}
static struct json_object *read_file_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *function = json_object_new_object();
json_object_object_add(function, "name", json_object_new_string("read_file"));
json_object_object_add(function, "description",
json_object_new_string("Reads / opens / loads a file and returns its contents as a string."));
struct json_object *parameters = json_object_new_object();
json_object_object_add(parameters, "type", json_object_new_string("object"));
struct json_object *properties = json_object_new_object();
struct json_object *path = json_object_new_object();
json_object_object_add(path, "type", json_object_new_string("string"));
json_object_object_add(path, "description",
json_object_new_string("Path to the file to read / open / load."));
json_object_object_add(properties, "path", path);
json_object_object_add(parameters, "properties", properties);
struct json_object *required = json_object_new_array();
json_object_array_add(required, json_object_new_string("path"));
json_object_object_add(parameters, "required", required);
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(function, "parameters", parameters);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) {
json_object_object_add(function, "strict", json_object_new_boolean(1));
}
json_object_object_add(root, "function", function);
return root;
}
static void write_file_print_action(const char *name, struct json_object *args) {
(void)name;
if (!args) return;
struct json_object *path;
if (json_object_object_get_ex(args, "path", &path)) {
fprintf(stderr, " -> Writing file: %s\n", json_object_get_string(path));
}
}
static char *write_file_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *path_obj, *content_obj;
if (!json_object_object_get_ex(args, "path", &path_obj)) {
return strdup("Error: missing 'path' argument");
}
if (!json_object_object_get_ex(args, "content", &content_obj)) {
return strdup("Error: missing 'content' argument");
}
const char *path = json_object_get_string(path_obj);
const char *new_content = json_object_get_string(content_obj);
// Read old content for diff
char *old_content = NULL;
FILE *old_fp = fopen(path, "r");
if (old_fp) {
fseek(old_fp, 0, SEEK_END);
long size = ftell(old_fp);
rewind(old_fp);
if (size >= 0) {
old_content = malloc((size_t)size + 1);
if (old_content) {
size_t rs = fread(old_content, 1, (size_t)size, old_fp);
old_content[rs] = '\0';
}
}
fclose(old_fp);
}
if (old_content) {
r_diff_print(path, old_content, new_content);
free(old_content);
}
ensure_parent_directory_exists(path);
FILE *fp = fopen(path, "w+");
if (!fp) return strdup("Failed to open file for writing!");
fwrite(new_content, 1, strlen(new_content), fp);
fclose(fp);
return strdup("File successfully written.");
}
static struct json_object *write_file_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *function = json_object_new_object();
json_object_object_add(function, "name", json_object_new_string("write_file"));
json_object_object_add(function, "description",
json_object_new_string("Writes / saves / stores content to a file. Content should be in plain format, not json encoded."));
struct json_object *parameters = json_object_new_object();
json_object_object_add(parameters, "type", json_object_new_string("object"));
struct json_object *properties = json_object_new_object();
struct json_object *path = json_object_new_object();
json_object_object_add(path, "type", json_object_new_string("string"));
json_object_object_add(path, "description",
json_object_new_string("Path to the plain file to write / save / store."));
json_object_object_add(properties, "path", path);
struct json_object *content = json_object_new_object();
json_object_object_add(content, "type", json_object_new_string("string"));
json_object_object_add(content, "description",
json_object_new_string("Plain content to write / save / store into the file."));
json_object_object_add(properties, "content", content);
json_object_object_add(parameters, "properties", properties);
struct json_object *required = json_object_new_array();
json_object_array_add(required, json_object_new_string("path"));
json_object_array_add(required, json_object_new_string("content"));
json_object_object_add(parameters, "required", required);
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(function, "parameters", parameters);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) {
json_object_object_add(function, "strict", json_object_new_boolean(1));
}
json_object_object_add(root, "function", function);
return root;
}
static void directory_glob_print_action(const char *name, struct json_object *args) {
(void)name;
if (!args) return;
struct json_object *path;
if (json_object_object_get_ex(args, "path", &path)) {
fprintf(stderr, " -> Listing: %s\n", json_object_get_string(path));
}
}
static char *directory_glob_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *path_obj;
if (!json_object_object_get_ex(args, "path", &path_obj)) {
return strdup("Error: missing 'path' argument");
}
char target_dir[PATH_MAX];
strncpy(target_dir, json_object_get_string(path_obj), PATH_MAX - 1);
target_dir[PATH_MAX - 1] = '\0';
if (strcmp(target_dir, ".") == 0) {
target_dir[0] = '*';
target_dir[1] = '\0';
}
glob_t results;
struct stat file_stat;
char mod_time[20], create_time[20];
if (glob(target_dir, GLOB_TILDE, NULL, &results) != 0) {
return strdup("[]");
}
json_object *json_array = json_object_new_array();
for (size_t i = 0; i < results.gl_pathc; i++) {
const char *file_path = results.gl_pathv[i];
if (stat(file_path, &file_stat) == -1) continue;
format_time(file_stat.st_mtime, mod_time, sizeof(mod_time));
format_time(file_stat.st_ctime, create_time, sizeof(create_time));
json_object *json_entry = json_object_new_object();
json_object_object_add(json_entry, "name", json_object_new_string(file_path));
json_object_object_add(json_entry, "modification_date", json_object_new_string(mod_time));
json_object_object_add(json_entry, "creation_date", json_object_new_string(create_time));
json_object_object_add(json_entry, "type", json_object_new_string(get_file_type(&file_stat)));
json_object_object_add(json_entry, "size_bytes", json_object_new_int64(file_stat.st_size));
json_object_array_add(json_array, json_entry);
}
globfree(&results);
char *result = strdup(json_object_to_json_string_ext(json_array, JSON_C_TO_STRING_PRETTY));
json_object_put(json_array);
return result;
}
static struct json_object *directory_glob_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *function = json_object_new_object();
json_object_object_add(function, "name", json_object_new_string("directory_glob"));
json_object_object_add(function, "description",
json_object_new_string("List the contents of a specified directory in glob format. Result is a JSON array containing objects with keys: name, modification_date(iso), creation_date(iso), type, and size_bytes."));
struct json_object *parameters = json_object_new_object();
json_object_object_add(parameters, "type", json_object_new_string("object"));
struct json_object *properties = json_object_new_object();
struct json_object *directory = json_object_new_object();
json_object_object_add(directory, "type", json_object_new_string("string"));
json_object_object_add(directory, "description",
json_object_new_string("Path to the directory to list in glob format."));
json_object_object_add(properties, "path", directory);
json_object_object_add(parameters, "properties", properties);
struct json_object *required = json_object_new_array();
json_object_array_add(required, json_object_new_string("path"));
json_object_object_add(parameters, "required", required);
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(function, "parameters", parameters);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) {
json_object_object_add(function, "strict", json_object_new_boolean(1));
}
json_object_object_add(root, "function", function);
return root;
}
static void mkdir_print_action(const char *name, struct json_object *args) {
(void)name;
if (!args) return;
struct json_object *path;
if (json_object_object_get_ex(args, "path", &path)) {
fprintf(stderr, " -> Creating directory: %s\n", json_object_get_string(path));
}
}
static char *mkdir_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *path_obj;
if (!json_object_object_get_ex(args, "path", &path_obj)) {
return strdup("Error: missing 'path' argument");
}
return make_dir_recursive(json_object_get_string(path_obj));
}
static struct json_object *mkdir_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *function = json_object_new_object();
json_object_object_add(function, "name", json_object_new_string("mkdir"));
json_object_object_add(function, "description",
json_object_new_string("Creates a new directory with the specified path."));
struct json_object *parameters = json_object_new_object();
json_object_object_add(parameters, "type", json_object_new_string("object"));
struct json_object *properties = json_object_new_object();
struct json_object *path = json_object_new_object();
json_object_object_add(path, "type", json_object_new_string("string"));
json_object_object_add(path, "description",
json_object_new_string("Path of the directory to create."));
json_object_object_add(properties, "path", path);
json_object_object_add(parameters, "properties", properties);
struct json_object *required = json_object_new_array();
json_object_array_add(required, json_object_new_string("path"));
json_object_object_add(parameters, "required", required);
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(function, "parameters", parameters);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) {
json_object_object_add(function, "strict", json_object_new_boolean(1));
}
json_object_object_add(root, "function", function);
return root;
}
static void chdir_print_action(const char *name, struct json_object *args) {
(void)name;
if (!args) return;
struct json_object *path;
if (json_object_object_get_ex(args, "path", &path)) {
fprintf(stderr, " -> Changing directory: %s\n", json_object_get_string(path));
}
}
static char *chdir_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *path_obj;
if (!json_object_object_get_ex(args, "path", &path_obj)) {
return strdup("Error: missing 'path' argument");
}
if (chdir(json_object_get_string(path_obj)) != 0) {
return strdup("Failed to change directory!");
}
return strdup("Directory successfully changed.");
}
static struct json_object *chdir_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *function = json_object_new_object();
json_object_object_add(function, "name", json_object_new_string("chdir"));
json_object_object_add(function, "description",
json_object_new_string("Changes the current working directory to the specified path. Call this function when `cd` is prompted."));
struct json_object *parameters = json_object_new_object();
json_object_object_add(parameters, "type", json_object_new_string("object"));
struct json_object *properties = json_object_new_object();
struct json_object *path = json_object_new_object();
json_object_object_add(path, "type", json_object_new_string("string"));
json_object_object_add(path, "description",
json_object_new_string("Path to change the current working directory to."));
json_object_object_add(properties, "path", path);
json_object_object_add(parameters, "properties", properties);
struct json_object *required = json_object_new_array();
json_object_array_add(required, json_object_new_string("path"));
json_object_object_add(parameters, "required", required);
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(function, "parameters", parameters);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) {
json_object_object_add(function, "strict", json_object_new_boolean(1));
}
json_object_object_add(root, "function", function);
return root;
}
static void getpwd_print_action(const char *name, struct json_object *args) {
(void)name;
(void)args;
fprintf(stderr, " -> Getting current directory\n");
}
static char *getpwd_execute(tool_t *self, struct json_object *args) {
(void)self;
(void)args;
char *cwd = malloc(PATH_MAX);
if (!cwd) return strdup("Failed to allocate memory for current working directory!");
if (!getcwd(cwd, PATH_MAX)) {
free(cwd);
return strdup("Failed to get current working directory!");
}
return cwd;
}
static struct json_object *getpwd_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *function = json_object_new_object();
json_object_object_add(function, "name", json_object_new_string("getpwd"));
json_object_object_add(function, "description",
json_object_new_string("Returns the current working directory as a string."));
struct json_object *parameters = json_object_new_object();
json_object_object_add(parameters, "type", json_object_new_string("object"));
json_object_object_add(parameters, "properties", json_object_new_object());
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(function, "parameters", parameters);
json_object_object_add(root, "function", function);
return root;
}