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