💻 My personal website blog.kacaii.dev/
blog gleam lustre

:truck: extract metadata handling to its own module

+68 -57
+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 - let assert Ok(post) = post.parse(path: file_path) as "Parse post" 25 + let assert Ok(post) = post.from_string(path: file_path) as "Parse post" 26 26 27 - let new_file_name = post.to_kebab_case(post) <> ".md" 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 - |> post.parse 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 - case list.find(ctx.posts, fn(post) { post.to_kebab_case(post) == post_uri }) { 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 - html.a([attr.href("/posts/" <> post.to_kebab_case(post))], [ 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 - let href = attr.href("/posts/" <> post.to_kebab_case(post)) 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 + import blog/post/metadata 1 2 import frontmatter 2 3 import gleam/int 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 - import tom 12 11 13 12 pub opaque type PostError { 14 13 MissingFrontmatter 15 14 ReadError(simplifile.FileError) 16 - WrongMetadataField(field: String) 17 - MetadataError(tom.ParseError) 15 + Metadata(metadata.MetadataError) 18 16 } 19 17 20 18 pub type Post { 21 - Post(meta: Metadata, body: jot.Document) 19 + Post(meta: metadata.Metadata, body: jot.Document) 22 20 } 23 21 24 - pub type Metadata { 25 - Metadata( 26 - title: String, 27 - description: String, 28 - date: calendar.Date, 29 - tags: List(String), 30 - ) 31 - } 32 - 33 - pub fn to_kebab_case(post: Post) -> String { 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 - pub fn parse(path file: String) -> Result(Post, PostError) { 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 - use meta <- result.map(parse_metadata(fm)) 46 + use meta <- result.map( 47 + metadata.from_string(fm) 48 + |> result.map_error(Metadata), 49 + ) 50 + 58 51 Post(meta:, body: jot.parse(content)) 59 52 } 60 53 } 61 - } 62 - 63 - fn parse_metadata(metadata: String) -> Result(Metadata, PostError) { 64 - use toml <- result.try( 65 - tom.parse(metadata) 66 - |> result.map_error(MetadataError), 67 - ) 68 - 69 - use title <- toml_field(toml, tom.get_string, "title") 70 - use description <- toml_field(toml, tom.get_string, "description") 71 - use date <- toml_field(toml, tom.get_date, "date") 72 - use toml_tags <- toml_field(toml, tom.get_array, "tags") 73 - use tags <- result.map(parse_tags(toml_tags)) 74 - 75 - Metadata(title:, description:, date:, tags:) 76 - } 77 - 78 - fn parse_tags(tags: List(tom.Toml)) -> Result(List(String), PostError) { 79 - use tag <- list.try_map(tags) 80 - case tag { 81 - tom.String(str) -> Ok(str) 82 - _ -> Error(WrongMetadataField("tags")) 83 - } 84 - } 85 - 86 - fn toml_field( 87 - toml: toml_doc, 88 - get: fn(toml_doc, List(String)) -> Result(value, err), 89 - field: String, 90 - then: fn(value) -> Result(a, PostError), 91 - ) -> Result(a, PostError) { 92 - get(toml, [field]) 93 - |> result.replace_error(WrongMetadataField(field)) 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 + import gleam/list 2 + import gleam/result 3 + import gleam/time/calendar 4 + import tom 5 + 6 + pub type MetadataError { 7 + WrongMetadataField(field: String) 8 + MetadataError(tom.ParseError) 9 + } 10 + 11 + pub type Metadata { 12 + Metadata( 13 + title: String, 14 + description: String, 15 + date: calendar.Date, 16 + tags: List(String), 17 + ) 18 + } 19 + 20 + pub fn from_string(metadata: String) -> Result(Metadata, MetadataError) { 21 + use toml <- result.try( 22 + tom.parse(metadata) 23 + |> result.map_error(MetadataError), 24 + ) 25 + 26 + use title <- toml_field(toml, tom.get_string, "title") 27 + use description <- toml_field(toml, tom.get_string, "description") 28 + use date <- toml_field(toml, tom.get_date, "date") 29 + use toml_tags <- toml_field(toml, tom.get_array, "tags") 30 + use tags <- result.map(parse_tags(toml_tags)) 31 + 32 + Metadata(title:, description:, date:, tags:) 33 + } 34 + 35 + fn parse_tags(tags: List(tom.Toml)) -> Result(List(String), MetadataError) { 36 + use tag <- list.try_map(tags) 37 + case tag { 38 + tom.String(str) -> Ok(str) 39 + _ -> Error(WrongMetadataField("tags")) 40 + } 41 + } 42 + 43 + fn toml_field( 44 + toml: toml_doc, 45 + get: fn(toml_doc, List(String)) -> Result(value, err), 46 + field: String, 47 + then: fn(value) -> Result(a, MetadataError), 48 + ) -> Result(a, MetadataError) { 49 + get(toml, [field]) 50 + |> result.replace_error(WrongMetadataField(field)) 51 + |> result.try(then) 52 + }