diff --git a/lib/home73k/blog.ex b/lib/home73k/blog.ex index b330b57..c3e79ab 100644 --- a/lib/home73k/blog.ex +++ b/lib/home73k/blog.ex @@ -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] diff --git a/lib/home73k/blog/post.ex b/lib/home73k/blog/post.ex index 89f6c44..2f347b5 100644 --- a/lib/home73k/blog/post.ex +++ b/lib/home73k/blog/post.ex @@ -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 stripped. + defp parse_lede({fm, md}) do + {lede, body_md} = String.split(md, "", parts: 2) |> extract_lede() + {Map.put(fm, :lede, lede), String.replace(body_md, "", " ")} 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, "", 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, "", 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 diff --git a/lib/home73k/repo.ex b/lib/home73k/repo.ex deleted file mode 100644 index 23bd676..0000000 --- a/lib/home73k/repo.ex +++ /dev/null @@ -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 diff --git a/priv/content/2016/2016-05-01_initial-test-post.md b/priv/content/2016/2016-05-01_initial-test-post.md index 18a9b2d..f8376c2 100644 --- a/priv/content/2016/2016-05-01_initial-test-post.md +++ b/priv/content/2016/2016-05-01_initial-test-post.md @@ -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"] } --- diff --git a/priv/content/2020/08/2020-08-01_enable_vs_cli_env_in_ps.md b/priv/content/2020/08/2020-08-01_enable_vs_cli_env_in_ps.md index 6811354..b2ec8f6 100644 --- a/priv/content/2020/08/2020-08-01_enable_vs_cli_env_in_ps.md +++ b/priv/content/2020/08/2020-08-01_enable_vs_cli_env_in_ps.md @@ -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. 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. diff --git a/priv/content/2020/2020-12-29_moms-meatloaf.md b/priv/content/2020/2020-12-29_moms-meatloaf.md index 25d63c3..f212c30 100644 --- a/priv/content/2020/2020-12-29_moms-meatloaf.md +++ b/priv/content/2020/2020-12-29_moms-meatloaf.md @@ -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"] } ---