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’ve found some interesting projects, but with a lot of features that I don’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 "title": "my blog",
106 "indexTemplate": "index.html",
107 "postThumbTemplate": "postThumbTemplate.html",
108 "posts": [
109 {
110 "thumb": "post01thumb.md",
111 "md": "post01.md"
112 },
113 {
114 "thumb": "post02thumb.md",
115 "md": "post02.md"
116 }
117 ],
118 "copyRaw": [
119 "css",
120 "js"
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"><!DOCTYPE html>
132<html>
133<head>
134 <title>[blogo-title]</title>
135</head>
136<body>
137 <div>
138 [blogo-content]
139 </div>
140</body>
141</html>
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"><div class="well">
151 [blogo-index-post-template]
152</div>
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("hello world");
168'''
169</code></pre>
170
171<h2>Let’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 "encoding/json"
183 "io/ioutil"
184)
185
186//Post is the struct for each post of the blog
187type Post struct {
188 Thumb string `json:"thumb"`
189 Md string `json:"md"`
190}
191
192//Config gets the config.json file into struct
193type Config struct {
194 Title string `json:"title"`
195 IndexTemplate string `json:"indexTemplate"`
196 PostThumbTemplate string `json:"postThumbTemplate"`
197 Posts []Post `json:"posts"`
198 CopyRaw []string `json:"copyRaw"`
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), &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 "io/ioutil"
219 "os/exec"
220 "strings"
221
222 "github.com/fatih/color"
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, "\n")
242 return lines
243}
244
245func concatStringsWithJumps(lines []string) string {
246 var r string
247 for _, l := range lines {
248 r = r + l + "\n"
249 }
250 return r
251}
252
253func copyRaw(original string, destination string) {
254 color.Green(original + " --> to --> " + destination)
255 _, err := exec.Command("cp", "-rf", 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 "fmt"
268 "strings"
269
270 blackfriday "gopkg.in/russross/blackfriday.v2"
271)
272
273const directory = "blogo-input"
274
275func main() {
276 readConfig(directory + "/blogo.json")
277 fmt.Println(config)
278
279 // generate index page
280 indexTemplate := readFile(directory + "/" + config.IndexTemplate)
281 indexPostTemplate := readFile(directory + "/" + config.PostThumbTemplate)
282 var blogoIndex string
283 blogoIndex = ""
284 for _, post := range config.Posts {
285 mdpostthumb := readFile(directory + "/" + 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["[blogo-index-post-template]"] = htmlpostthumb
291 r := putHTMLToTemplate(indexPostTemplate, m)
292 filename := strings.Split(post.Md, ".")[0]
293 r = "<a href='" + filename + ".html'>" + r + "</a>"
294 blogoIndex = blogoIndex + r
295 }
296 //put the blogoIndex in the index.html
297 m := make(map[string]string)
298 m["[blogo-title]"] = config.Title
299 m["[blogo-content]"] = blogoIndex
300 r := putHTMLToTemplate(indexTemplate, m)
301 writeFile("index.html", r)
302
303 // generate posts pages
304 for _, post := range config.Posts {
305 mdcontent := readFile(directory + "/" + post.Md)
306 htmlcontent := string(blackfriday.Run([]byte(mdcontent)))
307
308 m := make(map[string]string)
309 m["[blogo-title]"] = config.Title
310 m["[blogo-content]"] = htmlcontent
311
312 r := putHTMLToTemplate(indexTemplate, m)
313 //fmt.Println(r)
314
315 filename := strings.Split(post.Md, ".")[0]
316 writeFile(filename+".html", r)
317 }
318
319 //copy raw
320 fmt.Println("copying raw:")
321 for _, dir := range config.CopyRaw {
322 copyRaw(directory+"/"+dir, ".")
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>> go build
353</code></pre>
354
355<p>And run it:</p>
356
357<pre><code>> ./blogo
358</code></pre>
359
360<p>Or we can just build and run to test:</p>
361
362<pre><code>> 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"><!DOCTYPE html>
372<html>
373<head>
374 <title>my blog</title>
375</head>
376<body>
377 <div class="row">
378 <a href='post01.html'>
379 <div class="col-md-3">
380 <h1>Post 01 thumb</h1>
381 <p>This is the description of the Post 01</p>
382 </div>
383 </a>
384 <a href='post02.html'>
385 <div class="col-md-3">
386 <p>Post 02 thumb</p>
387 </div>
388 </a>
389 </div>
390</body>
391</html>
392</code></pre>
393
394<ul>
395<li>post01.html</li>
396</ul>
397
398<pre><code class="language-html"><!DOCTYPE html>
399<html>
400<head>
401 <title>my blog</title>
402</head>
403<body>
404 <div>
405 <h1>Title of the Post 01</h1>
406 <p>Hi, this is the content of the post</p>
407 <pre>
408 <code class="language-js"> console.log(&quot;hello world&quot;);
409 </code>
410 </pre>
411 </div>
412</body>
413</html>
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’m using for this blog.</p>
419
420<p>There are lots of blog template engines nowadays, but maybe we don’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