aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--README.md81
-rw-r--r--files/base.html15
-rw-r--r--files/config.yaml16
-rw-r--r--files/first.md15
-rw-r--r--files/index.html14
-rw-r--r--files/index.xml21
-rw-r--r--files/post.html14
-rw-r--r--main.go100
9 files changed, 252 insertions, 26 deletions
diff --git a/.gitignore b/.gitignore
index 1be5c1c..eb87809 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
1public/ 1public/
2example/ 2example/
3junk/
4jbmafp \ No newline at end of file
diff --git a/README.md b/README.md
index 395da96..f36c880 100644
--- a/README.md
+++ b/README.md
@@ -9,3 +9,84 @@ This generator is not for people who need something more complicated. Use Hugo
9instead. But if you need a simple blog page that needs to spit out an RSS feed 9instead. But if you need a simple blog page that needs to spit out an RSS feed
10or two and have the option to define different templates for different posts, 10or two and have the option to define different templates for different posts,
11well then this might be useful to you. 11well then this might be useful to you.
12
13The only thing hard about this project is the spelling of its name.
14
15Some facts (will be more clear when you read the whole readme):
16
17- You cannot nest your markdown file under `content` folder. All files must be
18 in the root of `content` folder.
19- `public` folder gets automatically created on `jbmafp --build`.
20- All files in `static` folder will be moved to the root of `public` folder.
21- When you provide `url` in your markdown files, this will create these files in
22 the root of `public` folder. No nesting allowed.
23
24## Install
25
26```sh
27git clone git@github.com:mitjafelicijan/jbmafp.git
28cd jbmafp
29go install .
30```
31
32## Generate first site
33
34- Go to your projects folder or wherever you want to place the site.
35
36```sh
37mkdir my-ugly-website
38cd my-ugly-website
39jbmafp --init
40jbmafp --build
41```
42
43- Check out `public` folder and you will see a website. That is about it.
44- You can also do `jbmafp --help` to see all the option.
45
46## Understanding all this bullshit
47
48- Posts go into `content` folder.
49- You can change them in `templates` folder.
50- Each post must have. All of the fields are required. If you have ever used
51 Hugo, this is the same thing. Below is example `content/first.md`.
52
53```md
54---
55title: "My first post"
56url: first.html
57date: 2023-06-29T14:51:39+02:00
58type: post
59draft: false
60---
61
62This is my first post. It ain't much but it's an honest post.
63```
64
65- `type` is used all over the place. It is used to define a template file of the
66 page that will be generated. If type is `post` then the program will load
67 `templates/post.html` to handle generation of the page.
68- You can use whatever name you want. I use `note`, `post` as types to separate
69 all the pages into categories.
70- `type` is also used inside templates like:
71 ```html
72 <ul>
73 {{ range .Pages }}
74 {{ if eq .Type "post" }}
75 <li><a href="/{{ .RelPermalink }}">{{ .Title }}</a></li>
76 {{ end }}
77 {{ end }}
78 </ul>
79 ```
80- This is also use for generating RSS feed. Check `templates/index.xml` to see
81 the example.
82- This opens door to quite versatile build option.
83- You can trigger additional generation of content under `extras` field in
84 `config.yaml` file. RSS feed gets generated this way. `template` field tells
85 generator which file in `templates` folder to use and `url` tells generator
86 what the file should be called when its saved.
87
88## License
89
90[jbmafp](https://github.com/mitjafelicijan/jbmafp) was written by [Mitja
91Felicijan](https://mitjafelicijan.com) and is released under the BSD two-clause
92license, see the LICENSE file for more information.
diff --git a/files/base.html b/files/base.html
new file mode 100644
index 0000000..d965c25
--- /dev/null
+++ b/files/base.html
@@ -0,0 +1,15 @@
1<!DOCTYPE html>
2<html lang="{{ .Config.Language }}">
3 <head>
4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width,initial-scale=1">
6 <title>{{ block "title" . }}{{ .Config.Title }}{{ end }}</title>
7 <meta name="description" content="{{ block "description" . }}{{ .Config.Description }}{{ end }}">
8 <link rel="alternate" type="application/rss+xml" href="{{ .Config.BaseURL }}/index.xml">
9 </head>
10 <body>
11 <main>
12 {{ block "content" . }}{{ end }}
13 </main>
14 </body>
15</html>
diff --git a/files/config.yaml b/files/config.yaml
new file mode 100644
index 0000000..a89795f
--- /dev/null
+++ b/files/config.yaml
@@ -0,0 +1,16 @@
1title: "Title of your website"
2baseurl: "https://example.com"
3description: "My new shiny website"
4language: "en-us"
5
6# Code highlighting.
7# https://swapoff.org/chroma/playground/
8highlighting: "vs"
9
10# Minifies output HTML (including inline CSS, JS).
11minify: true
12
13# Other generaters, in this case RSS generator.
14extras:
15 - template: index.xml
16 url: index.xml
diff --git a/files/first.md b/files/first.md
new file mode 100644
index 0000000..9a4b97f
--- /dev/null
+++ b/files/first.md
@@ -0,0 +1,15 @@
1---
2title: "My first post"
3url: first.html
4date: 2023-06-29T14:51:39+02:00
5type: post
6draft: false
7---
8
9This is my first post. It ain't much but it's an honest post.
10
11```lua
12for k, v in pairs(arr) do
13 print(k, v[1], v[2], v[3])
14end
15```
diff --git a/files/index.html b/files/index.html
new file mode 100644
index 0000000..eeb2641
--- /dev/null
+++ b/files/index.html
@@ -0,0 +1,14 @@
1{{ template "base.html" . }}
2
3{{ define "content" }}
4<div>
5 <h2>Posts</h2>
6 <ul>
7 {{ range .Pages }}
8 {{ if eq .Type "post" }}
9 <li><a href="/{{ .RelPermalink }}">{{ .Title }}</a></li>
10 {{ end }}
11 {{ end }}
12 </ul>
13</div>
14{{ end }}
diff --git a/files/index.xml b/files/index.xml
new file mode 100644
index 0000000..830dd90
--- /dev/null
+++ b/files/index.xml
@@ -0,0 +1,21 @@
1<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
2 <channel>
3 <title>{{ .Config.Title }}'s posts</title>
4 <link>{{ .Config.BaseURL }}</link>
5 <description>{{ .Config.Description }}</description>
6 <language>{{ .Config.Language }}</language>
7
8 {{ range $idx, $page := .Pages }}
9 {{ if eq $page.Type "post" }}
10 <item>
11 <title>{{ $page.Title }}</title>
12 <link>{{ $.Config.BaseURL }}/{{ $page.RelPermalink }}</link>
13 <pubDate>{{ $page.Created.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</pubDate>
14 <guid>{{ $.Config.BaseURL }}/{{ $page.RelPermalink }}</guid>
15 <description>{{ $page.Summary }}</description>
16 <content:encoded>{{ $page.Raw }}</content:encoded>
17 </item>
18 {{ end }}
19 {{ end }}
20 </channel>
21</rss>
diff --git a/files/post.html b/files/post.html
new file mode 100644
index 0000000..28ffad2
--- /dev/null
+++ b/files/post.html
@@ -0,0 +1,14 @@
1{{ template "base.html" . }}
2
3{{ define "title" }}{{ .Page.Title }}{{ end }}
4{{ define "description" }}{{ .Page.Summary }}{{ end }}
5
6{{ define "content" }}
7<div>
8 <h1>{{ .Page.Title }}</h1>
9 <p>{{ .Page.Created.Format "Jan 2, 2006" }}</p>
10 <div>
11 {{ .Page.HTML }}
12 </div>
13</div>
14{{ end }}
diff --git a/main.go b/main.go
index 0fdc2da..226e3d6 100644
--- a/main.go
+++ b/main.go
@@ -32,6 +32,8 @@ import (
32 highlighting "github.com/yuin/goldmark-highlighting/v2" 32 highlighting "github.com/yuin/goldmark-highlighting/v2"
33 33
34 cp "github.com/otiai10/copy" 34 cp "github.com/otiai10/copy"
35
36 _ "embed"
35) 37)
36 38
37type ConfigExtrasItem struct { 39type ConfigExtrasItem struct {
@@ -64,6 +66,24 @@ type Page struct {
64 Draft bool 66 Draft bool
65} 67}
66 68
69//go:embed "files/config.yaml"
70var EmbedConfig string
71
72//go:embed "files/first.md"
73var EmbedPost string
74
75//go:embed "files/base.html"
76var EmbedTemplateBase string
77
78//go:embed "files/index.html"
79var EmbedTemplateIndex string
80
81//go:embed "files/post.html"
82var EmbedTemplatePost string
83
84//go:embed "files/index.xml"
85var EmbedTemplateFeed string
86
67// Function to clean HTML tags using bluemonday. 87// Function to clean HTML tags using bluemonday.
68func cleanHTMLTags(htmlString string) string { 88func cleanHTMLTags(htmlString string) string {
69 p := bluemonday.StrictPolicy() 89 p := bluemonday.StrictPolicy()
@@ -72,38 +92,36 @@ func cleanHTMLTags(htmlString string) string {
72} 92}
73 93
74func initializeProject(projectRoot string) { 94func initializeProject(projectRoot string) {
75 fmt.Println("Initializing new project") 95 log.Println("Initializing new project")
76}
77 96
78func main() { 97 if err := os.Mkdir(path.Join(projectRoot, "templates"), 0755); err != nil && !os.IsExist(err) {
79 projectRoot := os.Getenv("PROJECT_ROOT") 98 log.Println("Error creating directory:", err)
80 if projectRoot == "" { 99 return
81 projectRoot = "./"
82 } 100 }
83 101
84 fmt.Println("Come back later! Still WIP!") 102 if err := os.Mkdir(path.Join(projectRoot, "content"), 0755); err != nil && !os.IsExist(err) {
85 os.Exit(0) 103 log.Println("Error creating directory:", err)
86 104 return
87 // Parsing arguments.
88 var args struct {
89 Init bool `arg:"-i,--init" help:"initialize new project"`
90 Build bool `arg:"-b,--build" help:"build the website"`
91 } 105 }
92 106
93 arg.MustParse(&args) 107 if err := os.Mkdir(path.Join(projectRoot, "static"), 0755); err != nil && !os.IsExist(err) {
94 108 log.Println("Error creating directory:", err)
95 if !args.Init && !args.Build { 109 return
96 fmt.Println("No arguments provided. Try using `jbmafp --help`")
97 os.Exit(0)
98 } 110 }
99 111
100 if args.Init { 112 os.WriteFile(path.Join(projectRoot, "templates", ".gitkeep"), []byte{}, 0755)
101 initializeProject(projectRoot) 113 os.WriteFile(path.Join(projectRoot, "content", ".gitkeep"), []byte{}, 0755)
102 os.Exit(0) 114 os.WriteFile(path.Join(projectRoot, "static", ".gitkeep"), []byte{}, 0755)
103 }
104 115
105 os.Exit(0) 116 os.WriteFile(path.Join(projectRoot, "config.yaml"), []byte(EmbedConfig), 0755)
117 os.WriteFile(path.Join(projectRoot, "content", "first.md"), []byte(EmbedPost), 0755)
118 os.WriteFile(path.Join(projectRoot, "templates", "base.html"), []byte(EmbedTemplateBase), 0755)
119 os.WriteFile(path.Join(projectRoot, "templates", "index.html"), []byte(EmbedTemplateIndex), 0755)
120 os.WriteFile(path.Join(projectRoot, "templates", "post.html"), []byte(EmbedTemplatePost), 0755)
121 os.WriteFile(path.Join(projectRoot, "templates", "index.xml"), []byte(EmbedTemplateFeed), 0755)
122}
106 123
124func buildProject(projectRoot string) {
107 // Read config file. 125 // Read config file.
108 configFilepath := path.Join(projectRoot, "config.yaml") 126 configFilepath := path.Join(projectRoot, "config.yaml")
109 configFile, err := os.ReadFile(configFilepath) 127 configFile, err := os.ReadFile(configFilepath)
@@ -195,6 +213,12 @@ func main() {
195 return pages[i].Created.After(pages[j].Created) 213 return pages[i].Created.After(pages[j].Created)
196 }) 214 })
197 215
216 // Creates public folder if it doesn't exist yet.
217 if err := os.Mkdir(path.Join(projectRoot, "public"), 0755); err != nil && !os.IsExist(err) {
218 log.Println("Error creating directory:", err)
219 return
220 }
221
198 // Generate HTML files for all pages. 222 // Generate HTML files for all pages.
199 for _, page := range pages { 223 for _, page := range pages {
200 outFilepath := path.Join(projectRoot, "public", page.Meta["url"].(string)) 224 outFilepath := path.Join(projectRoot, "public", page.Meta["url"].(string))
@@ -237,9 +261,7 @@ func main() {
237 log.Println("Wrote", outFilepath) 261 log.Println("Wrote", outFilepath)
238 } else { 262 } else {
239 log.Println("Skipped", outFilepath) 263 log.Println("Skipped", outFilepath)
240
241 } 264 }
242
243 } 265 }
244 266
245 // Generates index page. 267 // Generates index page.
@@ -317,10 +339,36 @@ func main() {
317 339
318 outFilepath := path.Join(projectRoot, "public", extra.URL) 340 outFilepath := path.Join(projectRoot, "public", extra.URL)
319 os.WriteFile(outFilepath, []byte(buf.String()), 0755) 341 os.WriteFile(outFilepath, []byte(buf.String()), 0755)
320
321 } 342 }
322 } 343 }
323 344
324 // Guess we are done! 345 // Guess we are done!
325 log.Println("Done & done...") 346 log.Println("Done & done...")
326} 347}
348
349func main() {
350 projectRoot := os.Getenv("PROJECT_ROOT")
351 if projectRoot == "" {
352 projectRoot = "./"
353 }
354
355 var args struct {
356 Init bool `arg:"-i,--init" help:"initialize new project"`
357 Build bool `arg:"-b,--build" help:"build the website"`
358 }
359
360 arg.MustParse(&args)
361
362 if !args.Init && !args.Build {
363 fmt.Println("No arguments provided. Try using `jbmafp --help`")
364 os.Exit(0)
365 }
366
367 if args.Init {
368 initializeProject(projectRoot)
369 }
370
371 if args.Build {
372 buildProject(projectRoot)
373 }
374}