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.

348 lines
7.9 KiB

6 years ago
  1. # Static blog template engine implementation in Go
  2. *2017-12-26*
  3. 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.
  4. This is how I made [blogo](https://github.com/arnaucube/blogo) the blog static template engine to do this blog.
  5. ### Static blog template engine?
  6. 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.
  7. ## Structure
  8. What we wan to have in the input is:
  9. ```
  10. /blogo
  11. /input
  12. /css
  13. mycss.css
  14. /img
  15. post01_image.png
  16. index.html
  17. postThumbTemplate.html
  18. post01.md
  19. post01thumb.md
  20. post02.md
  21. post02thumb.md
  22. ```
  23. #### blogo.json
  24. This is the file that have the configuration that will be read by the Go script.
  25. ```json
  26. {
  27. "title": "my blog",
  28. "indexTemplate": "index.html",
  29. "postThumbTemplate": "postThumbTemplate.html",
  30. "posts": [
  31. {
  32. "thumb": "post01thumb.md",
  33. "md": "post01.md"
  34. },
  35. {
  36. "thumb": "post02thumb.md",
  37. "md": "post02.md"
  38. }
  39. ],
  40. "copyRaw": [
  41. "css",
  42. "js"
  43. ]
  44. }
  45. ```
  46. The *copyRaw* element, will be all the directories to copy raw to the output.
  47. #### index.html
  48. This is the file that will be used as the template for the main page and also for the posts pages.
  49. ```html
  50. <!DOCTYPE html>
  51. <html>
  52. <head>
  53. <title>[blogo-title]</title>
  54. </head>
  55. <body>
  56. <div>
  57. [blogo-content]
  58. </div>
  59. </body>
  60. </html>
  61. ```
  62. As we can see, we just need to define the html file, and in the title define the *[blogo-title]*, and in the content place the *[blogo-content]*.
  63. #### postThumbTemplate.html
  64. This is the file where is placed the html box for each post that will be displayed in the main page.
  65. ```html
  66. <div class="well">
  67. [blogo-index-post-template]
  68. </div>
  69. ```
  70. #### post01thumb.md
  71. ```markdown
  72. # Post 01 thumb
  73. This is the description of the Post 01
  74. ```
  75. #### post01.md
  76. ```markdown
  77. # Title of the Post 01
  78. Hi, this is the content of the post
  79. '''js
  80. console.log("hello world");
  81. '''
  82. ```
  83. ## Let's start to code
  84. 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.
  85. #### readConfig.go
  86. This is the file that reads the *blogo.json* file to get the blog configuration.
  87. ```go
  88. package main
  89. import (
  90. "encoding/json"
  91. "io/ioutil"
  92. )
  93. //Post is the struct for each post of the blog
  94. type Post struct {
  95. Thumb string `json:"thumb"`
  96. Md string `json:"md"`
  97. }
  98. //Config gets the config.json file into struct
  99. type Config struct {
  100. Title string `json:"title"`
  101. IndexTemplate string `json:"indexTemplate"`
  102. PostThumbTemplate string `json:"postThumbTemplate"`
  103. Posts []Post `json:"posts"`
  104. CopyRaw []string `json:"copyRaw"`
  105. }
  106. var config Config
  107. func readConfig(path string) {
  108. file, err := ioutil.ReadFile(path)
  109. check(err)
  110. content := string(file)
  111. json.Unmarshal([]byte(content), &config)
  112. }
  113. ```
  114. #### files.go, the operations with files
  115. We will need some file operation functions, so we have placed all in this file.
  116. ```go
  117. package main
  118. import (
  119. "io/ioutil"
  120. "os/exec"
  121. "strings"
  122. "github.com/fatih/color"
  123. )
  124. func readFile(path string) string {
  125. dat, err := ioutil.ReadFile(path)
  126. if err != nil {
  127. color.Red(path)
  128. }
  129. check(err)
  130. return string(dat)
  131. }
  132. func writeFile(path string, newContent string) {
  133. err := ioutil.WriteFile(path, []byte(newContent), 0644)
  134. check(err)
  135. color.Green(path)
  136. }
  137. func getLines(text string) []string {
  138. lines := strings.Split(text, "\n")
  139. return lines
  140. }
  141. func concatStringsWithJumps(lines []string) string {
  142. var r string
  143. for _, l := range lines {
  144. r = r + l + "\n"
  145. }
  146. return r
  147. }
  148. func copyRaw(original string, destination string) {
  149. color.Green(original + " --> to --> " + destination)
  150. _, err := exec.Command("cp", "-rf", original, destination).Output()
  151. check(err)
  152. }
  153. ```
  154. #### main.go
  155. To convert the HTML content to Markdown content, we will use https://github.com/russross/blackfriday
  156. ```go
  157. package main
  158. import (
  159. "fmt"
  160. "strings"
  161. blackfriday "gopkg.in/russross/blackfriday.v2"
  162. )
  163. const directory = "blogo-input"
  164. func main() {
  165. readConfig(directory + "/blogo.json")
  166. fmt.Println(config)
  167. // generate index page
  168. indexTemplate := readFile(directory + "/" + config.IndexTemplate)
  169. indexPostTemplate := readFile(directory + "/" + config.PostThumbTemplate)
  170. var blogoIndex string
  171. blogoIndex = ""
  172. for _, post := range config.Posts {
  173. mdpostthumb := readFile(directory + "/" + post.Thumb)
  174. htmlpostthumb := string(blackfriday.Run([]byte(mdpostthumb)))
  175. //put the htmlpostthumb in the blogo-index-post-template
  176. m := make(map[string]string)
  177. m["[blogo-index-post-template]"] = htmlpostthumb
  178. r := putHTMLToTemplate(indexPostTemplate, m)
  179. filename := strings.Split(post.Md, ".")[0]
  180. r = "<a href='" + filename + ".html'>" + r + "</a>"
  181. blogoIndex = blogoIndex + r
  182. }
  183. //put the blogoIndex in the index.html
  184. m := make(map[string]string)
  185. m["[blogo-title]"] = config.Title
  186. m["[blogo-content]"] = blogoIndex
  187. r := putHTMLToTemplate(indexTemplate, m)
  188. writeFile("index.html", r)
  189. // generate posts pages
  190. for _, post := range config.Posts {
  191. mdcontent := readFile(directory + "/" + post.Md)
  192. htmlcontent := string(blackfriday.Run([]byte(mdcontent)))
  193. m := make(map[string]string)
  194. m["[blogo-title]"] = config.Title
  195. m["[blogo-content]"] = htmlcontent
  196. r := putHTMLToTemplate(indexTemplate, m)
  197. //fmt.Println(r)
  198. filename := strings.Split(post.Md, ".")[0]
  199. writeFile(filename+".html", r)
  200. }
  201. //copy raw
  202. fmt.Println("copying raw:")
  203. for _, dir := range config.CopyRaw {
  204. copyRaw(directory+"/"+dir, ".")
  205. }
  206. }
  207. func putHTMLToTemplate(template string, m map[string]string) string {
  208. lines := getLines(template)
  209. var resultL []string
  210. for _, line := range lines {
  211. inserted := false
  212. for k, v := range m {
  213. if strings.Contains(line, k) {
  214. //in the line, change [tag] with the content
  215. lineReplaced := strings.Replace(line, k, v, -1)
  216. resultL = append(resultL, lineReplaced)
  217. inserted = true
  218. }
  219. }
  220. if inserted == false {
  221. resultL = append(resultL, line)
  222. }
  223. }
  224. result := concatStringsWithJumps(resultL)
  225. return result
  226. }
  227. ```
  228. ## Try it
  229. To try it, we need to compile the Go code:
  230. ```
  231. > go build
  232. ```
  233. And run it:
  234. ```
  235. > ./blogo
  236. ```
  237. Or we can just build and run to test:
  238. ```
  239. > go run *.go
  240. ```
  241. As the output, we will obtain the html pages with the content:
  242. - index.html
  243. ```html
  244. <!DOCTYPE html>
  245. <html>
  246. <head>
  247. <title>my blog</title>
  248. </head>
  249. <body>
  250. <div class="row">
  251. <a href='post01.html'>
  252. <div class="col-md-3">
  253. <h1>Post 01 thumb</h1>
  254. <p>This is the description of the Post 01</p>
  255. </div>
  256. </a>
  257. <a href='post02.html'>
  258. <div class="col-md-3">
  259. <p>Post 02 thumb</p>
  260. </div>
  261. </a>
  262. </div>
  263. </body>
  264. </html>
  265. ```
  266. - post01.html
  267. ```html
  268. <!DOCTYPE html>
  269. <html>
  270. <head>
  271. <title>my blog</title>
  272. </head>
  273. <body>
  274. <div>
  275. <h1>Title of the Post 01</h1>
  276. <p>Hi, this is the content of the post</p>
  277. <pre>
  278. <code class="language-js"> console.log(&quot;hello world&quot;);
  279. </code>
  280. </pre>
  281. </div>
  282. </body>
  283. </html>
  284. ```
  285. ## Conclusion
  286. 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.
  287. 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.
  288. The complete project code is able in https://github.com/arnaucube/blogo