dep

Package manager for embedded C libraries
git clone git://git.finwo.net/app/dep
Log | Files | Refs | README | LICENSE

commit b69dc51ddd26384d6eef7a44f7303699eefccb1a
parent 6f56d5bccfda5392198e75c3049a03cc93575daf
Author: finwo <finwo@pm.me>
Date:   Fri, 13 Mar 2026 12:51:03 +0100

Added basic repository management

Diffstat:
Msrc/command/command.h | 2+-
Msrc/command/init/main.c | 90++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/command/license/main.c | 12++++++------
Asrc/command/repository/main.c | 287+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/main.c | 36++++++++++++++++++++----------------
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; }