My pretty neat website
git clone git://soophie.de/soso
diff --git a/.clangd b/.clangd
new file mode 100644
index 0000000..873a35e
--- /dev/null
+++ b/.clangd
1
@@ -0,0 +1,12 @@2
+CompileFlags:3
+ Add:4
+ - -Wall5
+ - -Wextra6
+ - -Werror7
+ - -pedantic8
+ - -I/root/git/sock/src9
+ - -I/root/git/tpee/src10
+ - -I/root/git/tpee_ext/src11
+ - -I/root/git/buf/src12
+ - -I/root/git/orb/src13
+ - -I/root/git/md4c/src
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1cd86dc
--- /dev/null
+++ b/.gitignore
1
@@ -0,0 +1,2 @@2
+src/templ3
+soso
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..b20b1ec
--- /dev/null
+++ b/Makefile
1
@@ -0,0 +1,71 @@2
+CC := cc3
+CFLAGS := -Wall -Wextra -Werror -pedantic4
+CFLAGS_RELEASE := -O2 -DNDEBUG5
+CFLAGS_DEBUG := -g3 -O06
+# NOTE: exposes header impls into dynamically linked modules7
+LDFLAGS := -rdynamic8
+LDLIBS := \9
+ -lm \10
+ -lssl \11
+ -lgit212
+DEPS := \13
+ -I$(HOME)/git/sock/src \14
+ -I$(HOME)/git/tpee/src \15
+ -I$(HOME)/git/tpee_ext/src \16
+ -I$(HOME)/git/buf/src \17
+ -I$(HOME)/git/orb/src \18
+ -I$(HOME)/git/md4c/src19
+SOURCE := \20
+ src/main.c \21
+ $(HOME)/git/md4c/src/*.c \22
+ src/module/page.c \23
+ src/module/default.c \24
+ src/module/index.c \25
+ src/module/git_list.c \26
+ src/module/git_repo.c \27
+ src/module/git_repo_log.c \28
+ src/module/git_repo_tree.c \29
+ src/module/git_repo_refs.c \30
+ src/module/git_repo_commit.c \31
+ src/module/git_repo_blob.c32
+VALGRIND := valgrind \33
+ --leak-check=full \34
+ --show-leak-kinds=all \35
+ --track-origins=yes36
+TARGET := soso37
+38
+export DUC_HEADERPATH := ./src/templ/39
+export DUC_BUILDPATH := ./src/templ/40
+export DUC_LIBPATH := $(CFLAGS) $(DEPS)41
+42
+all: build43
+44
+build-templ:45
+ mkdir -p ./src/templ46
+ifdef TEMPL47
+ ductape ./templ/$(TEMPL).html48
+else49
+ ductape ./templ/*.html50
+endif51
+52
+build: $(SOURCE)53
+ $(CC) -o $(TARGET) $(CFLAGS) $(DEPS) $(LDFLAGS) $^ $(LDLIBS)54
+55
+build-release: $(SOURCE)56
+ $(CC) -o $(TARGET) $(CFLAGS) $(DEPS) $(CFLAGS_RELEASE) $(LDFLAGS) $^ $(LDLIBS)57
+58
+build-debug: $(SOURCE)59
+ $(CC) -o $(TARGET) $(CFLAGS) $(DEPS) $(CFLAGS_DEBUG) $(LDFLAGS) $^ $(LDLIBS)60
+61
+run: build62
+ ./$(TARGET)63
+64
+run-release: build-release65
+ ./$(TARGET)66
+67
+run-debug: build68
+ $(VALGRIND) ./$(TARGET)69
+70
+clean:71
+ rm -f ./$(TARGET)72
+ rm -rf ./src/templ
diff --git a/asset/font/Inconsolata-Variable.ttf b/asset/font/Inconsolata-Variable.ttf
new file mode 100644
index 0000000..95ad718
Binary files /dev/null and b/asset/font/Inconsolata-Variable.ttf differ
diff --git a/asset/style/main.css b/asset/style/main.css
new file mode 100644
index 0000000..dd52159
--- /dev/null
+++ b/asset/style/main.css
1
@@ -0,0 +1,298 @@2
+@font-face {3
+ font-family: 'Inconsolata';4
+ src: url('/asset/font/Inconsolata-Variable.ttf') format('truetype');5
+}6
+7
+:root {8
+ --ft-mono: 'Inconsolata', monospace;9
+ --ft-size: 14px;10
+ --ft-weight: 400;11
+}12
+13
+* {14
+ font-family: var(--ft-mono);15
+ box-sizing: border-box;16
+}17
+18
+html {19
+20
+ body {21
+ max-width: 900px;22
+ margin: 4rem auto;23
+ margin-bottom: 6rem;24
+ font-size: var(--ft-size);25
+ font-weight: var(--ft-weight);26
+ }27
+}28
+29
+nav {30
+ border-bottom: 1px solid #bbbbbb;31
+ padding-bottom: 1rem;32
+}33
+34
+pre {35
+ white-space: pre-wrap;36
+}37
+38
+ul.row-list {39
+ padding: 0px;40
+ width: 100%;41
+42
+ li.row {43
+ display: flex;44
+ gap: 0px 0.75rem;45
+ margin: 0px;46
+ padding: 0px;47
+ list-style-type: none;48
+49
+ &.row--head {50
+ margin-bottom: 0.5rem;51
+ }52
+53
+ span.col {54
+ display: inline-block;55
+56
+ &.col--oid {57
+ min-width: 7ch;58
+ max-width: 7ch;59
+ }60
+61
+ &.col--time {62
+ min-width: 10ch;63
+ max-width: 10ch;64
+ }65
+66
+ &.col--mode {67
+ min-width: 10ch;68
+ max-width: 10ch;69
+ }70
+71
+ &.col--owner {72
+ min-width: 20ch;73
+ max-width: 20ch;74
+ }75
+76
+ &.col--author {77
+ min-width: 20ch;78
+ max-width: 20ch;79
+ }80
+81
+ &.col--size {82
+ min-width: 20ch;83
+ max-width: 20ch;84
+ }85
+86
+ &.col--message {87
+ min-width: 50ch;88
+ flex: 1;89
+ }90
+91
+ &.col--description {92
+ min-width: 50ch;93
+ flex: 1;94
+ }95
+ }96
+ }97
+98
+ &.list--tree li.row span.col {99
+100
+ &.col--name {101
+ min-width: 50ch;102
+ flex: 1;103
+ }104
+ }105
+106
+ &.list--repo li.row span.col {107
+108
+ &.col--name {109
+ min-width: 30ch;110
+ max-width: 30ch;111
+ }112
+ }113
+114
+ &.list--refs li.row span.col {115
+116
+ &.col--name {117
+ min-width: 30ch;118
+ max-width: 30ch;119
+ }120
+ }121
+}122
+123
+.git--repo {124
+ margin-top: 2rem;125
+126
+ hr {127
+ border: none;128
+ border-top: 1px solid #bbbbbb;129
+ }130
+131
+ ul.link-list {132
+ display: flex;133
+ gap: 0px 0rem;134
+ padding: 0px;135
+ border-bottom: 1px solid #bbbbbb;136
+ margin-top: 2rem;137
+ margin-bottom: 0.5rem;138
+139
+ li.link {140
+ margin: 0px;141
+ margin-bottom: -1px;142
+ list-style-type: none;143
+ min-width: 12ch;144
+ border: 1px solid #bbbbbb;145
+146
+ &:not(:first-of-type) {147
+ margin-left: -1px;148
+ }149
+150
+ &.link--active {151
+ border-bottom: 1px solid #ffffff;152
+ z-index: 1;153
+ }154
+155
+ &:not(.link--active) {156
+ background-color: #eeeeee;157
+ }158
+159
+ a {160
+ display: block;161
+ padding: 0.4rem 1rem;162
+ padding-bottom: 0.5rem;163
+ text-align: center;164
+ }165
+ }166
+ }167
+168
+ select.repo--branch {169
+ margin: 0.5rem 0px;170
+ border: 1px solid #999999;171
+ padding: 0.25rem 0.5rem;172
+ cursor: pointer;173
+ min-width: 30ch;174
+175
+ &:hover {176
+ border-color: #777777;177
+ }178
+ }179
+180
+ .repo--tab {181
+ padding-top: 0.5rem;182
+ }183
+}184
+185
+.commit-diff {186
+ margin-top: 2rem;187
+188
+ pre {189
+ margin: 1rem 0px;190
+191
+ &.diff--meta {192
+ padding-top: 0.25rem;193
+ padding-bottom: 0.25rem;194
+ padding-left: 1ch;195
+ border: 1px solid #bbbbbb;196
+ background-color: #eeeeee;197
+198
+ &:has(+ pre.diff--code) {199
+ margin-bottom: -1rem;200
+ border-bottom: 0px;201
+ }202
+203
+ p {204
+ margin: 0px;205
+206
+ &:not(:first-of-type) {207
+ display: none;208
+ }209
+ }210
+ }211
+212
+ &.diff--code {213
+ padding-top: 0.5rem;214
+ padding-bottom: 0.5rem;215
+ border: 1px solid #bbbbbb;216
+217
+ p {218
+ display: flex;219
+ margin: 0px;220
+221
+ &:last-of-type {222
+ display: none;223
+ }224
+225
+ &.is-hunk {226
+ color: #5252ff;227
+ }228
+229
+ &.is-add {230
+231
+ span.ln {232
+ border-left: 3px solid #77d877;233
+ }234
+ }235
+236
+ &.is-rem {237
+238
+ span.ln {239
+ border-left: 3px solid #ff7f7f;240
+ }241
+ }242
+243
+ &:not(:hover) {244
+245
+ &.is-add {246
+ color: #2e862e;247
+ }248
+249
+ &.is-rem {250
+ color: #932323;251
+ }252
+253
+ &:target {254
+ background-color: #ffff9a !important;255
+256
+ span.ln {257
+258
+ a {259
+ color: #666666;260
+ }261
+ }262
+ }263
+ }264
+265
+ &:hover {266
+ background-color: #e1e1e1;267
+268
+ span.ln {269
+270
+ a {271
+ color: #666666;272
+ }273
+ }274
+ }275
+276
+ span.ln {277
+ min-width: max-content;278
+ user-select: none;279
+ padding-left: 1ch;280
+ margin-right: 2ch;281
+ border-left: 3px solid transparent;282
+283
+ a {284
+ text-decoration: none;285
+ color: #888888;286
+287
+ &:hover {288
+ text-decoration: underline;289
+ }290
+ }291
+ }292
+293
+ span.line {294
+295
+ }296
+ }297
+ }298
+ }299
+}
diff --git a/src/duc_macro.h b/src/duc_macro.h
new file mode 100644
index 0000000..5a0f426
--- /dev/null
+++ b/src/duc_macro.h
1
@@ -0,0 +1,30 @@2
+#pragma once3
+4
+#include <buf.h>5
+#include <orb.h>6
+#include "util.h"7
+8
+#define DUC_OUT ((orb_ctx_t *) out)9
+#define DUC_WRITE(str) { buf_write(DUC_OUT->out, str); }10
+11
+#define HTML(str) DUC_WRITE(str ? str : "")12
+#define STRING(str) { \13
+ buf_t buf = BUF_NULL; \14
+ buf_write(&buf, str); \15
+ buf_repall(&buf, "&", "&"); \16
+ buf_repall(&buf, "<", "<"); \17
+ buf_repall(&buf, ">", ">"); \18
+ buf_concat(DUC_OUT->out, buf); \19
+ buf_free(&buf); \20
+}21
+#define PRINTF(fmt, ...) { buf_writef(DUC_OUT->out, fmt, __VA_ARGS__); }22
+#define NUMBER(num) PRINTF("%d", num)23
+#define FORMAT_TIME(time, fmt) { \24
+ struct tm *tm = localtime(time); \25
+ char buffer[100]; \26
+ int len = strftime(buffer, 100, fmt, tm); \27
+ buffer[len] = '\0'; \28
+ STRING(buffer) \29
+}30
+31
+#define MARKDOWN(str) { util_md_html(str, DUC_OUT->out); }
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..2aa0013
--- /dev/null
+++ b/src/main.c
1
@@ -0,0 +1,227 @@2
+#include <unistd.h>3
+#include <signal.h>4
+#include <execinfo.h>5
+#include <git2.h>6
+7
+#define TPEE_IMPL8
+#include <tpee.h>9
+#define TPEE_EXT_IMPL10
+#include <tpee_ext.h>11
+#define BUF_IMPL12
+#include <buf.h>13
+#define ORB_IMPL14
+#include <orb.h>15
+#define UTIL_IMPL16
+#include "util.h" // IWYU pragma: keep17
+18
+#define PORT 808719
+20
+#include "module/page.h"21
+#include "module/default.h"22
+#include "module/index.h"23
+#include "module/git_list.h"24
+#include "module/git_repo.h"25
+26
+TPEE_GET("/", index_page) {27
+ (void) req;28
+ buf_t out = BUF_NULL;29
+ orb_ctx_t ctx = orb_ctx_create(&out);30
+ orb_module_t index_module = orb_module_create(&INDEX_TYPE, NULL);31
+ orb_module_t page_module = orb_module_create(&PAGE_TYPE, &((page_param_list_t) {32
+ .title = "Index",33
+ .module = &index_module,34
+ }));35
+ orb_module_run(&page_module, &ctx);36
+ tpee_resp_t resp = tpee_resp_create(TPEE_STATUS_OK);37
+ tpee_resp_body(&resp, (char *) out.bytes, out.len);38
+ buf_free(&out);39
+ return resp;40
+}41
+42
+TPEE_GET("/git", git_list_page) {43
+ (void) req;44
+ buf_t out = BUF_NULL;45
+ orb_ctx_t ctx = orb_ctx_create(&out);46
+ orb_module_t git_list_module = orb_module_create(&GIT_LIST_TYPE, &((git_list_param_list_t) {47
+ .path = "/home/git",48
+ }));49
+ orb_module_t page_module = orb_module_create(&PAGE_TYPE, &((page_param_list_t) {50
+ .title = "Index",51
+ .module = &git_list_module,52
+ }));53
+ orb_module_run(&page_module, &ctx);54
+ tpee_resp_t resp = tpee_resp_create(TPEE_STATUS_OK);55
+ tpee_resp_body(&resp, (char *) out.bytes, out.len);56
+ buf_free(&out);57
+ return resp;58
+}59
+60
+TPEE_GET("/git/*", git_repo_page) {61
+ char *repo_name = tpee_ext_get_path_var(req->url, "/git/{repo_name}", "repo_name");62
+ if (!repo_name) return tpee_resp_create(TPEE_STATUS_NOT_FOUND);63
+ char *tab_mode = NULL;64
+ if (tpee_ext_is_path_match(req->url, "/git/{repo_name}/{tab_mode}")) {65
+ tab_mode = tpee_ext_get_path_var(req->url, "/git/{repo_name}/{tab_mode}", "tab_mode");66
+ }67
+ if (!tab_mode) {68
+ tab_mode = strdup("log");69
+ }70
+ const char *tab_mode_list[] = { "log", "tree", "refs", "commit", "blob" };71
+ size_t tab_mode_list_len = sizeof(tab_mode_list) / sizeof(tab_mode_list[0]);72
+ bool is_tab_mode_valid = false;73
+ for (size_t i = 0; i < tab_mode_list_len; i++) {74
+ if (strcmp(tab_mode, tab_mode_list[i]) == 0) {75
+ is_tab_mode_valid = true;76
+ break;77
+ }78
+ }79
+ if (!is_tab_mode_valid) {80
+ return tpee_resp_create(TPEE_STATUS_NOT_FOUND);81
+ }82
+ const char *git_base_path = "/home/git/";83
+ size_t repo_path_len = strlen(git_base_path) + strlen(repo_name) + 1;84
+ char repo_path[repo_path_len];85
+ snprintf(repo_path, repo_path_len, "%s%s", git_base_path, repo_name);86
+ tpee_pairs_t query_list = tpee_req_queries(req);87
+ buf_t out = BUF_NULL;88
+ orb_ctx_t ctx = orb_ctx_create(&out);89
+ orb_module_t git_repo_module = orb_module_create(&GIT_REPO_TYPE, &((git_repo_param_list_t) {90
+ .repo_name = repo_name,91
+ .repo_path = repo_path,92
+ .tab_mode = tab_mode,93
+ .ref = tpee_pairs_get(&query_list, "ref"),94
+ .oid = tpee_pairs_get(&query_list, "oid"),95
+ }));96
+ orb_module_t page_module = orb_module_create(&PAGE_TYPE, &((page_param_list_t) {97
+ .title = repo_name,98
+ .module = &git_repo_module,99
+ }));100
+ orb_module_run(&page_module, &ctx);101
+ tpee_pairs_free(&query_list);102
+ if (tab_mode) {103
+ free(tab_mode);104
+ }105
+ free(repo_name);106
+ if (ctx.rc.is_fatal) {107
+ tpee_resp_t resp = tpee_resp_create(TPEE_STATUS_NOT_FOUND);108
+ buf_free(&out);109
+ return resp;110
+ }111
+ tpee_resp_t resp = tpee_resp_create(TPEE_STATUS_OK);112
+ tpee_resp_body(&resp, (char *) out.bytes, out.len);113
+ buf_free(&out);114
+ return resp;115
+}116
+117
+// #include "registry.h"118
+119
+// TPEE_GET("/orb/module/*", orb_module_serve) {120
+// char *module_id = tpee_ext_get_path_var(req->url, "/orb/module/{module_id}", "module_id");121
+// tpee_resp_t resp = tpee_resp_create(TPEE_STATUS_NOT_FOUND);122
+// if (module_id) {123
+// const orb_module_type_t *module_type = NULL;124
+// for (size_t i = 0; i < REGISTRY_LEN; i++) {125
+// const registry_entry_t *entry = ®ISTRY[i];126
+// if (strcmp(module_id, entry->id) == 0) {127
+// module_type = entry->type;128
+// break;129
+// }130
+// }131
+// if (module_type) {132
+// buf_t out = BUF_NULL;133
+// orb_ctx_t ctx = orb_ctx_create(&out);134
+// void *param_list = NULL;135
+// tpee_pairs_t query_list = tpee_req_queries(req);136
+// if (module_type == &INDEX_TYPE) {137
+// param_list = calloc(1, sizeof(index_param_list_t));138
+// index_param_list_t *index_param_list = param_list;139
+// index_param_list->title = tpee_pairs_get(&query_list, "title");140
+// }141
+// if (module_type == &ABOUT_TYPE) {142
+// param_list = calloc(1, sizeof(about_param_list_t));143
+// about_param_list_t *about_param_list = param_list;144
+// about_param_list->headline = tpee_pairs_get(&query_list, "headline");145
+// about_param_list->subline = tpee_pairs_get(&query_list, "subline");146
+// }147
+// orb_module_t module = orb_module_create(module_type, param_list);148
+// orb_module_run(&module, &ctx);149
+// tpee_pairs_free(&query_list);150
+// if (param_list) {151
+// free(param_list);152
+// }153
+// resp.status = TPEE_STATUS_OK;154
+// tpee_resp_body(&resp, (char *) out.bytes, out.len);155
+// buf_free(&out);156
+// }157
+// free(module_id);158
+// }159
+// return resp;160
+// }161
+162
+TPEE_GET("/asset/*", asset_serve) {163
+ const char *prefix = "/asset/";164
+ const char *filepath = "./asset/";165
+ size_t filename_len = strlen(filepath) + strlen(req->url) - strlen(prefix) + 1;166
+ char filename[filename_len];167
+ snprintf(filename, filename_len, "%s%s", filepath, req->url + strlen(prefix));168
+ tpee_resp_t resp = tpee_ext_serve_file(filename);169
+ return resp;170
+}171
+172
+TPEE_DEFAULT(default_page) {173
+ (void) req;174
+ buf_t out = BUF_NULL;175
+ orb_ctx_t ctx = orb_ctx_create(&out);176
+ orb_module_t default_module = orb_module_create(&DEFAULT_TYPE, NULL);177
+ orb_module_t page_module = orb_module_create(&PAGE_TYPE, &((page_param_list_t) {178
+ .title = "404",179
+ .module = &default_module,180
+ }));181
+ orb_module_run(&page_module, &ctx);182
+ tpee_resp_t resp = tpee_resp_create(TPEE_STATUS_OK);183
+ tpee_resp_body(&resp, (char *) out.bytes, out.len);184
+ buf_free(&out);185
+ return resp;186
+}187
+188
+void on_signal(int signal) {189
+ (void) signal;190
+ // do nothing191
+}192
+193
+void on_segfault(int signal) {194
+ void *list[10];195
+ size_t size = backtrace(list, 10);196
+ fprintf(stderr, "ERROR: signal %d:\n", signal);197
+ backtrace_symbols_fd(list, size, STDOUT_FILENO);198
+ exit(1);199
+}200
+201
+int main(void) {202
+ struct sigaction action;203
+ action.sa_handler = on_signal;204
+ action.sa_flags = 0;205
+ sigemptyset(&action.sa_mask);206
+ sigaction(SIGINT, &action, NULL);207
+ struct sigaction sigint_action;208
+ sigint_action.sa_handler = on_segfault;209
+ sigint_action.sa_flags = 0;210
+ sigemptyset(&sigint_action.sa_mask);211
+ sigaction(SIGSEGV, &sigint_action, NULL);212
+ tpee_t *tpee = tpee_init();213
+ if (tpee_bind(tpee, "0.0.0.0", PORT, NULL, 0) != TPEE_ERR_OK) {214
+ printf("failed to bind port!\n");215
+ return 1;216
+ }217
+ git_libgit2_init();218
+ git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 0);219
+ if (tpee_listen(tpee) == TPEE_ERR_OK) {220
+ printf("ready and listening (pid: %d)\n", getpid());221
+ pause();222
+ }223
+ git_libgit2_shutdown();224
+ printf("stopped\n");225
+ tpee_close(tpee);226
+ tpee_cleanup(tpee);227
+ return 0;228
+}
diff --git a/src/module/default.c b/src/module/default.c
new file mode 100644
index 0000000..808ac0a
--- /dev/null
+++ b/src/module/default.c
1
@@ -0,0 +1,18 @@2
+#include "default.h"3
+#include "../templ/duc_default.h"4
+5
+static orb_rc_t init(orb_module_t *self) {6
+ duc_default_t *data = (duc_default_t *) self->data;7
+ (void) data;8
+ return ORB_OK;9
+}10
+11
+static orb_rc_t render(orb_module_t *self, void *out) {12
+ return ORB_ERR(duc_default_render(out, (duc_default_t *) self->data));13
+}14
+15
+const orb_module_type_t DEFAULT_TYPE = {16
+ .size = sizeof(duc_default_t),17
+ .init = init,18
+ .render = render,19
+};
diff --git a/src/module/default.h b/src/module/default.h
new file mode 100644
index 0000000..41abaad
--- /dev/null
+++ b/src/module/default.h
1
@@ -0,0 +1,5 @@2
+#pragma once3
+4
+#include <orb.h>5
+6
+extern const orb_module_type_t DEFAULT_TYPE;
diff --git a/src/module/git_list.c b/src/module/git_list.c
new file mode 100644
index 0000000..999bb06
--- /dev/null
+++ b/src/module/git_list.c
1
@@ -0,0 +1,107 @@2
+#include <dirent.h>3
+#include "git_list.h"4
+5
+#include "../templ/duc_git_list.h"6
+7
+static orb_rc_t init(orb_module_t *self) {8
+ duc_git_list_t *data = self->data;9
+ (void) data;10
+ git_list_param_list_t *param_list = self->param_list;11
+ (void) param_list;12
+ return ORB_OK;13
+}14
+15
+static int _fetch_repo_meta_sort(const void *a, const void *b) {16
+ const git_repo_meta_t *repo_meta_a = a;17
+ const git_repo_meta_t *repo_meta_b = b;18
+ return strcmp(repo_meta_a->name, repo_meta_b->name);19
+}20
+21
+static int _fetch_read_text_file(char *filename, char **content) {22
+ FILE *fd = fopen(filename, "r");23
+ if (fd == NULL) return 1;24
+ fseek(fd, 0, SEEK_END);25
+ size_t len = ftell(fd);26
+ fseek(fd, 0, SEEK_SET);27
+ *content = calloc(1, sizeof(char) * (len + 1));28
+ fread(*content, sizeof(char), len, fd);29
+ (*content)[len] = '\0';30
+ fclose(fd);31
+ return 0;32
+}33
+34
+static int _fetch_read_git_file(char *repo_path, char *filename, char **content) {35
+ size_t file_path_len = strlen(repo_path) + strlen(filename) + 2;36
+ char file_path[file_path_len];37
+ snprintf(file_path, file_path_len, "%s/%s", repo_path, filename);38
+ return _fetch_read_text_file(file_path, content);39
+}40
+41
+static bool _fetch_git_file_exists(char *repo_path, char *filename) {42
+ size_t file_path_len = strlen(repo_path) + strlen(filename) + 2;43
+ char file_path[file_path_len];44
+ snprintf(file_path, file_path_len, "%s/%s", repo_path, filename);45
+ FILE *fp = fopen(file_path, "r");46
+ if (!fp) return false;47
+ fclose(fp);48
+ return true;49
+}50
+51
+static orb_rc_t fetch(orb_module_t *self) {52
+ duc_git_list_t *data = self->data;53
+ git_list_param_list_t *param_list = self->param_list;54
+ if (!param_list->path) return ORB_FATAL(1);55
+ DIR *dir = opendir(param_list->path);56
+ if (dir == NULL) return ORB_FATAL(2);57
+ struct dirent *entry;58
+ while ((entry = readdir(dir)) != NULL) {59
+ if (entry->d_type != DT_DIR) continue;60
+ if (strcmp(entry->d_name, ".") == 0) continue;61
+ if (strcmp(entry->d_name, "..") == 0) continue;62
+ size_t repo_path_len = strlen(param_list->path) + strlen(entry->d_name) + 2;63
+ char repo_path[repo_path_len];64
+ snprintf(repo_path, repo_path_len, "%s/%s", param_list->path, entry->d_name);65
+ bool is_public = _fetch_git_file_exists(repo_path, "git-daemon-export-ok");66
+ if (!is_public) continue;67
+ data->repo_meta_list = realloc(data->repo_meta_list, sizeof(git_repo_meta_t) * (data->repo_meta_list_len + 1));68
+ git_repo_meta_t *repo_meta = &data->repo_meta_list[data->repo_meta_list_len];69
+ memset(repo_meta, 0, sizeof(git_repo_meta_t));70
+ repo_meta->name = strdup(entry->d_name);71
+ _fetch_read_git_file(repo_path, "description", &repo_meta->description);72
+ _fetch_read_git_file(repo_path, "owner", &repo_meta->owner);73
+ repo_meta->is_public = is_public;74
+ repo_meta->is_archive = _fetch_git_file_exists(repo_path, ".archive");75
+ repo_meta->is_fork = _fetch_git_file_exists(repo_path, ".fork");76
+ data->repo_meta_list_len++;77
+ }78
+ closedir(dir);79
+ qsort(data->repo_meta_list, data->repo_meta_list_len, sizeof(git_repo_meta_t), _fetch_repo_meta_sort);80
+ return ORB_OK;81
+}82
+83
+static orb_rc_t render(orb_module_t *self, void *out) {84
+ return ORB_ERR(duc_git_list_render(out, (duc_git_list_t *) self->data));85
+}86
+87
+static void cleanup(orb_module_t *self) {88
+ duc_git_list_t *data = self->data;89
+ if (data->repo_meta_list && data->repo_meta_list_len > 0) {90
+ for (size_t i = 0; i < data->repo_meta_list_len; i++) {91
+ git_repo_meta_t *repo_meta = &data->repo_meta_list[i];92
+ free(repo_meta->name);93
+ if (repo_meta->description) free(repo_meta->description);94
+ if (repo_meta->owner) free(repo_meta->owner);95
+ }96
+ free(data->repo_meta_list);97
+ data->repo_meta_list = NULL;98
+ data->repo_meta_list_len = 0;99
+ }100
+}101
+102
+const orb_module_type_t GIT_LIST_TYPE = {103
+ .size = sizeof(duc_git_list_t),104
+ .init = init,105
+ .fetch = fetch,106
+ .render = render,107
+ .cleanup = cleanup,108
+};
diff --git a/src/module/git_list.h b/src/module/git_list.h
new file mode 100644
index 0000000..74d617f
--- /dev/null
+++ b/src/module/git_list.h
1
@@ -0,0 +1,18 @@2
+#pragma once3
+4
+#include <orb.h>5
+6
+typedef struct {7
+ char *path;8
+} git_list_param_list_t;9
+10
+typedef struct {11
+ char *name;12
+ char *description;13
+ char *owner;14
+ bool is_public;15
+ bool is_archive;16
+ bool is_fork;17
+} git_repo_meta_t;18
+19
+extern const orb_module_type_t GIT_LIST_TYPE;
diff --git a/src/module/git_repo.c b/src/module/git_repo.c
new file mode 100644
index 0000000..6db8d8b
--- /dev/null
+++ b/src/module/git_repo.c
1
@@ -0,0 +1,226 @@2
+#include <git2.h>3
+#include <git2/types.h>4
+#include "git_repo.h"5
+6
+#include "../templ/duc_git_repo.h"7
+8
+static orb_rc_t init(orb_module_t *self) {9
+ duc_git_repo_t *data = self->data;10
+ git_repo_param_list_t *param_list = self->param_list;11
+ if (param_list) {12
+ data->repo = (git_repo_t) {13
+ .path = param_list->repo_path,14
+ .meta = {0},15
+ .handle = NULL,16
+ .ref_oid = param_list->ref,17
+ .oid = param_list->oid,18
+ .ref_head = NULL,19
+ .ref = NULL,20
+ .ref_list = NULL,21
+ .ref_list_len = 0,22
+ };23
+ data->repo.meta.name = param_list->repo_name;24
+ data->tab_mode = param_list->tab_mode;25
+ }26
+ return ORB_OK;27
+}28
+29
+static int _fetch_read_text_file(char *filename, char **content) {30
+ FILE *fd = fopen(filename, "r");31
+ if (fd == NULL) return 1;32
+ fseek(fd, 0, SEEK_END);33
+ size_t len = ftell(fd);34
+ fseek(fd, 0, SEEK_SET);35
+ *content = calloc(1, sizeof(char) * (len + 1));36
+ fread(*content, sizeof(char), len, fd);37
+ (*content)[len] = '\0';38
+ fclose(fd);39
+ return 0;40
+}41
+42
+static int _fetch_read_git_file(char *repo_path, char *filename, char **content) {43
+ size_t file_path_len = strlen(repo_path) + strlen(filename) + 2;44
+ char file_path[file_path_len];45
+ snprintf(file_path, file_path_len, "%s/%s", repo_path, filename);46
+ return _fetch_read_text_file(file_path, content);47
+}48
+49
+static bool _fetch_git_file_exists(char *repo_path, char *filename) {50
+ size_t file_path_len = strlen(repo_path) + strlen(filename) + 2;51
+ char file_path[file_path_len];52
+ snprintf(file_path, file_path_len, "%s/%s", repo_path, filename);53
+ FILE *fp = fopen(file_path, "r");54
+ if (!fp) return false;55
+ fclose(fp);56
+ return true;57
+}58
+59
+static char *_fetch_git_file_oid(git_repository *repo, char *ref_name, char *filename) {60
+ size_t spec_len = strlen(ref_name) + strlen(filename) + 2;61
+ char spec[spec_len];62
+ snprintf(spec, spec_len, "%s:%s", ref_name, filename);63
+ git_object *obj_handle = NULL;64
+ if (git_revparse_single(&obj_handle, repo, spec) != 0) return NULL;65
+ char oid_str[GIT_OID_HEXSZ + 1];66
+ git_oid_tostr(oid_str, sizeof(oid_str), git_object_id(obj_handle));67
+ git_object_free(obj_handle);68
+ return strdup(oid_str);69
+}70
+71
+static orb_rc_t fetch(orb_module_t *self) {72
+ duc_git_repo_t *data = self->data;73
+ git_repo_param_list_t *param_list = self->param_list;74
+ if (!param_list->repo_path) return ORB_FATAL(1);75
+ bool is_public = _fetch_git_file_exists(param_list->repo_path, "git-daemon-export-ok");76
+ if (!is_public) return ORB_FATAL(2);77
+ git_repository *repo_handle = NULL;78
+ int code = git_repository_open(&repo_handle, param_list->repo_path);79
+ if (code < 0 || repo_handle == NULL) return ORB_FATAL(code);80
+ data->repo.handle = repo_handle;81
+ _fetch_read_git_file(data->repo.path, "description", &data->repo.meta.description);82
+ _fetch_read_git_file(data->repo.path, "owner", &data->repo.meta.owner);83
+ data->repo.meta.is_public = is_public;84
+ data->repo.meta.is_archive = _fetch_git_file_exists(data->repo.path, ".archive");85
+ data->repo.meta.is_fork = _fetch_git_file_exists(data->repo.path, ".fork");86
+ git_reference *ref_head_handle = NULL;87
+ git_repository_head(&ref_head_handle, data->repo.handle);88
+ if (ref_head_handle) {89
+ data->repo.ref_head = calloc(1, sizeof(git_repo_ref_t));90
+ git_repo_ref_t *ref = data->repo.ref_head;91
+ const git_oid *oid = git_reference_target(ref_head_handle);92
+ char oid_str[GIT_OID_HEXSZ + 1];93
+ git_oid_tostr(oid_str, sizeof(oid_str), oid);94
+ ref->oid = strdup(oid_str);95
+ const char *ref_name = git_reference_name(ref_head_handle);96
+ if (git_reference_is_branch(ref_head_handle)) {97
+ ref->name = strdup(ref_name + strlen("refs/heads/"));98
+ ref->type = "branch";99
+ }100
+ if (git_reference_is_tag(ref_head_handle)) {101
+ ref->name = strdup(ref_name + strlen("refs/tags/"));102
+ ref->type = "tag";103
+ }104
+ if (git_reference_is_remote(ref_head_handle)) {105
+ ref->name = strdup(ref_name + strlen("refs/remotes/"));106
+ ref->type = "remote";107
+ }108
+ if (!ref->name) {109
+ ref->name = strdup(ref_name);110
+ }111
+ git_reference_free(ref_head_handle);112
+ data->repo.readme_oid = _fetch_git_file_oid(repo_handle, ref->name, "README.md");113
+ data->repo.license_oid = _fetch_git_file_oid(repo_handle, ref->name, "LICENSE");114
+ git_reference_iterator *iter = NULL;115
+ git_reference *ref_handle = NULL;116
+ git_reference_iterator_new(&iter, data->repo.handle);117
+ while (git_reference_next(&ref_handle, iter) == 0) {118
+ data->repo.ref_list = realloc(data->repo.ref_list, sizeof(git_repo_ref_t) * (data->repo.ref_list_len + 1));119
+ git_repo_ref_t *ref = &data->repo.ref_list[data->repo.ref_list_len];120
+ memset(ref, 0, sizeof(git_repo_ref_t));121
+ const git_oid *oid = git_reference_target(ref_handle);122
+ char oid_str[GIT_OID_HEXSZ + 1];123
+ git_oid_tostr(oid_str, sizeof(oid_str), oid);124
+ ref->oid = strdup(oid_str);125
+ const char *ref_name = git_reference_name(ref_handle);126
+ if (git_reference_is_branch(ref_handle)) {127
+ ref->name = strdup(ref_name + strlen("refs/heads/"));128
+ ref->type = "branch";129
+ }130
+ if (git_reference_is_tag(ref_handle)) {131
+ ref->name = strdup(ref_name + strlen("refs/tags/"));132
+ ref->type = "tag";133
+ }134
+ if (git_reference_is_remote(ref_handle)) {135
+ ref->name = strdup(ref_name + strlen("refs/remotes/"));136
+ ref->type = "remote";137
+ }138
+ if (!ref->name) {139
+ ref->name = strdup(ref_name);140
+ }141
+ git_reference_free(ref_handle);142
+ data->repo.ref_list_len++;143
+ }144
+ git_reference_iterator_free(iter);145
+ }146
+ if (data->repo.ref_head && data->repo.ref_oid && strcmp(data->repo.ref_head->oid, data->repo.ref_oid) != 0) {147
+ git_oid ref_oid;148
+ if (git_oid_fromstr(&ref_oid, data->repo.ref_oid) == 0) {149
+ git_reference_iterator *iter = NULL;150
+ git_reference *ref_handle = NULL;151
+ git_reference_iterator_new(&iter, data->repo.handle);152
+ while (git_reference_next(&ref_handle, iter) == 0) {153
+ const git_oid *oid = git_reference_target(ref_handle);154
+ if (oid && git_oid_equal(oid, &ref_oid)) {155
+ data->repo.ref = calloc(1, sizeof(git_repo_ref_t));156
+ git_repo_ref_t *ref = data->repo.ref;157
+ ref->oid = strdup(data->repo.ref_oid);158
+ const char *ref_name = git_reference_name(ref_handle);159
+ if (git_reference_is_branch(ref_handle)) {160
+ ref->name = strdup(ref_name + strlen("refs/heads/"));161
+ ref->type = "branch";162
+ }163
+ if (git_reference_is_tag(ref_handle)) {164
+ ref->name = strdup(ref_name + strlen("refs/tags/"));165
+ ref->type = "tag";166
+ }167
+ if (git_reference_is_remote(ref_handle)) {168
+ ref->name = strdup(ref_name + strlen("refs/remotes/"));169
+ ref->type = "remote";170
+ }171
+ if (!ref->name) {172
+ ref->name = strdup(ref_name);173
+ }174
+ }175
+ git_reference_free(ref_handle);176
+ }177
+ git_reference_iterator_free(iter);178
+ }179
+ }180
+ return ORB_OK;181
+}182
+183
+static orb_rc_t render(orb_module_t *self, void *out) {184
+ return ORB_ERR(duc_git_repo_render(out, (duc_git_repo_t *) self->data));185
+}186
+187
+static void cleanup(orb_module_t *self) {188
+ duc_git_repo_t *data = self->data;189
+ if (data->repo.handle) {190
+ git_repository_free(data->repo.handle);191
+ data->repo.handle = NULL;192
+ }193
+ if (data->repo.meta.description) free(data->repo.meta.description);194
+ if (data->repo.meta.owner) free(data->repo.meta.owner);195
+ if (data->repo.ref_head) {196
+ free(data->repo.ref_head->oid);197
+ free(data->repo.ref_head->name);198
+ free(data->repo.ref_head);199
+ data->repo.ref_head = NULL;200
+ }201
+ if (data->repo.ref) {202
+ free(data->repo.ref->oid);203
+ free(data->repo.ref->name);204
+ free(data->repo.ref);205
+ data->repo.ref = NULL;206
+ }207
+ if (data->repo.readme_oid) free(data->repo.readme_oid);208
+ if (data->repo.license_oid) free(data->repo.license_oid);209
+ if (data->repo.ref_list && data->repo.ref_list_len > 0) {210
+ for (size_t i = 0; i < data->repo.ref_list_len; i++) {211
+ git_repo_ref_t *ref = &data->repo.ref_list[i];212
+ free(ref->oid);213
+ free(ref->name);214
+ }215
+ free(data->repo.ref_list);216
+ data->repo.ref_list = NULL;217
+ data->repo.ref_list_len = 0;218
+ }219
+}220
+221
+const orb_module_type_t GIT_REPO_TYPE = {222
+ .size = sizeof(duc_git_repo_t),223
+ .init = init,224
+ .fetch = fetch,225
+ .render = render,226
+ .cleanup = cleanup,227
+};
diff --git a/src/module/git_repo.h b/src/module/git_repo.h
new file mode 100644
index 0000000..b145385
--- /dev/null
+++ b/src/module/git_repo.h
1
@@ -0,0 +1,58 @@2
+#pragma once3
+4
+#include <git2.h>5
+#include <time.h>6
+#include <orb.h>7
+#include "git_list.h"8
+9
+typedef struct {10
+ char *repo_name;11
+ char *repo_path;12
+ char *ref;13
+ char *oid;14
+ const char *tab_mode;15
+} git_repo_param_list_t;16
+17
+typedef struct {18
+ char *oid;19
+ char *name;20
+ const char *type;21
+} git_repo_ref_t;22
+23
+typedef struct {24
+ char *oid;25
+ char *message;26
+ char *author_name;27
+ char *author_email;28
+ time_t time;29
+} git_repo_commit_t;30
+31
+typedef struct {32
+ char *oid;33
+ char *name;34
+ char *content;35
+ size_t size;36
+} git_repo_blob_t;37
+38
+typedef struct {39
+ char *oid;40
+ char *name;41
+ char *mode;42
+ size_t size;43
+} git_repo_tree_entry_t;44
+45
+typedef struct {46
+ char *path;47
+ git_repo_meta_t meta;48
+ git_repository *handle;49
+ char *ref_oid;50
+ char *oid;51
+ git_repo_ref_t *ref_head;52
+ git_repo_ref_t *ref;53
+ git_repo_ref_t *ref_list;54
+ size_t ref_list_len;55
+ char *readme_oid;56
+ char *license_oid;57
+} git_repo_t;58
+59
+extern const orb_module_type_t GIT_REPO_TYPE;
diff --git a/src/module/git_repo_blob.c b/src/module/git_repo_blob.c
new file mode 100644
index 0000000..d3be875
--- /dev/null
+++ b/src/module/git_repo_blob.c
1
@@ -0,0 +1,63 @@2
+#include "git_repo.h"3
+#include "git_repo_blob.h"4
+5
+#include "../templ/duc_git_repo_blob.h"6
+7
+static orb_rc_t init(orb_module_t *self) {8
+ duc_git_repo_blob_t *data = self->data;9
+ git_repo_blob_param_list_t *param_list = self->param_list;10
+ if (param_list) {11
+ data->is_markdown = param_list->is_markdown;12
+ }13
+ return ORB_OK;14
+}15
+16
+static orb_rc_t fetch(orb_module_t *self) {17
+ duc_git_repo_blob_t *data = self->data;18
+ git_repo_blob_param_list_t *param_list = self->param_list;19
+ if (!param_list) return ORB_FATAL(1);20
+ if (!param_list->repo) return ORB_FATAL(2);21
+ if (!param_list->repo->oid) return ORB_FATAL(3);22
+ data->repo = param_list->repo;23
+ git_oid blob_oid;24
+ if (git_oid_fromstr(&blob_oid, data->repo->oid) != 0) return ORB_FATAL(4);25
+ git_blob *blob_handle = NULL;26
+ if (git_blob_lookup(&blob_handle, data->repo->handle, &blob_oid) != 0) return ORB_FATAL(5);27
+ data->blob = calloc(1, sizeof(git_repo_blob_t));28
+ git_repo_blob_t *blob = data->blob;29
+ blob->oid = strdup(data->repo->oid);30
+ blob->name = NULL;31
+ size_t size = (size_t) git_blob_rawsize(blob_handle);32
+ const char *content = git_blob_rawcontent(blob_handle);33
+ if (content) {34
+ blob->content = malloc(size + 1);35
+ memcpy(blob->content, content, size);36
+ blob->content[size] = '\0';37
+ blob->size = size;38
+ }39
+ git_blob_free(blob_handle);40
+ return ORB_OK;41
+}42
+43
+static orb_rc_t render(orb_module_t *self, void *out) {44
+ return ORB_ERR(duc_git_repo_blob_render(out, (duc_git_repo_blob_t *) self->data));45
+}46
+47
+static void cleanup(orb_module_t *self) {48
+ duc_git_repo_blob_t *data = self->data;49
+ if (data->blob) {50
+ free(data->blob->oid);51
+ if (data->blob->name) free(data->blob->name);52
+ if (data->blob->content) free(data->blob->content);53
+ free(data->blob);54
+ data->blob = NULL;55
+ }56
+}57
+58
+const orb_module_type_t GIT_REPO_BLOB_TYPE = {59
+ .size = sizeof(duc_git_repo_blob_t),60
+ .init = init,61
+ .fetch = fetch,62
+ .render = render,63
+ .cleanup = cleanup,64
+};
diff --git a/src/module/git_repo_blob.h b/src/module/git_repo_blob.h
new file mode 100644
index 0000000..023d81b
--- /dev/null
+++ b/src/module/git_repo_blob.h
1
@@ -0,0 +1,11 @@2
+#pragma once3
+4
+#include <orb.h>5
+#include "git_repo.h"6
+7
+typedef struct {8
+ git_repo_t *repo;9
+ bool is_markdown;10
+} git_repo_blob_param_list_t;11
+12
+extern const orb_module_type_t GIT_REPO_BLOB_TYPE;
diff --git a/src/module/git_repo_commit.c b/src/module/git_repo_commit.c
new file mode 100644
index 0000000..6dceb23
--- /dev/null
+++ b/src/module/git_repo_commit.c
1
@@ -0,0 +1,104 @@2
+#include <buf.h>3
+#include <git2/diff.h>4
+#include "git_repo.h"5
+#include "git_repo_commit.h"6
+7
+#include "../templ/duc_git_repo_commit.h"8
+9
+static orb_rc_t fetch(orb_module_t *self) {10
+ duc_git_repo_commit_t *data = self->data;11
+ git_repo_commit_param_list_t *param_list = self->param_list;12
+ if (!param_list) return ORB_FATAL(1);13
+ if (!param_list->repo) return ORB_FATAL(2);14
+ if (!param_list->repo->oid) return ORB_FATAL(3);15
+ data->repo = param_list->repo;16
+ git_oid commit_oid;17
+ if (git_oid_fromstr(&commit_oid, data->repo->oid) != 0) return ORB_FATAL(4);18
+ git_commit *commit_handle = NULL;19
+ git_commit *parent_commit_handle = NULL;20
+ git_commit_lookup(&commit_handle, data->repo->handle, &commit_oid);21
+ data->commit = calloc(1, sizeof(git_repo_commit_t));22
+ git_repo_commit_t *commit = data->commit;23
+ commit->oid = strdup(data->repo->oid);24
+ commit->message = strdup(git_commit_message(commit_handle));25
+ commit->author_name = strdup(git_commit_author(commit_handle)->name);26
+ commit->author_email = strdup(git_commit_author(commit_handle)->email);27
+ commit->time = (time_t) git_commit_time(commit_handle);28
+ if (git_commit_parentcount(commit_handle) > 0) {29
+ git_commit_parent(&parent_commit_handle, commit_handle, 0);30
+ const git_oid *parent_oid = git_commit_id(parent_commit_handle);31
+ data->parent_commit_oid = malloc(sizeof(char) * (GIT_OID_HEXSZ + 1));32
+ git_oid_tostr(data->parent_commit_oid, GIT_OID_HEXSZ + 1, parent_oid);33
+ }34
+ git_tree *tree_handle = NULL;35
+ git_tree *parent_tree_handle = NULL;36
+ git_commit_tree(&tree_handle, commit_handle);37
+ if (parent_commit_handle != NULL) {38
+ git_commit_tree(&parent_tree_handle, parent_commit_handle);39
+ }40
+ git_diff *diff_handle = NULL;41
+ git_diff_tree_to_tree(&diff_handle, data->repo->handle, parent_tree_handle, tree_handle, NULL);42
+ size_t num_deltas = git_diff_num_deltas(diff_handle);43
+ buf_t out = BUF_NULL;44
+ for (size_t i = 0; i < num_deltas; i++) {45
+ git_patch *patch_handle = NULL;46
+ git_patch_from_diff(&patch_handle, diff_handle, i);47
+ git_buf buf_handle = GIT_BUF_INIT_CONST(NULL, 0);48
+ git_patch_to_buf(&buf_handle, patch_handle);49
+ buf_write_len(&out, buf_handle.ptr, buf_handle.size);50
+ buf_write(&out, "\n\n");51
+ git_buf_dispose(&buf_handle);52
+ git_patch_free(patch_handle);53
+ }54
+ buf_write_len(&out, "\0", 1);55
+ // git_diff_stats *stats_handle = NULL;56
+ // if (git_diff_get_stats(&stats_handle, diff_handle)) {57
+ // size_t files_changed = git_diff_stats_files_changed(stats_handle);58
+ // size_t insertions = git_diff_stats_insertions(stats_handle);59
+ // size_t deletions = git_diff_stats_deletions(stats_handle);60
+ // git_diff_stats_free(stats_handle);61
+ // }62
+ git_diff_free(diff_handle);63
+ git_tree_free(tree_handle);64
+ if (parent_tree_handle) {65
+ git_tree_free(parent_tree_handle);66
+ }67
+ git_commit_free(commit_handle);68
+ if (parent_commit_handle) {69
+ git_commit_free(parent_commit_handle);70
+ }71
+ data->diff = (char *) out.bytes;72
+ return ORB_OK;73
+}74
+75
+static orb_rc_t render(orb_module_t *self, void *out) {76
+ return ORB_ERR(duc_git_repo_commit_render(out, (duc_git_repo_commit_t *) self->data));77
+}78
+79
+static void cleanup(orb_module_t *self) {80
+ duc_git_repo_commit_t *data = self->data;81
+ if (data->commit) {82
+ git_repo_commit_t *commit = data->commit;83
+ free(commit->oid);84
+ free(commit->message);85
+ free(commit->author_name);86
+ free(commit->author_email);87
+ free(commit);88
+ commit = NULL;89
+ }90
+ if (data->parent_commit_oid) {91
+ free(data->parent_commit_oid);92
+ data->parent_commit_oid = NULL;93
+ }94
+ if (data->diff) {95
+ free(data->diff);96
+ data->diff = NULL;97
+ }98
+}99
+100
+const orb_module_type_t GIT_REPO_COMMIT_TYPE = {101
+ .size = sizeof(duc_git_repo_commit_t),102
+ .fetch = fetch,103
+ .render = render,104
+.cleanup = cleanup,105
+};
diff --git a/src/module/git_repo_commit.h b/src/module/git_repo_commit.h
new file mode 100644
index 0000000..d650633
--- /dev/null
+++ b/src/module/git_repo_commit.h
1
@@ -0,0 +1,10 @@2
+#pragma once3
+4
+#include <orb.h>5
+#include "git_repo.h"6
+7
+typedef struct {8
+ git_repo_t *repo;9
+} git_repo_commit_param_list_t;10
+11
+extern const orb_module_type_t GIT_REPO_COMMIT_TYPE;
diff --git a/src/module/git_repo_log.c b/src/module/git_repo_log.c
new file mode 100644
index 0000000..021e998
--- /dev/null
+++ b/src/module/git_repo_log.c
1
@@ -0,0 +1,73 @@2
+#include <git2.h>3
+#include "git_repo.h"4
+#include "git_repo_log.h"5
+6
+#include "../templ/duc_git_repo_log.h"7
+8
+static orb_rc_t fetch(orb_module_t *self) {9
+ duc_git_repo_log_t *data = self->data;10
+ git_repo_log_param_list_t *param_list = self->param_list;11
+ if (!param_list) return ORB_ERR(1);12
+ if (!param_list->repo) return ORB_ERR(2);13
+ data->repo = param_list->repo;14
+ git_revwalk *walker = NULL;15
+ git_revwalk_new(&walker, param_list->repo->handle);16
+ git_revwalk_sorting(walker, GIT_SORT_TIME);17
+ int code = 0;18
+ if (param_list->repo->ref) {19
+ git_oid ref_oid;20
+ git_oid_fromstr(&ref_oid, param_list->repo->ref->oid);21
+ code = git_revwalk_push(walker, &ref_oid);22
+ }23
+ else {24
+ code = git_revwalk_push_head(walker);25
+ }26
+ if (code != 0) {27
+ git_revwalk_free(walker);28
+ return ORB_ERR(code);29
+ }30
+ git_oid oid;31
+ while (git_revwalk_next(&oid, walker) == 0) {32
+ git_commit *commit_handle = NULL;33
+ git_commit_lookup(&commit_handle, param_list->repo->handle, &oid);34
+ data->commit_list = realloc(data->commit_list, sizeof(git_repo_commit_t) * (data->commit_list_len + 1));35
+ git_repo_commit_t *commit = &data->commit_list[data->commit_list_len];36
+ commit->oid = malloc(sizeof(char) * (GIT_OID_HEXSZ + 1));37
+ git_oid_tostr(commit->oid, GIT_OID_HEXSZ + 1, &oid);38
+ commit->message = strdup(git_commit_message(commit_handle));39
+ commit->author_name = strdup(git_commit_author(commit_handle)->name);40
+ commit->author_email = strdup(git_commit_author(commit_handle)->email);41
+ commit->time = (time_t) git_commit_time(commit_handle);42
+ git_commit_free(commit_handle);43
+ data->commit_list_len++;44
+ }45
+ git_revwalk_free(walker);46
+ return ORB_OK;47
+}48
+49
+static orb_rc_t render(orb_module_t *self, void *out) {50
+ return ORB_ERR(duc_git_repo_log_render(out, (duc_git_repo_log_t *) self->data));51
+}52
+53
+static void cleanup(orb_module_t *self) {54
+ duc_git_repo_log_t *data = self->data;55
+ if (data->commit_list && data->commit_list_len > 0) {56
+ for (size_t i = 0; i < data->commit_list_len; i++) {57
+ git_repo_commit_t *commit = &data->commit_list[i];58
+ free(commit->oid);59
+ free(commit->message);60
+ free(commit->author_name);61
+ free(commit->author_email);62
+ }63
+ free(data->commit_list);64
+ data->commit_list = NULL;65
+ data->commit_list_len = 0;66
+ }67
+}68
+69
+const orb_module_type_t GIT_REPO_LOG_TYPE = {70
+ .size = sizeof(duc_git_repo_log_t),71
+ .fetch = fetch,72
+ .render = render,73
+ .cleanup = cleanup,74
+};
diff --git a/src/module/git_repo_log.h b/src/module/git_repo_log.h
new file mode 100644
index 0000000..b47f0e3
--- /dev/null
+++ b/src/module/git_repo_log.h
1
@@ -0,0 +1,10 @@2
+#pragma once3
+4
+#include <orb.h>5
+#include "git_repo.h"6
+7
+typedef struct {8
+ git_repo_t *repo;9
+} git_repo_log_param_list_t;10
+11
+extern const orb_module_type_t GIT_REPO_LOG_TYPE;
diff --git a/src/module/git_repo_refs.c b/src/module/git_repo_refs.c
new file mode 100644
index 0000000..2257907
--- /dev/null
+++ b/src/module/git_repo_refs.c
1
@@ -0,0 +1,70 @@2
+#include <git2.h>3
+#include "git_repo.h"4
+#include "git_repo_refs.h"5
+6
+#include "../templ/duc_git_repo_refs.h"7
+8
+static orb_rc_t fetch(orb_module_t *self) {9
+ duc_git_repo_refs_t *data = self->data;10
+ git_repo_refs_param_list_t *param_list = self->param_list;11
+ if (!param_list) return ORB_FATAL(1);12
+ if (!param_list->repo) return ORB_FATAL(2);13
+ data->repo = param_list->repo;14
+ git_reference_iterator *iter = NULL;15
+ git_reference *ref_handle = NULL;16
+ git_reference_iterator_new(&iter, param_list->repo->handle);17
+ while (git_reference_next(&ref_handle, iter) == 0) {18
+ data->ref_list = realloc(data->ref_list, sizeof(git_repo_ref_t) * (data->ref_list_len + 1));19
+ git_repo_ref_t *ref = &data->ref_list[data->ref_list_len];20
+ memset(ref, 0, sizeof(git_repo_ref_t));21
+ const git_oid *oid = git_reference_target(ref_handle);22
+ char oid_str[GIT_OID_HEXSZ + 1];23
+ git_oid_tostr(oid_str, sizeof(oid_str), oid);24
+ ref->oid = strdup(oid_str);25
+ const char *ref_name = git_reference_name(ref_handle);26
+ if (git_reference_is_branch(ref_handle)) {27
+ ref->name = strdup(ref_name + strlen("refs/heads/"));28
+ ref->type = "branch";29
+ }30
+ if (git_reference_is_tag(ref_handle)) {31
+ ref->name = strdup(ref_name + strlen("refs/tags/"));32
+ ref->type = "tag";33
+ }34
+ if (git_reference_is_remote(ref_handle)) {35
+ ref->name = strdup(ref_name + strlen("refs/remotes/"));36
+ ref->type = "remote";37
+ }38
+ if (!ref->name) {39
+ ref->name = strdup(ref_name);40
+ }41
+ git_reference_free(ref_handle);42
+ data->ref_list_len++;43
+ }44
+ git_reference_iterator_free(iter);45
+ return ORB_OK;46
+}47
+48
+static orb_rc_t render(orb_module_t *self, void *out) {49
+ return ORB_ERR(duc_git_repo_refs_render(out, (duc_git_repo_refs_t *) self->data));50
+}51
+52
+static void cleanup(orb_module_t *self) {53
+ duc_git_repo_refs_t *data = self->data;54
+ if (data->ref_list && data->ref_list_len > 0) {55
+ for (size_t i = 0; i < data->ref_list_len; i++) {56
+ git_repo_ref_t *ref = &data->ref_list[i];57
+ free(ref->oid);58
+ free(ref->name);59
+ }60
+ free(data->ref_list);61
+ data->ref_list = NULL;62
+ data->ref_list_len = 0;63
+ }64
+}65
+66
+const orb_module_type_t GIT_REPO_REFS_TYPE = {67
+ .size = sizeof(duc_git_repo_refs_t),68
+ .fetch = fetch,69
+ .render = render,70
+ .cleanup = cleanup,71
+};
diff --git a/src/module/git_repo_refs.h b/src/module/git_repo_refs.h
new file mode 100644
index 0000000..47b9a7c
--- /dev/null
+++ b/src/module/git_repo_refs.h
1
@@ -0,0 +1,10 @@2
+#pragma once3
+4
+#include <orb.h>5
+#include "git_repo.h"6
+7
+typedef struct {8
+ git_repo_t *repo;9
+} git_repo_refs_param_list_t;10
+11
+extern const orb_module_type_t GIT_REPO_REFS_TYPE;
diff --git a/src/module/git_repo_tree.c b/src/module/git_repo_tree.c
new file mode 100644
index 0000000..2ec8d50
--- /dev/null
+++ b/src/module/git_repo_tree.c
1
@@ -0,0 +1,97 @@2
+#include <git2.h>3
+#include "git_repo.h"4
+#include "git_repo_tree.h"5
+6
+#include "../templ/duc_git_repo_tree.h"7
+8
+static int _fetch_tree_cb(const char *root, const git_tree_entry *entry_handle, void *data) {9
+ git_repo_tree_cb_t *handle = data;10
+ const char *name = git_tree_entry_name(entry_handle);11
+ git_filemode_t mode_handle = git_tree_entry_filemode(entry_handle);12
+ if (mode_handle != GIT_FILEMODE_TREE) {13
+ *handle->entry_list = realloc(*handle->entry_list, sizeof(git_repo_tree_entry_t) * (*handle->entry_list_len + 1));14
+ git_repo_tree_entry_t *entry = &(*handle->entry_list)[*handle->entry_list_len];15
+ memset(entry, 0, sizeof(git_repo_tree_entry_t));16
+ const git_oid *oid = git_tree_entry_id(entry_handle);17
+ char oid_str[GIT_OID_HEXSZ + 1];18
+ git_oid_tostr(oid_str, sizeof(oid_str), oid);19
+ entry->oid = strdup(oid_str);20
+ size_t filepath_len = strlen(root) + strlen(name);21
+ char filepath[filepath_len + 1];22
+ snprintf(filepath, sizeof(filepath), "%s%s", root, name);23
+ entry->name = strdup(filepath);24
+ entry->mode = strdup((mode_handle == GIT_FILEMODE_BLOB_EXECUTABLE) ? "-rwx-xr-x" : (mode_handle == GIT_FILEMODE_TREE) ? "d---------" : "-rw-r--r--");25
+ git_object_t obj_type = git_tree_entry_type(entry_handle);26
+ if (obj_type == GIT_OBJECT_BLOB) {27
+ git_object *obj_handle = NULL;28
+ git_tree_entry_to_object(&obj_handle, handle->repo_handle, entry_handle);29
+ git_blob *blob_handle = (git_blob *) obj_handle;30
+ entry->size = (size_t) git_blob_rawsize(blob_handle);31
+ git_object_free(obj_handle);32
+ }33
+ (*handle->entry_list_len)++;34
+ }35
+ return 0;36
+}37
+38
+static orb_rc_t fetch(orb_module_t *self) {39
+ duc_git_repo_tree_t *data = self->data;40
+ git_repo_tree_param_list_t *param_list = self->param_list;41
+ if (!param_list) return ORB_ERR(1);42
+ if (!param_list->repo) return ORB_ERR(2);43
+ data->repo = param_list->repo;44
+ git_object *obj = NULL;45
+ git_commit *commit_handle = NULL;46
+ char *ref_oid = NULL;47
+ if (param_list->repo->ref_head) {48
+ ref_oid = param_list->repo->ref_head->oid;49
+ }50
+ if (param_list->repo->ref) {51
+ ref_oid = param_list->repo->ref->oid;52
+ }53
+ if (!ref_oid) return ORB_ERR(3);54
+ const char *spec_suffix = "^{commit}";55
+ size_t spec_len = strlen(ref_oid) + strlen(spec_suffix) + 1;56
+ char spec[spec_len];57
+ snprintf(spec, spec_len, "%s%s", ref_oid, spec_suffix);58
+ if (git_revparse_single(&obj, param_list->repo->handle, spec) == 0) {59
+ commit_handle = (git_commit *) obj;60
+ git_tree *tree_handle = NULL;61
+ if (git_commit_tree(&tree_handle, commit_handle) == 0) {62
+ git_tree_walk(tree_handle, GIT_TREEWALK_PRE, _fetch_tree_cb, &((git_repo_tree_cb_t) {63
+ .repo_handle = param_list->repo->handle,64
+ .entry_list = &data->entry_list,65
+ .entry_list_len = &data->entry_list_len,66
+ }));67
+ git_tree_free(tree_handle);68
+ }69
+ git_object_free(obj);70
+ }71
+ return ORB_OK;72
+}73
+74
+static orb_rc_t render(orb_module_t *self, void *out) {75
+ return ORB_ERR(duc_git_repo_tree_render(out, (duc_git_repo_tree_t *) self->data));76
+}77
+78
+static void cleanup(orb_module_t *self) {79
+ duc_git_repo_tree_t *data = self->data;80
+ if (data->entry_list && data->entry_list_len > 0) {81
+ for (size_t i = 0; i < data->entry_list_len; i++) {82
+ git_repo_tree_entry_t *entry = &data->entry_list[i];83
+ free(entry->oid);84
+ free(entry->name);85
+ free(entry->mode);86
+ }87
+ free(data->entry_list);88
+ data->entry_list = NULL;89
+ data->entry_list_len = 0;90
+ }91
+}92
+93
+const orb_module_type_t GIT_REPO_TREE_TYPE = {94
+ .size = sizeof(duc_git_repo_tree_t),95
+ .fetch = fetch,96
+ .render = render,97
+ .cleanup = cleanup,98
+};
diff --git a/src/module/git_repo_tree.h b/src/module/git_repo_tree.h
new file mode 100644
index 0000000..1803a5d
--- /dev/null
+++ b/src/module/git_repo_tree.h
1
@@ -0,0 +1,16 @@2
+#pragma once3
+4
+#include <orb.h>5
+#include "git_repo.h"6
+7
+typedef struct {8
+ git_repo_t *repo;9
+} git_repo_tree_param_list_t;10
+11
+typedef struct {12
+ git_repository *repo_handle;13
+ git_repo_tree_entry_t **entry_list;14
+ size_t *entry_list_len;15
+} git_repo_tree_cb_t;16
+17
+extern const orb_module_type_t GIT_REPO_TREE_TYPE;
diff --git a/src/module/index.c b/src/module/index.c
new file mode 100644
index 0000000..450e03e
--- /dev/null
+++ b/src/module/index.c
1
@@ -0,0 +1,22 @@2
+#include "index.h"3
+#include "../templ/duc_index.h"4
+5
+static orb_rc_t init(orb_module_t *self) {6
+ duc_index_t *data = (duc_index_t *) self->data;7
+ (void) data;8
+ if (self->param_list) {9
+ index_param_list_t *param_list = (index_param_list_t *) self->param_list;10
+ (void) param_list;11
+ }12
+ return ORB_OK;13
+}14
+15
+static orb_rc_t render(orb_module_t *self, void *out) {16
+ return ORB_ERR(duc_index_render(out, (duc_index_t *) self->data));17
+}18
+19
+const orb_module_type_t INDEX_TYPE = {20
+ .size = sizeof(duc_index_t),21
+ .init = init,22
+ .render = render,23
+};
diff --git a/src/module/index.h b/src/module/index.h
new file mode 100644
index 0000000..5d881e7
--- /dev/null
+++ b/src/module/index.h
1
@@ -0,0 +1,9 @@2
+#pragma once3
+4
+#include <orb.h>5
+6
+typedef struct {7
+ char *title;8
+} index_param_list_t;9
+10
+extern const orb_module_type_t INDEX_TYPE;
diff --git a/src/module/page.c b/src/module/page.c
new file mode 100644
index 0000000..c002658
--- /dev/null
+++ b/src/module/page.c
1
@@ -0,0 +1,22 @@2
+#include "page.h"3
+#include "../templ/duc_page.h"4
+5
+static orb_rc_t init(orb_module_t *self) {6
+ duc_page_t *data = (duc_page_t *) self->data;7
+ if (self->param_list) {8
+ page_param_list_t *param_list = (page_param_list_t *) self->param_list;9
+ data->title = param_list->title;10
+ data->module = param_list->module;11
+ }12
+ return ORB_OK;13
+}14
+15
+static orb_rc_t render(orb_module_t *self, void *out) {16
+ return ORB_ERR(duc_page_render(out, (duc_page_t *) self->data));17
+}18
+19
+const orb_module_type_t PAGE_TYPE = {20
+ .size = sizeof(duc_page_t),21
+ .init = init,22
+ .render = render,23
+};
diff --git a/src/module/page.h b/src/module/page.h
new file mode 100644
index 0000000..25761d8
--- /dev/null
+++ b/src/module/page.h
1
@@ -0,0 +1,10 @@2
+#pragma once3
+4
+#include <orb.h>5
+6
+typedef struct {7
+ char *title;8
+ orb_module_t *module;9
+} page_param_list_t;10
+11
+extern const orb_module_type_t PAGE_TYPE;
diff --git a/src/registry.h b/src/registry.h
new file mode 100644
index 0000000..d7e56c3
--- /dev/null
+++ b/src/registry.h
1
@@ -0,0 +1,34 @@2
+#pragma once3
+4
+#include <tpee.h>5
+#include <orb.h>6
+#include "module/page.h"7
+#include "module/default.h"8
+#include "module/index.h"9
+#include "module/git_list.h"10
+#include "module/git_repo.h"11
+#include "module/git_repo_log.h"12
+#include "module/git_repo_tree.h"13
+#include "module/git_repo_refs.h"14
+#include "module/git_repo_commit.h"15
+#include "module/git_repo_blob.h"16
+17
+typedef struct {18
+ const char* id;19
+ const orb_module_type_t *type;20
+} registry_entry_t;21
+22
+static const registry_entry_t REGISTRY[] = {23
+ { "page", &PAGE_TYPE },24
+ { "default", &DEFAULT_TYPE },25
+ { "index", &INDEX_TYPE },26
+ { "git_list", &GIT_LIST_TYPE },27
+ { "git_repo", &GIT_REPO_TYPE },28
+ { "git_repo_log", &GIT_REPO_LOG_TYPE },29
+ { "git_repo_tree", &GIT_REPO_TREE_TYPE },30
+ { "git_repo_refs", &GIT_REPO_REFS_TYPE },31
+ { "git_repo_commit", &GIT_REPO_COMMIT_TYPE },32
+ { "git_repo_blob", &GIT_REPO_BLOB_TYPE },33
+};34
+35
+static const size_t REGISTRY_LEN = sizeof(REGISTRY) / sizeof(REGISTRY[0]);
diff --git a/src/util.h b/src/util.h
new file mode 100644
index 0000000..a40024b
--- /dev/null
+++ b/src/util.h
1
@@ -0,0 +1,24 @@2
+#pragma once3
+4
+void util_md_html(const char *str, void *out);5
+6
+#ifdef UTIL_IMPL7
+8
+#include <md4c-html.h>9
+10
+static void _util_md_html_parse_cb(const MD_CHAR *str, MD_SIZE len, void *out) {11
+ buf_write_len((buf_t *) out, str, len);12
+}13
+14
+void util_md_html(const char *str, void *out) {15
+ if (!str || !out) return;16
+ unsigned parser_flags =17
+ MD_FLAG_STRIKETHROUGH |18
+ MD_FLAG_TABLES |19
+ MD_FLAG_HIGHLIGHT |20
+ MD_FLAG_TASKLISTS;21
+ unsigned renderer_flags = 0;22
+ md_html(str, strlen(str), _util_md_html_parse_cb, out, parser_flags, renderer_flags);23
+}24
+25
+#endif // UTIL_IMPL
diff --git a/templ/default.html b/templ/default.html
new file mode 100644
index 0000000..9b8276a
--- /dev/null
+++ b/templ/default.html
1
@@ -0,0 +1,33 @@2
+% #include "src/duc_macro.h" %3
+4
+% DUC_TEMPLDEF(5
+ duc_default,6
+) %7
+8
+<style>9
+10
+ .default {11
+ margin: 4rem 0px;12
+13
+ h1 {14
+ text-align: center;15
+ }16
+17
+ h3 {18
+ text-align: center;19
+ }20
+21
+ p {22
+ text-align: center;23
+ }24
+ }25
+26
+</style>27
+28
+<div class="default">29
+ <h1>404</h1>30
+ <h3>Page not found</h3>31
+ <p>This page doesn't exist or has been moved.</p>32
+</div>33
+34
+% DUC_ENDTEMPL %
diff --git a/templ/git_list.html b/templ/git_list.html
new file mode 100644
index 0000000..9d46f81
--- /dev/null
+++ b/templ/git_list.html
1
@@ -0,0 +1,31 @@2
+% #include "src/duc_macro.h" %3
+% #include "src/module/git_list.h" %4
+5
+% DUC_TEMPLDEF(6
+ duc_git_list,7
+ git_repo_meta_t *repo_meta_list;8
+ size_t repo_meta_list_len;9
+) %10
+11
+<ul class="row-list list--repo">12
+ <li class="row row--head">13
+ <span class="col col--name">NAME</span>14
+ <span class="col col--description">DESCRIPTION</span>15
+ <span class="col col--owner">OWNER</span>16
+ </li>17
+ % for (size_t i = 0; i < ctx->repo_meta_list_len; i++) {18
+ git_repo_meta_t *repo_meta = &ctx->repo_meta_list[i];19
+ %20
+ <li class="row">21
+ <span class="col col--name">22
+ <a href="/git/% STRING(repo_meta->name) %">% STRING(repo_meta->name) %</a>23
+ % if (repo_meta->is_archive) { %<span>(archive)</span>% } %24
+ % if (repo_meta->is_fork) { %<span>(fork)</span>% } %25
+ </span>26
+ <span class="col col--description">% STRING(repo_meta->description) %</span>27
+ <span class="col col--owner">% STRING(repo_meta->owner) %</span>28
+ </li>29
+ % } %30
+</ul>31
+32
+% DUC_ENDTEMPL %
diff --git a/templ/git_repo.html b/templ/git_repo.html
new file mode 100644
index 0000000..b3fad4c
--- /dev/null
+++ b/templ/git_repo.html
1
@@ -0,0 +1,97 @@2
+% #include "src/duc_macro.h" %3
+% #include "src/module/git_repo.h" %4
+% #include "src/module/git_repo_log.h" %5
+% #include "src/module/git_repo_tree.h" %6
+% #include "src/module/git_repo_refs.h" %7
+% #include "src/module/git_repo_commit.h" %8
+% #include "src/module/git_repo_blob.h" %9
+10
+% DUC_TEMPLDEF(11
+ duc_git_repo,12
+ git_repo_t repo;13
+ const char *tab_mode;14
+) %15
+16
+<div class="git--repo">17
+ <h2>% STRING(ctx->repo.meta.name) %</h2>18
+ <p>19
+ % if (ctx->repo.meta.description) { % <span>% STRING(ctx->repo.meta.description) %</span><br/> % } %20
+ git clone <a href="git://soophie.de/% STRING(ctx->repo.meta.name) %">git://soophie.de/% STRING(ctx->repo.meta.name) %</a>21
+ </p>22
+ <ul class="link-list">23
+ <li class="link % if (strcmp(ctx->tab_mode, "log") == 0 || strcmp(ctx->tab_mode, "commit") == 0) { %link--active% } %"><a href="/git/% STRING(ctx->repo.meta.name) if (ctx->repo.ref) { %?ref=% STRING(ctx->repo.ref->oid) } %">log</a></li>24
+ % bool is_readme = ctx->repo.oid && ctx->repo.readme_oid && strcmp(ctx->repo.oid, ctx->repo.readme_oid) == 0; %25
+ % bool is_license = ctx->repo.oid && ctx->repo.license_oid && strcmp(ctx->repo.oid, ctx->repo.license_oid) == 0; %26
+ <li class="link % if (strcmp(ctx->tab_mode, "tree") == 0 || (strcmp(ctx->tab_mode, "blob") == 0 && !is_readme && !is_license)) { %link--active% } %"><a href="/git/% STRING(ctx->repo.meta.name) %/tree% if (ctx->repo.ref) { %?ref=% STRING(ctx->repo.ref->oid) } %">tree</a></li>27
+ <li class="link % if (strcmp(ctx->tab_mode, "refs") == 0) { %link--active% } %"><a href="/git/% STRING(ctx->repo.meta.name) %/refs% if (ctx->repo.ref) { %?ref=% STRING(ctx->repo.ref->oid) } %">refs</a></li>28
+ % if (ctx->repo.readme_oid) { %29
+ <li class="link % if (strcmp(ctx->tab_mode, "blob") == 0 && is_readme) { %link--active% } %"><a href="/git/% STRING(ctx->repo.meta.name) %/blob?% if (ctx->repo.ref) { %ref=% STRING(ctx->repo.ref->oid) %&% } %oid=% STRING(ctx->repo.readme_oid) %">README</a></li>30
+ % } %31
+ % if (ctx->repo.license_oid) { %32
+ <li class="link % if (strcmp(ctx->tab_mode, "blob") == 0 && is_license) { %link--active% } %"><a href="/git/% STRING(ctx->repo.meta.name) %/blob?% if (ctx->repo.ref) { %ref=% STRING(ctx->repo.ref->oid) %&% } %oid=% STRING(ctx->repo.license_oid) %">LICENSE</a></li>33
+ % } %34
+ </ul>35
+ %36
+ char *tab = "";37
+ if (ctx->tab_mode) {38
+ if (strcmp(ctx->tab_mode, "log") == 0) tab = "log";39
+ if (strcmp(ctx->tab_mode, "commit") == 0) tab = "log";40
+ if (strcmp(ctx->tab_mode, "tree") == 0) tab = "tree";41
+ if (strcmp(ctx->tab_mode, "blob") == 0) tab = "tree";42
+ if (strcmp(ctx->tab_mode, "refs") == 0) tab = "refs";43
+ }44
+ bool is_branch_select = true;45
+ if (ctx->repo.oid) {46
+ if (ctx->repo.readme_oid && strcmp(ctx->repo.oid, ctx->repo.readme_oid) == 0) is_branch_select = false;47
+ if (ctx->repo.license_oid && strcmp(ctx->repo.oid, ctx->repo.license_oid) == 0) is_branch_select = false;48
+ }49
+ %50
+ % if (is_branch_select) { %51
+ <select class="repo--branch" onchange="window.location.href = '/git/% STRING(ctx->repo.meta.name) %/% STRING(tab) %?ref=' + this.value">52
+ % for (size_t i = 0; i < ctx->repo.ref_list_len; i++) {53
+ git_repo_ref_t *ref = &ctx->repo.ref_list[i];54
+ if (!ref->name || !ref->oid) continue;55
+ bool is_selected = (ctx->repo.ref && strcmp(ctx->repo.ref->oid, ref->oid) == 0)56
+ || (!ctx->repo.ref && ctx->repo.ref_head && strcmp(ctx->repo.ref_head->oid, ref->oid) == 0);57
+ %58
+ <option value="% STRING(ref->oid) %" % if (is_selected) { %selected% } %>59
+ % if (ref->type) { STRING(ref->type) %:% } STRING(ref->name) %60
+ </option>61
+ % } %62
+ </select>63
+ % } %64
+ <div class="repo--tab">65
+ % if (ctx->tab_mode && strcmp(ctx->tab_mode, "log") == 0) {66
+ ORB_MODULE_DEF(GIT_REPO_LOG_TYPE, &((git_repo_log_param_list_t) {67
+ .repo = &ctx->repo,68
+ }))69
+ } %70
+ % if (ctx->tab_mode && strcmp(ctx->tab_mode, "tree") == 0) {71
+ ORB_MODULE_DEF(GIT_REPO_TREE_TYPE, &((git_repo_tree_param_list_t) {72
+ .repo = &ctx->repo,73
+ }))74
+ } %75
+ % if (ctx->tab_mode && strcmp(ctx->tab_mode, "refs") == 0) {76
+ ORB_MODULE_DEF(GIT_REPO_REFS_TYPE, &((git_repo_refs_param_list_t) {77
+ .repo = &ctx->repo,78
+ }))79
+ } %80
+ % if (ctx->tab_mode && strcmp(ctx->tab_mode, "commit") == 0) {81
+ ORB_MODULE_DEF(GIT_REPO_COMMIT_TYPE, &((git_repo_commit_param_list_t) {82
+ .repo = &ctx->repo,83
+ }))84
+ } %85
+ % if (ctx->tab_mode && strcmp(ctx->tab_mode, "blob") == 0) {86
+ bool is_markdown = false;87
+ if (ctx->repo.oid) {88
+ if (ctx->repo.readme_oid && strcmp(ctx->repo.oid, ctx->repo.readme_oid) == 0) is_markdown = true;89
+ }90
+ ORB_MODULE_DEF(GIT_REPO_BLOB_TYPE, &((git_repo_blob_param_list_t) {91
+ .repo = &ctx->repo,92
+ .is_markdown = is_markdown,93
+ }))94
+ } %95
+ </div>96
+</div>97
+98
+% DUC_ENDTEMPL %
diff --git a/templ/git_repo_blob.html b/templ/git_repo_blob.html
new file mode 100644
index 0000000..2a0c059
--- /dev/null
+++ b/templ/git_repo_blob.html
1
@@ -0,0 +1,22 @@2
+% #include "src/duc_macro.h" %3
+% #include "src/module/git_repo.h" %4
+% #include "src/module/git_repo_blob.h" %5
+6
+% DUC_TEMPLDEF(7
+ duc_git_repo_blob,8
+ git_repo_t *repo;9
+ git_repo_blob_t *blob;10
+ bool is_markdown;11
+) %12
+13
+% if (ctx->is_markdown) { %14
+ <div class="blob--markdown">% MARKDOWN(ctx->blob->content) %</div>15
+% } else { %16
+ <p>17
+ <span>blob: % STRING(ctx->blob->oid) %</span><br/>18
+ <span>size: % NUMBER(ctx->blob->size) %</span>19
+ </p>20
+ <pre class="blob--code">% STRING(ctx->blob->content) %</pre>21
+% } %22
+23
+% DUC_ENDTEMPL %
diff --git a/templ/git_repo_commit.html b/templ/git_repo_commit.html
new file mode 100644
index 0000000..3ac9e24
--- /dev/null
+++ b/templ/git_repo_commit.html
1
@@ -0,0 +1,82 @@2
+% #include "src/duc_macro.h" %3
+% #include "src/module/git_repo.h" %4
+% #include "src/module/git_repo_commit.h" %5
+% #include <math.h> %6
+7
+% DUC_TEMPLDEF(8
+ duc_git_repo_commit,9
+ git_repo_t *repo;10
+ git_repo_commit_t *commit;11
+ char *parent_commit_oid;12
+ char *diff;13
+) %14
+15
+<p>16
+ <h3>% STRING(ctx->commit->message) %</h3>17
+ <span>commit: <a href="/git/% STRING(ctx->repo->meta.name) %/commit?oid=% STRING(ctx->commit->oid) %">% STRING(ctx->commit->oid) %</a></span><br/>18
+ % if (ctx->parent_commit_oid) { %19
+ <span>parent: <a href="/git/% STRING(ctx->repo->meta.name) %/commit?oid=% STRING(ctx->parent_commit_oid) %">% STRING(ctx->parent_commit_oid) %</a></span><br/>20
+ % } %21
+ <span>author: % STRING(ctx->commit->author_name) % <<a href="mailto:% STRING(ctx->commit->author_email) %">% STRING(ctx->commit->author_email) %</a>></span><br/>22
+ <span>time: % FORMAT_TIME(&ctx->commit->time, "%%Y-%%m-%%d %%H:%%M") %</span></br>23
+</p>24
+<div class="commit-diff">25
+ % if (ctx->diff) { %26
+ <pre class="diff--meta">%27
+28
+ size_t len = strlen(ctx->diff);29
+ size_t i = 0;30
+ size_t ln = 0;31
+ size_t sol = 0;32
+ while (i < len) {33
+ char c = ctx->diff[i];34
+ if (c == '\n') {35
+ ln++;36
+ }37
+ i++;38
+ }39
+ size_t pad = floor(log10(ln + 1)) + 1;40
+ i = 0;41
+ ln = 0;42
+ sol = 0;43
+ size_t df = 0;44
+ bool is_meta = false;45
+ while (i < len) {46
+ char c = ctx->diff[i];47
+ if (c == '\n') {48
+ size_t ln_len = i - sol;49
+ char line[ln_len + 1];50
+ memcpy(line, ctx->diff + sol, ln_len);51
+ line[ln_len] = '\0';52
+ sol = i + 1;53
+ bool is_diff = strncmp(line, "diff --git", 10) == 0;54
+ bool is_hunk = strncmp(line, "@@ ", 3) == 0;55
+ if (is_diff) {56
+ is_meta = true;57
+ }58
+ if (is_diff && ln != 0) {59
+ df++;60
+ %</pre>%61
+ %<pre class="diff--meta">%62
+ }63
+ if (is_hunk && is_meta) {64
+ is_meta = false;65
+ ln = 0;66
+ %</pre>%67
+ %<pre class="diff--code">%68
+ }69
+ if (is_meta) {70
+ %<p>%STRING(line)%</p>%71
+ }72
+ else {73
+ %<p id="% NUMBER(df) %-% NUMBER(ln + 1) %" class="% if (is_hunk) { %is-hunk% } % % if (strncmp(line, "+", 1) == 0 && strncmp(line, "+++", 3) != 0) { %is-add% } % % if (strncmp(line, "-", 1) == 0 && strncmp(line, "---", 3) != 0) { %is-rem% } %"><span class="ln"><a href="#% NUMBER(df) %-% NUMBER(ln + 1) %">% PRINTF("%%*ld", pad, ln + 1) %</a></span><span class="line"><code>% STRING(line) %</code></span></p>%74
+ }75
+ ln++;76
+ }77
+ i++;78
+ }79
+ %</pre>80
+ % } %81
+</div>82
+83
+% DUC_ENDTEMPL %
diff --git a/templ/git_repo_log.html b/templ/git_repo_log.html
new file mode 100644
index 0000000..19d3593
--- /dev/null
+++ b/templ/git_repo_log.html
1
@@ -0,0 +1,32 @@2
+% #include "src/duc_macro.h" %3
+% #include "src/module/git_repo.h" %4
+% #include "src/module/git_repo_log.h" %5
+6
+% DUC_TEMPLDEF(7
+ duc_git_repo_log,8
+ git_repo_t *repo;9
+ git_repo_commit_t *commit_list;10
+ size_t commit_list_len;11
+) %12
+13
+<p>% NUMBER(ctx->commit_list_len) % commit% if (ctx->commit_list_len != 1) { %s% } %</p>14
+<ul class="row-list list--log">15
+ <li class="row row--head">16
+ <span class="col col--oid">OID</span>17
+ <span class="col col--time">TIME</span>18
+ <span class="col col--message">MESSAGE</span>19
+ <span class="col col--author">AUTHOR</span>20
+ </li>21
+ % for (size_t i = 0; i < ctx->commit_list_len; i++) {22
+ git_repo_commit_t *commit = &ctx->commit_list[i];23
+ %24
+ <li class="row">25
+ <span class="col col--oid">% PRINTF("%%.7s", commit->oid) %</span>26
+ <span class="col col--time">% FORMAT_TIME(&commit->time, "%%Y-%%m-%%d") %</span>27
+ <span class="col col--message"><a href="/git/% STRING(ctx->repo->meta.name) %/commit?% if (ctx->repo->ref) { %ref=% STRING(ctx->repo->ref->oid) %&% } %oid=% STRING(commit->oid) %">% STRING(commit->message) %</a></span>28
+ <span class="col col--author">% STRING(commit->author_name) %</span>29
+ </li>30
+ % } %31
+</ul>32
+33
+% DUC_ENDTEMPL %
diff --git a/templ/git_repo_refs.html b/templ/git_repo_refs.html
new file mode 100644
index 0000000..93e6fc6
--- /dev/null
+++ b/templ/git_repo_refs.html
1
@@ -0,0 +1,27 @@2
+% #include "src/duc_macro.h" %3
+% #include "src/module/git_repo.h" %4
+% #include "src/module/git_repo_refs.h" %5
+6
+% DUC_TEMPLDEF(7
+ duc_git_repo_refs,8
+ git_repo_t *repo;9
+ git_repo_ref_t *ref_list;10
+ size_t ref_list_len;11
+) %12
+13
+<ul class="row-list list--refs">14
+ <li class="row row--head">15
+ <span class="col col--oid">OID</a></span>16
+ <span class="col col--name">NAME</span>17
+ </li>18
+ % for (size_t i = 0; i < ctx->ref_list_len; i++) {19
+ git_repo_ref_t *ref = &ctx->ref_list[i];20
+ %21
+ <li class="row">22
+ <span class="col col--oid">% PRINTF("%%.7s", ref->oid) %</span>23
+ <span class="col col--name"><a href="/git/% STRING(ctx->repo->meta.name) if (strcmp(ctx->repo->ref_head->oid, ref->oid) != 0) { %?ref=% STRING(ref->oid) } %">% if (ref->type) { STRING(ref->type) %:% } STRING(ref->name) %</a></span>24
+ </li>25
+ % } %26
+</ul>27
+28
+% DUC_ENDTEMPL %
diff --git a/templ/git_repo_tree.html b/templ/git_repo_tree.html
new file mode 100644
index 0000000..a1c2cc8
--- /dev/null
+++ b/templ/git_repo_tree.html
1
@@ -0,0 +1,31 @@2
+% #include "src/duc_macro.h" %3
+% #include "src/module/git_repo.h" %4
+% #include "src/module/git_repo_tree.h" %5
+6
+% DUC_TEMPLDEF(7
+ duc_git_repo_tree,8
+ git_repo_t *repo;9
+ git_repo_tree_entry_t *entry_list;10
+ size_t entry_list_len;11
+) %12
+13
+<ul class="row-list list--tree">14
+ <li class="row row--head">15
+ <span class="col col--oid">OID</span>16
+ <span class="col col--mode">MODE</span>17
+ <span class="col col--name">NAME</span>18
+ <span class="col col--size">SIZE</span>19
+ </li>20
+ % for (size_t i = 0; i < ctx->entry_list_len; i++) {21
+ git_repo_tree_entry_t *entry = &ctx->entry_list[i];22
+ %23
+ <li class="row">24
+ <span class="col col--oid">% PRINTF("%%.7s", entry->oid) %</span>25
+ <span class="col col--mode">% STRING(entry->mode) %</span>26
+ <span class="col col--name"><a href="/git/% STRING(ctx->repo->meta.name) %/blob?% if (ctx->repo->ref) { %ref=% STRING(ctx->repo->ref->oid) %&% } %oid=% STRING(entry->oid) %">% STRING(entry->name) %</a></span>27
+ <span class="col col--size">% NUMBER(entry->size) %</span>28
+ </li>29
+ % } %30
+</ul>31
+32
+% DUC_ENDTEMPL %
diff --git a/templ/index.html b/templ/index.html
new file mode 100644
index 0000000..541300b
--- /dev/null
+++ b/templ/index.html
1
@@ -0,0 +1,12 @@2
+% #include "src/duc_macro.h" %3
+4
+% DUC_TEMPLDEF(5
+ duc_index,6
+) %7
+8
+<p>Hi there! I'm Sophie, and welcome to my own little place on the internet.</p>9
+<p>I'm a self-thought programmer who likes to travel and learn new stuff. I enjoy listening to music, being outside in nature, and having a chat with a good cup of coffee.</p>10
+<p>I'm currently working as an SAP E-Commerce Consultant at <i>innovate Software GmbH</i>. My expertise lies in developing custom-tailored software solutions for the SAP Commerce ecosystem.</p>11
+<p>You can get in touch with me via E-mail: <a href="mailto:info@soophie.de">info@soophie.de</a></p>12
+13
+% DUC_ENDTEMPL %
diff --git a/templ/page.html b/templ/page.html
new file mode 100644
index 0000000..00e8e83
--- /dev/null
+++ b/templ/page.html
1
@@ -0,0 +1,27 @@2
+% #include "src/duc_macro.h" %3
+4
+% DUC_TEMPLDEF(5
+ duc_page,6
+ char *title;7
+ orb_module_t *module;8
+) %9
+10
+<!DOCTYPE html>11
+<html lang="en">12
+ <head>13
+ <meta charset="utf-8">14
+ <meta name="viewport" content="width=device-width, initial-scale=1">15
+ <title>% STRING(ctx->title) %</title>16
+ <link rel="stylesheet" href="/asset/style/main.css">17
+ </head>18
+ <body>19
+ <h1>so://phie</h1>20
+ <nav>21
+ <a href="/">home</a>22
+ <a href="/git">git</a>23
+ </nav>24
+ % ORB_MODULE(ctx->module) %25
+ </body>26
+</html>27
+28
+% DUC_ENDTEMPL %