Refactor out the use of rice in favor of embed (#2)

Co-authored-by: James Mills <prologic@shortcircuit.net.au>
Reviewed-on: https://git.mills.io/prologic/spyda/pulls/2
This commit is contained in:
James Mills
2022-10-05 00:19:23 +00:00
parent 7001f3bf51
commit d4ab395515
13 changed files with 270 additions and 178 deletions

View File

@@ -10,11 +10,6 @@ import (
"strings"
"time"
rice "github.com/GeertJohan/go.rice"
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/html"
"github.com/gomarkdown/markdown/parser"
"github.com/james4k/fmatter"
"github.com/julienschmidt/httprouter"
log "github.com/sirupsen/logrus"
"github.com/vcraescu/go-paginator"
@@ -37,65 +32,6 @@ func (s *Server) NotFoundHandler(w http.ResponseWriter, r *http.Request) {
s.render("404", w, ctx)
}
type FrontMatter struct {
Title string
}
// PageHandler ...
func (s *Server) PageHandler(name string) httprouter.Handle {
box := rice.MustFindBox("pages")
mdTpl := box.MustString(fmt.Sprintf("%s.md", name))
return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
ctx := NewContext(s.config, s.db, r)
md, err := RenderString(mdTpl, ctx)
if err != nil {
log.WithError(err).Errorf("error rendering page %s", name)
ctx.Error = true
ctx.Message = "Error loading help page! Please contact support."
s.render("error", w, ctx)
return
}
var frontmatter FrontMatter
content, err := fmatter.Read([]byte(md), &frontmatter)
if err != nil {
log.WithError(err).Error("error parsing front matter")
ctx.Error = true
ctx.Message = "Error loading page! Please contact support."
s.render("error", w, ctx)
return
}
extensions := parser.CommonExtensions | parser.AutoHeadingIDs
p := parser.NewWithExtensions(extensions)
htmlFlags := html.CommonFlags
opts := html.RendererOptions{
Flags: htmlFlags,
Generator: "",
}
renderer := html.NewRenderer(opts)
html := markdown.ToHTML(content, p, renderer)
var title string
if frontmatter.Title != "" {
title = frontmatter.Title
} else {
title = strings.Title(name)
}
ctx.Title = title
ctx.Page = name
ctx.Content = template.HTML(html)
s.render("page", w, ctx)
}
}
// IndexHandler ...
func (s *Server) IndexHandler() httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {

170
internal/page_handlers.go Normal file
View File

@@ -0,0 +1,170 @@
// Copyright 2020-present Yarn.social
// SPDX-License-Identifier: AGPL-3.0-or-later
package internal
import (
"embed"
"fmt"
"html/template"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/html"
"github.com/gomarkdown/markdown/parser"
"github.com/james4k/fmatter"
"github.com/julienschmidt/httprouter"
sync "github.com/sasha-s/go-deadlock"
log "github.com/sirupsen/logrus"
)
const pagesDir = "pages"
//go:embed pages/*.md
var builtinPages embed.FS
type FrontMatter struct {
Title string
Description string
}
type Page struct {
Content string
LastModified time.Time
}
// PageHandler ...
func (s *Server) PageHandler(name string) httprouter.Handle {
pagesBaseDir := filepath.Join(s.config.Data, pagesDir)
pageMutex := &sync.RWMutex{}
pageCache := make(map[string]*Page)
getPage := func(name string) (*Page, error) {
fn := filepath.Join(pagesBaseDir, fmt.Sprintf("%s.md", name))
pageMutex.RLock()
page, isCached := pageCache[name]
pageMutex.RUnlock()
if isCached && FileExists(fn) {
if fileInfo, err := os.Stat(fn); err == nil {
if fileInfo.ModTime().After(page.LastModified) {
data, err := os.ReadFile(fn)
if err != nil {
log.WithError(err).Warnf("error reading page %s", name)
return page, nil
}
page.Content = string(data)
page.LastModified = fileInfo.ModTime()
pageMutex.Lock()
pageCache[name] = page
pageMutex.Unlock()
return page, nil
}
}
}
page = &Page{}
if FileExists(fn) {
fileInfo, err := os.Stat(fn)
if err != nil {
log.WithError(err).Errorf("error getting page stats")
return nil, err
}
page.LastModified = fileInfo.ModTime()
data, err := os.ReadFile(fn)
if err != nil {
log.WithError(err).Errorf("error reading page %s", name)
return nil, err
}
page.Content = string(data)
} else {
fn := filepath.Join(pagesDir, fmt.Sprintf("%s.md", name))
data, err := builtinPages.ReadFile(fn)
if err != nil {
log.WithError(err).Errorf("error reading custom page %s", name)
return nil, err
}
page.Content = string(data)
}
pageMutex.Lock()
pageCache[name] = page
pageMutex.Unlock()
return page, nil
}
return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
ctx := NewContext(s.config, s.db, r)
page, err := getPage(name)
if err != nil {
if os.IsNotExist(err) {
ctx.Error = true
ctx.Message = "Page Not Found"
s.render("404", w, ctx)
return
}
ctx.Error = true
ctx.Message = "Error reading page"
s.render("message", w, ctx)
return
}
markdownContent, err := RenderHTML(page.Content, ctx)
if err != nil {
log.WithError(err).Errorf("error rendering page %s", name)
ctx.Error = true
ctx.Message = "Error rendering page"
s.render("error", w, ctx)
return
}
var frontmatter FrontMatter
content, err := fmatter.Read([]byte(markdownContent), &frontmatter)
if err != nil {
log.WithError(err).Error("error parsing front matter")
ctx.Error = true
ctx.Message = "Error loading page"
s.render("error", w, ctx)
return
}
extensions := parser.CommonExtensions | parser.AutoHeadingIDs
p := parser.NewWithExtensions(extensions)
htmlFlags := html.CommonFlags
opts := html.RendererOptions{
Flags: htmlFlags,
Generator: "",
}
renderer := html.NewRenderer(opts)
html := markdown.ToHTML(content, p, renderer)
var title string
if frontmatter.Title != "" {
title = frontmatter.Title
} else {
title = strings.Title(name)
}
ctx.Title = title
ctx.Meta.Description = frontmatter.Description
ctx.Page = name
ctx.Content = template.HTML(html)
s.render("page", w, ctx)
}
}

View File

@@ -10,11 +10,10 @@ import (
"syscall"
"time"
rice "github.com/GeertJohan/go.rice"
"git.mills.io/prologic/observe"
"github.com/NYTimes/gziphandler"
"github.com/gabstv/merger"
"github.com/justinas/nosurf"
"git.mills.io/prologic/observe"
"github.com/robfig/cron"
log "github.com/sirupsen/logrus"
"github.com/unrolled/logger"
@@ -23,6 +22,7 @@ import (
"git.mills.io/prologic/spyda/internal/auth"
"git.mills.io/prologic/spyda/internal/passwords"
"git.mills.io/prologic/spyda/internal/session"
"git.mills.io/prologic/spyda/internal/static"
)
var (
@@ -284,12 +284,12 @@ func (s *Server) initRoutes() {
s.router.ServeFiles("/img/*filepath", http.Dir("./internal/static/img"))
s.router.ServeFiles("/js/*filepath", http.Dir("./internal/static/js"))
} else {
cssBox := rice.MustFindBox("static/css").HTTPBox()
imgBox := rice.MustFindBox("static/img").HTTPBox()
jsBox := rice.MustFindBox("static/js").HTTPBox()
s.router.ServeFilesWithCacheControl("/css/:commit/*filepath", cssBox)
s.router.ServeFilesWithCacheControl("/img/:commit/*filepath", imgBox)
s.router.ServeFilesWithCacheControl("/js/:commit/*filepath", jsBox)
cssFS := static.GetSubFilesystem("css")
imgFS := static.GetSubFilesystem("img")
jsFS := static.GetSubFilesystem("js")
s.router.ServeFilesWithCacheControl("/css/:commit/*filepath", cssFS)
s.router.ServeFilesWithCacheControl("/img/:commit/*filepath", imgFS)
s.router.ServeFilesWithCacheControl("/js/:commit/*filepath", jsFS)
}
s.router.NotFound = http.HandlerFunc(s.NotFoundHandler)

File diff suppressed because one or more lines are too long

View File

@@ -1,36 +0,0 @@
var u=function(parameter,context){if(!(this instanceof u)){return new u(parameter,context);}
if(parameter instanceof u){return parameter;}
if(typeof parameter==='string'){parameter=this.select(parameter,context);}
if(parameter&&parameter.nodeName){parameter=[parameter];}
this.nodes=this.slice(parameter);};u.prototype={get length(){return this.nodes.length;}};u.prototype.nodes=[];u.prototype.addClass=function(){return this.eacharg(arguments,function(el,name){el.classList.add(name);});};u.prototype.adjacent=function(html,data,callback){if(typeof data==='number'){if(data===0){data=[];}else{data=new Array(data).join().split(',').map(Number.call,Number);}}
return this.each(function(node,j){var fragment=document.createDocumentFragment();u(data||{}).map(function(el,i){var part=(typeof html==='function')?html.call(this,el,i,node,j):html;if(typeof part==='string'){return this.generate(part);}
return u(part);}).each(function(n){this.isInPage(n)?fragment.appendChild(u(n).clone().first()):fragment.appendChild(n);});callback.call(this,node,fragment);});};u.prototype.after=function(html,data){return this.adjacent(html,data,function(node,fragment){node.parentNode.insertBefore(fragment,node.nextSibling);});};u.prototype.append=function(html,data){return this.adjacent(html,data,function(node,fragment){node.appendChild(fragment);});};u.prototype.args=function(args,node,i){if(typeof args==='function'){args=args(node,i);}
if(typeof args!=='string'){args=this.slice(args).map(this.str(node,i));}
return args.toString().split(/[\s,]+/).filter(function(e){return e.length;});};u.prototype.array=function(callback){callback=callback;var self=this;return this.nodes.reduce(function(list,node,i){var val;if(callback){val=callback.call(self,node,i);if(!val)val=false;if(typeof val==='string')val=u(val);if(val instanceof u)val=val.nodes;}else{val=node.innerHTML;}
return list.concat(val!==false?val:[]);},[]);};u.prototype.attr=function(name,value,data){data=data?'data-':'';return this.pairs(name,value,function(node,name){return node.getAttribute(data+name);},function(node,name,value){node.setAttribute(data+name,value);});};u.prototype.before=function(html,data){return this.adjacent(html,data,function(node,fragment){node.parentNode.insertBefore(fragment,node);});};u.prototype.children=function(selector){return this.map(function(node){return this.slice(node.children);}).filter(selector);};u.prototype.clone=function(){return this.map(function(node,i){var clone=node.cloneNode(true);var dest=this.getAll(clone);this.getAll(node).each(function(src,i){for(var key in this.mirror){if(this.mirror[key]){this.mirror[key](src,dest.nodes[i]);}}});return clone;});};u.prototype.getAll=function getAll(context){return u([context].concat(u('*',context).nodes));};u.prototype.mirror={};u.prototype.mirror.events=function(src,dest){if(!src._e)return;for(var type in src._e){src._e[type].forEach(function(ref){u(dest).on(type,ref.callback);});}};u.prototype.mirror.select=function(src,dest){if(u(src).is('select')){dest.value=src.value;}};u.prototype.mirror.textarea=function(src,dest){if(u(src).is('textarea')){dest.value=src.value;}};u.prototype.closest=function(selector){return this.map(function(node){do{if(u(node).is(selector)){return node;}}while((node=node.parentNode)&&node!==document);});};u.prototype.data=function(name,value){return this.attr(name,value,true);};u.prototype.each=function(callback){this.nodes.forEach(callback.bind(this));return this;};u.prototype.eacharg=function(args,callback){return this.each(function(node,i){this.args(args,node,i).forEach(function(arg){callback.call(this,node,arg);},this);});};u.prototype.empty=function(){return this.each(function(node){while(node.firstChild){node.removeChild(node.firstChild);}});};u.prototype.filter=function(selector){var callback=function(node){node.matches=node.matches||node.msMatchesSelector||node.webkitMatchesSelector;return node.matches(selector||'*');};if(typeof selector==='function')callback=selector;if(selector instanceof u){callback=function(node){return(selector.nodes).indexOf(node)!==-1;};}
return u(this.nodes.filter(callback));};u.prototype.find=function(selector){return this.map(function(node){return u(selector||'*',node);});};u.prototype.first=function(){return this.nodes[0]||false;};u.prototype.generate=function(html){if(/^\s*<tr[> ]/.test(html)){return u(document.createElement('table')).html(html).children().children().nodes;}else if(/^\s*<t(h|d)[> ]/.test(html)){return u(document.createElement('table')).html(html).children().children().children().nodes;}else if(/^\s*</.test(html)){return u(document.createElement('div')).html(html).children().nodes;}else{return document.createTextNode(html);}};u.prototype.handle=function(){var args=this.slice(arguments).map(function(arg){if(typeof arg==='function'){return function(e){e.preventDefault();arg.apply(this,arguments);};}
return arg;},this);return this.on.apply(this,args);};u.prototype.hasClass=function(){return this.is('.'+this.args(arguments).join('.'));};u.prototype.html=function(text){if(text===undefined){return this.first().innerHTML||'';}
return this.each(function(node){node.innerHTML=text;});};u.prototype.is=function(selector){return this.filter(selector).length>0;};u.prototype.isInPage=function isInPage(node){return(node===document.body)?false:document.body.contains(node);};u.prototype.last=function(){return this.nodes[this.length-1]||false;};u.prototype.map=function(callback){return callback?u(this.array(callback)).unique():this;};u.prototype.not=function(filter){return this.filter(function(node){return!u(node).is(filter||true);});};u.prototype.off=function(events,cb,cb2){var cb_filter_off=(cb==null&&cb2==null);var sel=null;var cb_to_be_removed=cb;if(typeof cb==='string'){sel=cb;cb_to_be_removed=cb2;}
return this.eacharg(events,function(node,event){u(node._e?node._e[event]:[]).each(function(ref){if(cb_filter_off||(ref.orig_callback===cb_to_be_removed&&ref.selector===sel)){node.removeEventListener(event,ref.callback);}});});};u.prototype.on=function(events,cb,cb2){var sel=null;var orig_callback=cb;if(typeof cb==='string'){sel=cb;orig_callback=cb2;cb=function(e){var args=arguments;var targetFound=false;u(e.currentTarget).find(sel).each(function(target){if(target===e.target||target.contains(e.target)){targetFound=true;try{Object.defineProperty(e,'currentTarget',{get:function(){return target;}});}catch(err){}
cb2.apply(target,args);}});if(!targetFound&&e.currentTarget===e.target){cb2.apply(e.target,args);}};}
var callback=function(e){return cb.apply(this,[e].concat(e.detail||[]));};return this.eacharg(events,function(node,event){node.addEventListener(event,callback);node._e=node._e||{};node._e[event]=node._e[event]||[];node._e[event].push({callback:callback,orig_callback:orig_callback,selector:sel});});};u.prototype.pairs=function(name,value,get,set){if(typeof value!=='undefined'){var nm=name;name={};name[nm]=value;}
if(typeof name==='object'){return this.each(function(node){for(var key in name){set(node,key,name[key]);}});}
return this.length?get(this.first(),name):'';};u.prototype.param=function(obj){return Object.keys(obj).map(function(key){return this.uri(key)+'='+this.uri(obj[key]);}.bind(this)).join('&');};u.prototype.parent=function(selector){return this.map(function(node){return node.parentNode;}).filter(selector);};u.prototype.prepend=function(html,data){return this.adjacent(html,data,function(node,fragment){node.insertBefore(fragment,node.firstChild);});};u.prototype.remove=function(){return this.each(function(node){if(node.parentNode){node.parentNode.removeChild(node);}});};u.prototype.removeClass=function(){return this.eacharg(arguments,function(el,name){el.classList.remove(name);});};u.prototype.replace=function(html,data){var nodes=[];this.adjacent(html,data,function(node,fragment){nodes=nodes.concat(this.slice(fragment.children));node.parentNode.replaceChild(fragment,node);});return u(nodes);};u.prototype.scroll=function(){this.first().scrollIntoView({behavior:'smooth'});return this;};u.prototype.select=function(parameter,context){parameter=parameter.replace(/^\s*/,'').replace(/\s*$/,'');if(/^</.test(parameter)){return u().generate(parameter);}
return(context||document).querySelectorAll(parameter);};u.prototype.serialize=function(){var self=this;return this.slice(this.first().elements).reduce(function(query,el){if(!el.name||el.disabled||el.type==='file')return query;if(/(checkbox|radio)/.test(el.type)&&!el.checked)return query;if(el.type==='select-multiple'){u(el.options).each(function(opt){if(opt.selected){query+='&'+self.uri(el.name)+'='+self.uri(opt.value);}});return query;}
return query+'&'+self.uri(el.name)+'='+self.uri(el.value);},'').slice(1);};u.prototype.siblings=function(selector){return this.parent().children(selector).not(this);};u.prototype.size=function(){return this.first().getBoundingClientRect();};u.prototype.slice=function(pseudo){if(!pseudo||pseudo.length===0||typeof pseudo==='string'||pseudo.toString()==='[object Function]')return[];return pseudo.length?[].slice.call(pseudo.nodes||pseudo):[pseudo];};u.prototype.str=function(node,i){return function(arg){if(typeof arg==='function'){return arg.call(this,node,i);}
return arg.toString();};};u.prototype.text=function(text){if(text===undefined){return this.first().textContent||'';}
return this.each(function(node){node.textContent=text;});};u.prototype.toggleClass=function(classes,addOrRemove){if(!!addOrRemove===addOrRemove){return this[addOrRemove?'addClass':'removeClass'](classes);}
return this.eacharg(classes,function(el,name){el.classList.toggle(name);});};u.prototype.trigger=function(events){var data=this.slice(arguments).slice(1);return this.eacharg(events,function(node,event){var ev;var opts={bubbles:true,cancelable:true,detail:data};try{ev=new window.CustomEvent(event,opts);}catch(e){ev=document.createEvent('CustomEvent');ev.initCustomEvent(event,true,true,data);}
node.dispatchEvent(ev);});};u.prototype.unique=function(){return u(this.nodes.reduce(function(clean,node){var istruthy=node!==null&&node!==undefined&&node!==false;return(istruthy&&clean.indexOf(node)===-1)?clean.concat(node):clean;},[]));};u.prototype.uri=function(str){return encodeURIComponent(str).replace(/!/g,'%21').replace(/'/g,'%27').replace(/\(/g,'%28').replace(/\)/g,'%29').replace(/\*/g,'%2A').replace(/%20/g,'+');};u.prototype.wrap=function(selector){function findDeepestNode(node){while(node.firstElementChild){node=node.firstElementChild;}
return u(node);}
return this.map(function(node){return u(selector).each(function(n){findDeepestNode(n).append(node.cloneNode(true));node.parentNode.replaceChild(n,node);});});};if(typeof module==='object'&&module.exports){module.exports=u;module.exports.u=u;}
(function(){if(typeof window.Element==="undefined"||"classList"in document.documentElement)return;var prototype=Array.prototype,push=prototype.push,splice=prototype.splice,join=prototype.join;function DOMTokenList(el){this.el=el;var classes=el.className.replace(/^\s+|\s+$/g,'').split(/\s+/);for(var i=0;i<classes.length;i++){push.call(this,classes[i]);}};DOMTokenList.prototype={add:function(token){if(this.contains(token))return;push.call(this,token);this.el.className=this.toString();},contains:function(token){return this.el.className.indexOf(token)!=-1;},item:function(index){return this[index]||null;},remove:function(token){if(!this.contains(token))return;for(var i=0;i<this.length;i++){if(this[i]==token)break;}
splice.call(this,i,1);this.el.className=this.toString();},toString:function(){return join.call(this,' ');},toggle:function(token){if(!this.contains(token)){this.add(token);}else{this.remove(token);}
return this.contains(token);}};window.DOMTokenList=DOMTokenList;function defineElementGetter(obj,prop,getter){if(Object.defineProperty){Object.defineProperty(obj,prop,{get:getter});}else{obj.__defineGetter__(prop,getter);}}
defineElementGetter(Element.prototype,'classList',function(){return new DOMTokenList(this);});})();var Twix=(function(){function Twix(){}
Twix.ajax=function(options){options=options||{url:""};options.type=options.type.toUpperCase()||'GET';options.headers=options.headers||{};options.timeout=parseInt(options.timeout)||0;options.success=options.success||function(){};options.error=options.error||function(){};options.async=typeof options.async==='undefined'?true:options.async;var client=new XMLHttpRequest();if(options.timeout>0){client.timeout=options.timeout;client.ontimeout=function(){options.error('timeout','timeout',client);};}
client.open(options.type,options.url,options.async);for(var i in options.headers){if(options.headers.hasOwnProperty(i)){client.setRequestHeader(i,options.headers[i]);}}
client.send(options.data);client.onreadystatechange=function(){if(this.readyState==4&&((this.status>=200&&this.status<300)||this.status==304)){var data=this.responseText;var contentType=this.getResponseHeader('Content-Type');if(contentType&&contentType.match(/json/)){data=JSON.parse(this.responseText);}
options.success(data,this.statusText,this);}else if(this.readyState==4){options.error(this.status,this.statusText,this);}};if(options.async==false){if(client.readyState==4&&((client.status>=200&&client.status<300)||client.status==304)){options.success(client.responseText,client);}else if(client.readyState==4){options.error(client.status,client.statusText,client);}}
return client;};var _ajax=function(type,url,data,callback){if(typeof data==="function"){callback=data;data=undefined;}
return Twix.ajax({url:url,data:data,type:type,success:callback});};Twix.get=function(url,data,callback){return _ajax("GET",url,data,callback);};Twix.head=function(url,data,callback){return _ajax("HEAD",url,data,callback);};Twix.post=function(url,data,callback){return _ajax("POST",url,data,callback);};Twix.patch=function(url,data,callback){return _ajax("PATCH",url,data,callback);};Twix.put=function(url,data,callback){return _ajax("PUT",url,data,callback);};Twix.delete=function(url,data,callback){return _ajax("DELETE",url,data,callback);};Twix.options=function(url,data,callback){return _ajax("OPTIONS",url,data,callback);};return Twix;})();__=Twix;u("#burgerMenu").on("click",function(e){e.preventDefault();if(u("#mainNav").hasClass("responsive")){u("#mainNav").removeClass("responsive");}else{u("#mainNav").addClass("responsive");}});

36
internal/static/static.go Normal file
View File

@@ -0,0 +1,36 @@
package static
import (
"embed"
"io/fs"
"log"
"net/http"
)
//go:embed css/*.css
//go:embed img/*.png img/*.svg
//go:embed js/*.js
var files embed.FS
// MustGetFile returns the contents of a file from static as bytes.
func MustGetFile(name string) []byte {
b, err := files.ReadFile(name)
if err != nil {
panic(err)
}
return b
}
// GetFilesystem returns a http.FileSystem for the static files.
func GetFilesystem() http.FileSystem {
return http.FS(files)
}
// GetSubFilesystem returns a http.FileSystem for the static sub-files.
func GetSubFilesystem(name string) http.FileSystem {
fsys, err := fs.Sub(files, name)
if err != nil {
log.Fatalf("error loading sub-filesystem for %q: %s", name, err)
}
return http.FS(fsys)
}

View File

@@ -5,15 +5,16 @@ import (
"fmt"
"html/template"
"io"
"os"
"io/fs"
"path/filepath"
"strings"
"sync"
rice "github.com/GeertJohan/go.rice"
"github.com/Masterminds/sprig"
humanize "github.com/dustin/go-humanize"
log "github.com/sirupsen/logrus"
"git.mills.io/prologic/spyda/internal/templates"
)
const (
@@ -57,20 +58,14 @@ func (m *TemplateManager) LoadTemplates() error {
m.Lock()
defer m.Unlock()
box, err := rice.FindBox("templates")
if err != nil {
log.WithError(err).Errorf("error finding templates")
return fmt.Errorf("error finding templates: %w", err)
}
err = box.Walk("", func(path string, info os.FileInfo, err error) error {
err := fs.WalkDir(templates.FS(), ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
log.WithError(err).Error("error talking templates")
log.WithError(err).Error("error walking templates")
return fmt.Errorf("error walking templates: %w", err)
}
fname := info.Name()
if !info.IsDir() && fname != baseTemplate {
fname := d.Name()
if !d.IsDir() && fname != baseTemplate {
// Skip _partials.html and also editor swap files, to improve the development
// cycle. Editors often add suffixes to their swap files, e.g "~" or ".swp"
// (Vim) and those files are not parsable as templates, causing panics.
@@ -82,9 +77,9 @@ func (m *TemplateManager) LoadTemplates() error {
t := template.New(name).Option("missingkey=zero")
t.Funcs(m.funcMap)
template.Must(t.Parse(box.MustString(fname)))
template.Must(t.Parse(box.MustString(partialsTemplate)))
template.Must(t.Parse(box.MustString(baseTemplate)))
template.Must(t.Parse(templates.MustGetTemplate(fname)))
template.Must(t.Parse(templates.MustGetTemplate(partialsTemplate)))
template.Must(t.Parse(templates.MustGetTemplate(baseTemplate)))
m.templates[name] = t
}
@@ -136,3 +131,15 @@ func (m *TemplateManager) Exec(name string, ctx *Context) (io.WriterTo, error) {
return buf, nil
}
// RenderHTML ...
func RenderHTML(tpl string, ctx *Context) (string, error) {
t := template.Must(template.New("tpl").Parse(tpl))
buf := bytes.NewBuffer([]byte{})
err := t.Execute(buf, ctx)
if err != nil {
return "", err
}
return buf.String(), nil
}

View File

@@ -0,0 +1,22 @@
package templates
import (
"embed"
"io/fs"
)
//go:embed *.html
var templates embed.FS
// MustGetTemplate returns a template string with the given name.
func MustGetTemplate(name string) string {
b, err := templates.ReadFile(name)
if err != nil {
panic(err)
}
return string(b)
}
func FS() fs.FS {
return templates
}