You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

516 lines
16 KiB

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