First version, whatever that means

Author Mitja Felicijan <m@mitjafelicijan.com> 2023-07-08 21:26:40 +0200
Committer Mitja Felicijan <m@mitjafelicijan.com> 2023-07-08 21:26:40 +0200
Commit d11b6ebe944b13e504608094424217b5d6b39a53 (patch)
-rw-r--r-- .gitignore 2
-rw-r--r-- README.md 81
-rw-r--r-- files/base.html 15
-rw-r--r-- files/config.yaml 16
-rw-r--r-- files/first.md 15
-rw-r--r-- files/index.html 14
-rw-r--r-- files/index.xml 21
-rw-r--r-- files/post.html 14
-rw-r--r-- main.go 100
9 files changed, 252 insertions, 26 deletions
diff --git a/.gitignore b/.gitignore
1
public/
1
public/
2
example/
2
example/
  
3
junk/
  
4
jbmafp
diff --git a/README.md b/README.md
...
9
instead. But if you need a simple blog page that needs to spit out an RSS feed
9
instead. But if you need a simple blog page that needs to spit out an RSS feed
10
or two and have the option to define different templates for different posts,
10
or two and have the option to define different templates for different posts,
11
well then this might be useful to you.
11
well then this might be useful to you.
  
12
  
  
13
The only thing hard about this project is the spelling of its name.
  
14
  
  
15
Some 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
  
27
git clone git@github.com:mitjafelicijan/jbmafp.git
  
28
cd jbmafp
  
29
go 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
  
37
mkdir my-ugly-website
  
38
cd my-ugly-website
  
39
jbmafp --init
  
40
jbmafp --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
---
  
55
title: "My first post"
  
56
url: first.html
  
57
date: 2023-06-29T14:51:39+02:00
  
58
type: post
  
59
draft: false
  
60
---
  
61
  
  
62
This 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
  
91
Felicijan](https://mitjafelicijan.com) and is released under the BSD two-clause
  
92
license, see the LICENSE file for more information.
diff --git a/files/base.html b/files/base.html
  
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
  
1
title: "Title of your website"
  
2
baseurl: "https://example.com"
  
3
description: "My new shiny website"
  
4
language: "en-us"
  
5
  
  
6
# Code highlighting.
  
7
# https://swapoff.org/chroma/playground/
  
8
highlighting: "vs"
  
9
  
  
10
# Minifies output HTML (including inline CSS, JS).
  
11
minify: true
  
12
  
  
13
# Other generaters, in this case RSS generator.
  
14
extras:
  
15
  - template: index.xml
  
16
    url: index.xml
diff --git a/files/first.md b/files/first.md
  
1
---
  
2
title: "My first post"
  
3
url: first.html
  
4
date: 2023-06-29T14:51:39+02:00
  
5
type: post
  
6
draft: false
  
7
---
  
8
  
  
9
This is my first post. It ain't much but it's an honest post.
  
10
  
  
11
```lua
  
12
for k, v in pairs(arr) do
  
13
  print(k, v[1], v[2], v[3])
  
14
end
  
15
```
diff --git a/files/index.html b/files/index.html
  
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
  
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
  
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
...
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
  
37
type ConfigExtrasItem struct {
39
type ConfigExtrasItem struct {
...
64
	Draft        bool
66
	Draft        bool
65
}
67
}
66
  
68
  
  
69
//go:embed "files/config.yaml"
  
70
var EmbedConfig string
  
71
  
  
72
//go:embed "files/first.md"
  
73
var EmbedPost string
  
74
  
  
75
//go:embed "files/base.html"
  
76
var EmbedTemplateBase string
  
77
  
  
78
//go:embed "files/index.html"
  
79
var EmbedTemplateIndex string
  
80
  
  
81
//go:embed "files/post.html"
  
82
var EmbedTemplatePost string
  
83
  
  
84
//go:embed "files/index.xml"
  
85
var EmbedTemplateFeed string
  
86
  
67
// Function to clean HTML tags using bluemonday.
87
// Function to clean HTML tags using bluemonday.
68
func cleanHTMLTags(htmlString string) string {
88
func cleanHTMLTags(htmlString string) string {
69
	p := bluemonday.StrictPolicy()
89
	p := bluemonday.StrictPolicy()
...
72
}
92
}
73
  
93
  
74
func initializeProject(projectRoot string) {
94
func initializeProject(projectRoot string) {
75
	fmt.Println("Initializing new project")
95
	log.Println("Initializing new project")
76
}
  
77
  
96
  
78
func 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
  
  
124
func 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
		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
			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
  
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
  
  
349
func 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
}