commit b69dc51ddd26384d6eef7a44f7303699eefccb1a
parent 6f56d5bccfda5392198e75c3049a03cc93575daf
Author: finwo <finwo@pm.me>
Date: Fri, 13 Mar 2026 12:51:03 +0100
Added basic repository management
Diffstat:
5 files changed, 359 insertions(+), 68 deletions(-)
diff --git a/src/command/command.h b/src/command/command.h
@@ -1,5 +1,5 @@
struct cmd_struct {
- void *next;
+ void *next;
const char **name;
int (*fn)(int, const char **);
};
diff --git a/src/command/init/main.c b/src/command/init/main.c
@@ -1,58 +1,58 @@
+#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
-#include <limits.h>
#include "command/command.h"
static int cmd_init(int argc, const char **argv) {
- const char *target_dir = ".";
- if (argc >= 2) {
- target_dir = argv[1];
- }
-
- // Check if target directory exists and is accessible
- if (access(target_dir, F_OK | X_OK) != 0) {
- fprintf(stderr, "Error: directory '%s' does not exist or is not accessible\n", target_dir);
- return 1;
- }
-
- // Build path to .dep file
- char dep_path[PATH_MAX];
- int ret = snprintf(dep_path, sizeof(dep_path), "%s/.dep", target_dir);
- if (ret < 0 || ret >= sizeof(dep_path)) {
- fprintf(stderr, "Error: path too long\n");
- return 1;
- }
-
- // Check if .dep already exists
- if (access(dep_path, F_OK) == 0) {
- printf("Target directory already initialized\n");
- return 0;
- }
-
- // Create empty .dep file
- FILE *f = fopen(dep_path, "w");
- if (!f) {
- fprintf(stderr, "Error: could not create .dep file in '%s'\n", target_dir);
- return 1;
- }
- fclose(f);
-
- printf("Initialized successfully\n");
+ const char *target_dir = ".";
+ if (argc >= 2) {
+ target_dir = argv[1];
+ }
+
+ // Check if target directory exists and is accessible
+ if (access(target_dir, F_OK | X_OK) != 0) {
+ fprintf(stderr, "Error: directory '%s' does not exist or is not accessible\n", target_dir);
+ return 1;
+ }
+
+ // Build path to .dep file
+ char dep_path[PATH_MAX];
+ int ret = snprintf(dep_path, sizeof(dep_path), "%s/.dep", target_dir);
+ if (ret < 0 || ret >= sizeof(dep_path)) {
+ fprintf(stderr, "Error: path too long\n");
+ return 1;
+ }
+
+ // Check if .dep already exists
+ if (access(dep_path, F_OK) == 0) {
+ printf("Target directory already initialized\n");
return 0;
+ }
+
+ // Create empty .dep file
+ FILE *f = fopen(dep_path, "w");
+ if (!f) {
+ fprintf(stderr, "Error: could not create .dep file in '%s'\n", target_dir);
+ return 1;
+ }
+ fclose(f);
+
+ printf("Initialized successfully\n");
+ return 0;
}
void __attribute__((constructor)) cmd_init_setup(void) {
- struct cmd_struct *cmd = calloc(1, sizeof(struct cmd_struct));
- if (!cmd) {
- fprintf(stderr, "Failed to allocate memory for init command\n");
- return;
- }
- cmd->next = commands;
- cmd->fn = cmd_init;
- static const char *init_names[] = { "init", NULL };
- cmd->name = init_names;
- commands = cmd;
+ struct cmd_struct *cmd = calloc(1, sizeof(struct cmd_struct));
+ if (!cmd) {
+ fprintf(stderr, "Failed to allocate memory for init command\n");
+ return;
+ }
+ cmd->next = commands;
+ cmd->fn = cmd_init;
+ static const char *init_names[] = {"init", NULL};
+ cmd->name = init_names;
+ commands = cmd;
}
diff --git a/src/command/license/main.c b/src/command/license/main.c
@@ -15,10 +15,10 @@ int cmd_license(int argc, const char **argv) {
}
void __attribute__((constructor)) cmd_license_setup() {
- struct cmd_struct *cmd = calloc(1, sizeof(struct cmd_struct));
- cmd->next = commands;
- cmd->fn = cmd_license;
- static const char *license_names[] = { "license", NULL };
- cmd->name = license_names;
- commands = cmd;
+ struct cmd_struct *cmd = calloc(1, sizeof(struct cmd_struct));
+ cmd->next = commands;
+ cmd->fn = cmd_license;
+ static const char *license_names[] = {"license", NULL};
+ cmd->name = license_names;
+ commands = cmd;
}
diff --git a/src/command/repository/main.c b/src/command/repository/main.c
@@ -0,0 +1,287 @@
+#include <dirent.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "../command.h"
+#include "cofyc/argparse.h"
+
+#define REPO_DIR_DEFAULT "/.config/finwo/dep/repositories.d/"
+
+static char *get_repo_dir(void) {
+ const char *home = getenv("HOME");
+ if (!home) {
+ fprintf(stderr, "Error: HOME environment variable not set\n");
+ return NULL;
+ }
+ size_t len = strlen(home) + strlen(REPO_DIR_DEFAULT) + 1;
+ char *path = malloc(len);
+ if (!path) {
+ fprintf(stderr, "Error: out of memory\n");
+ return NULL;
+ }
+ snprintf(path, len, "%s%s", home, REPO_DIR_DEFAULT);
+ return path;
+}
+
+static int cmd_repository_list(int argc, const char **argv);
+static int cmd_repository_add(int argc, const char **argv);
+static int cmd_repository_remove(int argc, const char **argv);
+
+static const char *const usages[] = {
+ "repository <subcommand> [options]",
+ NULL,
+};
+
+static int cmd_repository(int argc, const char **argv) {
+ struct argparse_option options[] = {
+ OPT_HELP(),
+ OPT_END(),
+ };
+ struct argparse argparse;
+ argparse_init(&argparse, options, usages, 0);
+ argc = argparse_parse(&argparse, argc, argv);
+
+ // If no subcommand provided, show available subcommands
+ if (argc < 1) {
+ printf("Available subcommands:\n");
+ printf(" list List the names of the repositories\n");
+ printf(" add Add a repository: add <name> <url>\n");
+ printf(" remove Remove a repository: remove <name>\n");
+ return 0;
+ }
+
+ // Dispatch to the appropriate subcommand handler
+ if (!strcmp(argv[0], "list")) {
+ return cmd_repository_list(argc - 1, argv + 1);
+ } else if (!strcmp(argv[0], "add")) {
+ return cmd_repository_add(argc - 1, argv + 1);
+ } else if (!strcmp(argv[0], "remove")) {
+ return cmd_repository_remove(argc - 1, argv + 1);
+ } else {
+ fprintf(stderr, "Error: unknown subcommand '%s'\n", argv[0]);
+ return 1;
+ }
+}
+
+// List all repository names from files in the repository directory
+// Files are parsed line by line, with '#' starting comments
+static int cmd_repository_list(int argc, const char **argv) {
+ (void)argc;
+ (void)argv;
+
+ char *repo_dir = get_repo_dir();
+ if (!repo_dir) {
+ return 1;
+ }
+ DIR *dir = opendir(repo_dir);
+ free(repo_dir);
+ if (!dir) {
+ fprintf(stderr, "Error: could not open repository directory\n");
+ return 1;
+ }
+
+ struct dirent *entry;
+ // Iterate through all files in the repository directory
+ while ((entry = readdir(dir)) != NULL) {
+ // Skip . and .. entries
+ if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
+
+ struct stat st;
+ char *repo_dir2 = get_repo_dir();
+ if (!repo_dir2) {
+ closedir(dir);
+ return 1;
+ }
+ char filepath[PATH_MAX];
+ snprintf(filepath, sizeof(filepath), "%s%s", repo_dir2, entry->d_name);
+ free(repo_dir2);
+ // Skip non-regular files
+ if (stat(filepath, &st) < 0 || !S_ISREG(st.st_mode)) continue;
+
+ FILE *f = fopen(filepath, "r");
+ if (!f) continue;
+
+ // Parse each line in the file
+ char line[LINE_MAX];
+ while (fgets(line, sizeof(line), f)) {
+ // Remove trailing newline
+ line[strcspn(line, "\n")] = '\0';
+ // Strip comments (everything after '#')
+ char *comment = strchr(line, '#');
+ if (comment) *comment = '\0';
+ // Extract the first word as the repository name
+ char *name = strtok(line, " \t");
+ if (name && name[0] != '\0') {
+ printf("%s\n", name);
+ }
+ }
+ fclose(f);
+ }
+
+ closedir(dir);
+ return 0;
+}
+
+// Add a repository entry to the 00-managed file
+// Format: <name> <url>
+static int cmd_repository_add(int argc, const char **argv) {
+ if (argc < 2) {
+ fprintf(stderr, "Error: add requires <name> and <url>\n");
+ fprintf(stderr, "Usage: repository add <name> <url>\n");
+ return 1;
+ }
+
+ const char *name = argv[0];
+ const char *url = argv[1];
+
+ // Build the file path: ~/.config/finwo/dep/repositories.d/00-managed
+ char *repo_dir = get_repo_dir();
+ if (!repo_dir) {
+ return 1;
+ }
+ char filepath[PATH_MAX];
+ snprintf(filepath, sizeof(filepath), "%s00-managed", repo_dir);
+ free(repo_dir);
+
+ // Open the file in append mode
+ FILE *f = fopen(filepath, "a");
+ if (!f) {
+ fprintf(stderr, "Error: could not open file '%s' for writing\n", filepath);
+ return 1;
+ }
+
+ // Write the repository entry: name url
+ fprintf(f, "%s %s\n", name, url);
+ fclose(f);
+
+ printf("Repository '%s' added.\n", name);
+ return 0;
+}
+
+// Remove a repository entry by name from all files in the repository directory
+// If a file becomes empty after removal, it is deleted
+static int cmd_repository_remove(int argc, const char **argv) {
+ if (argc < 1) {
+ fprintf(stderr, "Error: remove requires <name>\n");
+ fprintf(stderr, "Usage: repository remove <name>\n");
+ return 1;
+ }
+
+ const char *name = argv[0];
+ int found = 0;
+
+ char *repo_dir = get_repo_dir();
+ if (!repo_dir) {
+ return 1;
+ }
+ DIR *dir = opendir(repo_dir);
+ free(repo_dir);
+ if (!dir) {
+ fprintf(stderr, "Error: could not open repository directory\n");
+ return 1;
+ }
+
+ struct dirent *entry;
+ // Iterate through all files in the repository directory
+ while ((entry = readdir(dir)) != NULL) {
+ // Skip . and .. entries
+ if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
+
+ char *repo_dir2 = get_repo_dir();
+ if (!repo_dir2) {
+ closedir(dir);
+ return 1;
+ }
+ char filepath[PATH_MAX];
+ snprintf(filepath, sizeof(filepath), "%s%s", repo_dir2, entry->d_name);
+ free(repo_dir2);
+
+ struct stat st;
+ // Skip non-regular files
+ if (stat(filepath, &st) < 0 || !S_ISREG(st.st_mode)) continue;
+
+ // Create a temporary file for writing the modified content
+ char temp_path[PATH_MAX];
+ snprintf(temp_path, sizeof(temp_path), "%s.tmp", filepath);
+
+ FILE *in = fopen(filepath, "r");
+ FILE *out = fopen(temp_path, "w");
+ if (!in || !out) {
+ fprintf(stderr, "Error: could not open files for processing '%s'\n", filepath);
+ if (in) fclose(in);
+ if (out) fclose(out);
+ closedir(dir);
+ return 1;
+ }
+
+ // Process each line, removing lines that match the repository name
+ char line[LINE_MAX];
+ while (fgets(line, sizeof(line), in)) {
+ // Make a copy for parsing (preserving original line for output)
+ char line_copy[LINE_MAX];
+ strncpy(line_copy, line, sizeof(line_copy) - 1);
+ line_copy[sizeof(line_copy) - 1] = '\0';
+
+ // Strip comments before matching
+ line_copy[strcspn(line_copy, "\n")] = '\0';
+ char *comment = strchr(line_copy, '#');
+ if (comment) *comment = '\0';
+ char *line_name = strtok(line_copy, " \t");
+
+ // Skip lines that match the repository name
+ if (line_name && !strcmp(line_name, name)) {
+ found = 1;
+ continue;
+ }
+ // Write the original line back to the temp file
+ fputs(line, out);
+ }
+
+ fclose(in);
+ fclose(out);
+
+ // Replace original file with the temporary file
+ if (rename(temp_path, filepath) < 0) {
+ fprintf(stderr, "Error: could not replace file '%s'\n", filepath);
+ closedir(dir);
+ return 1;
+ }
+
+ // If the file is now empty, remove it
+ if (stat(filepath, &st) == 0 && st.st_size == 0) {
+ if (remove(filepath) < 0) {
+ fprintf(stderr, "Error: could not remove empty file '%s'\n", filepath);
+ closedir(dir);
+ return 1;
+ }
+ }
+ }
+
+ closedir(dir);
+
+ if (!found) {
+ fprintf(stderr, "Warning: repository '%s' not found\n", name);
+ } else {
+ printf("Repository '%s' removed.\n", name);
+ }
+
+ return 0;
+}
+
+// Register the repository command with the command system
+void __attribute__((constructor)) cmd_repository_setup(void) {
+ struct cmd_struct *cmd = calloc(1, sizeof(struct cmd_struct));
+ if (!cmd) {
+ fprintf(stderr, "Failed to allocate memory for repository command\n");
+ return;
+ }
+ cmd->next = commands;
+ cmd->fn = cmd_repository;
+ static const char *repository_names[] = {"repository", "repo", "r", NULL};
+ cmd->name = repository_names;
+ commands = cmd;
+}
diff --git a/src/main.c b/src/main.c
@@ -64,22 +64,26 @@ int main(int argc, const char **argv) {
return -1;
}
- /* Try to run command with args provided. */
- struct cmd_struct *cmd = commands;
- while (cmd) {
- const char **name = cmd->name;
- while (*name) {
- if (!strcmp(*name, argv[0])) {
- goto found;
- }
- name++;
- }
- cmd = cmd->next;
- }
- cmd = NULL;
+ /* Try to run command with args provided. */
+ struct cmd_struct *cmd = commands;
+ while (cmd) {
+ const char **name = cmd->name;
+ while (*name) {
+ if (!strcmp(*name, argv[0])) {
+ goto found;
+ }
+ name++;
+ }
+ cmd = cmd->next;
+ }
found:
- if (cmd) {
- return cmd->fn(argc, argv);
- }
+
+ if (cmd) {
+ return cmd->fn(argc, argv);
+ } else {
+ fprintf(stderr, "Unknown command: %s\n", argv[0]);
+ return 1;
+ }
+
return 0;
}