aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorfrosty <frostinator33@airmail.cc>2026-10-03 13:30:21 -0400
committerfrosty <frostinator33@airmail.cc>2026-10-03 13:30:21 -0400
commit65500761a632d22057d3b3e709b91dde168e125b (patch)
tree0ff30abf8de4120e5ed8db296688e0d85b65736b /src
parentcb716af752fb0b46aa8f78d3cbd5189a09642f39 (diff)
feat: add --exclude/--include
Diffstat (limited to 'src')
-rw-r--r--src/Cli/Cli.c34
-rw-r--r--src/Cli/Cli.h3
-rw-r--r--src/FileProcessor/FileProcessor.c45
-rw-r--r--src/FileProcessor/FileProcessor.h4
-rw-r--r--src/Main.c19
-rw-r--r--src/Pattern/Pattern.c148
-rw-r--r--src/Pattern/Pattern.h27
7 files changed, 269 insertions, 11 deletions
diff --git a/src/Cli/Cli.c b/src/Cli/Cli.c
index 3648493..c7569da 100644
--- a/src/Cli/Cli.c
+++ b/src/Cli/Cli.c
@@ -16,6 +16,8 @@ void cli_print_usage(const char *program_name) {
" -d, --directory DIRECTORY Process a directory recursively\n"
" -o, --output OUTPUT Set the output path\n"
" -f, --overwrite Overwrite an existing output directory\n"
+ " --include PATTERN Keep entries matching PATTERN\n"
+ " --exclude PATTERN Drop entries matching PATTERN\n"
" -l, --log-level LEVEL error, warning, info, debug, or silent\n"
" -v, --version Show the program version\n"
" -h, --help Show this help\n",
@@ -28,12 +30,29 @@ static const char *strip_optional_equals(const char *value) {
return value != NULL && value[0] == '=' ? value + 1 : value;
}
+static CliParseResult append_pattern(CliOptions *options, PatternKind kind,
+ const char *value, const char *name) {
+ const char *pattern = strip_optional_equals(value);
+
+ if (pattern_list_append(&options->patterns, kind, pattern) != 0) {
+ logger_error("invalid %s pattern '%s'", name, pattern);
+ return CLI_PARSE_ERROR;
+ }
+ return CLI_PARSE_OK;
+}
+
+void cli_free_options(CliOptions *options) {
+ pattern_list_clear(&options->patterns);
+}
+
CliParseResult cli_parse(int argc, char **argv, CliOptions *options) {
static const struct option long_options[] = {
{"directory", required_argument, NULL, 'd'},
{"output", required_argument, NULL, 'o'},
{"overwrite", no_argument, NULL, 'f'},
{"force", no_argument, NULL, 'f'},
+ {"include", required_argument, NULL, 1},
+ {"exclude", required_argument, NULL, 2},
{"log-level", required_argument, NULL, 'l'},
{"version", no_argument, NULL, 'v'},
{"help", no_argument, NULL, 'h'},
@@ -46,6 +65,9 @@ CliParseResult cli_parse(int argc, char **argv, CliOptions *options) {
options->output_path = NULL;
options->overwrite_output = 0;
options->log_level = LOG_LEVEL_INFO;
+ options->patterns.items = NULL;
+ options->patterns.length = 0;
+ options->patterns.capacity = 0;
while ((option = getopt_long(argc, argv, "d:o:fl:vh", long_options, NULL)) !=
-1) {
@@ -59,6 +81,18 @@ CliParseResult cli_parse(int argc, char **argv, CliOptions *options) {
case 'f':
options->overwrite_output = 1;
break;
+ case 1:
+ if (append_pattern(options, PATTERN_INCLUDE, optarg, "include") !=
+ CLI_PARSE_OK) {
+ return CLI_PARSE_ERROR;
+ }
+ break;
+ case 2:
+ if (append_pattern(options, PATTERN_EXCLUDE, optarg, "exclude") !=
+ CLI_PARSE_OK) {
+ return CLI_PARSE_ERROR;
+ }
+ break;
case 'l':
if (logger_parse_level(optarg, &options->log_level) != 0) {
logger_error("unknown log level '%s'", optarg);
diff --git a/src/Cli/Cli.h b/src/Cli/Cli.h
index 9695fd4..e574c88 100644
--- a/src/Cli/Cli.h
+++ b/src/Cli/Cli.h
@@ -2,6 +2,7 @@
#define YAPSSG_CLI_H
#include "Logger/Logger.h"
+#include "Pattern/Pattern.h"
typedef enum {
INPUT_TYPE_NONE,
@@ -22,9 +23,11 @@ typedef struct {
const char *output_path;
int overwrite_output;
LogLevel log_level;
+ PatternList patterns;
} CliOptions;
CliParseResult cli_parse(int argc, char **argv, CliOptions *options);
+void cli_free_options(CliOptions *options);
void cli_print_usage(const char *program_name);
#endif
diff --git a/src/FileProcessor/FileProcessor.c b/src/FileProcessor/FileProcessor.c
index d856fb4..84efece 100644
--- a/src/FileProcessor/FileProcessor.c
+++ b/src/FileProcessor/FileProcessor.c
@@ -6,6 +6,7 @@
#include "Logger/Logger.h"
#include "LuaEngine/LuaEngine.h"
#include "Path/Path.h"
+#include "Pattern/Pattern.h"
#include <dirent.h>
#include <errno.h>
@@ -206,28 +207,49 @@ static int copy_symlink(const char *input_path, const char *output_path,
return 0;
}
-static int copy_tree(const char *input_path, const char *output_path) {
+static int copy_tree(const char *input_path, const char *output_path,
+ const char *relative_path, const PatternList *patterns,
+ PatternKind subtree_kind) {
struct stat status;
DIR *directory;
+ PatternKind contents_kind;
int result = 0;
if (lstat(input_path, &status) != 0) {
logger_error("cannot inspect %s: %s", input_path, strerror(errno));
return -1;
}
- if (S_ISLNK(status.st_mode)) {
- return copy_symlink(input_path, output_path, &status);
- }
- if (S_ISREG(status.st_mode)) {
+
+ if (S_ISLNK(status.st_mode) || S_ISREG(status.st_mode)) {
+ if (pattern_list_resolve(patterns, relative_path, 0, subtree_kind) ==
+ PATTERN_EXCLUDE) {
+ logger_debug("excluding %s", input_path);
+ return 0;
+ }
+ if (S_ISLNK(status.st_mode)) {
+ return copy_symlink(input_path, output_path, &status);
+ }
return has_html_extension(input_path)
? process_file(input_path, output_path)
: copy_regular_file(input_path, output_path, status.st_mode);
}
+
if (!S_ISDIR(status.st_mode)) {
logger_error("unsupported file type: %s", input_path);
return -1;
}
+ if (relative_path[0] == '\0') {
+ contents_kind = pattern_list_default_kind(patterns);
+ } else {
+ contents_kind =
+ pattern_list_resolve(patterns, relative_path, 1, subtree_kind);
+ if (contents_kind == PATTERN_EXCLUDE) {
+ logger_debug("excluding directory %s", input_path);
+ return 0;
+ }
+ }
+
if (mkdir(output_path, status.st_mode & 0777) != 0) {
logger_error("cannot create directory %s: %s", output_path,
strerror(errno));
@@ -244,6 +266,7 @@ static int copy_tree(const char *input_path, const char *output_path) {
struct dirent *entry;
char *input_child;
char *output_child;
+ char *relative_child;
errno = 0;
entry = readdir(directory);
@@ -261,14 +284,17 @@ static int copy_tree(const char *input_path, const char *output_path) {
input_child = path_join(input_path, entry->d_name);
output_child = path_join(output_path, entry->d_name);
- if (input_child == NULL || output_child == NULL) {
+ relative_child = path_join(relative_path, entry->d_name);
+ if (input_child == NULL || output_child == NULL || relative_child == NULL) {
logger_error("unable to allocate child path");
result = -1;
} else {
- result = copy_tree(input_child, output_child);
+ result = copy_tree(input_child, output_child, relative_child, patterns,
+ contents_kind);
}
free(input_child);
free(output_child);
+ free(relative_child);
}
if (closedir(directory) != 0 && result == 0) {
@@ -346,7 +372,7 @@ static int remove_tree(const char *path) {
}
int process_directory(const char *input_path, const char *output_path,
- int overwrite_output) {
+ int overwrite_output, const PatternList *patterns) {
struct stat status;
struct stat output_status;
char *resolved_input;
@@ -387,7 +413,8 @@ int process_directory(const char *input_path, const char *output_path,
result = -1;
} else {
logger_info("processing directory %s -> %s", input_path, output_path);
- result = copy_tree(input_path, output_path);
+ result = copy_tree(input_path, output_path, "", patterns,
+ pattern_list_default_kind(patterns));
}
free(resolved_input);
diff --git a/src/FileProcessor/FileProcessor.h b/src/FileProcessor/FileProcessor.h
index 90027d7..55a00bb 100644
--- a/src/FileProcessor/FileProcessor.h
+++ b/src/FileProcessor/FileProcessor.h
@@ -1,8 +1,10 @@
#ifndef YAPSSG_FILE_PROCESSOR_H
#define YAPSSG_FILE_PROCESSOR_H
+#include "Pattern/Pattern.h"
+
int process_file(const char *input_path, const char *output_path);
int process_directory(const char *input_path, const char *output_path,
- int overwrite_output);
+ int overwrite_output, const PatternList *patterns);
#endif
diff --git a/src/Main.c b/src/Main.c
index b7c77f1..0acadf1 100644
--- a/src/Main.c
+++ b/src/Main.c
@@ -6,6 +6,13 @@
#include <libxml/parser.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
+
+static const char *base_name(const char *path) {
+ const char *slash = strrchr(path, '/');
+
+ return slash == NULL ? path : slash + 1;
+}
static int run(int argc, char **argv) {
CliOptions options;
@@ -16,6 +23,7 @@ static int run(int argc, char **argv) {
parse_result = cli_parse(argc, argv, &options);
if (parse_result != CLI_PARSE_OK) {
+ cli_free_options(&options);
return parse_result == CLI_PARSE_ERROR ? EXIT_FAILURE : EXIT_SUCCESS;
}
@@ -26,6 +34,7 @@ static int run(int argc, char **argv) {
automatic_output = path_make_default_output(options.input_path);
if (automatic_output == NULL) {
logger_error("unable to allocate output path");
+ cli_free_options(&options);
return EXIT_FAILURE;
}
output_path = automatic_output;
@@ -33,12 +42,20 @@ static int run(int argc, char **argv) {
if (options.input_type == INPUT_TYPE_DIRECTORY) {
result = process_directory(options.input_path, output_path,
- options.overwrite_output);
+ options.overwrite_output, &options.patterns);
+ } else if (pattern_list_resolve(
+ &options.patterns, base_name(options.input_path), 0,
+ pattern_list_default_kind(&options.patterns)) ==
+ PATTERN_EXCLUDE) {
+ logger_error("input file is excluded by the active patterns: %s",
+ options.input_path);
+ result = -1;
} else {
result = process_file(options.input_path, output_path);
}
free(automatic_output);
+ cli_free_options(&options);
return result == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
diff --git a/src/Pattern/Pattern.c b/src/Pattern/Pattern.c
new file mode 100644
index 0000000..9147e5e
--- /dev/null
+++ b/src/Pattern/Pattern.c
@@ -0,0 +1,148 @@
+#define _POSIX_C_SOURCE 200809L
+
+#include "Pattern/Pattern.h"
+
+#include <fnmatch.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+static char *duplicate_string(const char *value, size_t length) {
+ char *copy = malloc(length + 1);
+
+ if (copy != NULL) {
+ memcpy(copy, value, length);
+ copy[length] = '\0';
+ }
+ return copy;
+}
+
+static int grow_pattern_list(PatternList *list) {
+ size_t larger_capacity;
+ Pattern *larger_items;
+
+ if (list->capacity > SIZE_MAX / (2 * sizeof(*list->items))) {
+ return -1;
+ }
+
+ larger_capacity = list->capacity == 0 ? 4 : list->capacity * 2;
+ larger_items = realloc(list->items, larger_capacity * sizeof(*list->items));
+ if (larger_items == NULL) {
+ return -1;
+ }
+
+ list->items = larger_items;
+ list->capacity = larger_capacity;
+ return 0;
+}
+
+int pattern_list_append(PatternList *list, PatternKind kind,
+ const char *pattern) {
+ size_t length;
+ int directory_only;
+ Pattern *entry;
+
+ if (pattern == NULL) {
+ return -1;
+ }
+
+ length = strlen(pattern);
+ directory_only = length > 0 && pattern[length - 1] == '/';
+ if (directory_only) {
+ --length;
+ }
+ if (length == 0) {
+ return -1;
+ }
+
+ if (list->length == list->capacity && grow_pattern_list(list) != 0) {
+ return -1;
+ }
+
+ entry = &list->items[list->length];
+ entry->kind = kind;
+ entry->directory_only = directory_only;
+ entry->pattern = duplicate_string(pattern, length);
+ if (entry->pattern == NULL) {
+ return -1;
+ }
+
+ ++list->length;
+ return 0;
+}
+
+void pattern_list_clear(PatternList *list) {
+ size_t index;
+
+ for (index = 0; index < list->length; ++index) {
+ free(list->items[index].pattern);
+ }
+ free(list->items);
+ list->items = NULL;
+ list->length = 0;
+ list->capacity = 0;
+}
+
+static int has_include(const PatternList *list) {
+ size_t index;
+
+ for (index = 0; index < list->length; ++index) {
+ if (list->items[index].kind == PATTERN_INCLUDE) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+PatternKind pattern_list_default_kind(const PatternList *list) {
+ return has_include(list) ? PATTERN_EXCLUDE : PATTERN_INCLUDE;
+}
+
+static const char *base_name(const char *path) {
+ const char *slash = strrchr(path, '/');
+
+ return slash == NULL ? path : slash + 1;
+}
+
+static int matches_anchored(const char *pattern, const char *path) {
+ const char *cursor = path;
+
+ if (fnmatch(pattern, path, FNM_PATHNAME) == 0) {
+ return 1;
+ }
+ while (*cursor != '\0') {
+ cursor = strchr(cursor, '/');
+ if (cursor == NULL) {
+ break;
+ }
+ ++cursor;
+ if (fnmatch(pattern, cursor, FNM_PATHNAME) == 0) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int matches_pattern(const Pattern *pattern, const char *path) {
+ if (strchr(pattern->pattern, '/') == NULL) {
+ return fnmatch(pattern->pattern, base_name(path), 0) == 0;
+ }
+ return matches_anchored(pattern->pattern, path);
+}
+
+PatternKind pattern_list_resolve(const PatternList *list, const char *path,
+ int is_directory, PatternKind fallback) {
+ size_t index;
+
+ for (index = 0; index < list->length; ++index) {
+ const Pattern *pattern = &list->items[index];
+
+ if (pattern->directory_only && !is_directory) {
+ continue;
+ }
+ if (matches_pattern(pattern, path)) {
+ return pattern->kind;
+ }
+ }
+ return fallback;
+} \ No newline at end of file
diff --git a/src/Pattern/Pattern.h b/src/Pattern/Pattern.h
new file mode 100644
index 0000000..52b817f
--- /dev/null
+++ b/src/Pattern/Pattern.h
@@ -0,0 +1,27 @@
+#ifndef YAPSSG_PATTERN_H
+#define YAPSSG_PATTERN_H
+
+#include <stddef.h>
+
+typedef enum { PATTERN_INCLUDE, PATTERN_EXCLUDE } PatternKind;
+
+typedef struct {
+ PatternKind kind;
+ char *pattern;
+ int directory_only;
+} Pattern;
+
+typedef struct {
+ Pattern *items;
+ size_t length;
+ size_t capacity;
+} PatternList;
+
+int pattern_list_append(PatternList *list, PatternKind kind,
+ const char *pattern);
+void pattern_list_clear(PatternList *list);
+PatternKind pattern_list_default_kind(const PatternList *list);
+PatternKind pattern_list_resolve(const PatternList *list, const char *path,
+ int is_directory, PatternKind fallback);
+
+#endif \ No newline at end of file