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