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 <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’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>
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 "title": "my blog",
110 "indexTemplate": "index.html",
111 "postThumbTemplate": "postThumbTemplate.html",
112 "posts": [
113 {
114 "thumb": "post01thumb.md",
115 "md": "post01.md"
116 },
117 {
118 "thumb": "post02thumb.md",
119 "md": "post02.md"
120 }
121 ],
122 "copyRaw": [
123 "css",
124 "js"
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"><!DOCTYPE html>
136<html>
137<head>
138 <title>[blogo-title]</title>
139</head>
140<body>
141 <div>
142 [blogo-content]
143 </div>
144</body>
145</html>
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"><div class="well">
155 [blogo-index-post-template]
156</div>
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("hello world");
172'''
173</code></pre>
174
175<h2>Let’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 "encoding/json"
187 "io/ioutil"
188)
189
190//Post is the struct for each post of the blog
191type Post struct {
192 Thumb string `json:"thumb"`
193 Md string `json:"md"`
194}
195
196//Config gets the config.json file into struct
197type Config struct {
198 Title string `json:"title"`
199 IndexTemplate string `json:"indexTemplate"`
200 PostThumbTemplate string `json:"postThumbTemplate"`
201 Posts []Post `json:"posts"`
202 CopyRaw []string `json:"copyRaw"`
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), &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 "io/ioutil"
223 "os/exec"
224 "strings"
225
226 "github.com/fatih/color"
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, "\n")
246 return lines
247}
248
249func concatStringsWithJumps(lines []string) string {
250 var r string
251 for _, l := range lines {
252 r = r + l + "\n"
253 }
254 return r
255}
256
257func copyRaw(original string, destination string) {
258 color.Green(original + " --> to --> " + destination)
259 _, err := exec.Command("cp", "-rf", 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 "fmt"
272 "strings"
273
274 blackfriday "gopkg.in/russross/blackfriday.v2"
275)
276
277const directory = "blogo-input"
278
279func main() {
280 readConfig(directory + "/blogo.json")
281 fmt.Println(config)
282
283 // generate index page
284 indexTemplate := readFile(directory + "/" + config.IndexTemplate)
285 indexPostTemplate := readFile(directory + "/" + config.PostThumbTemplate)
286 var blogoIndex string
287 blogoIndex = ""
288 for _, post := range config.Posts {
289 mdpostthumb := readFile(directory + "/" + 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["[blogo-index-post-template]"] = htmlpostthumb
295 r := putHTMLToTemplate(indexPostTemplate, m)
296 filename := strings.Split(post.Md, ".")[0]
297 r = "<a href='" + filename + ".html'>" + r + "</a>"
298 blogoIndex = blogoIndex + r
299 }
300 //put the blogoIndex in the index.html
301 m := make(map[string]string)
302 m["[blogo-title]"] = config.Title
303 m["[blogo-content]"] = blogoIndex
304 r := putHTMLToTemplate(indexTemplate, m)
305 writeFile("index.html", r)
306
307 // generate posts pages
308 for _, post := range config.Posts {
309 mdcontent := readFile(directory + "/" + post.Md)
310 htmlcontent := string(blackfriday.Run([]byte(mdcontent)))
311
312 m := make(map[string]string)
313 m["[blogo-title]"] = config.Title
314 m["[blogo-content]"] = htmlcontent
315
316 r := putHTMLToTemplate(indexTemplate, m)
317 //fmt.Println(r)
318
319 filename := strings.Split(post.Md, ".")[0]
320 writeFile(filename+".html", r)
321 }
322
323 //copy raw
324 fmt.Println("copying raw:")
325 for _, dir := range config.CopyRaw {
326 copyRaw(directory+"/"+dir, ".")
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>> go build
357</code></pre>
358
359<p>And run it:</p>
360
361<pre><code>> ./blogo
362</code></pre>
363
364<p>Or we can just build and run to test:</p>
365
366<pre><code>> 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"><!DOCTYPE html>
376<html>
377<head>
378 <title>my blog</title>
379</head>
380<body>
381 <div class="row">
382 <a href='post01.html'>
383 <div class="col-md-3">
384 <h1>Post 01 thumb</h1>
385 <p>This is the description of the Post 01</p>
386 </div>
387 </a>
388 <a href='post02.html'>
389 <div class="col-md-3">
390 <p>Post 02 thumb</p>
391 </div>
392 </a>
393 </div>
394</body>
395</html>
396</code></pre>
397
398<ul>
399<li>post01.html</li>
400</ul>
401
402<pre><code class="language-html"><!DOCTYPE html>
403<html>
404<head>
405 <title>my blog</title>
406</head>
407<body>
408 <div>
409 <h1>Title of the Post 01</h1>
410 <p>Hi, this is the content of the post</p>
411 <pre>
412 <code class="language-js"> console.log(&quot;hello world&quot;);
413 </code>
414 </pre>
415 </div>
416</body>
417</html>
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’m using for this blog.</p>
423
424<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>
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