summaryrefslogtreecommitdiff
path: root/src/simplewiki_main.c
diff options
context:
space:
mode:
authorLinnnus <[email protected]>2024-02-17 00:26:40 +0100
committerLinnnus <[email protected]>2024-02-17 00:47:08 +0100
commitad937750d754676f8e9e01ce2c1021ad1a80ee04 (patch)
tree470920cffe3d30f466ba837b68ac885a1a9f0aef /src/simplewiki_main.c
parentdd4f820139f81ae5013824f24c5392b567562c41 (diff)
feat: Add creole utility
This patch a) gives the different binaries' main() files more meaning full names and b) adds the creole binary, which works like smu except for my dialect of Creole (which should hopefully approach proper Creole as this repo matures).
Diffstat (limited to 'src/simplewiki_main.c')
-rw-r--r--src/simplewiki_main.c173
1 files changed, 173 insertions, 0 deletions
diff --git a/src/simplewiki_main.c b/src/simplewiki_main.c
new file mode 100644
index 0000000..5c04467
--- /dev/null
+++ b/src/simplewiki_main.c
@@ -0,0 +1,173 @@
+#include "arena.h"
+#include "die.h"
+#include "strutil.h"
+#include "creole.h"
+
+// #include <assert.h>
+#include <errno.h> // errno, EEXIST
+#include <git2.h> // git_*
+#include <stdbool.h> // false
+#include <stdio.h>
+#include <stdlib.h> // EXIT_SUCCESS
+#include <sys/stat.h> // mkdir
+#include <sys/types.h> // mode_t
+
+void xmkdir(const char *path, mode_t mode, bool exist_ok) {
+ if (mkdir(path, mode) < 0) {
+ if (exist_ok && errno == EEXIST) {
+ return;
+ } else {
+ die_errno("failed to mkdir %s", path);
+ }
+ }
+}
+
+void process_other_file(const char *path, const char *source, size_t source_len) {
+ FILE *out = fopen(path, "w");
+ printf("Copying: %s\n", path);
+ if (out == NULL) {
+ die_errno("failed to open %s for writing", path);
+ }
+ if (fwrite(source, 1, source_len, out) < source_len) {
+ die_errno("failed to write content to %s\n", path);
+ }
+ fclose(out);
+}
+
+void process_markup_file(struct arena *a, const char *path, const char *source, size_t source_len) {
+ char *out_path = replace_suffix(a, path, ".txt", ".html");
+ printf("Generating: %s\n", out_path);
+ FILE *out = fopen(out_path, "w");
+ if (out == NULL) {
+ die_errno("failed to open %s for writing", path);
+ }
+ render_creole(out, source, source_len);
+ fclose(out);
+}
+
+void process_dir(const char *path) {
+ xmkdir(path, 0755, false);
+}
+
+void list_tree(struct arena *a, struct git_repository *repo, struct git_tree *tree, const char *prefix) {
+ // Grab a snapshot of the arena.
+ // All memory allocated within the arena in this subcalltree will be freed.
+ // This is effectively the same as allocating a new arena for each call to list_tree.
+ struct arena snapshot = *a;
+
+ size_t tree_count = git_tree_entrycount(tree);
+ for (size_t i = 0; i < tree_count; ++i) {
+ // Read the entry.
+ const struct git_tree_entry *entry;
+ if ((entry = git_tree_entry_byindex(tree, i)) == NULL) {
+ die("read tree item");
+ }
+
+ // Construct path to entry.
+ const char *entry_out_path = joinpath(a, prefix, git_tree_entry_name(entry));
+
+ // entry->obj fail on submodules. just ignore them.
+ struct git_object *obj;
+ if (git_tree_entry_to_object(&obj, repo, entry) == 0) {
+ git_object_t type = git_object_type(obj);
+ switch (type) {
+ case GIT_OBJECT_BLOB: {
+ struct git_blob *blob = (struct git_blob *)obj;
+ const char *source = git_blob_rawcontent(blob);
+ if (source == NULL) {
+ die_git("get source for blob %s", git_oid_tostr_s(git_object_id(obj)));
+ }
+ size_t source_len = git_blob_rawsize(blob);
+ if (endswith(entry_out_path, ".txt") && !git_blob_is_binary(blob)) {
+ process_markup_file(a, entry_out_path, source, source_len);
+ } else {
+ process_other_file(entry_out_path, source, source_len);
+ }
+ git_object_free(obj);
+ } break;
+ case GIT_OBJECT_TREE: {
+ process_dir(entry_out_path);
+ list_tree(a, repo, (struct git_tree *)obj, entry_out_path);
+ git_object_free(obj);
+ } break;
+ default: {
+ // Ignore whatever weird thing this is.
+ git_object_free(obj);
+ } break;
+ }
+ }
+ }
+
+ // Restore snapshot.
+ *a = snapshot;
+}
+
+int main(int argc, char *argv[])
+{
+ if (argc != 3) {
+ die("Usage: %s git-path out-path", argv[0]);
+ }
+ char *git_path = argv[1];
+ char *out_path = argv[2];
+
+ // Initialize libgit. Note that calling git_libgit2_shutdown is not
+ // necessary, as per this snippet from the documentation:
+ //
+ // > Usually you don’t need to call the shutdown function as the operating
+ // > system will take care of reclaiming resources, but if your
+ // > application uses libgit2 in some areas which are not usually active,
+ // > you can use
+ //
+ // That's good news!
+ if (git_libgit2_init() < 0) {
+ die_git("initialize libgit");
+ }
+
+ // Do not search outside the git repository. GIT_CONFIG_LEVEL_APP is the highest level currently.
+ // for (int i = 1; i <= GIT_CONFIG_LEVEL_APP; i++) {
+ // if (git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, i, "") < 0) {
+ // die_git("set search path");
+ // }
+ // }
+
+ // Don't require the repository to be owned by the current user.
+ git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 0);
+
+ struct git_repository *repo;
+ if (git_repository_open_ext(&repo, git_path, GIT_REPOSITORY_OPEN_NO_SEARCH, NULL) < 0) {
+ die_git("open repository");
+ }
+
+ // Find HEAD.
+ struct git_object *head;
+ const struct git_oid *head_id;
+ if (git_revparse_single(&head, repo, "HEAD") < 0) {
+ die_git("parse HEAD");
+ }
+ head_id = git_object_id(head);
+ git_object_free(head);
+
+ // Get a handle to the tree at head.
+ struct git_commit *commit;
+ if (git_commit_lookup(&commit, repo, head_id) < 0) {
+ die_git("look up head commit");
+ }
+ struct git_tree *tree;
+ if (git_commit_tree(&tree, commit) < 0) {
+ die_git("get tree for commit %s", git_oid_tostr_s(git_commit_id(commit)));
+ }
+
+ // Create the initial output directory.
+ xmkdir(out_path, 0755, true);
+
+ struct arena a = arena_create(1024);
+ list_tree(&a, repo, tree, out_path);
+#ifndef NDEBUG
+ arena_destroy(&a);
+#endif
+
+#ifndef NDEBUG
+ git_libgit2_shutdown();
+#endif
+ return EXIT_SUCCESS;
+}