As my first blog post, I thought it would be fun to share the code for this blog as my Hello World of sorts. The structure of this is fairly simple, I store blogs as text files, then parse out the sections that I need to build HTML. I'm using OCaml + the Ocsigen web framework.
First, let's define a few types to make this easy.
type body_content =
| Text of string
| Code of string
type blog_post = {
title : string;
body : body_content list
}
Blog posts are written and stored as a single text file, with the fist line of the file being the title, and the remainer being the body, which will be broken up into text and code sections, marked with code tags. All files are stored with a .blog extension, and are in a single directory. I think this makes the process of writing blogs with embedded code very simple, as they can be created with any text editor and will be available to the blog once they are stored in the proper directory.
Pulling the files from the directory is easy, first the directory contents that end in .blog are read in, next the Title is parsed, and finally the remainer of the file is parsed into either Code or Text.
let dir_contents dir =
let rec loop result = function
| f::fs when Sys.is_directory f ->
Sys.readdir f
|> Array.to_list
|> List.map (Filename.concat f)
|> List.append fs
|> loop result
| f::fs -> loop (f::result) fs
| [] -> result
in
loop [] [dir]
let get_blog_files () =
dir_contents "posts"
|> List.filter (fun s -> Str.split_delim (Str.regexp "[.]") s |> List.mem "blog")
let rec read_lines ic =
try
let new_line = input_line ic ^ "\n" in
new_line ^ (read_lines ic)
with
End_of_file ->
close_in ic;
""
let rec body_content_of_text ?(acc = []) l =
match l with
| [] -> List.rev acc
| (Str.Text t) :: tl -> body_content_of_text ~acc:(Text t :: acc) tl
| Str.Delim _ :: Str.Text t :: Str.Delim _ :: tl -> body_content_of_text ~acc:(Code t :: acc) tl
| _ -> []
let blog_body_of_text text =
Str.full_split (Str.regexp "<code>\n") text
|> body_content_of_text
let blog_of_file file =
let ic = open_in file in
let title = input_line ic in
let body = blog_body_of_text (read_lines ic) in
{title = title; body = body}
let get_blogs () =
get_blog_files ()
|> List.map blog_of_file
After that, we just have to parse the blogs into html and send to the client with Ocsigen.
{shared{
open Eliom_lib
open Eliom_content
open Html5.D
open BlogUtils
}}
module ThomasBrittain_app =
Eliom_registration.App (
struct
let application_name = "ThomasBrittain"
end)
let main_service =
Eliom_service.App.service ~path:[] ~get_params:Eliom_parameter.unit ()
let build_html = function
| Text t -> div ~a:[a_class ["blog_text"]] [pcdata t]
| Code c -> div ~a:[a_class ["blog_code"]] [pcdata c]
let html_blog_body (blog_text : body_content list) =
List.map (build_html) blog_text
let html_of_blog_post (blog_post : blog_post) =
div ~a:[a_class ["blog_post"]]
[div ~a:[a_class ["blog_title"]] [pcdata blog_post.title];
div ~a:[a_class ["blog_body"]] (html_blog_body blog_post.body)
]
let html_of_blog_posts () =
List.map (html_of_blog_post) (get_blogs ())
let () =
ThomasBrittain_app.register
~service:main_service
(fun () () ->
Lwt.return
(Eliom_tools.F.html
~title:"Thomas Brittain"
~css:[["css";"ThomasBrittain.css"]]
~other_head:[syntax_cdn_link; syntax_js_link]
Html5.F.(body (html_of_blog_posts ())
)
)
Blogception.