incorporating content and refining post parsing
This commit is contained in:
parent
f6c316d4fa
commit
b1b9c09a79
6 changed files with 118 additions and 107 deletions
|
@ -4,10 +4,10 @@ defmodule Home73k.Blog do
|
||||||
Application.ensure_all_started(:earmark)
|
Application.ensure_all_started(:earmark)
|
||||||
|
|
||||||
@content_path Application.compile_env(:home73k, [:content_repo, :path], "./priv/content")
|
@content_path Application.compile_env(:home73k, [:content_repo, :path], "./priv/content")
|
||||||
|> Path.expand()
|
|
||||||
|
|
||||||
posts_paths =
|
posts_paths =
|
||||||
@content_path
|
@content_path
|
||||||
|
|> Path.expand()
|
||||||
|> Path.join("**/*.md")
|
|> Path.join("**/*.md")
|
||||||
|> Path.wildcard()
|
|> Path.wildcard()
|
||||||
|
|
||||||
|
@ -17,13 +17,13 @@ defmodule Home73k.Blog do
|
||||||
Post.parse!(post_path)
|
Post.parse!(post_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @posts posts
|
@posts posts
|
||||||
@posts Enum.sort_by(posts, & &1.date, {:desc, NaiveDateTime})
|
# @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_posts, do: @posts
|
||||||
def list_tags, do: @tags
|
# def list_tags, do: @tags
|
||||||
|
|
||||||
# defmodule NotFoundError do
|
# defmodule NotFoundError do
|
||||||
# defexception [:message, plug_status: 404]
|
# defexception [:message, plug_status: 404]
|
||||||
|
|
|
@ -1,71 +1,135 @@
|
||||||
defmodule Home73k.Blog.Post do
|
defmodule Home73k.Blog.Post do
|
||||||
@enforce_keys [:title, :slug, :date, :author, :tags, :summary, :body]
|
@enforce_keys [:title, :slug, :date, :author, :tags, :lede, :body]
|
||||||
defstruct [:title, :slug, :date, :author, :tags, :summary, :body]
|
defstruct [:title, :slug, :date, :author, :tags, :lede, :body]
|
||||||
|
|
||||||
@title_slug_regex ~r/[^a-zA-Z0-9 ]/
|
@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
|
def parse!(post_path) do
|
||||||
post_path
|
post_path
|
||||||
|> File.read()
|
|> File.read()
|
||||||
|> parse_raw_file_data()
|
|> split_raw_file_data()
|
||||||
|
|> parse_frontmatter()
|
||||||
|
|> parse_lede()
|
||||||
end
|
end
|
||||||
|
|
||||||
defp parse_raw_file_data({:ok, post_data}) do
|
# """ split_raw_file_data/1
|
||||||
post_data
|
# If we receive {:ok, file_data}, we split frontmatter from markdown
|
||||||
|> String.split("---", parts: 3)
|
# content and return [raw_frontmatter, markdown]. Otherwise return nil.
|
||||||
|> parse_split_file_data()
|
# """
|
||||||
|
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
|
end
|
||||||
|
|
||||||
defp parse_raw_file_data(_), do: nil
|
defp parse_frontmatter(nil), do: nil
|
||||||
|
|
||||||
defp parse_split_file_data(["", fm, md]) do
|
# """ parse_lede/1
|
||||||
Code.eval_string(fm)
|
# Look for lede/excerpt/summary in content and extract it if present.
|
||||||
|> parse_summary(md)
|
# 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
|
end
|
||||||
|
|
||||||
defp parse_split_file_data(_), do: nil
|
defp parse_lede(_), do: nil
|
||||||
|
|
||||||
defp parse_summary({%{summary: summ} = fm, _}, md) do
|
# TODO:
|
||||||
Earmark.as_html(md)
|
# |> parse_body()
|
||||||
|> parse_post(Earmark.as_html(summ), fm)
|
# - 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
|
end
|
||||||
|
|
||||||
defp parse_summary({%{} = fm, _}, md) do
|
# """ extract_lede
|
||||||
String.split(md, "<!--more-->", parts: 2)
|
# Handle split of post body. If lede found, return as html with body.
|
||||||
|> parse_summary(fm)
|
# Otherwise return nil with body.
|
||||||
end
|
# """
|
||||||
|
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
|
# defp parse_split_file_data(_), do: nil
|
||||||
Earmark.as_html(md)
|
|
||||||
|> parse_post({:ok, nil, []}, fm)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp parse_title_to_slug(title) do
|
# defp parse_lede({%{summary: summ} = fm, _}, md) do
|
||||||
Regex.replace(@title_slug_regex, title, "")
|
# Earmark.as_html(md)
|
||||||
|> String.replace(" ", "-")
|
# |> parse_post(Earmark.as_html(summ), fm)
|
||||||
|> String.downcase()
|
# end
|
||||||
end
|
|
||||||
|
|
||||||
defp build_post(main_html, summ_html, fm) do
|
# defp parse_lede({%{} = fm, _}, md) do
|
||||||
fm
|
# String.split(md, "<!--more-->", parts: 2)
|
||||||
|> Map.put_new(:slug, parse_title_to_slug(fm.title))
|
# |> parse_lede(fm)
|
||||||
|> Map.put_new(:author, "Author Name")
|
# end
|
||||||
|> 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
|
# defp parse_lede([summ, _] = parts, fm) do
|
||||||
post = build_post(main_html, summ_html, fm)
|
# parts
|
||||||
struct!(__MODULE__, post)
|
# |> Enum.join(" ")
|
||||||
end
|
# |> 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
|
end
|
||||||
|
|
|
@ -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
|
|
|
@ -4,12 +4,7 @@
|
||||||
slug: "markdown-for-blog-posts",
|
slug: "markdown-for-blog-posts",
|
||||||
date: ~N[2016-05-01 13:30:00],
|
date: ~N[2016-05-01 13:30:00],
|
||||||
author: "Adam Piontek",
|
author: "Adam Piontek",
|
||||||
tags: ["sample", "demo"],
|
tags: ["sample", "demo"]
|
||||||
summary: """
|
|
||||||
This summary could get long.
|
|
||||||
|
|
||||||
We might even have multiple lines!
|
|
||||||
"""
|
|
||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -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.
|
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.
|
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.
|
||||||
|
|
|
@ -3,8 +3,7 @@
|
||||||
title: "Mom's Meatloaf",
|
title: "Mom's Meatloaf",
|
||||||
date: ~N[2020-12-29 01:00:00],
|
date: ~N[2020-12-29 01:00:00],
|
||||||
author: "Adam Piontek",
|
author: "Adam Piontek",
|
||||||
tags: ["food", "recipe", "mealprep", "pandemiceats", "plaguecooking"],
|
tags: ["food", "recipe", "mealprep", "pandemiceats", "plaguecooking"]
|
||||||
summary: "A meatloaf recipe from a very special mom"
|
|
||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue