tangled
alpha
login
or
join now
kacaii.dev
/
blog
0
fork
atom
💻 My personal website
blog.kacaii.dev/
blog
gleam
lustre
0
fork
atom
overview
issues
pulls
pipelines
:truck: extract metadata handling to its own module
kacaii.dev
2 months ago
fb6426cf
39f8c636
0/1
fly.yml
failed
30s
+68
-57
7 changed files
expand all
collapse all
unified
split
dev
blog_dev.gleam
src
blog
page
content.gleam
posts.gleam
recent_posts.gleam
post
metadata.gleam
post.gleam
blog.gleam
+2
-2
dev/blog_dev.gleam
···
22
22
23
23
use file_name <- list.each(entries)
24
24
let file_path = path.join(posts_path, file_name)
25
25
-
let assert Ok(post) = post.parse(path: file_path) as "Parse post"
25
25
+
let assert Ok(post) = post.from_string(path: file_path) as "Parse post"
26
26
27
27
-
let new_file_name = post.to_kebab_case(post) <> ".md"
27
27
+
let new_file_name = post.slug(post) <> ".md"
28
28
let new_file_path =
29
29
string.replace(in: file_path, each: file_name, with: new_file_name)
30
30
+1
-1
src/blog.gleam
···
31
31
32
32
posts_path
33
33
|> path.join(file_name)
34
34
-
|> post.parse
34
34
+
|> post.from_string
35
35
})
36
36
37
37
list.sort(posts, post.compare)
+1
-1
src/blog/page/content.gleam
···
13
13
import wisp
14
14
15
15
pub fn handle_request(ctx: web.Context, post_uri: String) -> wisp.Response {
16
16
-
case list.find(ctx.posts, fn(post) { post.to_kebab_case(post) == post_uri }) {
16
16
+
case list.find(ctx.posts, fn(post) { post.slug(post) == post_uri }) {
17
17
Error(_) -> wisp.not_found()
18
18
Ok(post) -> {
19
19
let title = post.meta.title
+1
-1
src/blog/page/posts.gleam
···
46
46
class("flex-col p-4 mx-auto w-full rounded-lg shadow-sm bg-ctp-mantle")
47
47
48
48
html.li([li_styles], [
49
49
-
html.a([attr.href("/posts/" <> post.to_kebab_case(post))], [
49
49
+
html.a([attr.href("/posts/" <> post.slug(post))], [
50
50
html.h2([class("text-2xl font-bold text-pretty")], [
51
51
html.text(meta.title),
52
52
]),
+1
-1
src/blog/page/recent_posts.gleam
···
39
39
string.join([day, month, year], with: " ")
40
40
}
41
41
42
42
-
let href = attr.href("/posts/" <> post.to_kebab_case(post))
42
42
+
let href = attr.href("/posts/" <> post.slug(post))
43
43
let style = class("flex flex-col p-4 mx-auto w-full rounded-lg bg-ctp-mantle")
44
44
45
45
html.li([style], [
+10
-51
src/blog/post.gleam
···
1
1
+
import blog/post/metadata
1
2
import frontmatter
2
3
import gleam/int
3
3
-
import gleam/list
4
4
import gleam/option
5
5
import gleam/order
6
6
import gleam/result
···
8
8
import gleam/time/calendar
9
9
import jot
10
10
import simplifile
11
11
-
import tom
12
11
13
12
pub opaque type PostError {
14
13
MissingFrontmatter
15
14
ReadError(simplifile.FileError)
16
16
-
WrongMetadataField(field: String)
17
17
-
MetadataError(tom.ParseError)
15
15
+
Metadata(metadata.MetadataError)
18
16
}
19
17
20
18
pub type Post {
21
21
-
Post(meta: Metadata, body: jot.Document)
19
19
+
Post(meta: metadata.Metadata, body: jot.Document)
22
20
}
23
21
24
24
-
pub type Metadata {
25
25
-
Metadata(
26
26
-
title: String,
27
27
-
description: String,
28
28
-
date: calendar.Date,
29
29
-
tags: List(String),
30
30
-
)
31
31
-
}
32
32
-
33
33
-
pub fn to_kebab_case(post: Post) -> String {
22
22
+
pub fn slug(post: Post) -> String {
34
23
string.lowercase(post.meta.title)
35
24
|> string.replace(" ", "-")
36
25
|> string.replace("_", "-")
···
43
32
|> string.replace("/", "")
44
33
}
45
34
46
46
-
pub fn parse(path file: String) -> Result(Post, PostError) {
35
35
+
pub fn from_string(path file: String) -> Result(Post, PostError) {
47
36
use raw <- result.try(
48
37
simplifile.read(file)
49
38
|> result.map_error(ReadError),
···
54
43
Error(MissingFrontmatter)
55
44
56
45
frontmatter.Extracted(frontmatter: option.Some(fm), content:) -> {
57
57
-
use meta <- result.map(parse_metadata(fm))
46
46
+
use meta <- result.map(
47
47
+
metadata.from_string(fm)
48
48
+
|> result.map_error(Metadata),
49
49
+
)
50
50
+
58
51
Post(meta:, body: jot.parse(content))
59
52
}
60
53
}
61
61
-
}
62
62
-
63
63
-
fn parse_metadata(metadata: String) -> Result(Metadata, PostError) {
64
64
-
use toml <- result.try(
65
65
-
tom.parse(metadata)
66
66
-
|> result.map_error(MetadataError),
67
67
-
)
68
68
-
69
69
-
use title <- toml_field(toml, tom.get_string, "title")
70
70
-
use description <- toml_field(toml, tom.get_string, "description")
71
71
-
use date <- toml_field(toml, tom.get_date, "date")
72
72
-
use toml_tags <- toml_field(toml, tom.get_array, "tags")
73
73
-
use tags <- result.map(parse_tags(toml_tags))
74
74
-
75
75
-
Metadata(title:, description:, date:, tags:)
76
76
-
}
77
77
-
78
78
-
fn parse_tags(tags: List(tom.Toml)) -> Result(List(String), PostError) {
79
79
-
use tag <- list.try_map(tags)
80
80
-
case tag {
81
81
-
tom.String(str) -> Ok(str)
82
82
-
_ -> Error(WrongMetadataField("tags"))
83
83
-
}
84
84
-
}
85
85
-
86
86
-
fn toml_field(
87
87
-
toml: toml_doc,
88
88
-
get: fn(toml_doc, List(String)) -> Result(value, err),
89
89
-
field: String,
90
90
-
then: fn(value) -> Result(a, PostError),
91
91
-
) -> Result(a, PostError) {
92
92
-
get(toml, [field])
93
93
-
|> result.replace_error(WrongMetadataField(field))
94
94
-
|> result.try(then)
95
54
}
96
55
97
56
pub fn compare(a: Post, b: Post) -> order.Order {
+52
src/blog/post/metadata.gleam
···
1
1
+
import gleam/list
2
2
+
import gleam/result
3
3
+
import gleam/time/calendar
4
4
+
import tom
5
5
+
6
6
+
pub type MetadataError {
7
7
+
WrongMetadataField(field: String)
8
8
+
MetadataError(tom.ParseError)
9
9
+
}
10
10
+
11
11
+
pub type Metadata {
12
12
+
Metadata(
13
13
+
title: String,
14
14
+
description: String,
15
15
+
date: calendar.Date,
16
16
+
tags: List(String),
17
17
+
)
18
18
+
}
19
19
+
20
20
+
pub fn from_string(metadata: String) -> Result(Metadata, MetadataError) {
21
21
+
use toml <- result.try(
22
22
+
tom.parse(metadata)
23
23
+
|> result.map_error(MetadataError),
24
24
+
)
25
25
+
26
26
+
use title <- toml_field(toml, tom.get_string, "title")
27
27
+
use description <- toml_field(toml, tom.get_string, "description")
28
28
+
use date <- toml_field(toml, tom.get_date, "date")
29
29
+
use toml_tags <- toml_field(toml, tom.get_array, "tags")
30
30
+
use tags <- result.map(parse_tags(toml_tags))
31
31
+
32
32
+
Metadata(title:, description:, date:, tags:)
33
33
+
}
34
34
+
35
35
+
fn parse_tags(tags: List(tom.Toml)) -> Result(List(String), MetadataError) {
36
36
+
use tag <- list.try_map(tags)
37
37
+
case tag {
38
38
+
tom.String(str) -> Ok(str)
39
39
+
_ -> Error(WrongMetadataField("tags"))
40
40
+
}
41
41
+
}
42
42
+
43
43
+
fn toml_field(
44
44
+
toml: toml_doc,
45
45
+
get: fn(toml_doc, List(String)) -> Result(value, err),
46
46
+
field: String,
47
47
+
then: fn(value) -> Result(a, MetadataError),
48
48
+
) -> Result(a, MetadataError) {
49
49
+
get(toml, [field])
50
50
+
|> result.replace_error(WrongMetadataField(field))
51
51
+
|> result.try(then)
52
52
+
}