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="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
 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="https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/katex.min.css" integrity="sha384-Um5gpz1odJg5Z4HAmzPtgZKdTBHZdw8S29IecapCSB31ligYPhHQZMIlWLYQGVoc" crossorigin="anonymous">
 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        <a href="/" style="color:#000;float:right;">arnaucube.com</a>
 44      </div>
 45      <img style="height:5px; width:100%; margin-top:8px;" src="img/gradient-line.jpg" />
 46    </nav>
 47
 48
 49
 50    <div class="container" style="margin-top:40px;max-width:800px;">
 51      <h1>Static blog template engine implementation in Go</h1>
 52
 53<p><em>2017-12-26</em></p>
 54
 55<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>
 56
 57<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>
 58
 59<h3>Static blog template engine?</h3>
 60
 61<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>
 62
 63<h2>Structure</h2>
 64
 65<p>What we wan to have in the input is:</p>
 66
 67<pre><code>/blogo
 68    /input
 69        /css
 70            mycss.css
 71        /img
 72            post01_image.png
 73        index.html
 74        postThumbTemplate.html
 75        post01.md
 76        post01thumb.md
 77        post02.md
 78        post02thumb.md
 79</code></pre>
 80
 81<h4>blogo.json</h4>
 82
 83<p>This is the file that have the configuration that will be read by the Go script.</p>
 84
 85<pre><code class="language-json">{
 86    &quot;title&quot;: &quot;my blog&quot;,
 87    &quot;indexTemplate&quot;: &quot;index.html&quot;,
 88    &quot;postThumbTemplate&quot;: &quot;postThumbTemplate.html&quot;,
 89    &quot;posts&quot;: [
 90        {
 91            &quot;thumb&quot;: &quot;post01thumb.md&quot;,
 92            &quot;md&quot;: &quot;post01.md&quot;
 93        },
 94        {
 95            &quot;thumb&quot;: &quot;post02thumb.md&quot;,
 96            &quot;md&quot;: &quot;post02.md&quot;
 97        }
 98    ],
 99    &quot;copyRaw&quot;: [
100      &quot;css&quot;,
101      &quot;js&quot;
102    ]
103}
104</code></pre>
105
106<p>The <em>copyRaw</em> element, will be all the directories to copy raw to the output.</p>
107
108<h4>index.html</h4>
109
110<p>This is the file that will be used as the template for the main page and also for the posts pages.</p>
111
112<pre><code class="language-html">&lt;!DOCTYPE html&gt;
113&lt;html&gt;
114&lt;head&gt;
115    &lt;title&gt;[blogo-title]&lt;/title&gt;
116&lt;/head&gt;
117&lt;body&gt;
118    &lt;div&gt;
119        [blogo-content]
120    &lt;/div&gt;
121&lt;/body&gt;
122&lt;/html&gt;
123</code></pre>
124
125<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>
126
127<h4>postThumbTemplate.html</h4>
128
129<p>This is the file where is placed the html box for each post that will be displayed in the main page.</p>
130
131<pre><code class="language-html">&lt;div class=&quot;well&quot;&gt;
132    [blogo-index-post-template]
133&lt;/div&gt;
134</code></pre>
135
136<h4>post01thumb.md</h4>
137
138<pre><code class="language-markdown"># Post 01 thumb
139This is the description of the Post 01
140</code></pre>
141
142<h4>post01.md</h4>
143
144<pre><code class="language-markdown"># Title of the Post 01
145Hi, this is the content of the post
146
147'''js
148    console.log(&quot;hello world&quot;);
149'''
150</code></pre>
151
152<h2>Let&rsquo;s start to code</h2>
153
154<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>
155
156<h4>readConfig.go</h4>
157
158<p>This is the file that reads the <em>blogo.json</em> file to get the blog configuration.</p>
159
160<pre><code class="language-go">package main
161
162import (
163	&quot;encoding/json&quot;
164	&quot;io/ioutil&quot;
165)
166
167//Post is the struct for each post of the blog
168type Post struct {
169	Thumb string `json:&quot;thumb&quot;`
170	Md    string `json:&quot;md&quot;`
171}
172
173//Config gets the config.json file into struct
174type Config struct {
175	Title             string   `json:&quot;title&quot;`
176	IndexTemplate     string   `json:&quot;indexTemplate&quot;`
177	PostThumbTemplate string   `json:&quot;postThumbTemplate&quot;`
178	Posts             []Post   `json:&quot;posts&quot;`
179	CopyRaw           []string `json:&quot;copyRaw&quot;`
180}
181
182var config Config
183
184func readConfig(path string) {
185	file, err := ioutil.ReadFile(path)
186	check(err)
187	content := string(file)
188	json.Unmarshal([]byte(content), &amp;config)
189}
190</code></pre>
191
192<h4>files.go, the operations with files</h4>
193
194<p>We will need some file operation functions, so we have placed all in this file.</p>
195
196<pre><code class="language-go">package main
197
198import (
199	&quot;io/ioutil&quot;
200	&quot;os/exec&quot;
201	&quot;strings&quot;
202
203	&quot;github.com/fatih/color&quot;
204)
205
206func readFile(path string) string {
207	dat, err := ioutil.ReadFile(path)
208	if err != nil {
209		color.Red(path)
210	}
211	check(err)
212	return string(dat)
213}
214
215func writeFile(path string, newContent string) {
216	err := ioutil.WriteFile(path, []byte(newContent), 0644)
217	check(err)
218	color.Green(path)
219}
220
221func getLines(text string) []string {
222	lines := strings.Split(text, &quot;\n&quot;)
223	return lines
224}
225
226func concatStringsWithJumps(lines []string) string {
227	var r string
228	for _, l := range lines {
229		r = r + l + &quot;\n&quot;
230	}
231	return r
232}
233
234func copyRaw(original string, destination string) {
235	color.Green(original + &quot; --&gt; to --&gt; &quot; + destination)
236	_, err := exec.Command(&quot;cp&quot;, &quot;-rf&quot;, original, destination).Output()
237	check(err)
238}
239</code></pre>
240
241<h4>main.go</h4>
242
243<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>
244
245<pre><code class="language-go">package main
246
247import (
248	&quot;fmt&quot;
249	&quot;strings&quot;
250
251	blackfriday &quot;gopkg.in/russross/blackfriday.v2&quot;
252)
253
254const directory = &quot;blogo-input&quot;
255
256func main() {
257	readConfig(directory + &quot;/blogo.json&quot;)
258	fmt.Println(config)
259
260	// generate index page
261	indexTemplate := readFile(directory + &quot;/&quot; + config.IndexTemplate)
262	indexPostTemplate := readFile(directory + &quot;/&quot; + config.PostThumbTemplate)
263	var blogoIndex string
264	blogoIndex = &quot;&quot;
265	for _, post := range config.Posts {
266		mdpostthumb := readFile(directory + &quot;/&quot; + post.Thumb)
267		htmlpostthumb := string(blackfriday.Run([]byte(mdpostthumb)))
268
269		//put the htmlpostthumb in the blogo-index-post-template
270		m := make(map[string]string)
271		m[&quot;[blogo-index-post-template]&quot;] = htmlpostthumb
272		r := putHTMLToTemplate(indexPostTemplate, m)
273		filename := strings.Split(post.Md, &quot;.&quot;)[0]
274		r = &quot;&lt;a href='&quot; + filename + &quot;.html'&gt;&quot; + r + &quot;&lt;/a&gt;&quot;
275		blogoIndex = blogoIndex + r
276	}
277	//put the blogoIndex in the index.html
278	m := make(map[string]string)
279	m[&quot;[blogo-title]&quot;] = config.Title
280	m[&quot;[blogo-content]&quot;] = blogoIndex
281	r := putHTMLToTemplate(indexTemplate, m)
282	writeFile(&quot;index.html&quot;, r)
283
284	// generate posts pages
285	for _, post := range config.Posts {
286		mdcontent := readFile(directory + &quot;/&quot; + post.Md)
287		htmlcontent := string(blackfriday.Run([]byte(mdcontent)))
288
289		m := make(map[string]string)
290		m[&quot;[blogo-title]&quot;] = config.Title
291		m[&quot;[blogo-content]&quot;] = htmlcontent
292
293		r := putHTMLToTemplate(indexTemplate, m)
294		//fmt.Println(r)
295
296		filename := strings.Split(post.Md, &quot;.&quot;)[0]
297		writeFile(filename+&quot;.html&quot;, r)
298	}
299
300	//copy raw
301	fmt.Println(&quot;copying raw:&quot;)
302	for _, dir := range config.CopyRaw {
303		copyRaw(directory+&quot;/&quot;+dir, &quot;.&quot;)
304	}
305}
306
307func putHTMLToTemplate(template string, m map[string]string) string {
308	lines := getLines(template)
309	var resultL []string
310	for _, line := range lines {
311		inserted := false
312		for k, v := range m {
313			if strings.Contains(line, k) {
314				//in the line, change [tag] with the content
315				lineReplaced := strings.Replace(line, k, v, -1)
316				resultL = append(resultL, lineReplaced)
317				inserted = true
318			}
319		}
320		if inserted == false {
321			resultL = append(resultL, line)
322		}
323	}
324	result := concatStringsWithJumps(resultL)
325	return result
326}
327</code></pre>
328
329<h2>Try it</h2>
330
331<p>To try it, we need to compile the Go code:</p>
332
333<pre><code>&gt; go build
334</code></pre>
335
336<p>And run it:</p>
337
338<pre><code>&gt; ./blogo
339</code></pre>
340
341<p>Or we can just build and run to test:</p>
342
343<pre><code>&gt; go run *.go
344</code></pre>
345
346<p>As the output, we will obtain the html pages with the content:</p>
347
348<ul>
349<li>index.html</li>
350</ul>
351
352<pre><code class="language-html">&lt;!DOCTYPE html&gt;
353&lt;html&gt;
354&lt;head&gt;
355    &lt;title&gt;my blog&lt;/title&gt;
356&lt;/head&gt;
357&lt;body&gt;
358    &lt;div class=&quot;row&quot;&gt;
359        &lt;a href='post01.html'&gt;
360            &lt;div class=&quot;col-md-3&quot;&gt;
361                &lt;h1&gt;Post 01 thumb&lt;/h1&gt;
362                &lt;p&gt;This is the description of the Post 01&lt;/p&gt;
363            &lt;/div&gt;
364        &lt;/a&gt;
365        &lt;a href='post02.html'&gt;
366            &lt;div class=&quot;col-md-3&quot;&gt;
367                &lt;p&gt;Post 02 thumb&lt;/p&gt;
368            &lt;/div&gt;
369        &lt;/a&gt;
370    &lt;/div&gt;
371&lt;/body&gt;
372&lt;/html&gt;
373</code></pre>
374
375<ul>
376<li>post01.html</li>
377</ul>
378
379<pre><code class="language-html">&lt;!DOCTYPE html&gt;
380&lt;html&gt;
381&lt;head&gt;
382    &lt;title&gt;my blog&lt;/title&gt;
383&lt;/head&gt;
384&lt;body&gt;
385    &lt;div&gt;
386        &lt;h1&gt;Title of the Post 01&lt;/h1&gt;
387        &lt;p&gt;Hi, this is the content of the post&lt;/p&gt;
388        &lt;pre&gt;
389            &lt;code class=&quot;language-js&quot;&gt;    console.log(&amp;quot;hello world&amp;quot;);
390            &lt;/code&gt;
391        &lt;/pre&gt;
392    &lt;/div&gt;
393&lt;/body&gt;
394&lt;/html&gt;
395</code></pre>
396
397<h2>Conclusion</h2>
398
399<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>
400
401<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>
402
403<p>The complete project code is able in <a href="https://github.com/arnaucube/blogo">https://github.com/arnaucube/blogo</a></p>
404
405    </div>
406
407    <footer style="text-align:center; margin-top:100px;margin-bottom:50px;">
408      <div class="container">
409        <div class="row">
410          <ul class="list-inline">
411            <li><a href="https://twitter.com/arnaucube"
412                   style="color:gray;text-decoration:none;"
413                   target="_blank">twitter.com/arnaucube</a>
414            </li>
415            <li><a href="https://github.com/arnaucube"
416                   style="color:gray;text-decoration:none;"
417                   target="_blank">github.com/arnaucube</a>
418            </li>
419          </ul>
420        </div>
421        <div class="row" style="display:inline-block;">
422          Blog made with <a href="http://github.com/arnaucube/blogo/"
423                            target="_blank" style="color: gray;text-decoration:none;">Blogo</a>
424        </div>
425      </div>
426    </footer>
427
428    <script>
429    </script>
430    <script src="js/external-links.js"></script>
431    <script>hljs.initHighlightingOnLoad();</script>
432    <script defer src="https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/katex.min.js" integrity="sha384-YNHdsYkH6gMx9y3mRkmcJ2mFUjTd0qNQQvY9VYZgQd7DcN7env35GzlmFaZ23JGp" crossorigin="anonymous"></script>
433    <script defer src="https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/contrib/auto-render.min.js" integrity="sha384-vZTG03m+2yp6N6BNi5iM4rW4oIwk5DfcNdFfxkk9ZWpDriOkXX8voJBFrAO7MpVl" crossorigin="anonymous"></script>
434    <script>
435      document.addEventListener("DOMContentLoaded", function() {
436        renderMathInElement(document.body, {
437          displayMode: false,
438          // customised options
439          // • auto-render specific keys, e.g.:
440          delimiters: [
441            {left: '$$', right: '$$', display: true},
442            {left: '$', right: '$', display: false},
443          ],
444          // • rendering keys, e.g.:
445          throwOnError : true
446        });
447      });
448    </script>
449    <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
450
451
452  </body>
453</html>
454