arnaucube
arnaucube/arnaucube.comPublic Self-hosted repository
arnaucube.com/public/blogo.html
public/blogo.htmlRaw
  1<!DOCTYPE html>
  2<html lang="en">
  3
  4  <head>
  5    <meta name="description" content="How has this blog been made? In this post we will see how to develop a minimalistic static blog template engine with Go." />
  6    <meta charset="utf-8">
  7    <title> Static blog template engine implementation in Go - arnaucube - blog</title>
  8    <meta name="title" content=" Static blog template engine implementation in Go - arnaucube - blog">
  9    <meta name="description" content="How has this blog been made? In this post we will see how to develop a minimalistic static blog template engine with Go.">
 10
 11    <meta property="og:title" content=" Static blog template engine implementation in Go - arnaucube - blog" />
 12    <meta property="og:description" content="How has this blog been made? In this post we will see how to develop a minimalistic static blog template engine with Go." />
 13    <meta property="og:url" content="https://arnaucube.com/blog/blogo.html" />
 14    <meta property="og:type" content="article" />
 15    <meta property="og:image" content="https://arnaucube.com/blog/" />
 16    <meta name="twitter:title" content=" Static blog template engine implementation in Go - arnaucube - blog">
 17    <meta name="twitter:description" content="How has this blog been made? In this post we will see how to develop a minimalistic static blog template engine with Go.">
 18    <meta name="twitter:image" content="https://arnaucube.com/blog/">
 19    <meta name="twitter:card" content="summary_large_image">
 20    <meta name="author" content="arnaucube">
 21
 22    <meta name="viewport" content="width=device-width, initial-scale=1">
 23
 24    <link href="css/bootstrap.min.css" rel="stylesheet">
 25    <link rel="stylesheet" href="css/style.css">
 26
 27    <!-- highlightjs -->
 28    <!-- <link rel="stylesheet" href="js/highlightjs/atom-one-dark.css"> -->
 29    <link rel="stylesheet" href="js/highlightjs/gruvbox-dark.css">
 30    <script src="js/highlightjs/highlight.pack.js"></script>
 31
 32    <!-- katex -->
 33    <link rel="stylesheet" href="js/katex/katex.min.css">
 34  </head>
 35
 36  <body>
 37
 38    <!-- o_gradient_background" -->
 39    <nav id="mainNav" class="navbar navbar-default navbar-fixed-top"
 40                      style="height:50px;font-size:130%;">
 41      <div class="container">
 42        <a href="/blog" style="color:#000;">Blog index</a>
 43        <div style="float:right;">
 44          <a href="/" style="color:#000;display:inline-block;">arnaucube.com</a>
 45          <div class="onoffswitch" style="margin:10px;display:inline-block;" title="change theme">
 46            <input onclick="switchTheme()" type="checkbox" name="onoffswitch" class="onoffswitch-checkbox"
 47              id="themeSwitcher">
 48            <label class="onoffswitch-label" for="themeSwitcher"></label>
 49          </div>
 50        </div>
 51      </div>
 52      <img style="height:5px; width:100%; margin-top:8px;" src="img/gradient-line.jpg" />
 53    </nav>
 54
 55
 56
 57    <div class="container" style="margin-top:40px;max-width:800px;">
 58      <h1>Static blog template engine implementation in Go</h1>
 59
 60<p><em>2017-12-26</em></p>
 61
 62<p>Some days ago, I decided to start this blog, to put there all the thoughts and ideas that goes through my mind. After some research, I&rsquo;ve found some interesting projects, but with a lot of features that I don&rsquo;t need to use. So I decided to write my own minimalistic static blog template engine with Go lang.</p>
 63
 64<p>This is how I made <a href="https://github.com/arnaucube/blogo">blogo</a> the blog static template engine to do this blog.</p>
 65
 66<h3>Static blog template engine?</h3>
 67
 68<p>The main idea is to be able to write the blog posts in Markdown files, and with the template engine, output the HTML files ready to upload in the web hosting server.</p>
 69
 70<h2>Structure</h2>
 71
 72<p>What we wan to have in the input is:</p>
 73
 74<pre><code>/blogo
 75    /input
 76        /css
 77            mycss.css
 78        /img
 79            post01_image.png
 80        index.html
 81        postThumbTemplate.html
 82        post01.md
 83        post01thumb.md
 84        post02.md
 85        post02thumb.md
 86</code></pre>
 87
 88<h4>blogo.json</h4>
 89
 90<p>This is the file that have the configuration that will be read by the Go script.</p>
 91
 92<pre><code class="language-json">{
 93    &quot;title&quot;: &quot;my blog&quot;,
 94    &quot;indexTemplate&quot;: &quot;index.html&quot;,
 95    &quot;postThumbTemplate&quot;: &quot;postThumbTemplate.html&quot;,
 96    &quot;posts&quot;: [
 97        {
 98            &quot;thumb&quot;: &quot;post01thumb.md&quot;,
 99            &quot;md&quot;: &quot;post01.md&quot;
100        },
101        {
102            &quot;thumb&quot;: &quot;post02thumb.md&quot;,
103            &quot;md&quot;: &quot;post02.md&quot;
104        }
105    ],
106    &quot;copyRaw&quot;: [
107      &quot;css&quot;,
108      &quot;js&quot;
109    ]
110}
111</code></pre>
112
113<p>The <em>copyRaw</em> element, will be all the directories to copy raw to the output.</p>
114
115<h4>index.html</h4>
116
117<p>This is the file that will be used as the template for the main page and also for the posts pages.</p>
118
119<pre><code class="language-html">&lt;!DOCTYPE html&gt;
120&lt;html&gt;
121&lt;head&gt;
122    &lt;title&gt;[blogo-title]&lt;/title&gt;
123&lt;/head&gt;
124&lt;body&gt;
125    &lt;div&gt;
126        [blogo-content]
127    &lt;/div&gt;
128&lt;/body&gt;
129&lt;/html&gt;
130</code></pre>
131
132<p>As we can see, we just need to define the html file, and in the title define the <em>[blogo-title]</em>, and in the content place the <em>[blogo-content]</em>.</p>
133
134<h4>postThumbTemplate.html</h4>
135
136<p>This is the file where is placed the html box for each post that will be displayed in the main page.</p>
137
138<pre><code class="language-html">&lt;div class=&quot;well&quot;&gt;
139    [blogo-index-post-template]
140&lt;/div&gt;
141</code></pre>
142
143<h4>post01thumb.md</h4>
144
145<pre><code class="language-markdown"># Post 01 thumb
146This is the description of the Post 01
147</code></pre>
148
149<h4>post01.md</h4>
150
151<pre><code class="language-markdown"># Title of the Post 01
152Hi, this is the content of the post
153
154'''js
155    console.log(&quot;hello world&quot;);
156'''
157</code></pre>
158
159<h2>Let&rsquo;s start to code</h2>
160
161<p>As we have exposed, we want to develop a Go lang script that from some HTML template and the Markdown text files, generates the complete blog with the main page and all the posts.</p>
162
163<h4>readConfig.go</h4>
164
165<p>This is the file that reads the <em>blogo.json</em> file to get the blog configuration.</p>
166
167<pre><code class="language-go">package main
168
169import (
170	&quot;encoding/json&quot;
171	&quot;io/ioutil&quot;
172)
173
174//Post is the struct for each post of the blog
175type Post struct {
176	Thumb string `json:&quot;thumb&quot;`
177	Md    string `json:&quot;md&quot;`
178}
179
180//Config gets the config.json file into struct
181type Config struct {
182	Title             string   `json:&quot;title&quot;`
183	IndexTemplate     string   `json:&quot;indexTemplate&quot;`
184	PostThumbTemplate string   `json:&quot;postThumbTemplate&quot;`
185	Posts             []Post   `json:&quot;posts&quot;`
186	CopyRaw           []string `json:&quot;copyRaw&quot;`
187}
188
189var config Config
190
191func readConfig(path string) {
192	file, err := ioutil.ReadFile(path)
193	check(err)
194	content := string(file)
195	json.Unmarshal([]byte(content), &amp;config)
196}
197</code></pre>
198
199<h4>files.go, the operations with files</h4>
200
201<p>We will need some file operation functions, so we have placed all in this file.</p>
202
203<pre><code class="language-go">package main
204
205import (
206	&quot;io/ioutil&quot;
207	&quot;os/exec&quot;
208	&quot;strings&quot;
209
210	&quot;github.com/fatih/color&quot;
211)
212
213func readFile(path string) string {
214	dat, err := ioutil.ReadFile(path)
215	if err != nil {
216		color.Red(path)
217	}
218	check(err)
219	return string(dat)
220}
221
222func writeFile(path string, newContent string) {
223	err := ioutil.WriteFile(path, []byte(newContent), 0644)
224	check(err)
225	color.Green(path)
226}
227
228func getLines(text string) []string {
229	lines := strings.Split(text, &quot;\n&quot;)
230	return lines
231}
232
233func concatStringsWithJumps(lines []string) string {
234	var r string
235	for _, l := range lines {
236		r = r + l + &quot;\n&quot;
237	}
238	return r
239}
240
241func copyRaw(original string, destination string) {
242	color.Green(original + &quot; --&gt; to --&gt; &quot; + destination)
243	_, err := exec.Command(&quot;cp&quot;, &quot;-rf&quot;, original, destination).Output()
244	check(err)
245}
246</code></pre>
247
248<h4>main.go</h4>
249
250<p>To convert the HTML content to Markdown content, we will use <a href="https://github.com/russross/blackfriday">https://github.com/russross/blackfriday</a></p>
251
252<pre><code class="language-go">package main
253
254import (
255	&quot;fmt&quot;
256	&quot;strings&quot;
257
258	blackfriday &quot;gopkg.in/russross/blackfriday.v2&quot;
259)
260
261const directory = &quot;blogo-input&quot;
262
263func main() {
264	readConfig(directory + &quot;/blogo.json&quot;)
265	fmt.Println(config)
266
267	// generate index page
268	indexTemplate := readFile(directory + &quot;/&quot; + config.IndexTemplate)
269	indexPostTemplate := readFile(directory + &quot;/&quot; + config.PostThumbTemplate)
270	var blogoIndex string
271	blogoIndex = &quot;&quot;
272	for _, post := range config.Posts {
273		mdpostthumb := readFile(directory + &quot;/&quot; + post.Thumb)
274		htmlpostthumb := string(blackfriday.Run([]byte(mdpostthumb)))
275
276		//put the htmlpostthumb in the blogo-index-post-template
277		m := make(map[string]string)
278		m[&quot;[blogo-index-post-template]&quot;] = htmlpostthumb
279		r := putHTMLToTemplate(indexPostTemplate, m)
280		filename := strings.Split(post.Md, &quot;.&quot;)[0]
281		r = &quot;&lt;a href='&quot; + filename + &quot;.html'&gt;&quot; + r + &quot;&lt;/a&gt;&quot;
282		blogoIndex = blogoIndex + r
283	}
284	//put the blogoIndex in the index.html
285	m := make(map[string]string)
286	m[&quot;[blogo-title]&quot;] = config.Title
287	m[&quot;[blogo-content]&quot;] = blogoIndex
288	r := putHTMLToTemplate(indexTemplate, m)
289	writeFile(&quot;index.html&quot;, r)
290
291	// generate posts pages
292	for _, post := range config.Posts {
293		mdcontent := readFile(directory + &quot;/&quot; + post.Md)
294		htmlcontent := string(blackfriday.Run([]byte(mdcontent)))
295
296		m := make(map[string]string)
297		m[&quot;[blogo-title]&quot;] = config.Title
298		m[&quot;[blogo-content]&quot;] = htmlcontent
299
300		r := putHTMLToTemplate(indexTemplate, m)
301		//fmt.Println(r)
302
303		filename := strings.Split(post.Md, &quot;.&quot;)[0]
304		writeFile(filename+&quot;.html&quot;, r)
305	}
306
307	//copy raw
308	fmt.Println(&quot;copying raw:&quot;)
309	for _, dir := range config.CopyRaw {
310		copyRaw(directory+&quot;/&quot;+dir, &quot;.&quot;)
311	}
312}
313
314func putHTMLToTemplate(template string, m map[string]string) string {
315	lines := getLines(template)
316	var resultL []string
317	for _, line := range lines {
318		inserted := false
319		for k, v := range m {
320			if strings.Contains(line, k) {
321				//in the line, change [tag] with the content
322				lineReplaced := strings.Replace(line, k, v, -1)
323				resultL = append(resultL, lineReplaced)
324				inserted = true
325			}
326		}
327		if inserted == false {
328			resultL = append(resultL, line)
329		}
330	}
331	result := concatStringsWithJumps(resultL)
332	return result
333}
334</code></pre>
335
336<h2>Try it</h2>
337
338<p>To try it, we need to compile the Go code:</p>
339
340<pre><code>&gt; go build
341</code></pre>
342
343<p>And run it:</p>
344
345<pre><code>&gt; ./blogo
346</code></pre>
347
348<p>Or we can just build and run to test:</p>
349
350<pre><code>&gt; go run *.go
351</code></pre>
352
353<p>As the output, we will obtain the html pages with the content:</p>
354
355<ul>
356<li>index.html</li>
357</ul>
358
359<pre><code class="language-html">&lt;!DOCTYPE html&gt;
360&lt;html&gt;
361&lt;head&gt;
362    &lt;title&gt;my blog&lt;/title&gt;
363&lt;/head&gt;
364&lt;body&gt;
365    &lt;div class=&quot;row&quot;&gt;
366        &lt;a href='post01.html'&gt;
367            &lt;div class=&quot;col-md-3&quot;&gt;
368                &lt;h1&gt;Post 01 thumb&lt;/h1&gt;
369                &lt;p&gt;This is the description of the Post 01&lt;/p&gt;
370            &lt;/div&gt;
371        &lt;/a&gt;
372        &lt;a href='post02.html'&gt;
373            &lt;div class=&quot;col-md-3&quot;&gt;
374                &lt;p&gt;Post 02 thumb&lt;/p&gt;
375            &lt;/div&gt;
376        &lt;/a&gt;
377    &lt;/div&gt;
378&lt;/body&gt;
379&lt;/html&gt;
380</code></pre>
381
382<ul>
383<li>post01.html</li>
384</ul>
385
386<pre><code class="language-html">&lt;!DOCTYPE html&gt;
387&lt;html&gt;
388&lt;head&gt;
389    &lt;title&gt;my blog&lt;/title&gt;
390&lt;/head&gt;
391&lt;body&gt;
392    &lt;div&gt;
393        &lt;h1&gt;Title of the Post 01&lt;/h1&gt;
394        &lt;p&gt;Hi, this is the content of the post&lt;/p&gt;
395        &lt;pre&gt;
396            &lt;code class=&quot;language-js&quot;&gt;    console.log(&amp;quot;hello world&amp;quot;);
397            &lt;/code&gt;
398        &lt;/pre&gt;
399    &lt;/div&gt;
400&lt;/body&gt;
401&lt;/html&gt;
402</code></pre>
403
404<h2>Conclusion</h2>
405
406<p>In this post, we have seen how to develop a very minimalistic static blog template engine with Go. In fact, is the blog engine that I&rsquo;m using for this blog.</p>
407
408<p>There are lots of blog template engines nowadays, but maybe we don&rsquo;t need sophisticated engine, and we just need a minimalistic one. In that case, we have seen how to develop one.</p>
409
410<p>The complete project code is able in <a href="https://github.com/arnaucube/blogo">https://github.com/arnaucube/blogo</a></p>
411
412    </div>
413
414    <footer style="text-align:center; margin-top:100px;margin-bottom:50px;">
415      <div class="container">
416        <div class="row">
417          <ul class="list-inline">
418            <li><a href="https://twitter.com/arnaucube"
419                   style="color:gray;text-decoration:none;"
420                   target="_blank">twitter.com/arnaucube</a>
421            </li>
422            <li><a href="https://github.com/arnaucube"
423                   style="color:gray;text-decoration:none;"
424                   target="_blank">github.com/arnaucube</a>
425            </li>
426          </ul>
427        </div>
428        <div class="row" style="display:inline-block;">
429          Blog made with <a href="http://github.com/arnaucube/blogo/"
430                            target="_blank" style="color: gray;text-decoration:none;">Blogo</a>
431        </div>
432      </div>
433    </footer>
434
435    <script>
436    </script>
437    <script src="js/external-links.js"></script>
438    <script>hljs.initHighlightingOnLoad();</script>
439    <script defer src="js/katex/katex.min.js"></script>
440    <script defer src="js/katex/auto-render.min.js"></script>
441    <script>
442      document.addEventListener("DOMContentLoaded", function() {
443        renderMathInElement(document.body, {
444          displayMode: false,
445          // customised options
446          // • auto-render specific keys, e.g.:
447          delimiters: [
448            {left: '$$', right: '$$', display: true},
449            {left: '$', right: '$', display: false},
450            {left: "\\[", right: "\\]", display: true},
451            {left: "\\(", right: "\\)", display: false},
452          ],
453          // • rendering keys, e.g.:
454          throwOnError : true
455        });
456      });
457
458      ///
459      let theme = localStorage.getItem("theme");
460      if ((theme === "light-theme")||(theme==null)) {
461          theme = "light-theme";
462          document.getElementById("themeSwitcher").checked = false;
463      } else if (theme === "dark-theme") {
464          theme = "dark-theme";
465          document.getElementById("themeSwitcher").checked = true;
466      }
467      document.body.className = theme;
468      localStorage.setItem("theme", theme);
469
470      function switchTheme() {
471        theme = localStorage.getItem("theme");
472        if (theme === "light-theme") {
473          theme = "dark-theme";
474          document.getElementById("themeSwitcher").checked = true;
475        } else {
476          theme = "light-theme";
477          document.getElementById("themeSwitcher").checked = false;
478        }
479        document.body.className = theme;
480        localStorage.setItem("theme", theme);
481
482        console.log(theme);
483      }
484    </script>
485  <script>
486    function tagLinks(tagName) {
487      var tags = document.getElementsByTagName(tagName);
488      for (var i=0, hElem; hElem =  tags[i]; i++) {
489        if (hElem.parentNode.className=="row postThumb") {
490          continue;
491        }
492        hElem.id = hElem.innerHTML.toLowerCase().replace(" ", "-");
493        hElem.innerHTML = "<a style='text-decoration:none;color:black;' href='#"+hElem.id+"'>"+hElem.innerHTML+"</a>";
494      }
495    }
496    tagLinks("h2");
497    tagLinks("h3");
498    tagLinks("h4");
499    tagLinks("h5");
500    </script>
501    <script src="js/mermaid.min.js"></script>
502
503
504  </body>
505</html>
506