arnaucube
arnaucube/arnaucube.comPublic Self-hosted repository
arnaucube.com/blog/blogo.html
blog/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    <link rel="icon" type="image/png" href="img/logoArnauCubeFavicon.png">
 23
 24    <meta name="viewport" content="width=device-width, initial-scale=1">
 25
 26    <link href="css/bootstrap.min.css" rel="stylesheet">
 27    <link rel="stylesheet" href="css/style.css">
 28
 29    <!-- highlightjs -->
 30    <!-- <link rel="stylesheet" href="js/highlightjs/atom-one-dark.css"> -->
 31    <link rel="stylesheet" href="js/highlightjs/atom-one-light.css">
 32    <!-- <link rel="stylesheet" href="js/highlightjs/gruvbox-dark.css"> -->
 33    <script src="js/highlightjs/highlight.pack.js"></script>
 34
 35    <!-- katex -->
 36    <link rel="stylesheet" href="js/katex/katex.min.css">
 37  <link rel="alternate" type="application/rss+xml" title="arnaucube - blog RSS" href="https://arnaucube.com/blog/rss.xml">
 38</head>
 39
 40  <body>
 41
 42    <!-- o_gradient_background" -->
 43    <nav id="mainNav" class="navbar navbar-default navbar-fixed-top"
 44                      style="height:50px;font-size:100%;">
 45      <div class="container">
 46        <div style="float:left;">
 47        <a href="/blog" style="color:#000;display:inline-block;">blog</a>
 48        <span style="margin-right:20px; margin-left:20px;">|</span>
 49        <a href="/blog/notes.html" style="font-size:90%;color:#000;display:inline-block;">notes</a>
 50        </div>
 51        <div style="float:right;">
 52          <a href="/" style="color:#000;display:inline-block;margin-right:10px;">arnaucube.com</a>
 53          <a href="/blog/rss.xml" target="_blank"
 54            type="application/rss+xml" title="subscribe via RSS" aria-label="subscribe via RSS">
 55              <img width="12px;" src="img/rss.png" />
 56          </a>
 57          <div class="onoffswitch" style="margin-left:0px;display:inline-block;" title="change theme">
 58            <input onclick="switchTheme()" type="checkbox" name="onoffswitch" class="onoffswitch-checkbox"
 59              id="themeSwitcher">
 60            <label class="onoffswitch-label" for="themeSwitcher"></label>
 61          </div>
 62        </div>
 63      </div>
 64      <!-- <img style="height:5px; width:100%; margin-top:8px;" src="img/gradient-line.jpg" /> -->
 65    </nav>
 66
 67
 68
 69    <div class="container" style="margin-top:0px;max-width:800px;">
 70      <h1>Static blog template engine implementation in Go</h1>
 71
 72<p><em>2017-12-26</em></p>
 73
 74<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>
 75
 76<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>
 77
 78<h3>Static blog template engine?</h3>
 79
 80<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>
 81
 82<h2>Structure</h2>
 83
 84<p>What we wan to have in the input is:</p>
 85
 86<pre><code>/blogo
 87    /input
 88        /css
 89            mycss.css
 90        /img
 91            post01_image.png
 92        index.html
 93        postThumbTemplate.html
 94        post01.md
 95        post01thumb.md
 96        post02.md
 97        post02thumb.md
 98</code></pre>
 99
100<h4>blogo.json</h4>
101
102<p>This is the file that have the configuration that will be read by the Go script.</p>
103
104<pre><code class="language-json">{
105    &quot;title&quot;: &quot;my blog&quot;,
106    &quot;indexTemplate&quot;: &quot;index.html&quot;,
107    &quot;postThumbTemplate&quot;: &quot;postThumbTemplate.html&quot;,
108    &quot;posts&quot;: [
109        {
110            &quot;thumb&quot;: &quot;post01thumb.md&quot;,
111            &quot;md&quot;: &quot;post01.md&quot;
112        },
113        {
114            &quot;thumb&quot;: &quot;post02thumb.md&quot;,
115            &quot;md&quot;: &quot;post02.md&quot;
116        }
117    ],
118    &quot;copyRaw&quot;: [
119      &quot;css&quot;,
120      &quot;js&quot;
121    ]
122}
123</code></pre>
124
125<p>The <em>copyRaw</em> element, will be all the directories to copy raw to the output.</p>
126
127<h4>index.html</h4>
128
129<p>This is the file that will be used as the template for the main page and also for the posts pages.</p>
130
131<pre><code class="language-html">&lt;!DOCTYPE html&gt;
132&lt;html&gt;
133&lt;head&gt;
134    &lt;title&gt;[blogo-title]&lt;/title&gt;
135&lt;/head&gt;
136&lt;body&gt;
137    &lt;div&gt;
138        [blogo-content]
139    &lt;/div&gt;
140&lt;/body&gt;
141&lt;/html&gt;
142</code></pre>
143
144<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>
145
146<h4>postThumbTemplate.html</h4>
147
148<p>This is the file where is placed the html box for each post that will be displayed in the main page.</p>
149
150<pre><code class="language-html">&lt;div class=&quot;well&quot;&gt;
151    [blogo-index-post-template]
152&lt;/div&gt;
153</code></pre>
154
155<h4>post01thumb.md</h4>
156
157<pre><code class="language-markdown"># Post 01 thumb
158This is the description of the Post 01
159</code></pre>
160
161<h4>post01.md</h4>
162
163<pre><code class="language-markdown"># Title of the Post 01
164Hi, this is the content of the post
165
166'''js
167    console.log(&quot;hello world&quot;);
168'''
169</code></pre>
170
171<h2>Let&rsquo;s start to code</h2>
172
173<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>
174
175<h4>readConfig.go</h4>
176
177<p>This is the file that reads the <em>blogo.json</em> file to get the blog configuration.</p>
178
179<pre><code class="language-go">package main
180
181import (
182	&quot;encoding/json&quot;
183	&quot;io/ioutil&quot;
184)
185
186//Post is the struct for each post of the blog
187type Post struct {
188	Thumb string `json:&quot;thumb&quot;`
189	Md    string `json:&quot;md&quot;`
190}
191
192//Config gets the config.json file into struct
193type Config struct {
194	Title             string   `json:&quot;title&quot;`
195	IndexTemplate     string   `json:&quot;indexTemplate&quot;`
196	PostThumbTemplate string   `json:&quot;postThumbTemplate&quot;`
197	Posts             []Post   `json:&quot;posts&quot;`
198	CopyRaw           []string `json:&quot;copyRaw&quot;`
199}
200
201var config Config
202
203func readConfig(path string) {
204	file, err := ioutil.ReadFile(path)
205	check(err)
206	content := string(file)
207	json.Unmarshal([]byte(content), &amp;config)
208}
209</code></pre>
210
211<h4>files.go, the operations with files</h4>
212
213<p>We will need some file operation functions, so we have placed all in this file.</p>
214
215<pre><code class="language-go">package main
216
217import (
218	&quot;io/ioutil&quot;
219	&quot;os/exec&quot;
220	&quot;strings&quot;
221
222	&quot;github.com/fatih/color&quot;
223)
224
225func readFile(path string) string {
226	dat, err := ioutil.ReadFile(path)
227	if err != nil {
228		color.Red(path)
229	}
230	check(err)
231	return string(dat)
232}
233
234func writeFile(path string, newContent string) {
235	err := ioutil.WriteFile(path, []byte(newContent), 0644)
236	check(err)
237	color.Green(path)
238}
239
240func getLines(text string) []string {
241	lines := strings.Split(text, &quot;\n&quot;)
242	return lines
243}
244
245func concatStringsWithJumps(lines []string) string {
246	var r string
247	for _, l := range lines {
248		r = r + l + &quot;\n&quot;
249	}
250	return r
251}
252
253func copyRaw(original string, destination string) {
254	color.Green(original + &quot; --&gt; to --&gt; &quot; + destination)
255	_, err := exec.Command(&quot;cp&quot;, &quot;-rf&quot;, original, destination).Output()
256	check(err)
257}
258</code></pre>
259
260<h4>main.go</h4>
261
262<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>
263
264<pre><code class="language-go">package main
265
266import (
267	&quot;fmt&quot;
268	&quot;strings&quot;
269
270	blackfriday &quot;gopkg.in/russross/blackfriday.v2&quot;
271)
272
273const directory = &quot;blogo-input&quot;
274
275func main() {
276	readConfig(directory + &quot;/blogo.json&quot;)
277	fmt.Println(config)
278
279	// generate index page
280	indexTemplate := readFile(directory + &quot;/&quot; + config.IndexTemplate)
281	indexPostTemplate := readFile(directory + &quot;/&quot; + config.PostThumbTemplate)
282	var blogoIndex string
283	blogoIndex = &quot;&quot;
284	for _, post := range config.Posts {
285		mdpostthumb := readFile(directory + &quot;/&quot; + post.Thumb)
286		htmlpostthumb := string(blackfriday.Run([]byte(mdpostthumb)))
287
288		//put the htmlpostthumb in the blogo-index-post-template
289		m := make(map[string]string)
290		m[&quot;[blogo-index-post-template]&quot;] = htmlpostthumb
291		r := putHTMLToTemplate(indexPostTemplate, m)
292		filename := strings.Split(post.Md, &quot;.&quot;)[0]
293		r = &quot;&lt;a href='&quot; + filename + &quot;.html'&gt;&quot; + r + &quot;&lt;/a&gt;&quot;
294		blogoIndex = blogoIndex + r
295	}
296	//put the blogoIndex in the index.html
297	m := make(map[string]string)
298	m[&quot;[blogo-title]&quot;] = config.Title
299	m[&quot;[blogo-content]&quot;] = blogoIndex
300	r := putHTMLToTemplate(indexTemplate, m)
301	writeFile(&quot;index.html&quot;, r)
302
303	// generate posts pages
304	for _, post := range config.Posts {
305		mdcontent := readFile(directory + &quot;/&quot; + post.Md)
306		htmlcontent := string(blackfriday.Run([]byte(mdcontent)))
307
308		m := make(map[string]string)
309		m[&quot;[blogo-title]&quot;] = config.Title
310		m[&quot;[blogo-content]&quot;] = htmlcontent
311
312		r := putHTMLToTemplate(indexTemplate, m)
313		//fmt.Println(r)
314
315		filename := strings.Split(post.Md, &quot;.&quot;)[0]
316		writeFile(filename+&quot;.html&quot;, r)
317	}
318
319	//copy raw
320	fmt.Println(&quot;copying raw:&quot;)
321	for _, dir := range config.CopyRaw {
322		copyRaw(directory+&quot;/&quot;+dir, &quot;.&quot;)
323	}
324}
325
326func putHTMLToTemplate(template string, m map[string]string) string {
327	lines := getLines(template)
328	var resultL []string
329	for _, line := range lines {
330		inserted := false
331		for k, v := range m {
332			if strings.Contains(line, k) {
333				//in the line, change [tag] with the content
334				lineReplaced := strings.Replace(line, k, v, -1)
335				resultL = append(resultL, lineReplaced)
336				inserted = true
337			}
338		}
339		if inserted == false {
340			resultL = append(resultL, line)
341		}
342	}
343	result := concatStringsWithJumps(resultL)
344	return result
345}
346</code></pre>
347
348<h2>Try it</h2>
349
350<p>To try it, we need to compile the Go code:</p>
351
352<pre><code>&gt; go build
353</code></pre>
354
355<p>And run it:</p>
356
357<pre><code>&gt; ./blogo
358</code></pre>
359
360<p>Or we can just build and run to test:</p>
361
362<pre><code>&gt; go run *.go
363</code></pre>
364
365<p>As the output, we will obtain the html pages with the content:</p>
366
367<ul>
368<li>index.html</li>
369</ul>
370
371<pre><code class="language-html">&lt;!DOCTYPE html&gt;
372&lt;html&gt;
373&lt;head&gt;
374    &lt;title&gt;my blog&lt;/title&gt;
375&lt;/head&gt;
376&lt;body&gt;
377    &lt;div class=&quot;row&quot;&gt;
378        &lt;a href='post01.html'&gt;
379            &lt;div class=&quot;col-md-3&quot;&gt;
380                &lt;h1&gt;Post 01 thumb&lt;/h1&gt;
381                &lt;p&gt;This is the description of the Post 01&lt;/p&gt;
382            &lt;/div&gt;
383        &lt;/a&gt;
384        &lt;a href='post02.html'&gt;
385            &lt;div class=&quot;col-md-3&quot;&gt;
386                &lt;p&gt;Post 02 thumb&lt;/p&gt;
387            &lt;/div&gt;
388        &lt;/a&gt;
389    &lt;/div&gt;
390&lt;/body&gt;
391&lt;/html&gt;
392</code></pre>
393
394<ul>
395<li>post01.html</li>
396</ul>
397
398<pre><code class="language-html">&lt;!DOCTYPE html&gt;
399&lt;html&gt;
400&lt;head&gt;
401    &lt;title&gt;my blog&lt;/title&gt;
402&lt;/head&gt;
403&lt;body&gt;
404    &lt;div&gt;
405        &lt;h1&gt;Title of the Post 01&lt;/h1&gt;
406        &lt;p&gt;Hi, this is the content of the post&lt;/p&gt;
407        &lt;pre&gt;
408            &lt;code class=&quot;language-js&quot;&gt;    console.log(&amp;quot;hello world&amp;quot;);
409            &lt;/code&gt;
410        &lt;/pre&gt;
411    &lt;/div&gt;
412&lt;/body&gt;
413&lt;/html&gt;
414</code></pre>
415
416<h2>Conclusion</h2>
417
418<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>
419
420<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>
421
422<p>The complete project code is able in <a href="https://github.com/arnaucube/blogo">https://github.com/arnaucube/blogo</a></p>
423
424    </div>
425
426    <footer style="text-align:center; margin-top:100px;margin-bottom:50px;">
427      <div class="container">
428        <br>
429        <a href="/blog">Go to main</a>
430        <br><br>
431        <div class="row">
432          <ul class="list-inline">
433            <li><a href="https://twitter.com/arnaucube"
434                   style="color:gray;text-decoration:none;"
435                   target="_blank">twitter.com/arnaucube</a>
436            </li>
437            <li><a href="https://github.com/arnaucube"
438                   style="color:gray;text-decoration:none;"
439                   target="_blank">github.com/arnaucube</a>
440            </li>
441          </ul>
442        </div>
443        <div class="row" style="display:inline-block;">
444          Blog made with <a href="http://github.com/arnaucube/blogo/"
445                            target="_blank" style="color: gray;text-decoration:none;">Blogo</a>
446        </div>
447      </div>
448    </footer>
449
450    <script>
451    </script>
452    <script src="js/external-links.js"></script>
453    <script>hljs.initHighlightingOnLoad();</script>
454    <script defer src="js/katex/katex.min.js"></script>
455    <script defer src="js/katex/auto-render.min.js"></script>
456    <script>
457      document.addEventListener("DOMContentLoaded", function() {
458        renderMathInElement(document.body, {
459          displayMode: false,
460          // customised options
461          // • auto-render specific keys, e.g.:
462          delimiters: [
463            {left: '$$', right: '$$', display: true},
464            {left: '$', right: '$', display: false},
465            {left: "\\[", right: "\\]", display: true},
466            {left: "\\(", right: "\\)", display: false},
467            {left: "\\begin{equation}", right: "\\end{equation}", display: true}
468          ],
469          // • rendering keys, e.g.:
470          throwOnError : true
471        });
472      });
473
474      ///
475      let theme = localStorage.getItem("theme");
476      if ((theme === "light-theme")||(theme==null)) {
477          theme = "light-theme";
478          document.getElementById("themeSwitcher").checked = false;
479      } else if (theme === "dark-theme") {
480          theme = "dark-theme";
481          document.getElementById("themeSwitcher").checked = true;
482      }
483      document.body.className = theme;
484      localStorage.setItem("theme", theme);
485
486      function switchTheme() {
487        theme = localStorage.getItem("theme");
488        if (theme === "light-theme") {
489          theme = "dark-theme";
490          document.getElementById("themeSwitcher").checked = true;
491        } else {
492          theme = "light-theme";
493          document.getElementById("themeSwitcher").checked = false;
494        }
495        document.body.className = theme;
496        localStorage.setItem("theme", theme);
497
498        console.log(theme);
499      }
500    </script>
501    <script>
502    function tagLinks(tagName) {
503      var tags = document.getElementsByTagName(tagName);
504      for (var i=0, hElem; hElem =  tags[i]; i++) {
505        if (hElem.parentNode.className=="row postThumb") {
506          continue;
507        }
508        hElem.id = hElem.innerHTML.toLowerCase().replace(" ", "-");
509        hElem.innerHTML = "<a style='color:black!important;' href='#"+hElem.id+"'>"+hElem.innerHTML+"</a>";
510      }
511    }
512    tagLinks("h2");
513    tagLinks("h3");
514    tagLinks("h4");
515    tagLinks("h5");
516    </script>
517    <script>
518function generateTOC(tocContainerId = "toc") {
519    const tocContainer = document.getElementById(tocContainerId);
520    if (!tocContainer) return;
521
522    const headings = document.querySelectorAll("h2, h3, h4, h5, h6");
523    if (!headings.length) return;
524
525    // Generate IDs if missing
526    headings.forEach((heading, index) => {
527        if (!heading.id) {
528            heading.id =
529                heading.textContent
530                    .trim()
531                    .toLowerCase()
532                    .replace(/[^\w\s-]/g, "")
533                    .replace(/\s+/g, "-") +
534                "-" +
535                index;
536        }
537    });
538
539    const toc = document.createElement("ul");
540    let currentLevel = 1;
541    let currentList = toc;
542    const stack = [toc];
543
544    headings.forEach((heading) => {
545        const level = parseInt(heading.tagName.substring(1), 10);
546
547        const li = document.createElement("li");
548        const a = document.createElement("a");
549
550        a.href = "#" + heading.id;
551        a.textContent = heading.textContent;
552
553        li.appendChild(a);
554
555        if (level > currentLevel) {
556            for (let i = currentLevel; i < level; i++) {
557                const nestedList = document.createElement("ul");
558
559                const lastLi =
560                    currentList.lastElementChild ||
561                    currentList.appendChild(document.createElement("ul"));
562
563                lastLi.appendChild(nestedList);
564
565                stack.push(nestedList);
566                currentList = nestedList;
567            }
568        } else if (level < currentLevel) {
569            for (let i = currentLevel; i > level; i--) {
570                stack.pop();
571            }
572            currentList = stack[stack.length - 1];
573        }
574
575        currentList.appendChild(li);
576        currentLevel = level;
577    });
578
579    tocContainer.innerHTML = "";
580    tocContainer.appendChild(toc);
581}
582
583document.addEventListener("DOMContentLoaded", () => {
584    generateTOC();
585});
586    </script>
587    <script src="js/mermaid.min.js"></script>
588
589  </body>
590</html>
591