gitout

A static git page generator
git clone https://git.bracken.jp/gitout.git
Log | Files | Refs | Submodules | README | LICENSE

commit c0e0ccdb636d9962622841f48a3e84c52dbbc67a
parent 4116626b696c0a4ee918e9b221b5724bca3451c2
Author: Chris Bracken <chris@bracken.jp>
Date:   Thu,  9 May 2024 21:35:43 +0900

Add gopher output support

gitout now writes output in gopher format when the -G option is supplied
to either gitout or gitout_index.

Diffstat:
Mformat.c | 157+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mformat.h | 12++++++++++++
Mgitout.c | 2++
Mgitout_index.c | 2++
Mwriter/BUILD.gn | 10++++++++--
Awriter/gopher/BUILD.gn | 41+++++++++++++++++++++++++++++++++++++++++
Awriter/gopher/commit.c | 210+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awriter/gopher/commit.h | 17+++++++++++++++++
Awriter/gopher/fileblob.c | 110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awriter/gopher/fileblob.h | 15+++++++++++++++
Awriter/gopher/files.c | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awriter/gopher/files.h | 15+++++++++++++++
Awriter/gopher/index_writer.c | 43+++++++++++++++++++++++++++++++++++++++++++
Awriter/gopher/index_writer.h | 14++++++++++++++
Awriter/gopher/log.c | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awriter/gopher/log.h | 21+++++++++++++++++++++
Awriter/gopher/page.c | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awriter/gopher/page.h | 18++++++++++++++++++
Awriter/gopher/refs.c | 145+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awriter/gopher/refs.h | 15+++++++++++++++
Awriter/gopher/repo_index.c | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awriter/gopher/repo_index.h | 16++++++++++++++++
Awriter/gopher/repo_writer.c | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awriter/gopher/repo_writer.h | 24++++++++++++++++++++++++
Mwriter/index_writer.c | 31+++++++++++++++++++++++++++++++
Mwriter/index_writer.h | 1+
Mwriter/repo_writer.c | 36++++++++++++++++++++++++++++++++++++
Mwriter/repo_writer.h | 1+
28 files changed, 1394 insertions(+), 2 deletions(-)

diff --git a/format.c b/format.c @@ -1,6 +1,7 @@ #include "format.h" #include <err.h> +#include <stdlib.h> #include <string.h> #include <time.h> @@ -110,3 +111,159 @@ void print_xml_encoded_len(FILE* out, } } } + +void print_gopher_text(FILE* out, const char* str, bool output_lf) { + print_gopher_text_len(out, str, -1, output_lf); +} + +void print_gopher_text_len(FILE* out, + const char* str, + ssize_t str_len, + bool output_lf) { + size_t len = (str_len >= 0) ? (size_t)str_len : strlen(str); + bool start_of_line = true; + for (size_t i = 0; i < len && str[i] != '\0'; i++) { + char c = str[i]; + if (output_lf && start_of_line && c == '[') { + fprintf(out, "[|"); + } else if (c == '\t') { + fprintf(out, " "); + } else if (c == '\r' || (!output_lf && c == '\n')) { + // Ignore. + } else { + fprintf(out, "%c", c); + } + start_of_line = (c == '\n'); + } +} + +void print_gopher_link(FILE* out, const char* str) { + for (size_t i = 0; str[i] != '\0'; i++) { + char c = str[i]; + if (c == '|') { + fprintf(out, "\\|"); + } else if (c == '\t') { + fprintf(out, " "); + } else if (c == '\r' || c == '\n') { + // Ignore. + } else { + fprintf(out, "%c", c); + } + } +} + +static size_t utf8_char_length(const char* str) { + unsigned char c = (unsigned char)*str; + if (c < 0x80) { + return 1; + } + if ((c & 0xe0) == 0xc0) { + return 2; + } + if ((c & 0xf0) == 0xe0) { + return 3; + } + if ((c & 0xf8) == 0xf0) { + return 4; + } + // Invalid UTF-8 sequence. Treat as single byte. + return 1; +} + +static bool is_zwj(const char* str) { + return str[0] == '\xe2' && str[1] == '\x80' && str[2] == '\x8d'; +} + +static bool is_variation_selector(const char* str) { + return str[0] == '\xef' && str[1] == '\xb8' && str[2] == '\x8f'; +} + +static size_t get_char_width(wchar_t c) { + // ASCII characters in the range [0-31] and [127-255] are control characters + // or non-printable characters + if ((c >= 0 && c <= 31) || (c >= 127 && c <= 255)) { + return 1; + } + + // Emoji range. + if (c >= 0x1F300 && c <= 0x1F6FF) { + return 2; + } + + // Extended pictographic characters. + if (c >= 0x1F900 && c <= 0x1F9FF) { + return 2; + } + + // Hiragana and Katakana ranges. + if ((c >= 0x3040 && c <= 0x309F) || (c >= 0x30A0 && c <= 0x30FF)) { + return 2; + } + + // Kanji range. + if (c >= 0x4E00 && c <= 0x9FFF) { + return 2; + } + return 1; +} + +// TODO(cbracken): There has to be a better way. +void print_gopher_link_padded(FILE* out, + const char* str, + size_t width, + char pad_char) { + size_t str_len = strlen(str); + size_t char_count = 0; + size_t last_char_width = 0; + size_t byte_count = 0; + while (byte_count < str_len) { + size_t pos = byte_count; + size_t char_length = utf8_char_length(&str[byte_count]); + byte_count += char_length; + if (char_count == width - 1 && byte_count < str_len) { + fprintf(out, "\u2026"); + last_char_width = 1; + char_count += last_char_width; + } else if (char_count < width) { + if (str[pos] == '|') { + fprintf(out, "\\|"); + char_count++; + last_char_width = 1; + } else if (str[pos] == '\t') { + for (size_t i = 0; i < 8 && char_count < width; i++) { + fprintf(out, " "); + char_count++; + } + last_char_width = 1; + } else if (str[pos] == '\r' || str[pos] == '\n') { + // Ignore. + last_char_width = 0; + } else if (char_length == 3 && is_zwj(&str[pos])) { + fprintf(out, "\xe2\x80\x8d"); + char_count -= last_char_width; + last_char_width = 0; + } else if (char_length == 3 && is_variation_selector(&str[pos])) { + fprintf(out, "\xef\xb8\x8f"); + char_count -= last_char_width; + last_char_width = 0; + } else { + for (size_t i = pos; i < byte_count; i++) { + fprintf(out, "%c", str[i]); + } + wchar_t wide_char; + if (mbtowc(&wide_char, &str[pos], MB_CUR_MAX) >= 0) { + last_char_width = get_char_width(wide_char); + } else { + last_char_width = 1; + } + char_count += last_char_width; + } + } + } + if (pad_char != '\0') { + while (char_count < width) { + fprintf(out, "%c", pad_char); + char_count++; + } + } +} diff --git a/format.h b/format.h @@ -32,4 +32,16 @@ void print_xml_encoded_len(FILE* out, ssize_t str_len, bool output_crlf); +void print_gopher_text(FILE* out, const char* str, bool output_lf); +void print_gopher_text_len(FILE* out, + const char* str, + ssize_t str_len, + bool output_lf); + +void print_gopher_link(FILE* out, const char* str); +void print_gopher_link_padded(FILE* out, + const char* str, + size_t width, + char pad_char); + #endif // GITOUT_FORMAT_H_ diff --git a/gitout.c b/gitout.c @@ -81,6 +81,8 @@ GitoutOptions* gitout_options_create(int argc, const char* argv[]) { options.baseurl = argv[++i]; } else if (argv[i][1] == 'H') { options.writer_type = kRepoWriterTypeHtml; + } else if (argv[i][1] == 'G') { + options.writer_type = kRepoWriterTypeGopher; } } // Must specify at least one repo directory. diff --git a/gitout_index.c b/gitout_index.c @@ -33,6 +33,8 @@ GitoutIndexOptions* gitout_index_options_create(int argc, const char* argv[]) { options.repo_dirs[options.repo_dir_count++] = argv[i]; } else if (argv[i][1] == 'H') { options.writer_type = kIndexWriterTypeHtml; + } else if (argv[i][1] == 'G') { + options.writer_type = kIndexWriterTypeGopher; } } GitoutIndexOptions* options_out = ecalloc(1, sizeof(GitoutIndexOptions)); diff --git a/writer/BUILD.gn b/writer/BUILD.gn @@ -4,7 +4,10 @@ source_set("index_writer") { "index_writer.h", ] configs += [ "//:gitout_config" ] - deps = [ "//writer/html:index_writer" ] + deps = [ + "//writer/gopher:index_writer", + "//writer/html:index_writer", + ] public_deps = [ "//git" ] } @@ -14,6 +17,9 @@ source_set("repo_writer") { "repo_writer.h", ] configs += [ "//:gitout_config" ] - deps = [ "//writer/html:repo_writer" ] + deps = [ + "//writer/gopher:repo_writer", + "//writer/html:repo_writer", + ] public_deps = [ "//git" ] } diff --git a/writer/gopher/BUILD.gn b/writer/gopher/BUILD.gn @@ -0,0 +1,41 @@ +source_set("index_writer") { + sources = [ + "index_writer.c", + "index_writer.h", + "repo_index.c", + "repo_index.h", + ] + configs += [ "//:gitout_config" ] + deps = [ + "//:format", + "//:utils", + ] + public_deps = [ "//git" ] +} + +source_set("repo_writer") { + sources = [ + "commit.c", + "commit.h", + "fileblob.c", + "fileblob.h", + "files.c", + "files.h", + "log.c", + "log.h", + "page.c", + "page.h", + "refs.c", + "refs.h", + "repo_writer.c", + "repo_writer.h", + ] + configs += [ "//:gitout_config" ] + deps = [ + "//:format", + "//:utils", + "//writer/atom", + "//writer/cache", + ] + public_deps = [ "//git" ] +} diff --git a/writer/gopher/commit.c b/writer/gopher/commit.c @@ -0,0 +1,210 @@ +#include "writer/gopher/commit.h" + +#include <err.h> +#include <limits.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "format.h" +#include "git/delta.h" +#include "utils.h" +#include "writer/gopher/page.h" + +struct GopherCommit { + FILE* out; + GopherPage* page; +}; + +static void gopher_commit_write_summary(GopherCommit* commit, + const CommitInfo* ci); +static void gopher_commit_write_diffstat(GopherCommit* commit, + const CommitInfo* ci); +static void gopher_commit_write_diffstat_row(GopherCommit* commit, + const DeltaInfo* delta); +static void gopher_commit_write_diff_content(GopherCommit* commit, + const CommitInfo* ci); +static void gopher_commit_write_diff_delta(GopherCommit* commit, + const DeltaInfo* delta); +static void gopher_commit_write_diff_hunk(GopherCommit* commit, + const Hunk* hunk); + +GopherCommit* gopher_commit_create(const RepoInfo* repo, + const char* oid, + const char* title) { + GopherCommit* commit = ecalloc(1, sizeof(GopherCommit)); + char filename[PATH_MAX]; + if (snprintf(filename, sizeof(filename), "%s.gph", oid) < 0) { + err(1, "snprintf"); + } + char path[PATH_MAX]; + path_concat(path, sizeof(path), "commit", filename); + commit->out = efopen(path, "w"); + commit->page = gopher_page_create(commit->out, repo, title, ""); + return commit; +} + +void gopher_commit_free(GopherCommit* commit) { + if (!commit) { + return; + } + fclose(commit->out); + commit->out = NULL; + gopher_page_free(commit->page); + commit->page = NULL; + free(commit); +} + +void gopher_commit_begin(GopherCommit* commit) { + gopher_page_begin(commit->page); +} + +void gopher_commit_add_commit(GopherCommit* commit, const CommitInfo* ci) { + static const uint64_t kDiffMaxFiles = 1000; + static const uint64_t kDiffMaxDeltas = 1000; + static const uint64_t kDiffMaxDeltaLines = 100000; + + gopher_commit_write_summary(commit, ci); + + if (ci->deltas_len == 0) { + return; + } + if (ci->filecount > kDiffMaxFiles || ci->deltas_len > kDiffMaxDeltas || + ci->addcount > kDiffMaxDeltaLines || ci->delcount > kDiffMaxDeltaLines) { + fprintf(commit->out, "Diff is too large, output suppressed.</pre>\n"); + return; + } + + gopher_commit_write_diffstat(commit, ci); + gopher_commit_write_diff_content(commit, ci); +} + +void gopher_commit_end(GopherCommit* commit) { + gopher_page_end(commit->page); +} + +void gopher_commit_write_summary(GopherCommit* commit, const CommitInfo* ci) { + FILE* out = commit->out; + fprintf(out, "[1|commit %s|../commit/%s.gph|server|port]\n", ci->oid, + ci->oid); + + if (ci->parentoid[0] != '\0') { + const char* oid = ci->parentoid; + fprintf(out, "[1|parent %s|../commit/%s.gph|server|port]\n", oid, oid); + } + + fprintf(out, "[h|Author: "); + print_gopher_link(commit->out, ci->author_name); + fprintf(out, " <"); + print_gopher_link(commit->out, ci->author_email); + fprintf(out, ">|URL:mailto:"); + print_gopher_link(commit->out, ci->author_email); + fprintf(out, "|server|port]\n"); + fprintf(out, "Date: "); + print_time(out, ci->author_time, ci->author_timezone_offset); + fprintf(out, "\n"); + + if (ci->msg) { + fprintf(out, "\n"); + print_gopher_text(commit->out, ci->msg, true); + fprintf(out, "\n"); + } +} + +void gopher_commit_write_diffstat(GopherCommit* commit, const CommitInfo* ci) { + fprintf(commit->out, "Diffstat:\n"); + for (size_t i = 0; i < ci->deltas_len; i++) { + gopher_commit_write_diffstat_row(commit, ci->deltas[i]); + } +} + +void gopher_commit_write_diffstat_row(GopherCommit* commit, + const DeltaInfo* delta) { + static const size_t kGraphWidth = 30; + + fprintf(commit->out, " %c ", delta->status); + char filename[PATH_MAX]; + if (strcmp(delta->old_file_path, delta->new_file_path) == 0) { + snprintf(filename, sizeof(filename), "%s", delta->old_file_path); + } else { + snprintf(filename, sizeof(filename), "%s -> %s", delta->old_file_path, + delta->new_file_path); + } + print_gopher_link_padded(commit->out, filename, 35, ' '); + + size_t changed = delta->addcount + delta->delcount; + fprintf(commit->out, " | "); + fprintf(commit->out, "%7zu ", changed); + char* added_graph = deltainfo_added_graph(delta, kGraphWidth); + fprintf(commit->out, "%s", added_graph); + free(added_graph); + char* deleted_graph = deltainfo_deleted_graph(delta, kGraphWidth); + fprintf(commit->out, "%s", deleted_graph); + free(deleted_graph); + fprintf(commit->out, "\n"); +} + +void gopher_commit_write_diff_content(GopherCommit* commit, + const CommitInfo* ci) { + FILE* out = commit->out; + fprintf(out, "\n%zu file%s changed, %zu insertion%s(+), %zu deletion%s(-)\n", + ci->filecount, ci->filecount == 1 ? "" : "s", // + ci->addcount, ci->addcount == 1 ? "" : "s", // + ci->delcount, ci->delcount == 1 ? "" : "s"); + fprintf(commit->out, "---\n"); + + for (size_t i = 0; i < ci->deltas_len; i++) { + gopher_commit_write_diff_delta(commit, ci->deltas[i]); + } +} + +void gopher_commit_write_diff_delta(GopherCommit* commit, + const DeltaInfo* delta) { + FILE* out = commit->out; + fprintf(out, "[1|diff --git a/"); + print_gopher_link(out, delta->old_file_path); + fprintf(out, " b/"); + print_gopher_link(out, delta->new_file_path); + fprintf(out, "|../file/"); + print_gopher_link(out, delta->new_file_path); + fprintf(out, ".gph|server|port]\n"); + + if (delta->is_binary) { + fprintf(out, "Binary files differ.\n"); + } else { + for (size_t i = 0; i < delta->hunks_len; i++) { + gopher_commit_write_diff_hunk(commit, delta->hunks[i]); + } + } +} + +void gopher_commit_write_diff_hunk(GopherCommit* commit, const Hunk* hunk) { + FILE* out = commit->out; + + // Output header. e.g. @@ -0,0 +1,3 @@ + print_gopher_text(out, hunk->header, false); + fprintf(out, "\n"); + + // Iterate over lines in hunk. + for (size_t i = 0; i < hunk->lines_len; i++) { + const HunkLine* line = hunk->lines[i]; + if (line->old_lineno == -1) { + // Added line. Prefix with +. + fprintf(out, "+"); + print_gopher_text_len(out, line->content, line->content_len, false); + fprintf(out, "\n"); + } else if (line->new_lineno == -1) { + // Removed line. Prefix with -. + fprintf(out, "-"); + print_gopher_text_len(out, line->content, line->content_len, false); + fprintf(out, "\n"); + } else { + // Unchanged line. Prefix with ' '. No link. + fprintf(out, " "); + print_gopher_text_len(out, line->content, line->content_len, false); + fprintf(out, "\n"); + } + } +} diff --git a/writer/gopher/commit.h b/writer/gopher/commit.h @@ -0,0 +1,17 @@ +#ifndef GITOUT_WRITER_GOPHER_COMMIT_H_ +#define GITOUT_WRITER_GOPHER_COMMIT_H_ + +#include "git/commit.h" +#include "git/repo.h" + +typedef struct GopherCommit GopherCommit; + +GopherCommit* gopher_commit_create(const RepoInfo* repo, + const char* oid, + const char* title); +void gopher_commit_free(GopherCommit* commit); +void gopher_commit_begin(GopherCommit* commit); +void gopher_commit_add_commit(GopherCommit* commit, const CommitInfo* ci); +void gopher_commit_end(GopherCommit* commit); + +#endif // GITOUT_WRITER_GOPHER_COMMIT_H_ diff --git a/writer/gopher/fileblob.c b/writer/gopher/fileblob.c @@ -0,0 +1,110 @@ +#include "writer/gopher/fileblob.h" + +#include <err.h> +#include <libgen.h> +#include <limits.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "format.h" +#include "utils.h" +#include "writer/gopher/page.h" + +struct GopherFileBlob { + const RepoInfo* repo; + FILE* out; + GopherPage* page; +}; + +GopherFileBlob* gopher_fileblob_create(const RepoInfo* repo, const char* path) { + GopherFileBlob* blob = ecalloc(1, sizeof(GopherFileBlob)); + blob->repo = repo; + + // Create directories. + char filename[PATH_MAX]; + if (snprintf(filename, sizeof(filename), "%s.gph", path) < 0) { + err(1, "snprintf"); + } + char out_path[PATH_MAX]; + path_concat(out_path, sizeof(out_path), "file", filename); + char dir[PATH_MAX]; + estrlcpy(dir, out_path, sizeof(dir)); + const char* d = dirname(dir); + if (!d) { + err(1, "dirname"); + } + mkdirp(d); + blob->out = efopen(out_path, "w"); + + // Compute the relative path. + char relpath[PATH_MAX]; + estrlcpy(relpath, "../", sizeof(relpath)); + for (const char* p = d; *p != '\0'; p++) { + if (*p == '/') { + estrlcat(relpath, "../", sizeof(relpath)); + } + } + estrlcpy(filename, path, sizeof(filename)); + const char* title = basename(filename); + if (!title) { + err(1, "basename"); + } + blob->page = gopher_page_create(blob->out, repo, title, relpath); + return blob; +} + +void gopher_fileblob_free(GopherFileBlob* blob) { + if (!blob) { + return; + } + fclose(blob->out); + blob->out = NULL; + gopher_page_free(blob->page); + blob->page = NULL; + free(blob); +} + +void gopher_fileblob_begin(GopherFileBlob* blob) { + gopher_page_begin(blob->page); +} + +void gopher_fileblob_add_file(GopherFileBlob* blob, const FileInfo* fi) { + FILE* out = blob->out; + + char path[PATH_MAX]; + estrlcpy(path, fi->repo_path, sizeof(path)); + const char* filename = basename(path); + if (!filename) { + err(1, "basename"); + } + print_gopher_text(out, filename, false); + fprintf(out, " (%zdB)\n", fi->size_bytes); + fprintf(out, "---\n"); + + if (fi->size_lines < 0) { + fprintf(out, "Binary file.\n"); + return; + } + + size_t i = 0; + const char* content = fi->content; + const char* end = content + strlen(content); + const char* cur_line = content; + while (cur_line) { + const char* next_line = strchr(cur_line, '\n'); + if (next_line || cur_line < end) { + size_t len = (next_line ? next_line : end) - cur_line; + i++; + fprintf(out, "%6zu ", i); + print_gopher_text_len(out, cur_line, len, false); + fprintf(out, "\n"); + } + cur_line = next_line ? next_line + 1 : NULL; + } +} + +void gopher_fileblob_end(GopherFileBlob* blob) { + gopher_page_end(blob->page); +} diff --git a/writer/gopher/fileblob.h b/writer/gopher/fileblob.h @@ -0,0 +1,15 @@ +#ifndef GITOUT_WRITER_GOPHER_FILEBLOB_H_ +#define GITOUT_WRITER_GOPHER_FILEBLOB_H_ + +#include "git/file.h" +#include "git/repo.h" + +typedef struct GopherFileBlob GopherFileBlob; + +GopherFileBlob* gopher_fileblob_create(const RepoInfo* repo, const char* path); +void gopher_fileblob_free(GopherFileBlob* blob); +void gopher_fileblob_begin(GopherFileBlob* blob); +void gopher_fileblob_add_file(GopherFileBlob* blob, const FileInfo* fi); +void gopher_fileblob_end(GopherFileBlob* blob); + +#endif // GITOUT_WRITER_GOPHER_FILEBLOB_H_ diff --git a/writer/gopher/files.c b/writer/gopher/files.c @@ -0,0 +1,61 @@ +#include "writer/gopher/files.h" + +#include <stdio.h> +#include <stdlib.h> + +#include "format.h" +#include "utils.h" +#include "writer/gopher/page.h" + +struct GopherFiles { + const RepoInfo* repo; + FILE* out; + GopherPage* page; +}; + +GopherFiles* gopher_files_create(const RepoInfo* repo) { + GopherFiles* files = ecalloc(1, sizeof(GopherFiles)); + files->repo = repo; + files->out = efopen("files.gph", "w"); + files->page = gopher_page_create(files->out, repo, "Files", ""); + return files; +} + +void gopher_files_free(GopherFiles* files) { + if (!files) { + return; + } + fclose(files->out); + files->out = NULL; + gopher_page_free(files->page); + files->page = NULL; + free(files); +} + +void gopher_files_begin(GopherFiles* files) { + FILE* out = files->out; + gopher_page_begin(files->page); + fprintf(out, "%-10.10s ", "Mode"); + fprintf(out, "%-50.50s ", "Name"); + fprintf(out, "%8.8s\n", "Size"); +} + +void gopher_files_add_file(GopherFiles* files, const FileInfo* fi) { + FILE* out = files->out; + fprintf(out, "[1|%s", fi->mode); + fprintf(out, " "); + print_gopher_link_padded(out, fi->display_path, 50, ' '); + fprintf(out, " "); + if (fi->size_lines >= 0) { + fprintf(files->out, "%7zdL", fi->size_lines); + } else if (fi->size_bytes >= 0) { + fprintf(files->out, "%7zdB", fi->size_bytes); + } + fprintf(out, "|file/"); + print_gopher_link(out, fi->repo_path); + fprintf(out, ".gph|server|port]\n"); +} + +void gopher_files_end(GopherFiles* files) { + gopher_page_end(files->page); +} diff --git a/writer/gopher/files.h b/writer/gopher/files.h @@ -0,0 +1,15 @@ +#ifndef GITOUT_WRITER_GOPHER_FILES_H_ +#define GITOUT_WRITER_GOPHER_FILES_H_ + +#include "git/file.h" +#include "git/repo.h" + +typedef struct GopherFiles GopherFiles; + +GopherFiles* gopher_files_create(const RepoInfo* repo); +void gopher_files_free(GopherFiles* files); +void gopher_files_begin(GopherFiles* files); +void gopher_files_add_file(GopherFiles* files, const FileInfo* fi); +void gopher_files_end(GopherFiles* files); + +#endif // GITOUT_WRITER_GOPHER_FILES_H_ diff --git a/writer/gopher/index_writer.c b/writer/gopher/index_writer.c @@ -0,0 +1,43 @@ +#include "writer/gopher/index_writer.h" + +#include <stdio.h> +#include <stdlib.h> + +#include "git/repo.h" +#include "utils.h" +#include "writer/gopher/repo_index.h" + +struct GopherIndexWriter { + GopherRepoIndex* index_; +}; + +GopherIndexWriter* gopher_indexwriter_create() { + GopherIndexWriter* writer = ecalloc(1, sizeof(GopherIndexWriter)); + FILE* out = stdout; + writer->index_ = gopher_repoindex_create(out); + return writer; +} + +void gopher_indexwriter_free(GopherIndexWriter* writer) { + if (!writer) { + return; + } + gopher_repoindex_free(writer->index_); + writer->index_ = NULL; + free(writer); +} + +void gopher_indexwriter_begin(void* writer) { + GopherIndexWriter* gopher_writer = (GopherIndexWriter*)writer; + gopher_repoindex_begin(gopher_writer->index_); +} + +void gopher_indexwriter_add_repo(void* writer, RepoInfo* ri) { + GopherIndexWriter* gopher_writer = (GopherIndexWriter*)writer; + gopher_repoindex_add_repo(gopher_writer->index_, ri); +} + +void gopher_indexwriter_end(void* writer) { + GopherIndexWriter* gopher_writer = (GopherIndexWriter*)writer; + gopher_repoindex_end(gopher_writer->index_); +} diff --git a/writer/gopher/index_writer.h b/writer/gopher/index_writer.h @@ -0,0 +1,14 @@ +#ifndef GITOUT_WRITER_GOPHER_INDEX_WRITER_H_ +#define GITOUT_WRITER_GOPHER_INDEX_WRITER_H_ + +#include "git/repo.h" + +typedef struct GopherIndexWriter GopherIndexWriter; + +GopherIndexWriter* gopher_indexwriter_create(); +void gopher_indexwriter_free(GopherIndexWriter* writer); +void gopher_indexwriter_begin(void* writer); +void gopher_indexwriter_add_repo(void* writer, RepoInfo* ri); +void gopher_indexwriter_end(void* writer); + +#endif // GITOUT_WRITER_GOPHER_INDEX_WRITER_H_ diff --git a/writer/gopher/log.c b/writer/gopher/log.c @@ -0,0 +1,101 @@ +#include "writer/gopher/log.h" + +#include <stdint.h> +#include <stdlib.h> + +#include "format.h" +#include "utils.h" +#include "writer/cache/cache.h" +#include "writer/gopher/page.h" + +struct GopherLog { + const RepoInfo* repo; + FILE* out; + Cache* cache; + GopherPage* page; + size_t remaining_commits; + size_t unlogged_commits; +}; + +static void write_commit_row(FILE* out, const CommitInfo* ci); + +GopherLog* gopher_log_create(const RepoInfo* repo) { + GopherLog* log = ecalloc(1, sizeof(GopherLog)); + log->repo = repo; + log->out = efopen("log.gph", "w"); + log->page = gopher_page_create(log->out, repo, "Log", ""); + log->remaining_commits = SIZE_MAX; + log->unlogged_commits = 0; + return log; +} + +void gopher_log_free(GopherLog* log) { + if (!log) { + return; + } + fclose(log->out); + log->out = NULL; + cache_free(log->cache); + log->cache = NULL; + gopher_page_free(log->page); + log->page = NULL; + free(log); +} + +void gopher_log_set_cachefile(GopherLog* log, const char* cachefile) { + log->cache = cache_create(cachefile, write_commit_row); +} + +void gopher_log_set_commit_limit(GopherLog* log, size_t count) { + log->remaining_commits = count; +} + +bool gopher_log_can_add_commits(const GopherLog* log) { + return !log->cache || cache_can_add_commits(log->cache); +} + +void gopher_log_begin(GopherLog* log) { + FILE* out = log->out; + gopher_page_begin(log->page); + fprintf(out, "%-16.16s ", "Date"); + fprintf(out, "%-40.40s ", "Commit message"); + fprintf(out, "%s\n", "Author"); +} + +void gopher_log_add_commit(GopherLog* log, const CommitInfo* ci) { + if (log->cache) { + cache_add_commit_row(log->cache, ci); + } else if (log->remaining_commits > 0) { + write_commit_row(log->out, ci); + log->remaining_commits--; + } else { + log->unlogged_commits++; + } +} + +void gopher_log_end(GopherLog* log) { + FILE* out = log->out; + if (log->cache) { + cache_write(log->cache); + cache_copy_log(log->cache, log->out); + } else if (log->unlogged_commits > 0) { + size_t count = log->unlogged_commits; + fprintf(out, "%16.16s %zu more commits remaining, fetch the repository\n", + "", count); + } + fprintf(out, "\n[0|Atom feed|atom.xml|server|port]\n"); + fprintf(out, "[0|Atom feed (tags)|tags.xml|server|port]\n"); + gopher_page_end(log->page); +} + +void write_commit_row(FILE* out, const CommitInfo* ci) { + fprintf(out, "[1|"); + print_time_short(out, ci->author_time); + fprintf(out, " "); + if (ci->summary) { + print_gopher_link_padded(out, ci->summary, 40, ' '); + fprintf(out, " "); + } + print_gopher_link(out, ci->author_name); + fprintf(out, "|commit/%s.gph|server|port]\n", ci->oid); +} diff --git a/writer/gopher/log.h b/writer/gopher/log.h @@ -0,0 +1,21 @@ +#ifndef GITOUT_WRITER_GOPHER_LOG_H_ +#define GITOUT_WRITER_GOPHER_LOG_H_ + +#include <stdbool.h> +#include <stdio.h> + +#include "git/commit.h" +#include "git/repo.h" + +typedef struct GopherLog GopherLog; + +GopherLog* gopher_log_create(const RepoInfo* repo); +void gopher_log_free(GopherLog* log); +void gopher_log_set_cachefile(GopherLog* log, const char* cachefile); +void gopher_log_set_commit_limit(GopherLog* log, size_t count); +bool gopher_log_can_add_commits(const GopherLog* log); +void gopher_log_begin(GopherLog* log); +void gopher_log_add_commit(GopherLog* log, const CommitInfo* commit); +void gopher_log_end(GopherLog* log); + +#endif // GITOUT_WRITER_GOPHER_LOG_H_ diff --git a/writer/gopher/page.c b/writer/gopher/page.c @@ -0,0 +1,81 @@ +#include "writer/gopher/page.h" + +#include <stdbool.h> +#include <stdlib.h> + +#include "format.h" +#include "utils.h" + +struct GopherPage { + FILE* out; + const RepoInfo* repo; + char* title; + char* relpath; +}; + +GopherPage* gopher_page_create(FILE* out, + const RepoInfo* repo, + const char* title, + const char* relpath) { + GopherPage* page = ecalloc(1, sizeof(GopherPage)); + page->out = out; + page->repo = repo; + page->title = estrdup(title); + page->relpath = estrdup(relpath); + return page; +} + +void gopher_page_free(GopherPage* page) { + if (!page) { + return; + } + free(page->title); + page->title = NULL; + free(page->relpath); + page->relpath = NULL; + free(page); +} + +void gopher_page_begin(GopherPage* page) { + FILE* out = page->out; + print_gopher_text(out, page->title, false); + if (page->title[0] != '\0' && page->repo->short_name[0] != '\0') { + fprintf(out, " - "); + print_gopher_text(out, page->repo->short_name, false); + } + if (page->title[0] != '\0' && page->repo->description[0] != '\0') { + fprintf(out, " - "); + print_gopher_text(out, page->repo->description, false); + fprintf(out, "\n"); + } + if (page->repo->clone_url[0] != '\0') { + fprintf(out, "[h|git clone "); + print_gopher_link(out, page->repo->clone_url); + fprintf(out, "|URL:"); + print_gopher_link(out, page->repo->clone_url); + fprintf(out, "|server|port]\n"); + } + fprintf(out, "[1|Log|%slog.gph|server|port]\n", page->relpath); + fprintf(out, "[1|Files|%sfiles.gph|server|port]\n", page->relpath); + fprintf(out, "[1|Refs|%srefs.gph|server|port]\n", page->relpath); + + if (page->repo->submodules[0] != '\0') { + fprintf(out, "[1|Submodules|%sfile/%s.gph|server|port]\n", page->relpath, + page->repo->submodules); + } + if (page->repo->readme[0] != '\0') { + fprintf(out, "[1|README|%sfile/%s.gph|server|port]\n", page->relpath, + page->repo->readme); + } + if (page->repo->license[0] != '\0') { + fprintf(out, "[1|LICENSE|%sfile/%s.gph|server|port]\n", page->relpath, + page->repo->license); + } + fprintf(out, "---\n"); +} + +void gopher_page_end(GopherPage* page) { + if (!page) { + return; + } +} diff --git a/writer/gopher/page.h b/writer/gopher/page.h @@ -0,0 +1,18 @@ +#ifndef GITOUT_WRITER_GOPHER_PAGE_H_ +#define GITOUT_WRITER_GOPHER_PAGE_H_ + +#include <stdio.h> + +#include "git/repo.h" + +typedef struct GopherPage GopherPage; + +GopherPage* gopher_page_create(FILE* out, + const RepoInfo* repo, + const char* title, + const char* relpath); +void gopher_page_free(GopherPage* page); +void gopher_page_begin(GopherPage* page); +void gopher_page_end(GopherPage* page); + +#endif // GITOUT_WRITER_GOPHER_PAGE_H_ diff --git a/writer/gopher/refs.c b/writer/gopher/refs.c @@ -0,0 +1,145 @@ +#include "writer/gopher/refs.h" + +#include <stdio.h> +#include <stdlib.h> + +#include "format.h" +#include "git/commit.h" +#include "utils.h" +#include "writer/gopher/page.h" + +typedef struct { + char* title; + char* id; + FILE* out; +} GopherRefsTable; + +struct GopherRefs { + const RepoInfo* repo; + FILE* out; + GopherPage* page; + GopherRefsTable* branches; + GopherRefsTable* tags; +}; + +static GopherRefsTable* gopher_refstable_create(const char* title, + const char* id, + FILE* out); +static void gopher_refstable_free(GopherRefsTable* table); +static void gopher_refstable_begin(GopherRefsTable* table); +static void gopher_refstable_add_ref(GopherRefsTable* table, + const ReferenceInfo* ri); +static void gopher_refstable_end(GopherRefsTable* table); + +GopherRefsTable* gopher_refstable_create(const char* title, + const char* id, + FILE* out) { + GopherRefsTable* table = ecalloc(1, sizeof(GopherRefsTable)); + table->title = estrdup(title); + table->id = estrdup(id); + table->out = out; + return table; +} + +void gopher_refstable_free(GopherRefsTable* table) { + if (!table) { + return; + } + free(table->title); + table->title = NULL; + free(table->id); + table->id = NULL; + free(table); +} + +void gopher_refstable_begin(GopherRefsTable* table) { + FILE* out = table->out; + fprintf(out, "%s\n", table->title); + fprintf(out, " %-32.32s", "Name"); + fprintf(out, " %-16.16s", "Last commit date"); + fprintf(out, " %s\n", "Author"); +} + +void gopher_refstable_add_ref(GopherRefsTable* table, const ReferenceInfo* ri) { + CommitInfo* ci = ri->ci; + FILE* out = table->out; + + fprintf(out, " "); + print_gopher_link_padded(out, ri->shorthand, 32, ' '); + fprintf(out, " "); + print_time_short(out, ci->author_time); + fprintf(out, " "); + print_gopher_link_padded(out, ci->author_name, 25, '\0'); + fprintf(out, "\n"); +} + +void gopher_refstable_end(GopherRefsTable* table) { + FILE* out = table->out; + fprintf(out, "\n"); +} + +GopherRefs* gopher_refs_create(const RepoInfo* repo) { + GopherRefs* refs = ecalloc(1, sizeof(GopherRefs)); + refs->repo = repo; + refs->out = efopen("refs.gph", "w"); + refs->page = gopher_page_create(refs->out, repo, "Refs", ""); + return refs; +} + +void gopher_refs_free(GopherRefs* refs) { + if (!refs) { + return; + } + fclose(refs->out); + refs->out = NULL; + gopher_page_free(refs->page); + refs->page = NULL; + gopher_refstable_free(refs->branches); + refs->branches = NULL; + gopher_refstable_free(refs->tags); + refs->tags = NULL; + free(refs); +} + +void gopher_refs_begin(GopherRefs* refs) { + gopher_page_begin(refs->page); +} + +void gopher_refs_add_ref(GopherRefs* refs, const ReferenceInfo* ri) { + switch (ri->type) { + case kReftypeBranch: + if (!refs->branches) { + refs->branches = + gopher_refstable_create("Branches", "branches", refs->out); + gopher_refstable_begin(refs->branches); + } + gopher_refstable_add_ref(refs->branches, ri); + break; + case kReftypeTag: + if (refs->branches) { + gopher_refstable_end(refs->branches); + gopher_refstable_free(refs->branches); + refs->branches = NULL; + } + if (!refs->tags) { + refs->tags = gopher_refstable_create("Tags", "tags", refs->out); + gopher_refstable_begin(refs->tags); + } + gopher_refstable_add_ref(refs->tags, ri); + break; + } +} + +void gopher_refs_end(GopherRefs* refs) { + if (refs->branches) { + gopher_refstable_end(refs->branches); + gopher_refstable_free(refs->branches); + refs->branches = NULL; + } + if (refs->tags) { + gopher_refstable_end(refs->tags); + gopher_refstable_free(refs->tags); + refs->tags = NULL; + } + gopher_page_end(refs->page); +} diff --git a/writer/gopher/refs.h b/writer/gopher/refs.h @@ -0,0 +1,15 @@ +#ifndef GITOUT_WRITER_GOPHER_REFS_H_ +#define GITOUT_WRITER_GOPHER_REFS_H_ + +#include "git/reference.h" +#include "git/repo.h" + +typedef struct GopherRefs GopherRefs; + +GopherRefs* gopher_refs_create(const RepoInfo* repo); +void gopher_refs_free(GopherRefs* refs); +void gopher_refs_begin(GopherRefs* refs); +void gopher_refs_add_ref(GopherRefs* refs, const ReferenceInfo* ri); +void gopher_refs_end(GopherRefs* refs); + +#endif // GITOUT_WRITER_GOPHER_REFS_H_ diff --git a/writer/gopher/repo_index.c b/writer/gopher/repo_index.c @@ -0,0 +1,57 @@ +#include "writer/gopher/repo_index.h" + +#include <stdlib.h> + +#include "format.h" +#include "git/commit.h" +#include "utils.h" + +struct GopherRepoIndex { + FILE* out; +}; + +GopherRepoIndex* gopher_repoindex_create(FILE* out) { + GopherRepoIndex* index = ecalloc(1, sizeof(GopherRepoIndex)); + index->out = out; + return index; +} + +void gopher_repoindex_free(GopherRepoIndex* index) { + if (!index) { + return; + } + fclose(index->out); + index->out = NULL; + free(index); +} + +void gopher_repoindex_begin(GopherRepoIndex* index) { + FILE* out = index->out; + fprintf(out, "Repositories\n\n"); + fprintf(out, "%-20.20s ", "Name"); + fprintf(out, "%-39.39s ", "Description"); + fprintf(out, "%s\n", "Last commit"); +} + +static void print_author_time(const CommitInfo* ci, void* user_data) { + FILE* out = (FILE*)user_data; + print_time_short(out, ci->author_time); +} + +void gopher_repoindex_add_repo(GopherRepoIndex* index, RepoInfo* ri) { + FILE* out = index->out; + fprintf(out, "[1|"); + print_gopher_link_padded(out, ri->short_name, 20, ' '); + fprintf(out, " "); + print_gopher_link_padded(out, ri->description, 39, ' '); + fprintf(out, " "); + repoinfo_for_commit(ri, "HEAD", print_author_time, out); + fprintf(out, "|%s/log.gph|server|port]\n", ri->short_name); +} + +void gopher_repoindex_end(GopherRepoIndex* index) { + // Stop the compiler from complaining about unused parameter. + if (!index) { + return; + } +} diff --git a/writer/gopher/repo_index.h b/writer/gopher/repo_index.h @@ -0,0 +1,16 @@ +#ifndef GITOUT_WRITER_GOPHER_REPOINDEX_H_ +#define GITOUT_WRITER_GOPHER_REPOINDEX_H_ + +#include "git/repo.h" + +#include <stdio.h> + +typedef struct GopherRepoIndex GopherRepoIndex; + +GopherRepoIndex* gopher_repoindex_create(FILE* out); +void gopher_repoindex_free(GopherRepoIndex* index); +void gopher_repoindex_begin(GopherRepoIndex* index); +void gopher_repoindex_add_repo(GopherRepoIndex* index, RepoInfo* ri); +void gopher_repoindex_end(GopherRepoIndex* index); + +#endif // GITOUT_WRITER_GOPHER_REPOINDEX_H_ diff --git a/writer/gopher/repo_writer.c b/writer/gopher/repo_writer.c @@ -0,0 +1,140 @@ +#include "writer/gopher/repo_writer.h" + +#include <err.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "git/commit.h" +#include "git/file.h" +#include "git/reference.h" +#include "utils.h" +#include "writer/atom/atom.h" +#include "writer/gopher/commit.h" +#include "writer/gopher/fileblob.h" +#include "writer/gopher/files.h" +#include "writer/gopher/log.h" +#include "writer/gopher/refs.h" + +struct GopherRepoWriter { + const RepoInfo* repo_; + GopherRefs* refs_; + GopherLog* log_; + Atom* atom_; + Atom* tags_; + GopherFiles* files_; +}; + +GopherRepoWriter* gopher_repowriter_create(const RepoInfo* repo) { + GopherRepoWriter* writer = ecalloc(1, sizeof(GopherRepoWriter)); + writer->repo_ = repo; + writer->refs_ = gopher_refs_create(repo); + writer->log_ = gopher_log_create(repo); + writer->atom_ = atom_create(repo, kAtomTypeAll); + writer->tags_ = atom_create(repo, kAtomTypeTags); + writer->files_ = gopher_files_create(repo); + return writer; +} + +void gopher_repowriter_free(GopherRepoWriter* writer) { + if (!writer) { + return; + } + gopher_refs_free(writer->refs_); + writer->refs_ = NULL; + gopher_log_free(writer->log_); + writer->log_ = NULL; + atom_free(writer->atom_); + writer->atom_ = NULL; + atom_free(writer->tags_); + writer->tags_ = NULL; + gopher_files_free(writer->files_); + writer->files_ = NULL; + free(writer); +} + +void gopher_repowriter_set_log_cachefile(void* writer, const char* cachefile) { + GopherRepoWriter* gopher_writer = (GopherRepoWriter*)writer; + gopher_log_set_cachefile(gopher_writer->log_, cachefile); +} + +void gopher_repowriter_set_log_commit_limit(void* writer, size_t count) { + GopherRepoWriter* gopher_writer = (GopherRepoWriter*)writer; + gopher_log_set_commit_limit(gopher_writer->log_, count); +} + +void gopher_repowriter_set_baseurl(void* writer, const char* baseurl) { + GopherRepoWriter* gopher_writer = (GopherRepoWriter*)writer; + atom_set_baseurl(gopher_writer->atom_, baseurl); + atom_set_baseurl(gopher_writer->tags_, baseurl); +} + +void gopher_repowriter_begin(void* writer) { + GopherRepoWriter* gopher_writer = (GopherRepoWriter*)writer; + mkdir("commit", S_IRWXU | S_IRWXG | S_IRWXO); + mkdir("file", S_IRWXU | S_IRWXG | S_IRWXO); + + gopher_refs_begin(gopher_writer->refs_); + gopher_log_begin(gopher_writer->log_); + atom_begin(gopher_writer->atom_); + atom_begin(gopher_writer->tags_); + gopher_files_begin(gopher_writer->files_); +} + +void gopher_repowriter_add_commit(void* writer, const CommitInfo* ci) { + GopherRepoWriter* gopher_writer = (GopherRepoWriter*)writer; + char filename[PATH_MAX]; + if (snprintf(filename, sizeof(filename), "%s.gph", ci->oid) < 0) { + err(1, "snprintf"); + } + char path[PATH_MAX]; + path_concat(path, sizeof(path), "commit", filename); + atom_add_commit(gopher_writer->atom_, ci, path, "", ""); + + if (gopher_log_can_add_commits(gopher_writer->log_)) { + gopher_log_add_commit(gopher_writer->log_, ci); + GopherCommit* commit = + gopher_commit_create(gopher_writer->repo_, ci->oid, ci->summary); + gopher_commit_begin(commit); + gopher_commit_add_commit(commit, ci); + gopher_commit_end(commit); + gopher_commit_free(commit); + } +} + +void gopher_repowriter_add_reference(void* writer, const ReferenceInfo* ri) { + GopherRepoWriter* gopher_writer = (GopherRepoWriter*)writer; + gopher_refs_add_ref(gopher_writer->refs_, ri); + if (ri->type == kReftypeTag) { + char filename[PATH_MAX]; + if (snprintf(filename, sizeof(filename), "%s.gph", ri->ci->oid) < 0) { + err(1, "snprintf"); + } + char path[PATH_MAX]; + path_concat(path, sizeof(path), "commit", filename); + atom_add_commit(gopher_writer->tags_, ri->ci, path, "", ri->shorthand); + } +} + +void gopher_repowriter_add_file(void* writer, const FileInfo* fi) { + GopherRepoWriter* gopher_writer = (GopherRepoWriter*)writer; + gopher_files_add_file(gopher_writer->files_, fi); + + GopherFileBlob* blob = + gopher_fileblob_create(gopher_writer->repo_, fi->repo_path); + gopher_fileblob_begin(blob); + gopher_fileblob_add_file(blob, fi); + gopher_fileblob_end(blob); + gopher_fileblob_free(blob); +} + +void gopher_repowriter_end(void* writer) { + GopherRepoWriter* gopher_writer = (GopherRepoWriter*)writer; + gopher_refs_end(gopher_writer->refs_); + gopher_log_end(gopher_writer->log_); + atom_end(gopher_writer->atom_); + atom_end(gopher_writer->tags_); + gopher_files_end(gopher_writer->files_); +} diff --git a/writer/gopher/repo_writer.h b/writer/gopher/repo_writer.h @@ -0,0 +1,24 @@ +#ifndef GITOUT_WRITER_GOPHER_REPO_WRITER_H_ +#define GITOUT_WRITER_GOPHER_REPO_WRITER_H_ + +#include <stddef.h> + +#include "git/commit.h" +#include "git/file.h" +#include "git/reference.h" +#include "git/repo.h" + +typedef struct GopherRepoWriter GopherRepoWriter; + +GopherRepoWriter* gopher_repowriter_create(const RepoInfo* repo); +void gopher_repowriter_free(GopherRepoWriter* writer); +void gopher_repowriter_set_log_cachefile(void* writer, const char* cachefile); +void gopher_repowriter_set_log_commit_limit(void* writer, size_t count); +void gopher_repowriter_set_baseurl(void* writer, const char* baseurl); +void gopher_repowriter_begin(void* writer); +void gopher_repowriter_add_commit(void* writer, const CommitInfo* ci); +void gopher_repowriter_add_reference(void* writer, const ReferenceInfo* ri); +void gopher_repowriter_add_file(void* writer, const FileInfo* fi); +void gopher_repowriter_end(void* writer); + +#endif // GITOUT_WRITER_GOPHER_REPO_WRITER_H_ diff --git a/writer/index_writer.c b/writer/index_writer.c @@ -4,6 +4,7 @@ #include <stdlib.h> #include "utils.h" +#include "writer/gopher/index_writer.h" #include "writer/html/index_writer.h" typedef void (*IndexWriterBegin)(void* impl); @@ -24,10 +25,15 @@ struct IndexWriter { static IndexWriter* htmlindexwriter_create(); static void htmlindexwriter_free(IndexWriter* writer); +static IndexWriter* gopherindexwriter_create(); +static void gopherindexwriter_free(IndexWriter* writer); + IndexWriter* indexwriter_create(IndexWriterType type) { switch (type) { case kIndexWriterTypeHtml: return htmlindexwriter_create(); + case kIndexWriterTypeGopher: + return gopherindexwriter_create(); } errx(1, "unknown IndexWriterType %d", type); } @@ -40,6 +46,9 @@ void indexwriter_free(IndexWriter* writer) { case kIndexWriterTypeHtml: htmlindexwriter_free(writer); return; + case kIndexWriterTypeGopher: + gopherindexwriter_free(writer); + return; } errx(1, "unknown IndexWriterType %d", writer->type); } @@ -77,3 +86,25 @@ void htmlindexwriter_free(IndexWriter* writer) { writer->impl = NULL; free(writer); } + +/* GopherIndexWriter setup/teardown. */ + +IndexWriter* gopherindexwriter_create() { + IndexWriter* writer = ecalloc(1, sizeof(IndexWriter)); + GopherIndexWriter* gopher_writer = gopher_indexwriter_create(); + writer->type = kIndexWriterTypeGopher; + writer->impl = gopher_writer; + writer->begin = gopher_indexwriter_begin; + writer->add_repo = gopher_indexwriter_add_repo; + writer->end = gopher_indexwriter_end; + return writer; +} + +void gopherindexwriter_free(IndexWriter* writer) { + if (!writer) { + return; + } + gopher_indexwriter_free(writer->impl); + writer->impl = NULL; + free(writer); +} diff --git a/writer/index_writer.h b/writer/index_writer.h @@ -5,6 +5,7 @@ typedef enum { kIndexWriterTypeHtml, + kIndexWriterTypeGopher, } IndexWriterType; typedef struct IndexWriter IndexWriter; diff --git a/writer/repo_writer.c b/writer/repo_writer.c @@ -4,6 +4,7 @@ #include <stdlib.h> #include "utils.h" +#include "writer/gopher/repo_writer.h" #include "writer/html/repo_writer.h" typedef void (*RepoWriterSetLogCachefile)(void* writer, const char* cachefile); @@ -36,10 +37,15 @@ struct RepoWriter { static RepoWriter* htmlrepowriter_create(RepoInfo* ri); static void htmlrepowriter_free(RepoWriter* writer); +static RepoWriter* gopherrepowriter_create(RepoInfo* ri); +static void gopherrepowriter_free(RepoWriter* writer); + RepoWriter* repowriter_create(RepoWriterType type, RepoInfo* ri) { switch (type) { case kRepoWriterTypeHtml: return htmlrepowriter_create(ri); + case kRepoWriterTypeGopher: + return gopherrepowriter_create(ri); } errx(1, "unknown RepoWriterType %d", type); } @@ -52,6 +58,9 @@ void repowriter_free(RepoWriter* writer) { case kRepoWriterTypeHtml: htmlrepowriter_free(writer); return; + case kRepoWriterTypeGopher: + gopherrepowriter_free(writer); + return; } errx(1, "unknown RepoWriterType %d", writer->type); } @@ -114,3 +123,30 @@ void htmlrepowriter_free(RepoWriter* writer) { writer->impl = NULL; free(writer); } + +/* GopherRepoWriter setup/teardown. */ + +RepoWriter* gopherrepowriter_create(RepoInfo* ri) { + RepoWriter* writer = ecalloc(1, sizeof(RepoWriter)); + GopherRepoWriter* gopher_writer = gopher_repowriter_create(ri); + writer->type = kRepoWriterTypeGopher; + writer->impl = gopher_writer; + writer->set_log_cachefile = gopher_repowriter_set_log_cachefile; + writer->set_log_commit_limit = gopher_repowriter_set_log_commit_limit; + writer->set_baseurl = gopher_repowriter_set_baseurl; + writer->begin = gopher_repowriter_begin; + writer->add_commit = gopher_repowriter_add_commit; + writer->add_reference = gopher_repowriter_add_reference; + writer->add_file = gopher_repowriter_add_file; + writer->end = gopher_repowriter_end; + return writer; +} + +void gopherrepowriter_free(RepoWriter* writer) { + if (!writer) { + return; + } + gopher_repowriter_free(writer->impl); + writer->impl = NULL; + free(writer); +} diff --git a/writer/repo_writer.h b/writer/repo_writer.h @@ -10,6 +10,7 @@ typedef enum { kRepoWriterTypeHtml, + kRepoWriterTypeGopher, } RepoWriterType; typedef struct RepoWriter RepoWriter;