From: Omar Polo Subject: gotwebd: render READMEs in the tree view To: gameoftrees@openbsd.org Date: Thu, 07 Dec 2023 15:30:51 +0100 This changes the tree view to render the README{,.txt,.md} file if found. In detail, the diff does: - adds a template API to write and sanitize a buffer (the existing apis expect a NUL-terminated string) - relaxes got_open_blob_for_output() to fall back to HEAD if no commit query argument is given. This is important since it's possible to reach a tree view without a commit query argument by clicking on 'tree' on the repository listing. - extends got_output_repo_tree() to keep track of README-ish files. The logic here can be improved, but it should be decent enough for a first implementation - change the tree view to dump the readme if found and if it's not a "binary" file Please note that the README is served as-is, akin to the blob view, and I don't have plans to render prettily markdown or other formats. Initially i thought it was a good idea but after noticing that lowdown is several times larger than the whole gotwebd codebase I've changed my mind. I also thought we could support only a subset, but that's is prone to keep adding stuff, so let's restain to showing the file as-is. if OK i'll commit the template bit first and then the rest. thoughs/comments/oks? diff /home/op/w/got commit - 6ecb0b8c6b2aa36b6af31c856909b1ddccdb301c path + /home/op/w/got blob - 65256f74800294b5cafb9831a20db2e5715f568b file + gotwebd/files/htdocs/gotwebd/gotweb.css --- gotwebd/files/htdocs/gotwebd/gotweb.css +++ gotwebd/files/htdocs/gotwebd/gotweb.css @@ -361,6 +361,14 @@ header.subtitle h2 { padding: 1px; width: 9.5em; } +#tree_content h2 { + margin-top: 40px; + padding: 0 20px 0; +} +#tree_content pre { + padding: 0 20px 20px; + white-space: pre-wrap; +} #diff { margin-top: 20px; blob - 769fcfb189a08622b7b60081be7781b819637fad file + gotwebd/got_operations.c --- gotwebd/got_operations.c +++ gotwebd/got_operations.c @@ -704,7 +704,7 @@ got_get_repo_tags(struct request *c, size_t limit) } int -got_output_repo_tree(struct request *c, +got_output_repo_tree(struct request *c, char **readme, int (*cb)(struct template *, struct got_tree_entry *)) { const struct got_error *error = NULL; @@ -718,10 +718,13 @@ got_output_repo_tree(struct request *c, struct got_tree_object *tree = NULL; struct got_tree_entry *te; struct repo_dir *repo_dir = t->repo_dir; + const char *name; + mode_t mode; char *escaped_name = NULL, *path = NULL; int nentries, i; TAILQ_INIT(&refs); + *readme = NULL; rc = TAILQ_FIRST(&t->repo_commits); @@ -758,6 +761,16 @@ got_output_repo_tree(struct request *c, for (i = 0; i < nentries; i++) { te = got_object_tree_get_entry(tree, i); + + name = got_tree_entry_get_name(te); + mode = got_tree_entry_get_mode(te); + if (!S_ISDIR(mode) && (!strcasecmp(name, "README") || + !strcasecmp(name, "README.md") || + !strcasecmp(name, "README.txt"))) { + free(*readme); + *readme = strdup(name); + } + if (cb(c->tp, te) == -1) { error = got_error(GOT_ERR_CANCELLED); break; @@ -774,6 +787,8 @@ got_output_repo_tree(struct request *c, free(commit_id); free(tree_id); if (error) { + free(*readme); + *readme = NULL; if (error->code != GOT_ERR_CANCELLED) log_warnx("%s: %s", __func__, error->msg); return -1; @@ -783,12 +798,11 @@ got_output_repo_tree(struct request *c, const struct got_error * got_open_blob_for_output(struct got_blob_object **blob, int *fd, - int *binary, struct request *c) + int *binary, struct request *c, const char *directory, const char *file, + const char *commitstr) { const struct got_error *error = NULL; - struct transport *t = c->t; - struct got_repository *repo = t->repo; - struct querystring *qs = c->t->qs; + struct got_repository *repo = c->t->repo; struct got_commit_object *commit = NULL; struct got_object_id *commit_id = NULL; struct got_reflist_head refs; @@ -801,8 +815,13 @@ got_open_blob_for_output(struct got_blob_object **blob *fd = -1; *binary = 0; - if (asprintf(&path, "%s%s%s", qs->folder ? qs->folder : "", - qs->folder ? "/" : "", qs->file) == -1) { + error = got_ref_list(&refs, repo, "refs/heads", + got_ref_cmp_by_name, NULL); + if (error) + goto done; + + if (asprintf(&path, "%s%s%s", directory ? directory : "", + directory ? "/" : "", file) == -1) { error = got_error_from_errno("asprintf"); goto done; } @@ -811,7 +830,10 @@ got_open_blob_for_output(struct got_blob_object **blob if (error) goto done; - error = got_repo_match_object_id(&commit_id, NULL, qs->commit, + if (commitstr == NULL) + commitstr = GOT_REF_HEAD; + + error = got_repo_match_object_id(&commit_id, NULL, commitstr, GOT_OBJ_TYPE_COMMIT, &refs, repo); if (error) goto done; @@ -863,6 +885,7 @@ got_open_blob_for_output(struct got_blob_object **blob *blob = NULL; } + got_ref_list_free(&refs); free(in_repo_path); free(commit_id); free(path); blob - 5a9e5219a1429b6a682151e03ff69c6f506f26b2 file + gotwebd/gotweb.c --- gotwebd/gotweb.c +++ gotwebd/gotweb.c @@ -212,7 +212,7 @@ gotweb_process_request(struct request *c) goto err; error = got_open_blob_for_output(&c->t->blob, &c->t->fd, - &binary, c); + &binary, c, qs->folder, qs->file, qs->commit); if (error) goto err; } blob - 4cd99ce853542b1e2a0684ce76da6d122f7cf05a file + gotwebd/gotwebd.h --- gotwebd/gotwebd.h +++ gotwebd/gotwebd.h @@ -518,10 +518,10 @@ const struct got_error *got_get_repo_commits(struct re const struct got_error *got_get_repo_tags(struct request *, size_t); const struct got_error *got_get_repo_heads(struct request *); const struct got_error *got_open_diff_for_output(FILE **, struct request *); -int got_output_repo_tree(struct request *, +int got_output_repo_tree(struct request *, char **, int (*)(struct template *, struct got_tree_entry *)); const struct got_error *got_open_blob_for_output(struct got_blob_object **, - int *, int *, struct request *); + int *, int *, struct request *, const char *, const char *, const char *); int got_output_blob_by_lines(struct template *, struct got_blob_object *, int (*)(struct template *, const char *, size_t)); const struct got_error *got_output_file_blame(struct request *, blob - bbcaa81e086ae388fea68770a80f18d48f3b28df file + gotwebd/pages.tmpl --- gotwebd/pages.tmpl +++ gotwebd/pages.tmpl @@ -643,9 +643,16 @@ nextsep(char *s, char **t) {{ define gotweb_render_tree(struct template *tp) }} {! + const struct got_error *error; struct request *c = tp->tp_arg; struct transport *t = c->t; + struct querystring *qs = t->qs; struct repo_commit *rc = TAILQ_FIRST(&t->repo_commits); + struct gotweb_url url; + char *readme = NULL; + int binary; + const uint8_t *buf; + size_t len; !}

Tree

@@ -665,9 +672,55 @@ nextsep(char *s, char **t)
- {{ render got_output_repo_tree(c, gotweb_render_tree_item) }} + {{ render got_output_repo_tree(c, &readme, gotweb_render_tree_item) }}
+ {{ if readme }} + {! + error = got_open_blob_for_output(&t->blob, &t->fd, &binary, c, + qs->folder, readme, qs->commit); + if (error) { + free(readme); + return (-1); + } + + memset(&url, 0, sizeof(url)); + url.index_page = -1; + url.page = -1; + url.action = BLOB; + url.path = t->qs->path; + url.file = readme; + url.folder = t->qs->folder; + url.commit = t->qs->commit; + !} + {{ if !binary }} +

+ + {{ readme }} + +

+
+        {!
+		for (;;) {
+			error = got_object_blob_read_block(&len, t->blob);
+			if (error) {
+				free(readme);
+				return (-1);
+			}
+			if (len == 0)
+				break;
+			buf = got_object_blob_get_read_buf(t->blob);
+			if (tp_write_htmlescape(tp, buf, len) == -1) {
+				free(readme);
+				return (-1);
+			}
+		}
+        !}
+      
+ {{ end }} + {{ end }} +{{ finally }} +{! free(readme); !} {{ end }} {{ define gotweb_render_tree_item(struct template *tp, blob - 5f1093f5488dc59cedb4551421207df0fb7e025d file + template/tmpl.c --- template/tmpl.c +++ template/tmpl.c @@ -97,37 +97,46 @@ tp_urlescape(struct template *tp, const char *str) return (0); } +static inline int +htmlescape(struct template *tp, char c) +{ + switch (c) { + case '<': + return tp_write(tp, "<", 4); + case '>': + return tp_write(tp, ">", 4); + case '&': + return tp_write(tp, "&", 5); + case '"': + return tp_write(tp, """, 6); + case '\'': + return tp_write(tp, "'", 6); + default: + return tp_write(tp, &c, 1); + } +} + int tp_htmlescape(struct template *tp, const char *str) { - int r; - if (str == NULL) return (0); for (; *str; ++str) { - switch (*str) { - case '<': - r = tp_write(tp, "<", 4); - break; - case '>': - r = tp_write(tp, ">", 4); - break; - case '&': - r = tp_write(tp, "&", 5); - break; - case '"': - r = tp_write(tp, """, 6); - break; - case '\'': - r = tp_write(tp, "'", 6); - break; - default: - r = tp_write(tp, str, 1); - break; - } + if (htmlescape(tp, *str) == -1) + return (-1); + } - if (r == -1) + return (0); +} + +int +tp_write_htmlescape(struct template *tp, const char *str, size_t len) +{ + size_t i; + + for (i = 0; i < len; ++i) { + if (htmlescape(tp, str[i]) == -1) return (-1); } blob - df3b74c2696e16f2a591fb656220ac7957c59a56 file + template/tmpl.h --- template/tmpl.h +++ template/tmpl.h @@ -36,6 +36,7 @@ int tp_writef(struct template *, const char *, ...) __attribute__((__format__ (printf, 2, 3))); int tp_urlescape(struct template *, const char *); int tp_htmlescape(struct template *, const char *); +int tp_write_htmlescape(struct template *, const char *, size_t); struct template *template(void *, tmpl_write, char *, size_t); int template_flush(struct template *);