|
// 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 <unistd.h>
|
|
#include <sys/wait.h>
|
|
|
|
static struct json_object *file_line_replace_get_description(void);
|
|
static char *file_line_replace_execute(tool_t *self, struct json_object *args);
|
|
static void file_line_replace_print_action(const char *name, struct json_object *args);
|
|
|
|
static struct json_object *file_apply_patch_get_description(void);
|
|
static char *file_apply_patch_execute(tool_t *self, struct json_object *args);
|
|
static void file_apply_patch_print_action(const char *name, struct json_object *args);
|
|
|
|
static const tool_vtable_t file_line_replace_vtable = {
|
|
.get_description = file_line_replace_get_description,
|
|
.execute = file_line_replace_execute,
|
|
.print_action = file_line_replace_print_action
|
|
};
|
|
|
|
static const tool_vtable_t file_apply_patch_vtable = {
|
|
.get_description = file_apply_patch_get_description,
|
|
.execute = file_apply_patch_execute,
|
|
.print_action = file_apply_patch_print_action
|
|
};
|
|
|
|
static tool_t file_line_replace_tool = { .vtable = &file_line_replace_vtable, .name = "file_line_replace" };
|
|
static tool_t file_apply_patch_tool = { .vtable = &file_apply_patch_vtable, .name = "file_apply_patch" };
|
|
|
|
tool_t *tool_file_line_replace_create(void) { return &file_line_replace_tool; }
|
|
tool_t *tool_file_apply_patch_create(void) { return &file_apply_patch_tool; }
|
|
|
|
static char *read_full_file(const char *path) {
|
|
FILE *fp = fopen(path, "r");
|
|
if (!fp) return NULL;
|
|
fseek(fp, 0, SEEK_END);
|
|
long size = ftell(fp);
|
|
rewind(fp);
|
|
char *content = malloc(size + 1);
|
|
if (content) {
|
|
size_t rs = fread(content, 1, size, fp);
|
|
content[rs] = '\0';
|
|
}
|
|
fclose(fp);
|
|
return content;
|
|
}
|
|
|
|
static void file_line_replace_print_action(const char *name, struct json_object *args) {
|
|
(void)name;
|
|
struct json_object *path;
|
|
if (json_object_object_get_ex(args, "path", &path)) {
|
|
fprintf(stderr, " -> Replacing lines in: %s\n", json_object_get_string(path));
|
|
}
|
|
}
|
|
|
|
static char *file_line_replace_execute(tool_t *self, struct json_object *args) {
|
|
(void)self;
|
|
struct json_object *path_obj, *start_obj, *end_obj, *replacement_obj;
|
|
if (!json_object_object_get_ex(args, "path", &path_obj) ||
|
|
!json_object_object_get_ex(args, "start_line", &start_obj) ||
|
|
!json_object_object_get_ex(args, "end_line", &end_obj) ||
|
|
!json_object_object_get_ex(args, "replacement", &replacement_obj)) {
|
|
return strdup("Error: missing arguments for file_line_replace");
|
|
}
|
|
|
|
const char *path = json_object_get_string(path_obj);
|
|
int start_line = json_object_get_int(start_obj);
|
|
int end_line = json_object_get_int(end_obj);
|
|
const char *replacement = json_object_get_string(replacement_obj);
|
|
|
|
char *old_content = read_full_file(path);
|
|
if (!old_content) return strdup("Error: could not read file");
|
|
|
|
// Split into lines
|
|
char **lines = NULL;
|
|
int count = 0;
|
|
char *copy = strdup(old_content);
|
|
char *saveptr;
|
|
char *line = strtok_r(copy, "\n", &saveptr);
|
|
while (line) {
|
|
char **expanded = realloc(lines, sizeof(char *) * (size_t)(count + 1));
|
|
if (!expanded) break;
|
|
lines = expanded;
|
|
lines[count++] = strdup(line);
|
|
line = strtok_r(NULL, "\n", &saveptr);
|
|
}
|
|
free(copy);
|
|
|
|
if (start_line < 1 || start_line > count || end_line < start_line || end_line > count) {
|
|
for (int i = 0; i < count; i++) free(lines[i]);
|
|
free(lines);
|
|
free(old_content);
|
|
return strdup("Error: invalid line range");
|
|
}
|
|
|
|
// Build new content
|
|
size_t new_size = 1024 * 1024; // start with 1MB
|
|
char *new_content = malloc(new_size);
|
|
new_content[0] = '\0';
|
|
size_t current_len = 0;
|
|
|
|
for (int i = 1; i <= count; i++) {
|
|
const char *to_add = NULL;
|
|
if (i < start_line || i > end_line) {
|
|
to_add = lines[i - 1];
|
|
} else if (i == start_line) {
|
|
to_add = replacement;
|
|
}
|
|
|
|
if (to_add) {
|
|
size_t add_len = strlen(to_add);
|
|
if (current_len + add_len + 2 >= new_size) {
|
|
new_size *= 2;
|
|
char *expanded_content = realloc(new_content, new_size);
|
|
if (!expanded_content) break;
|
|
new_content = expanded_content;
|
|
}
|
|
memcpy(new_content + current_len, to_add, add_len);
|
|
current_len += add_len;
|
|
new_content[current_len] = '\n';
|
|
current_len += 1;
|
|
new_content[current_len] = '\0';
|
|
}
|
|
}
|
|
|
|
r_diff_print(path, old_content, new_content);
|
|
|
|
FILE *fp = fopen(path, "w");
|
|
if (fp) {
|
|
fputs(new_content, fp);
|
|
fclose(fp);
|
|
snapshot_live_record(path, new_content);
|
|
}
|
|
|
|
for (int i = 0; i < count; i++) free(lines[i]);
|
|
free(lines);
|
|
free(old_content);
|
|
free(new_content);
|
|
|
|
return strdup("Lines replaced successfully.");
|
|
}
|
|
|
|
static void file_apply_patch_print_action(const char *name, struct json_object *args) {
|
|
(void)name;
|
|
struct json_object *path;
|
|
if (json_object_object_get_ex(args, "path", &path)) {
|
|
fprintf(stderr, " -> Applying patch to: %s\n", json_object_get_string(path));
|
|
}
|
|
}
|
|
|
|
static char *file_apply_patch_execute(tool_t *self, struct json_object *args) {
|
|
(void)self;
|
|
struct json_object *path_obj, *patch_obj;
|
|
if (!json_object_object_get_ex(args, "path", &path_obj) ||
|
|
!json_object_object_get_ex(args, "patch", &patch_obj)) {
|
|
return strdup("Error: missing arguments for file_apply_patch");
|
|
}
|
|
|
|
const char *path = json_object_get_string(path_obj);
|
|
const char *patch = json_object_get_string(patch_obj);
|
|
|
|
char *old_content = read_full_file(path);
|
|
|
|
char patch_file[] = "/tmp/r_patch_XXXXXX";
|
|
int fd = mkstemp(patch_file);
|
|
if (fd == -1) return strdup("Error: could not create temp patch file");
|
|
ssize_t written = write(fd, patch, strlen(patch));
|
|
close(fd);
|
|
|
|
if (written < (ssize_t)strlen(patch)) {
|
|
unlink(patch_file);
|
|
return strdup("Error: could not write full patch to temp file");
|
|
}
|
|
|
|
pid_t pid = fork();
|
|
int res = -1;
|
|
if (pid == 0) {
|
|
execl("/usr/bin/patch", "patch", path, patch_file, (char *)NULL);
|
|
_exit(127);
|
|
} else if (pid > 0) {
|
|
int status = 0;
|
|
waitpid(pid, &status, 0);
|
|
res = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
|
|
}
|
|
unlink(patch_file);
|
|
|
|
if (res != 0) {
|
|
free(old_content);
|
|
return strdup("Error: patch application failed");
|
|
}
|
|
|
|
char *new_content = read_full_file(path);
|
|
if (old_content && new_content) {
|
|
r_diff_print(path, old_content, new_content);
|
|
}
|
|
if (new_content) {
|
|
snapshot_live_record(path, new_content);
|
|
}
|
|
|
|
free(old_content);
|
|
free(new_content);
|
|
|
|
return strdup("Patch applied successfully.");
|
|
}
|
|
|
|
static struct json_object *file_line_replace_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("file_line_replace"));
|
|
json_object_object_add(function, "description", json_object_new_string("Replace a range of lines in a file with new content."));
|
|
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(properties, "path", path);
|
|
|
|
struct json_object *start = json_object_new_object();
|
|
json_object_object_add(start, "type", json_object_new_string("integer"));
|
|
json_object_object_add(properties, "start_line", start);
|
|
|
|
struct json_object *end = json_object_new_object();
|
|
json_object_object_add(end, "type", json_object_new_string("integer"));
|
|
json_object_object_add(properties, "end_line", end);
|
|
|
|
struct json_object *repl = json_object_new_object();
|
|
json_object_object_add(repl, "type", json_object_new_string("string"));
|
|
json_object_object_add(properties, "replacement", repl);
|
|
|
|
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("start_line"));
|
|
json_object_array_add(required, json_object_new_string("end_line"));
|
|
json_object_array_add(required, json_object_new_string("replacement"));
|
|
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 struct json_object *file_apply_patch_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("file_apply_patch"));
|
|
json_object_object_add(function, "description", json_object_new_string("Apply a unified diff patch to a file."));
|
|
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(properties, "path", path);
|
|
|
|
struct json_object *patch = json_object_new_object();
|
|
json_object_object_add(patch, "type", json_object_new_string("string"));
|
|
json_object_object_add(properties, "patch", patch);
|
|
|
|
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("patch"));
|
|
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;
|
|
}
|