Download raw body.
gotwebd: templates take 2
On Tue, Dec 13, 2022 at 11:42:34AM +0100, Omar Polo wrote: > here's a third version; compared to the previous this imports the > template utility in the top-level directory and moves the tests under > regress as per stsp suggestion (thanks!) > > i've also fixed a typo of the previous: in the template I called the > function gotweb_render_head/foot instead of header/footer. > I like this diredtion. There are some small grammatical tweaks needed in template.7, but that can be fixed in tree. I'm ok with this unless anyone else objects to this direction. > diff refs/heads/main refs/heads/tmpl > commit - b546f5e4a2345f89a875526edc57cf729564decf > commit + b816a10d37a9833f793b5f6b6491b05a95960ab3 > blob - bd9d446ba6e36728e565674927327fb69fc7b3c9 > blob + 819941027860474c6e36a936822d7c2f9c640bbc > --- Makefile > +++ Makefile > @@ -7,7 +7,7 @@ SUBDIR += gotweb gotwebd gotd gotsh gotctl > .endif > > .if make(clean) || make(obj) || make(release) > -SUBDIR += gotweb gotwebd gotd gotsh gotctl > +SUBDIR += gotweb gotwebd gotd gotsh gotctl template > .endif > > .if make(tags) || make(cleandir) > @@ -33,13 +33,19 @@ web: > diff -u got-dist.txt got-dist.txt.new > rm got-dist.txt.new > > +tmpl: > + ${MAKE} -C template > + > +tmpl-regress: > + ${MAKE} -C regress/template > + > web: > ${MAKE} -C gotweb > > web-install: > ${MAKE} -C gotweb install > > -webd: > +webd: tmpl > ${MAKE} -C gotwebd > > webd-install: > blob - 8744ffb84ade1da058f9e9837fdbbd91cd5130fc > blob + 1f2783a446360c7e3379bbca5be3bea0ee1e76c5 > --- gotwebd/Makefile > +++ gotwebd/Makefile > @@ -1,4 +1,5 @@ > .PATH:${.CURDIR}/../lib > +.PATH:${.CURDIR}/../template > > SUBDIR = libexec > > @@ -7,7 +8,7 @@ SRCS = config.c sockets.c log.c gotwebd.c parse.y pro > > PROG = gotwebd > SRCS = config.c sockets.c log.c gotwebd.c parse.y proc.c \ > - fcgi.c gotweb.c got_operations.c > + fcgi.c gotweb.c got_operations.c tmpl.c pages.c > SRCS += blame.c commit_graph.c delta.c diff.c \ > diffreg.c error.c fileindex.c object.c object_cache.c \ > object_idset.c object_parse.c opentemp.c path.c pack.c \ > @@ -21,9 +22,20 @@ MAN = ${PROG}.conf.5 ${PROG}.8 > object_open_privsep.c read_gitconfig_privsep.c \ > read_gotconfig_privsep.c pollfd.c reference_parse.c > > +.if exists(${.CURDIR}/../template/obj/template) > +TEMPLATE = ${.CURDIR}/../template/obj/template > +.else > +TEMPLATE = ${.CURDIR}/../template/template > +.endif > + > +.SUFFIXES: .tmpl > +.tmpl.c: > + ${TEMPLATE} -o $@ $< > + > MAN = ${PROG}.conf.5 ${PROG}.8 > > CPPFLAGS += -I${.CURDIR}/../include -I${.CURDIR}/../lib -I${.CURDIR} > +CPPFLAGS += -I${.CURDIR}/../template > LDADD += -lz -levent -lutil -lm > YFLAGS = > DPADD = ${LIBEVENT} ${LIBUTIL} > blob - 04e9e72ece31adeacc0aef6f6fb7cffbaace078a > blob + b4f85fc2121de39b9a1c7ff2f9fcab75f5b7da42 > --- gotwebd/fcgi.c > +++ gotwebd/fcgi.c > @@ -36,6 +36,7 @@ > > #include "proc.h" > #include "gotwebd.h" > +#include "tmpl.h" > > size_t fcgi_parse_record(uint8_t *, size_t, struct request *); > void fcgi_parse_begin_request(uint8_t *, uint16_t, struct request *, > @@ -288,6 +289,21 @@ fcgi_vprintf(struct request *c, const char *fmt, va_li > } > > int > +fcgi_puts(struct template *tp, const char *str) > +{ > + if (str == NULL) > + return 0; > + return fcgi_gen_binary_response(tp->tp_arg, str, strlen(str)); > +} > + > +int > +fcgi_putc(struct template *tp, int ch) > +{ > + uint8_t c = ch; > + return fcgi_gen_binary_response(tp->tp_arg, &c, 1); > +} > + > +int > fcgi_vprintf(struct request *c, const char *fmt, va_list ap) > { > char *str; > @@ -483,6 +499,7 @@ fcgi_cleanup_request(struct request *c) > event_del(&c->ev); > > close(c->fd); > + template_free(c->tp); > gotweb_free_transport(c->t); > free(c); > } > blob - fc0c94f6c50f1675561ef2a30f20c748df22416c > blob + 0ce90275bab1028c73efcf66d2d59a727f7ca7c7 > --- gotwebd/gotweb.c > +++ gotwebd/gotweb.c > @@ -51,11 +51,6 @@ enum gotweb_ref_tm { > #include "proc.h" > #include "gotwebd.h" > > -enum gotweb_ref_tm { > - TM_DIFF, > - TM_LONG, > -}; > - > static const struct querystring_keys querystring_keys[] = { > { "action", ACTION }, > { "commit", COMMIT }, > @@ -86,8 +81,6 @@ static const struct got_error *gotweb_render_header(st > char *); > static const struct got_error *gotweb_assign_querystring(struct querystring **, > char *, char *); > -static const struct got_error *gotweb_render_header(struct request *); > -static const struct got_error *gotweb_render_footer(struct request *); > static const struct got_error *gotweb_render_index(struct request *); > static const struct got_error *gotweb_init_repo_dir(struct repo_dir **, > const char *); > @@ -97,9 +90,7 @@ static const struct got_error *gotweb_render_navs(stru > struct server *, const char *, int); > static const struct got_error *gotweb_get_clone_url(char **, struct server *, > const char *, int); > -static const struct got_error *gotweb_render_navs(struct request *); > static const struct got_error *gotweb_render_blame(struct request *); > -static const struct got_error *gotweb_render_briefs(struct request *); > static const struct got_error *gotweb_render_commits(struct request *); > static const struct got_error *gotweb_render_diff(struct request *); > static const struct got_error *gotweb_render_summary(struct request *); > @@ -108,6 +99,8 @@ static void gotweb_free_querystring(struct querystring > static const struct got_error *gotweb_render_tree(struct request *); > static const struct got_error *gotweb_render_branches(struct request *); > > +const struct got_error *gotweb_render_navs(struct request *); > + > static void gotweb_free_querystring(struct querystring *); > static void gotweb_free_repo_dir(struct repo_dir *); > > @@ -195,11 +188,8 @@ render: > } > html = 1; > > - error = gotweb_render_header(c); > - if (error) { > - log_warnx("%s: %s", __func__, error->msg); > + if (gotweb_render_header(c->tp) == -1) > goto err; > - } > > if (error2) { > error = error2; > @@ -215,11 +205,8 @@ render: > } > break; > case BRIEFS: > - error = gotweb_render_briefs(c); > - if (error) { > - log_warnx("%s: %s", __func__, error->msg); > + if (gotweb_render_briefs(c->tp) == -1) > goto err; > - } > break; > case COMMITS: > error = gotweb_render_commits(c); > @@ -296,7 +283,7 @@ done: > return; > done: > if (html && srv != NULL) > - gotweb_render_footer(c); > + gotweb_render_footer(c->tp); > } > > struct server * > @@ -692,145 +679,7 @@ static const struct got_error * > return NULL; > } > > -static const struct got_error * > -gotweb_render_header(struct request *c) > -{ > - const struct got_error *err = NULL; > - struct server *srv = c->srv; > - struct querystring *qs = c->t->qs; > - int r; > - > - r = fcgi_printf(c, "<!doctype html>\n" > - "<html>\n" > - "<head>\n" > - "<title>%s</title>\n" > - "<meta charset='utf-8' />\n" > - "<meta name='viewport' content='initial-scale=.75' />\n" > - "<meta name='msapplication-TileColor' content='#da532c' />\n" > - "<meta name='theme-color' content='#ffffff'/>\n" > - "<link rel='apple-touch-icon' sizes='180x180'" > - " href='%sapple-touch-icon.png' />\n" > - "<link rel='icon' type='image/png' sizes='32x32'" > - " href='%sfavicon-32x32.png' />\n" > - "<link rel='icon' type='image/png' sizes='16x16'" > - " href='%sfavicon-16x16.png' />\n" > - "<link rel='manifest' href='%ssite.webmanifest'/>\n" > - "<link rel='mask-icon' href='%ssafari-pinned-tab.svg' />\n" > - "<link rel='stylesheet' type='text/css' href='%s%s' />\n" > - "</head>\n" > - "<body>\n" > - "<div id='gw_body'>\n" > - "<div id='header'>\n" > - "<div id='got_link'>" > - "<a href='%s' target='_blank'>" > - "<img src='%s%s' alt='logo' id='logo' />" > - "</a>\n" > - "</div>\n" /* #got_link */ > - "</div>\n" /* #header */ > - "<div id='site_path'>\n" > - "<div id='site_link'>\n" > - "<a href='?index_page=%d'>%s</a>", > - srv->site_name, > - c->script_name, > - c->script_name, > - c->script_name, > - c->script_name, > - c->script_name, > - c->script_name, srv->custom_css, > - srv->logo_url, > - c->script_name, srv->logo, > - qs->index_page, srv->site_link); > - if (r == -1) > - goto done; > - > - if (qs->path != NULL) { > - char *epath; > - > - if (fcgi_printf(c, " / ") == -1) > - goto done; > - > - err = gotweb_escape_html(&epath, qs->path); > - if (err) > - return err; > - r = gotweb_link(c, &(struct gotweb_url){ > - .action = SUMMARY, > - .index_page = -1, > - .page = -1, > - .path = qs->path, > - }, "%s", epath); > - free(epath); > - if (r == -1) > - goto done; > - } > - if (qs->action != INDEX) { > - const char *action = ""; > - > - switch (qs->action) { > - case BLAME: > - action = "blame"; > - break; > - case BRIEFS: > - action = "briefs"; > - break; > - case COMMITS: > - action = "commits"; > - break; > - case DIFF: > - action = "diff"; > - break; > - case SUMMARY: > - action = "summary"; > - break; > - case TAG: > - action = "tag"; > - break; > - case TAGS: > - action = "tags"; > - break; > - case TREE: > - action = "tree"; > - break; > - } > - > - if (fcgi_printf(c, " / %s", action) == -1) > - goto done; > - } > - > - fcgi_printf(c, "</div>\n" /* #site_path */ > - "</div>\n" /* #site_link */ > - "<div id='content'>\n"); > - > -done: > - return NULL; > -} > - > -static const struct got_error * > -gotweb_render_footer(struct request *c) > -{ > - const struct got_error *error = NULL; > - struct server *srv = c->srv; > - const char *siteowner = " "; > - char *escaped_owner = NULL; > - > - if (srv->show_site_owner) { > - error = gotweb_escape_html(&escaped_owner, srv->site_owner); > - if (error) > - return error; > - siteowner = escaped_owner; > - } > - > - fcgi_printf(c, "<div id='site_owner_wrapper'>\n" > - "<div id='site_owner'>%s</div>\n" > - "</div>\n" /* #site_owner_wrapper */ > - "</div>\n" /* #content */ > - "</div>\n" /* #gw_body */ > - "</body>\n</html>\n", siteowner); > - > - free(escaped_owner); > - return NULL; > -} > - > -static const struct got_error * > +const struct got_error * > gotweb_render_navs(struct request *c) > { > const struct got_error *error = NULL; > @@ -996,7 +845,7 @@ gotweb_render_index(struct request *c) > struct dirent **sd_dent = NULL; > unsigned int d_cnt, d_i, d_disp = 0; > unsigned int d_skipped = 0; > - int r, type; > + int type; > > d = opendir(srv->repos_path); > if (d == NULL) { > @@ -1011,26 +860,9 @@ gotweb_render_index(struct request *c) > goto done; > } > > - r = fcgi_printf(c, "<div id='index_header'>\n" > - "<div id='index_header_project'>Project</div>\n"); > - if (r == -1) > + if (gotweb_render_repo_table_hdr(c->tp) == -1) > goto done; > > - if (srv->show_repo_description) > - if (fcgi_printf(c, "<div id='index_header_description'>" > - "Description</div>\n") == -1) > - goto done; > - if (srv->show_repo_owner) > - if (fcgi_printf(c, "<div id='index_header_owner'>" > - "Owner</div>\n") == -1) > - goto done; > - if (srv->show_repo_age) > - if (fcgi_printf(c, "<div id='index_header_age'>" > - "Last Change</div>\n") == -1) > - goto done; > - if (fcgi_printf(c, "</div>\n") == -1) /* #index_header */ > - goto done; > - > for (d_i = 0; d_i < d_cnt; d_i++) { > if (srv->max_repos > 0 && t->prev_disp == srv->max_repos) > break; > @@ -1074,112 +906,9 @@ gotweb_render_index(struct request *c) > d_disp++; > t->prev_disp++; > > - if (fcgi_printf(c, "<div class='index_wrapper'>\n" > - "<div class='index_project'>") == -1) > + if (gotweb_render_repo_fragment(c->tp, repo_dir) == -1) > goto done; > > - r = gotweb_link(c, &(struct gotweb_url){ > - .action = SUMMARY, > - .index_page = -1, > - .page = -1, > - .path = repo_dir->name, > - }, "%s", repo_dir->name); > - if (r == -1) > - goto done; > - > - if (fcgi_printf(c, "</div>") == -1) /* .index_project */ > - goto done; > - > - if (srv->show_repo_description) { > - r = fcgi_printf(c, > - "<div class='index_project_description'>\n" > - "%s</div>\n", repo_dir->description); > - if (r == -1) > - goto done; > - } > - > - if (srv->show_repo_owner) { > - r = fcgi_printf(c, "<div class='index_project_owner'>" > - "%s</div>\n", repo_dir->owner); > - if (r == -1) > - goto done; > - } > - > - if (srv->show_repo_age) { > - r = fcgi_printf(c, "<div class='index_project_age'>" > - "%s</div>\n", repo_dir->age); > - if (r == -1) > - goto done; > - } > - > - if (fcgi_printf(c, "<div class='navs_wrapper'>" > - "<div class='navs'>") == -1) > - goto done; > - > - r = gotweb_link(c, &(struct gotweb_url){ > - .action = SUMMARY, > - .index_page = -1, > - .page = -1, > - .path = repo_dir->name > - }, "summary"); > - if (r == -1) > - goto done; > - > - if (fcgi_printf(c, " | ") == -1) > - goto done; > - > - r = gotweb_link(c, &(struct gotweb_url){ > - .action = BRIEFS, > - .index_page = -1, > - .page = -1, > - .path = repo_dir->name > - }, "commit briefs"); > - if (r == -1) > - goto done; > - > - if (fcgi_printf(c, " | ") == -1) > - goto done; > - > - r = gotweb_link(c, &(struct gotweb_url){ > - .action = COMMITS, > - .index_page = -1, > - .page = -1, > - .path = repo_dir->name > - }, "commits"); > - if (r == -1) > - goto done; > - > - if (fcgi_printf(c, " | ") == -1) > - goto done; > - > - r = gotweb_link(c, &(struct gotweb_url){ > - .action = TAGS, > - .index_page = -1, > - .page = -1, > - .path = repo_dir->name > - }, "tags"); > - if (r == -1) > - goto done; > - > - if (fcgi_printf(c, " | ") == -1) > - goto done; > - > - r = gotweb_link(c, &(struct gotweb_url){ > - .action = TREE, > - .index_page = -1, > - .page = -1, > - .path = repo_dir->name > - }, "tree"); > - if (r == -1) > - goto done; > - > - r = fcgi_printf(c, "</div>" /* .navs */ > - "<div class='dotted_line'></div>\n" > - "</div>\n" /* .navs_wrapper */ > - "</div>\n"); /* .index_wrapper */ > - if (r == -1) > - goto done; > - > gotweb_free_repo_dir(repo_dir); > repo_dir = NULL; > t->next_disp++; > @@ -1264,143 +993,6 @@ gotweb_render_briefs(struct request *c) > } > > static const struct got_error * > -gotweb_render_briefs(struct request *c) > -{ > - const struct got_error *error = NULL; > - struct repo_commit *rc = NULL; > - struct server *srv = c->srv; > - struct transport *t = c->t; > - struct querystring *qs = t->qs; > - struct repo_dir *repo_dir = t->repo_dir; > - char *smallerthan, *newline; > - char *age = NULL, *author = NULL, *msg = NULL; > - int r; > - > - r = fcgi_printf(c, "<div id='briefs_title_wrapper'>\n" > - "<div id='briefs_title'>Commit Briefs</div>\n" > - "</div>\n" /* #briefs_title_wrapper */ > - "<div id='briefs_content'>\n"); > - if (r == -1) > - goto done; > - > - if (qs->action == SUMMARY) { > - qs->action = BRIEFS; > - error = got_get_repo_commits(c, D_MAXSLCOMMDISP); > - } else > - error = got_get_repo_commits(c, srv->max_commits_display); > - if (error) > - goto done; > - > - TAILQ_FOREACH(rc, &t->repo_commits, entry) { > - error = gotweb_get_time_str(&age, rc->committer_time, TM_DIFF); > - if (error) > - goto done; > - > - smallerthan = strchr(rc->author, '<'); > - if (smallerthan) > - *smallerthan = '\0'; > - > - newline = strchr(rc->commit_msg, '\n'); > - if (newline) > - *newline = '\0'; > - > - error = gotweb_escape_html(&author, rc->author); > - if (error) > - goto done; > - error = gotweb_escape_html(&msg, rc->commit_msg); > - if (error) > - goto done; > - > - r = fcgi_printf(c, "<div class='briefs_age'>%s</div>\n" > - "<div class='briefs_author'>%s</div>\n" > - "<div class='briefs_log'>", > - age, author); > - if (r == -1) > - goto done; > - > - r = gotweb_link(c, &(struct gotweb_url){ > - .action = DIFF, > - .index_page = -1, > - .page = -1, > - .path = repo_dir->name, > - .commit = rc->commit_id, > - .headref = qs->headref, > - }, "%s", msg); > - if (r == -1) > - goto done; > - > - if (rc->refs_str) { > - char *refs; > - > - error = gotweb_escape_html(&refs, rc->refs_str); > - if (error) > - goto done; > - r = fcgi_printf(c, > - " <span class='refs_str'>(%s)</span>", refs); > - free(refs); > - if (r == -1) > - goto done; > - } > - if (fcgi_printf(c, "</div>\n") == -1) /* .briefs_log */ > - goto done; > - > - r = fcgi_printf(c, "<div class='navs_wrapper'>\n" > - "<div class='navs'>"); > - if (r == -1) > - goto done; > - > - r = gotweb_link(c, &(struct gotweb_url){ > - .action = DIFF, > - .index_page = -1, > - .page = -1, > - .path = repo_dir->name, > - .commit = rc->commit_id, > - .headref = qs->headref, > - }, "diff"); > - if (r == -1) > - goto done; > - > - if (fcgi_printf(c, " | ") == -1) > - goto done; > - > - r = gotweb_link(c, &(struct gotweb_url){ > - .action = TREE, > - .index_page = -1, > - .page = -1, > - .path = repo_dir->name, > - .commit = rc->commit_id, > - .headref = qs->headref, > - }, "tree"); > - if (r == -1) > - goto done; > - > - if (fcgi_printf(c, "</div>\n" /* .navs */ > - "</div>\n" /* .navs_wrapper */ > - "<div class='dotted_line'></div>\n") == -1) > - goto done; > - > - free(age); > - age = NULL; > - free(author); > - author = NULL; > - free(msg); > - msg = NULL; > - } > - > - if (t->next_id || t->prev_id) { > - error = gotweb_render_navs(c); > - if (error) > - goto done; > - } > - fcgi_printf(c, "</div>\n"); /* #briefs_content */ > -done: > - free(age); > - free(author); > - free(msg); > - return error; > -} > - > -static const struct got_error * > gotweb_render_commits(struct request *c) > { > const struct got_error *error = NULL; > @@ -1811,11 +1403,8 @@ gotweb_render_summary(struct request *c) > if (r == -1) > goto done; > > - error = gotweb_render_briefs(c); > - if (error) { > - log_warnx("%s: %s", __func__, error->msg); > + if (gotweb_render_briefs(c->tp) == -1) > goto done; > - } > > error = gotweb_render_tags(c); > if (error) { > @@ -2177,8 +1766,8 @@ static inline const char * > return escaped; > } > > -static inline const char * > -action_name(int action) > +const char * > +gotweb_action_name(int action) > { > switch (action) { > case BLAME: > @@ -2208,14 +1797,14 @@ static int > } > } > > -static int > -gotweb_print_url(struct request *c, struct gotweb_url *url) > +int > +gotweb_render_url(struct request *c, struct gotweb_url *url) > { > const char *sep = "?", *action; > char *tmp; > int r; > > - action = action_name(url->action); > + action = gotweb_action_name(url->action); > if (action != NULL) { > if (fcgi_printf(c, "?action=%s", action) == -1) > return -1; > @@ -2309,7 +1898,7 @@ gotweb_link(struct request *c, struct gotweb_url *url, > if (fcgi_printf(c, "<a href='") == -1) > return -1; > > - if (gotweb_print_url(c, url) == -1) > + if (gotweb_render_url(c, url) == -1) > return -1; > > if (fcgi_printf(c, "'>") == -1) > blob - 14426b3f4ad9787e7876dc233211e8fb8875cab6 > blob + 64fce7c6d8ee3f57305034f0c0434cff991a82bd > --- gotwebd/gotwebd.h > +++ gotwebd/gotwebd.h > @@ -202,10 +202,12 @@ struct request { > PRIV_FDS__MAX, > }; > > +struct template; > struct request { > struct socket *sock; > struct server *srv; > struct transport *t; > + struct template *tp; > struct event ev; > struct event tmo; > > @@ -415,6 +417,11 @@ extern struct gotwebd *gotwebd_env; > ACTIONS__MAX, > }; > > +enum gotweb_ref_tm { > + TM_DIFF, > + TM_LONG, > +}; > + > extern struct gotwebd *gotwebd_env; > > /* sockets.c */ > @@ -432,6 +439,8 @@ int gotweb_link(struct request *, struct gotweb_url *, > const struct got_error *gotweb_get_time_str(char **, time_t, int); > const struct got_error *gotweb_init_transport(struct transport **); > const struct got_error *gotweb_escape_html(char **, const char *); > +const char *gotweb_action_name(int); > +int gotweb_render_url(struct request *, struct gotweb_url *); > int gotweb_link(struct request *, struct gotweb_url *, const char *, ...) > __attribute__((__format__(printf, 3, 4))) > __attribute__((__nonnull__(3))); > @@ -440,6 +449,13 @@ void gotweb_free_transport(struct transport *); > void gotweb_process_request(struct request *); > void gotweb_free_transport(struct transport *); > > +/* pages.tmpl */ > +int gotweb_render_header(struct template *); > +int gotweb_render_footer(struct template *); > +int gotweb_render_repo_table_hdr(struct template *); > +int gotweb_render_repo_fragment(struct template *, struct repo_dir *); > +int gotweb_render_briefs(struct template *); > + > /* parse.y */ > int parse_config(const char *, struct gotwebd *); > int cmdline_symset(char *); > @@ -450,6 +466,8 @@ int fcgi_vprintf(struct request *, const char *, va_li > void fcgi_cleanup_request(struct request *); > void fcgi_create_end_record(struct request *); > void dump_fcgi_record(const char *, struct fcgi_record_header *); > +int fcgi_puts(struct template *, const char *); > +int fcgi_putc(struct template *, int); > int fcgi_vprintf(struct request *, const char *, va_list); > int fcgi_printf(struct request *, const char *, ...) > __attribute__((__format__(printf, 2, 3))) > blob - /dev/null > blob + 0d49898e174e9bfad77b158729eb04ab949bd736 (mode 644) > --- /dev/null > +++ gotwebd/pages.tmpl > @@ -0,0 +1,302 @@ > +{! > +/* > + * Copyright (c) 2022 Omar Polo <op@openbsd.org> > + * Copyright (c) 2016, 2019, 2020-2022 Tracey Emery <tracey@traceyemery.net> > + * > + * Permission to use, copy, modify, and distribute this software for any > + * purpose with or without fee is hereby granted, provided that the above > + * copyright notice and this permission notice appear in all copies. > + * > + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES > + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF > + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR > + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES > + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN > + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF > + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. > + */ > + > +#include <sys/types.h> > +#include <sys/queue.h> > + > +#include <event.h> > +#include <stdint.h> > +#include <stdlib.h> > +#include <string.h> > +#include <imsg.h> > + > +#include "proc.h" > + > +#include "gotwebd.h" > +#include "tmpl.h" > + > +const struct got_error *gotweb_render_navs(struct request *); > + > +static int > +gotweb_render_age(struct template *tp, time_t time, int ref_tm) > +{ > + const struct got_error *err; > + char *age; > + int r; > + > + err = gotweb_get_time_str(&age, time, ref_tm); > + if (err) > + return 0; > + r = tp->tp_puts(tp, age); > + free(age); > + return r; > +} > + > +!} > + > +{{ define gotweb_render_header(struct template *tp) }} > +{! > + struct request *c = tp->tp_arg; > + struct server *srv = c->srv; > + struct querystring *qs = c->t->qs; > + struct gotweb_url u_path; > + const char *prfx = c->script_name; > + const char *css = srv->custom_css; > + > + memset(&u_path, 0, sizeof(u_path)); > + u_path.index_page = -1; > + u_path.page = -1; > + u_path.action = SUMMARY; > +!} > +<!doctype html> > +<html> > + <head> > + <meta charset="utf-8" /> > + <title>{{ srv->site_name }}</title> > + <meta name="viewport" content="initial-scale=.75" /> > + <meta name="msapplication-TileColor" content="#da532c" /> > + <meta name="theme-color" content="#ffffff"/> > + <link rel="apple-touch-icon" sizes="180x180" href="{{ prfx }}apple-touch-icon.png" /> > + <link rel="icon" type="image/png" sizes="32x32" href="{{ prfx }}favicon-32x32.png" /> > + <link rel="icon" type="image/png" sizes="16x16" href="{{ prfx }}favicon-16x16.png" /> > + <link rel="manifest" href="{{ prfx }}site.webmanifest"/> > + <link rel="mask-icon" href="{{ prfx }}safari-pinned-tab.svg" /> > + <link rel="stylesheet" type="text/css" href="{{ prfx }}{{ css }}" /> > + </head> > + <body> > + <div id="gw_body"> > + <div id="header"> > + <div id="got_link"> > + <a href="{{ srv->logo_url }}" target="_blank"> > + <img src="{{ prfx }}{{ srv->logo }}" /> > + </a> > + </div> > + </div> > + <div id="site_path"> > + <div id="site_link"> > + <a href="?index_page={{ printf "%d", qs->index_page }}"> > + {{ srv->site_link }} > + </a> > + {{ if qs->path }} > + {! u_path.path = qs->path; !} > + {{ " / " }} > + <a href="{{ render gotweb_render_url(tp->tp_arg, &u_path)}}"> > + {{ qs->path }} > + </a> > + {{ end }} > + {{ if qs->action != INDEX }} > + {{ " / " }}{{ gotweb_action_name(qs->action) }} > + {{ end }} > + </div> > + </div> > + <div id="content"> > +{{ end }} > + > +{{ define gotweb_render_footer(struct template *tp) }} > +{! > + struct request *c = tp->tp_arg; > + struct server *srv = c->srv; > +!} > + <div id="site_owner_wrapper"> > + <div id="site_owner"> > + {{ if srv->show_site_owner }} > + {{ srv->site_owner }} > + {{ end }} > + </div> > + </div> > + </div> > + </div> > + </body> > +</html> > +{{ end }} > + > +{{ define gotweb_render_repo_table_hdr(struct template *tp) }} > +{! > + struct request *c = tp->tp_arg; > + struct server *srv = c->srv; > +!} > +<div id="index_header"> > + <div id="index_header_project"> > + Project > + </div> > + {{ if srv->show_repo_description }} > + <div id="index_header_description"> > + Description > + </div> > + {{ end }} > + {{ if srv->show_repo_owner }} > + <div id="index_header_owner"> > + Owner > + </div> > + {{ end }} > + {{ if srv->show_repo_age }} > + <div id="index_header_age"> > + Last Change > + </div> > + {{ end }} > +</div> > +{{ end }} > + > +{{ define gotweb_render_repo_fragment(struct template *tp, struct repo_dir *repo_dir) }} > +{! > + struct request *c = tp->tp_arg; > + struct server *srv = c->srv; > + struct gotweb_url summary = { > + .action = SUMMARY, > + .index_page = -1, > + .page = -1, > + .path = repo_dir->name, > + }, briefs = { > + .action = BRIEFS, > + .index_page = -1, > + .page = -1, > + .path = repo_dir->name, > + }, commits = { > + .action = COMMITS, > + .index_page = -1, > + .page = -1, > + .path = repo_dir->name, > + }, tags = { > + .action = TAGS, > + .index_page = -1, > + .page = -1, > + .path = repo_dir->name, > + }, tree = { > + .action = TREE, > + .index_page = -1, > + .page = -1, > + .path = repo_dir->name, > + }; > +!} > +<div class="index_wrapper"> > + <div class="index_project"> > + <a href="{{ render gotweb_render_url(tp->tp_arg, &summary) }}">{{ repo_dir->name }}</a> > + </div> > + {{ if srv->show_repo_description }} > + <div class="index_project_description"> > + {{ repo_dir->description }} > + </div> > + {{ end }} > + {{ if srv->show_repo_owner }} > + <div class="index_project_owner"> > + {{ repo_dir->owner }} > + </div> > + {{ end }} > + {{ if srv->show_repo_age }} > + <div class="index_project_age"> > + {{ repo_dir->age }} > + </div> > + {{ end }} > + <div class="navs_wrapper"> > + <div class="navs"> > + <a href="{{ render gotweb_render_url(tp->tp_arg, &summary) }}">summary</a> > + {{ " | " }} > + <a href="{{ render gotweb_render_url(tp->tp_arg, &briefs) }}">briefs</a> > + {{ " | " }} > + <a href="{{ render gotweb_render_url(tp->tp_arg, &commits) }}">commits</a> > + {{ " | " }} > + <a href="{{ render gotweb_render_url(tp->tp_arg, &tags) }}">tags</a> > + {{ " | " }} > + <a href="{{ render gotweb_render_url(tp->tp_arg, &tree) }}">tree</a> > + </div> > + <div class="dotted_line"></div> > + </div> > +</div> > +{{ end }} > + > +{{ define gotweb_render_briefs(struct template *tp) }} > +{! > + const struct got_error *error; > + struct request *c = tp->tp_arg; > + struct server *srv = c->srv; > + struct transport *t = c->t; > + struct querystring *qs = c->t->qs; > + struct repo_commit *rc; > + struct repo_dir *repo_dir = t->repo_dir; > + struct gotweb_url diff_url, tree_url; > + char *tmp; > + > + diff_url = (struct gotweb_url){ > + .action = DIFF, > + .index_page = -1, > + .page = -1, > + .path = repo_dir->name, > + .headref = qs->headref, > + }; > + tree_url = (struct gotweb_url){ > + .action = TREE, > + .index_page = -1, > + .page = -1, > + .path = repo_dir->name, > + .headref = qs->headref, > + }; > + > + if (qs->action == SUMMARY) { > + qs->action = BRIEFS; > + error = got_get_repo_commits(c, D_MAXSLCOMMDISP); > + } else > + error = got_get_repo_commits(c, srv->max_commits_display); > + if (error) > + return -1; > +!} > +<div id="briefs_title_wrapper"> > + <div id="briefs_title">Commit Briefs</div> > +</div> > +<div id="briefs_content"> > + {{ tailq-foreach rc &t->repo_commits entry }} > + {! > + diff_url.commit = rc->commit_id; > + tree_url.commit = rc->commit_id; > + > + tmp = strchr(rc->author, '<'); > + if (tmp) > + *tmp = '\0'; > + > + tmp = strchr(rc->commit_msg, '\n'); > + if (tmp) > + *tmp = '\0'; > + !} > + <div class="briefs_age"> > + {{ render gotweb_render_age(tp, rc->committer_time, TM_DIFF) }} > + </div> > + <div class="briefs_author"> > + {{ rc->author }} > + </div> > + <div class="briefs_log"> > + <a href="{{ render gotweb_render_url(tp->tp_arg, &diff_url) }}"> > + {{ rc->commit_msg }} > + </a> > + {{ if rc->refs_str }} > + {{ " " }} <span class="refs_str">({{ rc->refs_str }})</span> > + {{ end }} > + </a> > + </div> > + <div class="navs_wrapper"> > + <div class="navs"> > + <a href="{{ render gotweb_render_url(tp->tp_arg, &diff_url) }}">diff</a> > + {{ " | " }} > + <a href="{{ render gotweb_render_url(tp->tp_arg, &tree_url) }}">tree</a> > + </div> > + </div> > + <div class="dotted_line"></div> > + {{ end }} > + {{ if t->next_id || t->prev_id }} > + {! gotweb_render_navs(c); !} > + {{ end }} > +</div> > +{{ end }} > blob - c0691c5575dca8c931af0e8e30d9e086b5a5bccc > blob + cfba5dfb0e7368b0b37738d2eb71b53d437db169 > --- gotwebd/sockets.c > +++ gotwebd/sockets.c > @@ -55,6 +55,7 @@ > > #include "proc.h" > #include "gotwebd.h" > +#include "tmpl.h" > > #define SOCKS_BACKLOG 5 > #define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) > @@ -619,6 +620,15 @@ sockets_socket_accept(int fd, short event, void *arg) > return; > } > > + c->tp = template(c, fcgi_puts, fcgi_putc); > + if (c->tp == NULL) { > + log_warn("%s", __func__); > + close(s); > + cgi_inflight--; > + free(c); > + return; > + } > + > c->fd = s; > c->sock = sock; > memcpy(c->priv_fd, sock->priv_fd, sizeof(c->priv_fd)); > blob - /dev/null > blob + e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 (mode 644) > blob - /dev/null > blob + c2d0fad50754e9ec3740571ad5e77d7dc475ab26 (mode 644) > --- /dev/null > +++ regress/template/01-noise-only.tmpl > @@ -0,0 +1,2 @@ > +only > +noise > blob - /dev/null > blob + c35618a4287faab518c419c4bbbe06ecbca7f157 (mode 644) > --- /dev/null > +++ regress/template/02-only-verbatim.tmpl > @@ -0,0 +1,17 @@ > +{! > +#include <stdio.h> > + > +#include "tmpl.h" > +!} > + > +noise {! /* woops */ !} > +here > + > +{! > +int > +main(void) > +{ > + puts("hello, world!"); > + return (0); > +} > +!} > blob - /dev/null > blob + 270c611ee72c567bc1b2abec4cbc345bab9f15ba (mode 644) > --- /dev/null > +++ regress/template/02.expected > @@ -0,0 +1 @@ > +hello, world! > blob - /dev/null > blob + eee67b419ef25bca620f07fbef7169020bd6a490 (mode 644) > --- /dev/null > +++ regress/template/03-block.tmpl > @@ -0,0 +1,24 @@ > +{! > +#include <stdlib.h> > + > +#include "tmpl.h" > + > +int base(struct template *, const char *); > +!} > + > +{{ define base(struct template *tp, const char *title) }} > +{! char *foo = NULL; !} > +<!doctype html> > +<html> > + <head> > + <title>{{ title }}</title> > + </head> > + <body> {! /* TODO: frobnicate this line! */ !} > + <h1>{{ title }}</h1> > + {{ " | " }} > + {{ "other stuff" }} > + </body> > +</html> > +{{ finally }} > +{! free(foo); !} > +{{ end }} > blob - /dev/null > blob + 0f766820b9e5de1343ca8034bea3b2b0a4418221 (mode 644) > --- /dev/null > +++ regress/template/03.expected > @@ -0,0 +1,2 @@ > +<!doctype html><html><head><title> *hello* </title></head><body> <h1> *hello* </h1> | other stuff</body></html> > +<!doctype html><html><head><title><hello></title></head><body> <h1><hello></h1> | other stuff</body></html> > blob - /dev/null > blob + 5494a40b208b5f39f24c8a161095e089309d47fb (mode 644) > --- /dev/null > +++ regress/template/04-flow.tmpl > @@ -0,0 +1,33 @@ > +{! > +#include <stdlib.h> > +#include <string.h> > + > +#include "tmpl.h" > + > +int base(struct template *, const char *); > +!} > + > +{{ define base(struct template *tp, const char *title) }} > +{! char *foo = NULL; !} > +<!doctype html> > +<html> > + <head> > + <title>{{ title }}</title> > + </head> > + <body> > + <h1>{{ title }}</h1> > + {{ if strchr(title, '*') != NULL }} > + <p>"{{ title }}" has a '*' in it</p> > + {{ if 1 }} > + <p>tautology!</p> > + {{ end }} > + {{ else if strchr(title, '=') != NULL }} > + <p>"{{ title }}" has a '=' in it!</p> > + {{ else }} > + <p>"{{ title }}" doesn't have a '*' in it</p> > + {{ end }} > + </body> > +</html> > +{{ finally }} > +{! free(foo); !} > +{{ end }} > blob - /dev/null > blob + 32240e27d9a6dbeeb395f7e760e2a043e29b939c (mode 644) > --- /dev/null > +++ regress/template/04.expected > @@ -0,0 +1,2 @@ > +<!doctype html><html><head><title> *hello* </title></head><body><h1> *hello* </h1><p>" *hello* " has a '*' in it</p><p>tautology!</p></body></html> > +<!doctype html><html><head><title><hello></title></head><body><h1><hello></h1><p>"<hello>" doesn't have a '*' in it</p></body></html> > blob - /dev/null > blob + 5cb351ad4205991f16bb17f7caffc03c7594f15e (mode 644) > --- /dev/null > +++ regress/template/05-loop.tmpl > @@ -0,0 +1,43 @@ > +{! > +#include <sys/queue.h> > +#include <string.h> > +#include "lists.h" > +#include "tmpl.h" > + > +int list(struct template *, struct tailhead *); > +int base(struct template *, struct tailhead *); > + > +!} > + > +{{ define base(struct template *tp, struct tailhead *head) }} > +<!doctype html> > +<html> > + <body> > + {{ render list(tp, head) }} > + </body> > +</html> > +{{ end }} > + > +{{ define list(struct template *tp, struct tailhead *head) }} > +{! > + struct entry *np; > + int i; > +!} > + {{ if !TAILQ_EMPTY(head) }} > + <p>items:</p> > + <ul> > + {{ tailq-foreach np head entries }} > + <li>{{ np->text }}</li> > + {{ end }} > + </ul> > + {{ else }} > + <p>no items</p> > + {{ end }} > + > + <p> > + {{ for i = 0; i < 3; ++i }} > + hello{{ " " }} > + {{ end }} > + world! > + </p> > +{{ end }} > blob - /dev/null > blob + d4c20d67eeee5e6e0217d8abd4c19f2d440ba6d9 (mode 644) > --- /dev/null > +++ regress/template/05.expected > @@ -0,0 +1,2 @@ > +<!doctype html><html><body><p>items:</p><ul><li>1</li><li>2</li></ul><p>hello hello hello world!</p></body></html> > +<!doctype html><html><body><p>no items</p><p>hello hello hello world!</p></body></html> > blob - /dev/null > blob + 8a1d436209ad2a5cd22583fbb5c7c5b8cf06fdd7 (mode 644) > --- /dev/null > +++ regress/template/06-escape.tmpl > @@ -0,0 +1,19 @@ > +{! > +#include <stdlib.h> > + > +#include "tmpl.h" > + > +int base(struct template *, const char *); > +!} > + > +{{ define base(struct template *tp, const char *title) }} > +<!doctype html> > +<html> > + <head> > + <title>{{ title | urlescape }}</title> > + </head> > + <body> > + <h1>{{ title | unsafe }}</h1> > + </body> > +</html> > +{{ end }} > blob - /dev/null > blob + 6a9d734b454093206236753143743d95d5e473af (mode 644) > --- /dev/null > +++ regress/template/06.expected > @@ -0,0 +1,2 @@ > +<!doctype html><html><head><title>%20*hello*%20</title></head><body><h1> *hello* </h1></body></html> > +<!doctype html><html><head><title><hello></title></head><body><h1><hello></h1></body></html> > blob - /dev/null > blob + 0ad8f7070ecbf501efdda6004283ba83ce93ff15 (mode 644) > --- /dev/null > +++ regress/template/07-printf.tmpl > @@ -0,0 +1,13 @@ > +{! > +#include <stdio.h> > +#include <stdlib.h> > + > +#include "tmpl.h" > + > +int base(struct template *, const char *); > + > +!} > + > +{{ define base(struct template *tp, const char *title) }} > +{{ printf "%.2s:\t%d\n", title, 42 }} > +{{ end }} > blob - /dev/null > blob + 681daf3915b708a380ddc75467b47beceff9b367 (mode 644) > --- /dev/null > +++ regress/template/07.expected > @@ -0,0 +1,4 @@ > + *: 42 > + > +<h: 42 > + > blob - /dev/null > blob + f781b7ac44f61c8065695ac89ec64680ac84268c (mode 644) > --- /dev/null > +++ regress/template/Makefile > @@ -0,0 +1,55 @@ > +REGRESS_TARGETS = 00-empty \ > + 01-noise-only \ > + 02-only-verbatim \ > + 03-block \ > + 04-flow \ > + 05-loop \ > + 06-escape \ > + 07-printf > + > +REGRESS_CLEANUP = clean-comp > +NO_OBJ = Yes > + > +CFLAGS += -I../../template > + > +.PATH:../../template > + > +clean-comp: > + rm -f t got 0*.[cdo] runbase.[do] runlist.[do] tmpl.* > + > +.SUFFIXES: .tmpl .c .o > + > +.tmpl.c: > + ../../template/obj/template -o $@ $< > + > +00-empty: > + ../../template/obj/template 00-empty.tmpl >/dev/null > + > +01-noise-only: > + ../../template/obj/template 01-noise-only.tmpl >/dev/null > + > +02-only-verbatim: 02-only-verbatim.o tmpl.o > + ${CC} 02-only-verbatim.o tmpl.o -o t && ./t > got > + diff -u ${.CURDIR}/02.expected got > + > +03-block: 03-block.o runbase.o tmpl.o > + ${CC} 03-block.o runbase.o tmpl.o -o t && ./t > got > + diff -u ${.CURDIR}/03.expected got > + > +04-flow: 04-flow.o runbase.o tmpl.o > + ${CC} 04-flow.o runbase.o tmpl.o -o t && ./t > got > + diff -u ${.CURDIR}/04.expected got > + > +05-loop: 05-loop.o runlist.o tmpl.o > + ${CC} 05-loop.o runlist.o tmpl.o -o t && ./t > got > + diff -u ${.CURDIR}/05.expected got > + > +06-escape: 06-escape.o runbase.o tmpl.o > + ${CC} 06-escape.o runbase.o tmpl.o -o t && ./t > got > + diff -u ${.CURDIR}/06.expected got > + > +07-printf: 07-printf.o runbase.o tmpl.o > + ${CC} 07-printf.o runbase.o tmpl.o -o t && ./t > got > + diff -u ${.CURDIR}/07.expected got > + > +.include <bsd.regress.mk> > blob - /dev/null > blob + 7229706ecd6bfb0bb2f38be92950b7082f7a6c41 (mode 644) > --- /dev/null > +++ regress/template/lists.h > @@ -0,0 +1,7 @@ > +#include <sys/queue.h> > + > +TAILQ_HEAD(tailhead, entry); > +struct entry { > + char *text; > + TAILQ_ENTRY(entry) entries; > +}; > blob - /dev/null > blob + 0500bcadda792968ed1a7c8294ba9761ecc4ff71 (mode 644) > --- /dev/null > +++ regress/template/runbase.c > @@ -0,0 +1,67 @@ > +/* > + * Copyright (c) 2022 Omar Polo <op@omarpolo.com> > + * > + * Permission to use, copy, modify, and distribute this software for any > + * purpose with or without fee is hereby granted, provided that the above > + * copyright notice and this permission notice appear in all copies. > + * > + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES > + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF > + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR > + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES > + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN > + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF > + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. > + */ > + > +#include <err.h> > +#include <stdio.h> > +#include <stdlib.h> > + > +#include "tmpl.h" > + > +int base(struct template *, const char *title); > +int my_putc(struct template *, int); > +int my_puts(struct template *, const char *); > + > +int > +my_putc(struct template *tp, int c) > +{ > + FILE *fp = tp->tp_arg; > + > + if (putc(c, fp) < 0) > + return (-1); > + > + return (0); > +} > + > +int > +my_puts(struct template *tp, const char *s) > +{ > + FILE *fp = tp->tp_arg; > + > + if (fputs(s, fp) < 0) > + return (-1); > + > + return (0); > +} > + > +int > +main(int argc, char **argv) > +{ > + struct template *tp; > + > + if ((tp = template(stdout, my_puts, my_putc)) == NULL) > + err(1, "template"); > + > + if (base(tp, " *hello* ") == -1) > + return (1); > + puts(""); > + > + if (base(tp, "<hello>") == -1) > + return (1); > + puts(""); > + > + free(tp); > + return (0); > +} > blob - /dev/null > blob + 0ec0a95185c3eccde7793744bde73bc5d87efb82 (mode 644) > --- /dev/null > +++ regress/template/runlist.c > @@ -0,0 +1,87 @@ > +/* > + * Copyright (c) 2022 Omar Polo <op@omarpolo.com> > + * > + * Permission to use, copy, modify, and distribute this software for any > + * purpose with or without fee is hereby granted, provided that the above > + * copyright notice and this permission notice appear in all copies. > + * > + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES > + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF > + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR > + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES > + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN > + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF > + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. > + */ > + > +#include <err.h> > +#include <stdio.h> > +#include <stdlib.h> > + > +#include "tmpl.h" > +#include "lists.h" > + > +int base(struct template *, struct tailhead *); > +int my_putc(struct template *, int); > +int my_puts(struct template *, const char *); > + > +int > +my_putc(struct template *tp, int c) > +{ > + FILE *fp = tp->tp_arg; > + > + if (putc(c, fp) < 0) > + return (-1); > + > + return (0); > +} > + > +int > +my_puts(struct template *tp, const char *s) > +{ > + FILE *fp = tp->tp_arg; > + > + if (fputs(s, fp) < 0) > + return (-1); > + > + return (0); > +} > + > +int > +main(int argc, char **argv) > +{ > + struct template *tp; > + struct tailhead head; > + struct entry *np; > + int i; > + > + if ((tp = template(stdout, my_puts, my_putc)) == NULL) > + err(1, "template"); > + > + TAILQ_INIT(&head); > + for (i = 0; i < 2; ++i) { > + if ((np = calloc(1, sizeof(*np))) == NULL) > + err(1, "calloc"); > + if (asprintf(&np->text, "%d", i+1) == -1) > + err(1, "asprintf"); > + TAILQ_INSERT_TAIL(&head, np, entries); > + } > + > + if (base(tp, &head) == -1) > + return (1); > + puts(""); > + > + while ((np = TAILQ_FIRST(&head))) { > + TAILQ_REMOVE(&head, np, entries); > + free(np->text); > + free(np); > + } > + > + if (base(tp, &head) == -1) > + return (1); > + puts(""); > + > + free(tp); > + > + return (0); > +} > blob - /dev/null > blob + 7e95545a3d5b04f4123fd275efd2ad5186d17885 (mode 644) > --- /dev/null > +++ template/Makefile > @@ -0,0 +1,6 @@ > +PROG= template > +SRCS= template.c tmpl.c parse.y > + > +MAN= template.1 template.7 > + > +.include <bsd.prog.mk> > blob - /dev/null > blob + 34f42f83d0770ec2ab269538aac80935c414dfec (mode 644) > --- /dev/null > +++ template/parse.y > @@ -0,0 +1,737 @@ > +/* > + * Copyright (c) 2022 Omar Polo <op@omarpolo.com> > + * Copyright (c) 2007-2016 Reyk Floeter <reyk@openbsd.org> > + * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org> > + * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org> > + * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org> > + * Copyright (c) 2001 Markus Friedl. All rights reserved. > + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. > + * Copyright (c) 2001 Theo de Raadt. All rights reserved. > + * > + * Permission to use, copy, modify, and distribute this software for any > + * purpose with or without fee is hereby granted, provided that the above > + * copyright notice and this permission notice appear in all copies. > + * > + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES > + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF > + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR > + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES > + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN > + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF > + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. > + */ > + > +%{ > + > +#include <sys/queue.h> > + > +#include <ctype.h> > +#include <err.h> > +#include <stdio.h> > +#include <stdlib.h> > +#include <stdarg.h> > +#include <stdint.h> > +#include <string.h> > +#include <unistd.h> > + > +#ifndef nitems > +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) > +#endif > + > +TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); > +static struct file { > + TAILQ_ENTRY(file) entry; > + FILE *stream; > + char *name; > + size_t ungetpos; > + size_t ungetsize; > + unsigned char *ungetbuf; > + int eof_reached; > + int lineno; > + int errors; > +} *file, *topfile; > +int parse(FILE *, const char *); > +struct file *pushfile(const char *, int); > +int popfile(void); > +int yyparse(void); > +int yylex(void); > +int yyerror(const char *, ...) > + __attribute__((__format__ (printf, 1, 2))) > + __attribute__((__nonnull__ (1))); > +int kw_cmp(const void *, const void *); > +int lookup(char *); > +int igetc(void); > +int lgetc(int); > +void lungetc(int); > +int findeol(void); > + > +void dbg(void); > +void printq(const char *); > + > +extern int nodebug; > + > +static FILE *fp; > + > +static int block; > +static int in_define; > +static int errors; > +static int lastline = -1; > + > +typedef struct { > + union { > + char *string; > + } v; > + int lineno; > +} YYSTYPE; > + > +%} > + > +%token DEFINE ELSE END ERROR FINALLY FOR IF INCLUDE PRINTF > +%token RENDER TQFOREACH UNSAFE URLESCAPE > +%token <v.string> STRING > +%type <v.string> string > +%type <v.string> stringy > + > +%% > + > +grammar : /* empty */ > + | grammar include > + | grammar verbatim > + | grammar block > + | grammar error { file->errors++; } > + ; > + > +include : INCLUDE STRING { > + struct file *nfile; > + > + if ((nfile = pushfile($2, 0)) == NULL) { > + yyerror("failed to include file %s", $2); > + free($2); > + YYERROR; > + } > + free($2); > + > + file = nfile; > + lungetc('\n'); > + } > + ; > + > +verbatim : '!' verbatim1 '!' { > + if (in_define) { > + /* TODO: check template status and exit in case */ > + } > + } > + ; > + > +verbatim1 : /* empty */ > + | verbatim1 STRING { > + if (*$2 != '\0') { > + dbg(); > + fprintf(fp, "%s\n", $2); > + } > + free($2); > + } > + ; > + > +verbatims : /* empty */ > + | verbatims verbatim > + ; > + > +raw : STRING { > + dbg(); > + fprintf(fp, "if ((tp_ret = tp->tp_puts(tp, "); > + printq($1); > + fputs(")) == -1) goto err;\n", fp); > + > + free($1); > + } > + ; > + > +block : define body end { > + fputs("err:\n", fp); > + fputs("return tp_ret;\n", fp); > + fputs("}\n", fp); > + in_define = 0; > + } > + | define body finally end { > + fputs("return tp_ret;\n", fp); > + fputs("}\n", fp); > + in_define = 0; > + } > + ; > + > +define : '{' DEFINE string '}' { > + in_define = 1; > + > + dbg(); > + fprintf(fp, "int\n%s\n{\n", $3); > + fputs("int tp_ret = 0;\n", fp); > + > + free($3); > + } > + ; > + > +body : /* empty */ > + | body verbatim > + | body raw > + | body special > + ; > + > +special : '{' RENDER string '}' { > + dbg(); > + fprintf(fp, "if ((tp_ret = %s) == -1) goto err;\n", > + $3); > + free($3); > + } > + | printf > + | if body endif { fputs("}\n", fp); } > + | loop > + | '{' string '|' UNSAFE '}' { > + dbg(); > + fprintf(fp, > + "if ((tp_ret = tp->tp_puts(tp, %s)) == -1)\n", > + $2); > + fputs("goto err;\n", fp); > + free($2); > + } > + | '{' string '|' URLESCAPE '}' { > + dbg(); > + fprintf(fp, > + "if ((tp_ret = tp_urlescape(tp, %s)) == -1)\n", > + $2); > + fputs("goto err;\n", fp); > + free($2); > + } > + | '{' string '}' { > + dbg(); > + fprintf(fp, > + "if ((tp_ret = tp->tp_escape(tp, %s)) == -1)\n", > + $2); > + fputs("goto err;\n", fp); > + free($2); > + } > + ; > + > +printf : '{' PRINTF { > + dbg(); > + fprintf(fp, "if (asprintf(&tp->tp_tmp, "); > + } printfargs '}' { > + fputs(") == -1)\n", fp); > + fputs("goto err;\n", fp); > + fputs("if ((tp_ret = tp->tp_escape(tp, tp->tp_tmp)) " > + "== -1)\n", fp); > + fputs("goto err;\n", fp); > + fputs("free(tp->tp_tmp);\n", fp); > + fputs("tp->tp_tmp = NULL;\n", fp); > + } > + ; > + > +printfargs : /* empty */ > + | printfargs STRING { > + fprintf(fp, " %s", $2); > + free($2); > + } > + ; > + > +if : '{' IF stringy '}' { > + dbg(); > + fprintf(fp, "if (%s) {\n", $3); > + free($3); > + } > + ; > + > +endif : end > + | else body end > + | elsif body endif > + ; > + > +elsif : '{' ELSE IF stringy '}' { > + dbg(); > + fprintf(fp, "} else if (%s) {\n", $4); > + free($4); > + } > + ; > + > +else : '{' ELSE '}' { > + dbg(); > + fputs("} else {\n", fp); > + } > + ; > + > +loop : '{' FOR stringy '}' { > + fprintf(fp, "for (%s) {\n", $3); > + free($3); > + } body end { > + fputs("}\n", fp); > + } > + | '{' TQFOREACH STRING STRING STRING '}' { > + fprintf(fp, "TAILQ_FOREACH(%s, %s, %s) {\n", > + $3, $4, $5); > + free($3); > + free($4); > + free($5); > + } body end { > + fputs("}\n", fp); > + } > + ; > + > +end : '{' END '}' > + ; > + > +finally : '{' FINALLY '}' { > + dbg(); > + fputs("err:\n", fp); > + } verbatims > + ; > + > +string : STRING string { > + if (asprintf(&$$, "%s %s", $1, $2) == -1) > + err(1, "asprintf"); > + free($1); > + free($2); > + } > + | STRING > + ; > + > +stringy : STRING > + | STRING stringy { > + if (asprintf(&$$, "%s %s", $1, $2) == -1) > + err(1, "asprintf"); > + free($1); > + free($2); > + } > + | '|' stringy { > + if (asprintf(&$$, "|%s", $2) == -1) > + err(1, "asprintf"); > + free($2); > + } > + ; > + > +%% > + > +struct keywords { > + const char *k_name; > + int k_val; > +}; > + > +int > +yyerror(const char *fmt, ...) > +{ > + va_list ap; > + char *msg; > + > + file->errors++; > + va_start(ap, fmt); > + if (vasprintf(&msg, fmt, ap) == -1) > + err(1, "yyerror vasprintf"); > + va_end(ap); > + fprintf(stderr, "%s:%d: %s\n", file->name, yylval.lineno, msg); > + free(msg); > + return (0); > +} > + > +int > +kw_cmp(const void *k, const void *e) > +{ > + return (strcmp(k, ((const struct keywords *)e)->k_name)); > +} > + > +int > +lookup(char *s) > +{ > + /* this has to be sorted always */ > + static const struct keywords keywords[] = { > + { "define", DEFINE }, > + { "else", ELSE }, > + { "end", END }, > + { "finally", FINALLY }, > + { "for", FOR }, > + { "if", IF }, > + { "include", INCLUDE }, > + { "printf", PRINTF }, > + { "render", RENDER }, > + { "tailq-foreach", TQFOREACH }, > + { "unsafe", UNSAFE }, > + { "urlescape", URLESCAPE }, > + }; > + const struct keywords *p; > + > + p = bsearch(s, keywords, nitems(keywords), sizeof(keywords[0]), > + kw_cmp); > + > + if (p) > + return (p->k_val); > + else > + return (STRING); > +} > + > +#define START_EXPAND 1 > +#define DONE_EXPAND 2 > + > +static int expanding; > + > +int > +igetc(void) > +{ > + int c; > + > + while (1) { > + if (file->ungetpos > 0) > + c = file->ungetbuf[--file->ungetpos]; > + else > + c = getc(file->stream); > + > + if (c == START_EXPAND) > + expanding = 1; > + else if (c == DONE_EXPAND) > + expanding = 0; > + else > + break; > + } > + return (c); > +} > + > +int > +lgetc(int quotec) > +{ > + int c; > + > + if (quotec) { > + if ((c = igetc()) == EOF) { > + yyerror("reached end of filewhile parsing " > + "quoted string"); > + if (file == topfile || popfile() == EOF) > + return (EOF); > + return (quotec); > + } > + return (c); > + } > + > + c = igetc(); > + if (c == '\t' || c == ' ') { > + /* Compress blanks to a sigle space. */ > + do { > + c = getc(file->stream); > + } while (c == '\t' || c == ' '); > + ungetc(c, file->stream); > + c = ' '; > + } > + > + if (c == EOF) { > + /* > + * Fake EOL when hit EOF for the first time. This gets line > + * count rigchtif last line included file is syntactically > + * invalid and has no newline. > + */ > + if (file->eof_reached == 0) { > + file->eof_reached = 1; > + return ('\n'); > + } > + while (c == EOF) { > + if (file == topfile || popfile() == EOF) > + return (EOF); > + c = igetc(); > + } > + } > + return (c); > +} > + > +void > +lungetc(int c) > +{ > + if (c == EOF) > + return; > + > + if (file->ungetpos >= file->ungetsize) { > + void *p = reallocarray(file->ungetbuf, file->ungetsize, 2); > + if (p == NULL) > + err(1, "reallocarray"); > + file->ungetbuf = p; > + file->ungetsize *= 2; > + } > + file->ungetbuf[file->ungetpos++] = c; > +} > + > +int > +findeol(void) > +{ > + int c; > + > + /* skip to either EOF or the first real EOL */ > + while (1) { > + c = lgetc(0); > + if (c == '\n') { > + file->lineno++; > + break; > + } > + if (c == EOF) > + break; > + } > + return (ERROR); > +} > + > +int > +yylex(void) > +{ > + char buf[8096]; > + char *p = buf; > + int c; > + int token; > + int starting = 0; > + int ending = 0; > + int quote = 0; > + > + if (!in_define && block == 0) { > + while ((c = lgetc(0)) != '{' && c != EOF) { > + if (c == '\n') > + file->lineno++; > + } > + > + if (c == EOF) > + return (0); > + > +newblock: > + c = lgetc(0); > + if (c == '{' || c == '!') { > + if (c == '{') > + block = '}'; > + else > + block = c; > + return (c); > + } > + if (c == '\n') > + file->lineno++; > + } > + > + while ((c = lgetc(0)) == ' ' || c == '\t' || c == '\n') { > + if (c == '\n') > + file->lineno++; > + } > + > + if (c == EOF) { > + yyerror("unterminated block"); > + return (0); > + } > + > + yylval.lineno = file->lineno; > + > + if (block != 0 && c == block) { > + if ((c = lgetc(0)) == '}') { > + if (block == '!') { > + block = 0; > + return ('!'); > + } > + block = 0; > + return ('}'); > + } > + lungetc(c); > + c = block; > + } > + > + if (in_define && block == 0) { > + if (c == '{') > + goto newblock; > + > + do { > + if (starting) { > + if (c == '!' || c == '{') { > + lungetc(c); > + lungetc('{'); > + break; > + } > + starting = 0; > + lungetc(c); > + c = '{'; > + } else if (c == '{') { > + starting = 1; > + continue; > + } > + > + *p++ = c; > + if ((size_t)(p - buf) >= sizeof(buf)) { > + yyerror("string too long"); > + return (findeol()); > + } > + } while ((c = lgetc(0)) != EOF && c != '\n'); > + *p = '\0'; > + if (c == EOF) { > + yyerror("unterminated block"); > + return (0); > + } > + if (c == '\n') > + file->lineno++; > + if ((yylval.v.string = strdup(buf)) == NULL) > + err(1, "strdup"); > + return (STRING); > + } > + > + if (block == '!') { > + do { > + if (ending) { > + if (c == '}') { > + lungetc(c); > + lungetc(block); > + break; > + } > + ending = 0; > + lungetc(c); > + c = block; > + } else if (c == '!') { > + ending = 1; > + continue; > + } > + > + *p++ = c; > + if ((size_t)(p - buf) >= sizeof(buf)) { > + yyerror("line too long"); > + return (findeol()); > + } > + } while ((c = lgetc(0)) != EOF && c != '\n'); > + *p = '\0'; > + > + if (c == EOF) { > + yyerror("unterminated block"); > + return (0); > + } > + if (c == '\n') > + file->lineno++; > + > + if ((yylval.v.string = strdup(buf)) == NULL) > + err(1, "strdup"); > + return (STRING); > + } > + > + if (c == '|') > + return (c); > + > + do { > + if (!quote && isspace((unsigned char)c)) > + break; > + > + if (c == '"') > + quote = !quote; > + > + if (!quote && c == '|') { > + lungetc(c); > + break; > + } > + > + if (ending) { > + if (c == '}') { > + lungetc(c); > + lungetc('}'); > + break; > + } > + ending = 0; > + lungetc(c); > + c = block; > + } else if (!quote && c == '}') { > + ending = 1; > + continue; > + } > + > + *p++ = c; > + if ((size_t)(p - buf) >= sizeof(buf)) { > + yyerror("string too long"); > + return (findeol()); > + } > + } while ((c = lgetc(0)) != EOF); > + *p = '\0'; > + > + if (c == EOF) { > + yyerror(quote ? "unterminated quote" : "unterminated block"); > + return (0); > + } > + if (c == '\n') > + file->lineno++; > + if ((token = lookup(buf)) == STRING) > + if ((yylval.v.string = strdup(buf)) == NULL) > + err(1, "strdup"); > + return (token); > +} > + > +struct file * > +pushfile(const char *name, int secret) > +{ > + struct file *nfile; > + > + if ((nfile = calloc(1, sizeof(*nfile))) == NULL) > + err(1, "calloc"); > + if ((nfile->name = strdup(name)) == NULL) > + err(1, "strdup"); > + if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { > + warn("can't open %s", nfile->name); > + free(nfile->name); > + free(nfile); > + return (NULL); > + } > + nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0; > + nfile->ungetsize = 16; > + nfile->ungetbuf = malloc(nfile->ungetsize); > + if (nfile->ungetbuf == NULL) > + err(1, "malloc"); > + TAILQ_INSERT_TAIL(&files, nfile, entry); > + return (nfile); > +} > + > +int > +popfile(void) > +{ > + struct file *prev; > + > + if ((prev = TAILQ_PREV(file, files, entry)) != NULL) > + prev->errors += file->errors; > + > + TAILQ_REMOVE(&files, file, entry); > + fclose(file->stream); > + free(file->name); > + free(file->ungetbuf); > + free(file); > + file = prev; > + return (file ? 0 : EOF); > +} > + > +int > +parse(FILE *outfile, const char *filename) > +{ > + fp = outfile; > + > + if ((file = pushfile(filename, 0)) == 0) > + return (-1); > + topfile = file; > + > + yyparse(); > + errors = file->errors; > + popfile(); > + > + return (errors ? -1 : 0); > +} > + > +void > +dbg(void) > +{ > + if (nodebug) > + return; > + > + if (yylval.lineno == lastline + 1) { > + lastline = yylval.lineno; > + return; > + } > + lastline = yylval.lineno; > + > + fprintf(fp, "#line %d ", yylval.lineno); > + printq(file->name); > + putc('\n', fp); > +} > + > +void > +printq(const char *str) > +{ > + putc('"', fp); > + for (; *str; ++str) { > + if (*str == '"') > + putc('\\', fp); > + putc(*str, fp); > + } > + putc('"', fp); > +} > blob - /dev/null > blob + 4c3a752a31718ca8ad29543f9f2f61b00c13cc4e (mode 644) > --- /dev/null > +++ template/template.1 > @@ -0,0 +1,85 @@ > +.\" Copyright (c) 2022 Omar Polo <op@openbsd.org> > +.\" > +.\" Permission to use, copy, modify, and distribute this software for any > +.\" purpose with or without fee is hereby granted, provided that the above > +.\" copyright notice and this permission notice appear in all copies. > +.\" > +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES > +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF > +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR > +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES > +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN > +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF > +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. > +.\" > +.Dd November 25, 2022 > +.Dt TEMPLATE 1 > +.Os > +.Sh NAME > +.Nm template > +.Nd templating system compiler > +.Sh SYNOPSIS > +.Nm > +.Op Fl G > +.Op Fl o Ar out > +.Op Ar > +.Sh DESCRIPTION > +.Nm > +is an utility that converts files written in the > +.Xr template 7 > +format format to a set of routine writtens in the C programming > +language. > +.Nm > +converts the files given as arguments or from standard input, and > +writes to standard output. > +.Pp > +The options are as follows: > +.Bl -tag -width Ds > +.It Fl G > +Do not emit debug info in the generated source. > +It's disabled by default, unless > +.Nm > +is reading from standard input. > +.It Fl o Ar out > +Write output to file. > +.Ar out > +will be created or truncated if exists and will be removed if > +.Nm > +encounters any error. > +.El > +.Sh EXIT STATUS > +.Ex > +.Sh SEE ALSO > +.Xr template 7 > +.Sh AUTHORS > +.An -nosplit > +The > +.Nm > +utility was written by > +.An Omar Polo Aq Mt op@openbsd.org . > +.Sh CAVEATS > +The compiler is very naive, so there are quite a few shortcomings: > +.Bl -bullet -compact > +.It > +No attempt is made to validate the C code provided inline, nor the > +validity of the arguments to many constructs. > +.It > +The generated code assumes that a variable called > +.Va tp > +of type > +.Vt struct template * > +is in scope inside each block. > +.It > +Each block may have additional variables used for the template > +generation implicitly defined: to avoid clashes, don't name variables > +or arguments with the > +.Sq tp_ > +prefix. > +.It > +Blanks are, in most cases, trimmed. > +Normally this is not a problem, but a workaround is needed in case > +they need to be preserved, for e.g.: > +.Bd -literal -offset indent > +Name: {{ " " }} {{ render name_field(tp) }} > +.Ed > +.El > blob - /dev/null > blob + 31b32697b9affdd4231edc3b70f8abaf0bb8300c (mode 644) > --- /dev/null > +++ template/template.7 > @@ -0,0 +1,125 @@ > +.\" Copyright (c) 2022 Omar Polo <op@openbsd.org> > +.\" > +.\" Permission to use, copy, modify, and distribute this software for any > +.\" purpose with or without fee is hereby granted, provided that the above > +.\" copyright notice and this permission notice appear in all copies. > +.\" > +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES > +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF > +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR > +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES > +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN > +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF > +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. > +.\" > +.Dd November 25, 2022 > +.Dt TEMPLATE 7 > +.Os > +.Sh NAME > +.Nm template > +.Nd templating language > +.Sh DESCRIPTION > +.Nm > +is a language used to define programs that output data in some way. > +These programs are called > +.Dq templates . > +A > +.Nm > +file is assumed to be compiled using the > +.Xr template 1 > +utility into C code, to be further compiled as part of a bigger > +application. > +The language itself is format-agnostic and can thus be used to produce > +various type of outputs. > +.Pp > +There are two special sequences: > +.Bl -tag -width 9m > +.It Cm {{ Ar ... Cm }} > +used for > +.Nm > +special syntax. > +.It Cm {! Ar ... Cm !} > +used to include literal C code. > +This is the only special syntax permitted as top-level, except for block > +definition and includes. > +.El > +.Pp > +The basic unit of a > +.Nm > +file is the block. > +Each block is turned into a C function that output its content via some > +provided functions. > +Here's an example of a block: > +.Bd -literal -offset indent > +{{ define tp_base(struct template *tp, const char *title) }} > +<!doctype html> > +<html> > + <head> > + <title>{{ title }}</title> > + </head> > + <body> > + {{ render tp->tp_body(tp) }} > + </body> > +</html> > +{{ end }} > +.Ed > +.Ss SPECIAL SYNTAX > +This section is a reference for all the special syntaxes supported. > +.Bl -tag -indent Ds > +.It Cm {{ Ic include Ar file Cm }} > +Include additional template files. > +.It Cm {{ Ic define Ar name Ns ( Ar arguments ... ) Cm }} Ar body Cm {{ Ic end Cm }} > +Defines the block > +.Ar name > +with the given > +.Ar arguments . > +.Ar body > +will be outputted as-is via the provided functions > +.Pq i.e.\& is still escaped eventually > +and can contain all the special syntaxes documented here except > +.Ic include > +and > +.Ic define . > +.It Cm {{ Ic render Ar expression() Cm }} > +Executes > +.Ar expression() > +and terminate the template if it returns -1. > +It's used to render (call) another template. > +.It Cm {{ Ic printf Ar fmt , Ar arguments ... Cm }} > +Outputs the string that would be produced by calling > +.Xr printf 3 > +with the given > +.Ar fmt > +format string and the given > +.Ar arguments . > +.It Cm {{ Ic if Ar expr Cm }} Ar ... Cm {{ Ic elseif Ar expr Cm }} Ar ... Cm {{ Ic else Cm }} Ar ... Cm {{ Ic end Cm }} > +Conditional evaluation. > +.Ic elseif > +can be provided zero or more times, > +.Ic else > +only zero or one time and always for last. > +.It Cm {{ Ic for Ar ... ; Ar ... ; Ar ... Cm }} Ar ... Cm {{ Ic end Cm }} > +Looping construct similar to the C loop. > +.It Cm {{ Ic tailq-foreach Ar var head fieldname Cm }} Ar .. Cm {{ Ic end Cm }} > +Looping construct similar to the queue.h macro TAILQ_FOREACH. > +.It Cm {{ Ar expression Cm | Ic unsafe Cm }} > +Output > +.Ar expression > +as-is. > +.It Cm {{ Ar expression Cm | Ic urlescape Cm }} > +Output > +.Ar expression > +escaped in a way that can be made part of an URL. > +.It Cm {{ Ar expression Cm }} > +Output > +.Ar expression > +with the default escaping. > +.El > +.Sh SEE ALSO > +.Xr template 1 > +.Sh AUTHORS > +.An -nosplit > +The > +.Nm > +reference was written by > +.Ar Omar Polo Aq Mt op@openbsd.org . > blob - /dev/null > blob + f52199db7b8acc13c6f1a44dc1cf4222b189b3e7 (mode 644) > --- /dev/null > +++ template/template.c > @@ -0,0 +1,92 @@ > +/* > + * Copyright (c) 2022 Omar Polo <op@omarpolo.com> > + * > + * Permission to use, copy, modify, and distribute this software for any > + * purpose with or without fee is hereby granted, provided that the above > + * copyright notice and this permission notice appear in all copies. > + * > + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES > + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF > + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR > + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES > + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN > + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF > + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. > + */ > + > +#include <err.h> > +#include <stdio.h> > +#include <stdlib.h> > +#include <unistd.h> > + > +int parse(FILE *, const char *); > + > +int nodebug; > + > +static void __dead > +usage(void) > +{ > + fprintf(stderr, "usage: %s [file...]\n", > + getprogname()); > + exit(1); > +} > + > +int > +main(int argc, char **argv) > +{ > + FILE *fp = stdout; > + const char *out = NULL; > + int ch, i; > + > + while ((ch = getopt(argc, argv, "Go:")) != -1) { > + switch (ch) { > + case 'G': > + nodebug = 1; > + break; > + case 'o': > + out = optarg; > + break; > + default: > + usage(); > + } > + } > + argc -= optind; > + argv += optind; > + > + if (out && (fp = fopen(out, "w")) == NULL) > + err(1, "can't open %s", out); > + > + if (out && unveil(out, "wc") == -1) > + err(1, "unveil %s", out); > + if (unveil("/", "r") == -1) > + err(1, "unveil /"); > + if (pledge(out ? "stdio rpath cpath" : "stdio rpath", NULL) == -1) > + err(1, "pledge"); > + > + if (argc == 0) { > + nodebug = 1; > + if (parse(fp, "/dev/stdin") == -1) > + goto err; > + } else { > + for (i = 0; i < argc; ++i) > + if (parse(fp, argv[i]) == -1) > + goto err; > + } > + > + if (ferror(fp)) > + goto err; > + > + if (fclose(fp) == -1) { > + fp = NULL; > + goto err; > + } > + > + return (0); > + > +err: > + if (fp) > + fclose(fp); > + if (out && unlink(out) == -1) > + err(1, "unlink %s", out); > + return (1); > +} > blob - /dev/null > blob + 36bb4882a5fec3f37fd3b75d26c1514f42bb77f5 (mode 644) > --- /dev/null > +++ template/tmpl.c > @@ -0,0 +1,108 @@ > +/* > + * Copyright (c) 2022 Omar Polo <op@omarpolo.com> > + * > + * Permission to use, copy, modify, and distribute this software for any > + * purpose with or without fee is hereby granted, provided that the above > + * copyright notice and this permission notice appear in all copies. > + * > + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES > + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF > + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR > + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES > + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN > + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF > + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. > + */ > + > +#include <ctype.h> > +#include <stdio.h> > +#include <stdlib.h> > + > +#include "tmpl.h" > + > +int > +tp_urlescape(struct template *tp, const char *str) > +{ > + int r; > + char tmp[4]; > + > + if (str == NULL) > + return (0); > + > + for (; *str; ++str) { > + if (iscntrl((unsigned char)*str) || > + isspace((unsigned char)*str) || > + *str == '\'' || *str == '"' || *str == '\\') { > + r = snprintf(tmp, sizeof(tmp), "%%%2X", *str); > + if (r < 0 || (size_t)r >= sizeof(tmp)) > + return (0); > + if (tp->tp_puts(tp, tmp) == -1) > + return (-1); > + } else { > + if (tp->tp_putc(tp, *str) == -1) > + return (-1); > + } > + } > + > + return (0); > +} > + > +int > +tp_htmlescape(struct template *tp, const char *str) > +{ > + int r; > + > + if (str == NULL) > + return (0); > + > + for (; *str; ++str) { > + switch (*str) { > + case '<': > + r = tp->tp_puts(tp, "<"); > + break; > + case '>': > + r = tp->tp_puts(tp, ">"); > + break; > + case '&': > + r = tp->tp_puts(tp, "&"); > + break; > + case '"': > + r = tp->tp_puts(tp, """); > + break; > + case '\'': > + r = tp->tp_puts(tp, "'"); > + break; > + default: > + r = tp->tp_putc(tp, *str); > + break; > + } > + > + if (r == -1) > + return (-1); > + } > + > + return (0); > +} > + > +struct template * > +template(void *arg, tmpl_puts putsfn, tmpl_putc putcfn) > +{ > + struct template *tp; > + > + if ((tp = calloc(1, sizeof(*tp))) == NULL) > + return (NULL); > + > + tp->tp_arg = arg; > + tp->tp_escape = tp_htmlescape; > + tp->tp_puts = putsfn; > + tp->tp_putc = putcfn; > + > + return (tp); > +} > + > +void > +template_free(struct template *tp) > +{ > + free(tp->tp_tmp); > + free(tp); > +} > blob - /dev/null > blob + 2367b76c1353b204d520091d5212fd0a438e15cf (mode 644) > --- /dev/null > +++ template/tmpl.h > @@ -0,0 +1,39 @@ > +/* > + * Copyright (c) 2022 Omar Polo <op@omarpolo.com> > + * > + * Permission to use, copy, modify, and distribute this software for any > + * purpose with or without fee is hereby granted, provided that the above > + * copyright notice and this permission notice appear in all copies. > + * > + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES > + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF > + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR > + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES > + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN > + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF > + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. > + */ > + > +#ifndef TMPL_H > +#define TMPL_H > + > +struct template; > + > +typedef int (*tmpl_puts)(struct template *, const char *); > +typedef int (*tmpl_putc)(struct template *, int); > + > +struct template { > + void *tp_arg; > + char *tp_tmp; > + tmpl_puts tp_escape; > + tmpl_puts tp_puts; > + tmpl_putc tp_putc; > +}; > + > +int tp_urlescape(struct template *, const char *); > +int tp_htmlescape(struct template *, const char *); > + > +struct template *template(void *, tmpl_puts, tmpl_putc); > +void template_free(struct template *); > + > +#endif -- Tracey Emery
gotwebd: templates take 2