title | separator | verticalSeparator | theme | revealOptions | ||||
---|---|---|---|---|---|---|---|---|
Jelly Shot |
<!--s--> |
<!--v--> |
black |
|
A semi static blog engine
Static markdown blog by @seilund
- Phoenix
- No database
- No static file generation
- Generator
->
markdown|>
struct - Repository
->
Agent storing data - Watcher
->
GenServer for auto update
defp compile_file(file) do
with{:ok, matter, body} <- split_frontmatter(file),
{:ok, html, _} <- Earmark.as_html(body),
do: {:ok, into_post(file, matter, html)}
end
defp into_post(file, meta, html) do
data = %{
slug: file_to_slug(file),
content: html,
} |> Map.merge(meta)
struct(JellyShot.Post, data)
end
def start_link do
Agent.start_link(&get_initial_state/0, name: __MODULE__)
end
posts = File.ls!("priv/posts")
|> Enum.filter(&(Path.extname(&1) == ".md"))
|> Enum.map(&compile_async/1)
|> Enum.map(&Task.await/1)
|> Enum.reduce([], &valid_into_list/2)
|> Enum.sort(&sort/2)
def init(state) do
path = Path.expand("priv/posts")
:fs.start_link(:fs_watcher, path)
:fs.subscribe(:fs_watcher)
{:ok, state}
end
def handle_info({_pid, {:fs, :file_event}, {path, ev}}, _) do
new_state = cond do
Enum.member?(ev, :modified) ->
path
|> JellyShot.Post.file_to_slug
|> JellyShot.Repo.upsert_by_slug
end
end
def index(conn, params) do
{tmpl, headline, {:ok, posts}} = case params do
%{"author" => author} ->
{"list", "by author", Repo.get_by_author(author)}
%{"category" => category} ->
{"list", "by category", Repo.get_by_category(category)}
_ ->
{"index", "recent posts", Repo.list()}
end
render conn, "#{tmpl}.html", head: head, posts: posts
end
~ 250 sloc / file
- 12 posts in 406ms π°
- 384 posts in 3844ms π’
- ... π
We might hit a cap at some point
- Pattern matching
Agents
GenServer
with {:ok}