incorporating content and refining post parsing

This commit is contained in:
Adam Piontek 2021-04-01 13:30:11 -04:00
parent f6c316d4fa
commit b1b9c09a79
6 changed files with 118 additions and 107 deletions

View file

@ -4,10 +4,10 @@ defmodule Home73k.Blog do
Application.ensure_all_started(:earmark)
@content_path Application.compile_env(:home73k, [:content_repo, :path], "./priv/content")
|> Path.expand()
posts_paths =
@content_path
|> Path.expand()
|> Path.join("**/*.md")
|> Path.wildcard()
@ -17,13 +17,13 @@ defmodule Home73k.Blog do
Post.parse!(post_path)
end
# @posts posts
@posts Enum.sort_by(posts, & &1.date, {:desc, NaiveDateTime})
@posts posts
# @posts Enum.sort_by(posts, & &1.date, {:desc, NaiveDateTime})
@tags posts |> Stream.flat_map(& &1.tags) |> Stream.uniq() |> Enum.sort()
# @tags posts |> Stream.flat_map(& &1.tags) |> Stream.uniq() |> Enum.sort()
def list_posts, do: @posts
def list_tags, do: @tags
# def list_tags, do: @tags
# defmodule NotFoundError do
# defexception [:message, plug_status: 404]

View file

@ -1,71 +1,135 @@
defmodule Home73k.Blog.Post do
@enforce_keys [:title, :slug, :date, :author, :tags, :summary, :body]
defstruct [:title, :slug, :date, :author, :tags, :summary, :body]
@enforce_keys [:title, :slug, :date, :author, :tags, :lede, :body]
defstruct [:title, :slug, :date, :author, :tags, :lede, :body]
@title_slug_regex ~r/[^a-zA-Z0-9 ]/
@doc """
The public parse!/1 function begins the post parse process by reading
the file. By passing through a series of other functions, it ultimately
returns either a %Post{} or nil.
"""
def parse!(post_path) do
post_path
|> File.read()
|> parse_raw_file_data()
|> split_raw_file_data()
|> parse_frontmatter()
|> parse_lede()
end
defp parse_raw_file_data({:ok, post_data}) do
post_data
|> String.split("---", parts: 3)
|> parse_split_file_data()
# """ split_raw_file_data/1
# If we receive {:ok, file_data}, we split frontmatter from markdown
# content and return [raw_frontmatter, markdown]. Otherwise return nil.
# """
defp split_raw_file_data({:ok, file_data}), do: String.split(file_data, ~r/\n-{3,}\n/, parts: 2)
defp split_raw_file_data(_), do: nil
# """ parse_frontmatter/1
# If we receive [raw_frontmatter, markdown], we parse the frontmatter.
# Otherwise, return nil.
# """
defp parse_frontmatter([fm, md]) do
case parse_frontmatter_string(fm) do
{%{} = parsed_fm, _} -> {parsed_fm, md}
{:error, _} -> nil
end
end
defp parse_raw_file_data(_), do: nil
defp parse_frontmatter(nil), do: nil
defp parse_split_file_data(["", fm, md]) do
Code.eval_string(fm)
|> parse_summary(md)
# """ parse_lede/1
# Look for lede/excerpt/summary in content and extract it if present.
# We return updated frontmatter, and content with <!--more--> stripped.
defp parse_lede({fm, md}) do
{lede, body_md} = String.split(md, "<!--more-->", parts: 2) |> extract_lede()
{Map.put(fm, :lede, lede), String.replace(body_md, "<!--more-->", " ")}
end
defp parse_split_file_data(_), do: nil
defp parse_lede(_), do: nil
defp parse_summary({%{summary: summ} = fm, _}, md) do
Earmark.as_html(md)
|> parse_post(Earmark.as_html(summ), fm)
# TODO:
# |> parse_body()
# - convert to markdown
# - extract any code parts to mark with pygments?
# - figure that whole thing out
######################################################################
# HELPERS
######################################################################
# """ parse_frontmatter_string/1
# We expect raw frontmatter as a string that evaluates to an elixir
# map, so we try Code.eval_string/1 and rescue with nil if that raises
# """
defp parse_frontmatter_string(fm) do
try do
String.trim_leading(fm, "-")
|> Code.eval_string()
rescue
_ -> {:error, nil}
end
end
defp parse_summary({%{} = fm, _}, md) do
String.split(md, "<!--more-->", parts: 2)
|> parse_summary(fm)
end
# """ extract_lede
# Handle split of post body. If lede found, return as html with body.
# Otherwise return nil with body.
# """
defp extract_lede([lede, body]), do: {Earmark.as_html!(lede), body}
defp extract_lede([body]), do: {nil, body}
defp parse_summary([summ, _] = parts, fm) do
parts
|> Enum.join(" ")
|> Earmark.as_html()
|> parse_post(Earmark.as_html(summ), fm)
end
# ##################################################
# ##################################################
# ##################################################
# ##################################################
# ##################################################
# defp parse_split_file_data(["", fm, md]) do
# Code.eval_string(fm)
# |> parse_lede(md)
# end
defp parse_summary(md, fm) do
Earmark.as_html(md)
|> parse_post({:ok, nil, []}, fm)
end
# defp parse_split_file_data(_), do: nil
defp parse_title_to_slug(title) do
Regex.replace(@title_slug_regex, title, "")
|> String.replace(" ", "-")
|> String.downcase()
end
# defp parse_lede({%{summary: summ} = fm, _}, md) do
# Earmark.as_html(md)
# |> parse_post(Earmark.as_html(summ), fm)
# end
defp build_post(main_html, summ_html, fm) do
fm
|> Map.put_new(:slug, parse_title_to_slug(fm.title))
|> Map.put_new(:author, "Author Name")
|> Map.put_new(:tags, [])
|> Map.put(:summary, summ_html)
|> Map.put(:body, main_html)
end
# defp parse_lede({%{} = fm, _}, md) do
# String.split(md, "<!--more-->", parts: 2)
# |> parse_lede(fm)
# end
defp parse_post({:ok, main_html, _}, {:ok, summ_html, _}, fm) do
post = build_post(main_html, summ_html, fm)
struct!(__MODULE__, post)
end
# defp parse_lede([summ, _] = parts, fm) do
# parts
# |> Enum.join(" ")
# |> Earmark.as_html()
# |> parse_post(Earmark.as_html(summ), fm)
# end
defp parse_post(_, _, _), do: nil
# defp parse_lede(md, fm) do
# Earmark.as_html(md)
# |> parse_post({:ok, nil, []}, fm)
# end
# defp parse_title_to_slug(title) do
# Regex.replace(@title_slug_regex, title, "")
# |> String.replace(" ", "-")
# |> String.downcase()
# end
# defp build_post(main_html, summ_html, fm) do
# fm
# |> Map.put_new(:slug, parse_title_to_slug(fm.title))
# |> Map.put_new(:author, "Author Name")
# |> Map.put_new(:tags, [])
# |> Map.put(:summary, summ_html)
# |> Map.put(:body, main_html)
# end
# defp parse_post({:ok, main_html, _}, {:ok, summ_html, _}, fm) do
# post = build_post(main_html, summ_html, fm)
# struct!(__MODULE__, post)
# end
# defp parse_post(_, _, _), do: nil
end

View file

@ -1,43 +0,0 @@
defmodule Home73k.Repo do
@repo_url Application.compile_env(:home73k, [:content_repo, :url], nil)
@content_path Application.compile_env(:home73k, [:content_repo, :path], "./priv/content")
|> Path.expand()
######################################################################
# REPO SETUP
######################################################################
def get do
@content_path |> File.exists?() |> init_repo()
end
def content_path, do: @content_path
# If content path is absent, clone repo if url is present
defp init_repo(false) do
if @repo_url do
{:ok, repo} = Git.clone([@repo_url, @content_path])
repo
else
nil
end
end
# If content path exists, check for .git child and pull or return nil
defp init_repo(true) do
if git_data_dir_ok?() do
Git.new(@content_path)
else
nil
end
end
defp git_data_dir_ok? do
git_data_dir = Path.join(@content_path, ".git")
File.exists?(git_data_dir) && File.dir?(git_data_dir)
end
######################################################################
# REPO OPERATIONS
######################################################################
def update(repo), do: Git.pull(repo)
end

View file

@ -4,12 +4,7 @@
slug: "markdown-for-blog-posts",
date: ~N[2016-05-01 13:30:00],
author: "Adam Piontek",
tags: ["sample", "demo"],
summary: """
This summary could get long.
We might even have multiple lines!
"""
tags: ["sample", "demo"]
}
---

View file

@ -7,10 +7,6 @@
}
---
# Enable Visual Studio CLI environment in PowerShell
#coding #tech #elixir #windows #powershell #scripting
My initial problem: I have an elixir project I'm building primarily on linux, but I want it to work on Windows, too, and I'm using [bcrypt](https://github.com/riverrun/bcrypt_elixir), which [needs nmake to compile](https://github.com/riverrun/comeonin/wiki/Requirements#windows) on Windows.
One must install Visual Studio (VS), but that's not enough.<!--more--> Your terminal/PowerShell CLI environment won't know about VS by default. VS includes batch files to set up a dev environment, but if you run them in PowerShell, they bring you into a CMD environment, which is no help if you want to use PowerShell.

View file

@ -3,8 +3,7 @@
title: "Mom's Meatloaf",
date: ~N[2020-12-29 01:00:00],
author: "Adam Piontek",
tags: ["food", "recipe", "mealprep", "pandemiceats", "plaguecooking"],
summary: "A meatloaf recipe from a very special mom"
tags: ["food", "recipe", "mealprep", "pandemiceats", "plaguecooking"]
}
---