"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Omar Polo <op@omarpolo.com>
Subject:
gotwebd: render READMEs in the tree view
To:
gameoftrees@openbsd.org
Date:
Thu, 07 Dec 2023 15:30:51 +0100

Download raw body.

Thread
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;
 !}
 <header class='subtitle'>
   <h2>Tree</h2>
@@ -665,9 +672,55 @@ nextsep(char *s, char **t)
   </div>
   <hr />
   <table id="tree">
-    {{ render got_output_repo_tree(c, gotweb_render_tree_item) }}
+    {{ render got_output_repo_tree(c, &readme, gotweb_render_tree_item) }}
   </table>
+  {{ 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 }}
+      <h2>
+        <a href="{{ render gotweb_render_url(c, &url) }}">
+          {{ readme }}
+        </a>
+      </h2>
+      <pre>
+        {!
+		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);
+			}
+		}
+        !}
+      </pre>
+    {{ end }}
+  {{ end }}
 </div>
+{{ 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, "&lt;", 4);
+	case '>':
+		return tp_write(tp, "&gt;", 4);
+	case '&':
+		return tp_write(tp, "&amp;", 5);
+	case '"':
+		return tp_write(tp, "&quot;", 6);
+	case '\'':
+		return tp_write(tp, "&apos;", 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, "&lt;", 4);
-			break;
-		case '>':
-			r = tp_write(tp, "&gt;", 4);
-			break;
-		case '&':
-			r = tp_write(tp, "&amp;", 5);
-			break;
-		case '"':
-			r = tp_write(tp, "&quot;", 6);
-			break;
-		case '\'':
-			r = tp_write(tp, "&apos;", 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 *);