diff --git a/.gitignore b/.gitignore index 40bc87f..de96d9a 100644 --- a/.gitignore +++ b/.gitignore @@ -39,8 +39,5 @@ npm-debug.log # for vscode elixir_ls extension files /.elixir_ls -# blog content repo folder -/priv/content/ - # dev TODO.md diff --git a/lib/home73k/application.ex b/lib/home73k/application.ex index ea69fe1..ecc3a44 100644 --- a/lib/home73k/application.ex +++ b/lib/home73k/application.ex @@ -12,10 +12,9 @@ defmodule Home73k.Application do # Start the PubSub system {Phoenix.PubSub, name: Home73k.PubSub}, # Start the Endpoint (http/https) - Home73kWeb.Endpoint, + Home73kWeb.Endpoint # Start a worker by calling: Home73k.Worker.start_link(arg) # {Home73k.Worker, arg} - Home73k.Blog ] # See https://hexdocs.pm/elixir/Supervisor.html diff --git a/lib/home73k/blog.ex b/lib/home73k/blog.ex index 8f25f1c..b330b57 100644 --- a/lib/home73k/blog.ex +++ b/lib/home73k/blog.ex @@ -1,107 +1,29 @@ defmodule Home73k.Blog do - use GenServer + alias Home73k.Blog.Post - # - # Setup - # Application.ensure_all_started(:earmark) - @repo Home73k.Repo.get() + @content_path Application.compile_env(:home73k, [:content_repo, :path], "./priv/content") + |> Path.expand() - # - # Client - # + posts_paths = + @content_path + |> Path.join("**/*.md") + |> Path.wildcard() - def start_link(_opts) do - GenServer.start_link(__MODULE__, %{posts: [], tags: [], files: []}, name: __MODULE__) - end + posts = + for post_path <- posts_paths do + @external_resource Path.relative_to_cwd(post_path) + Post.parse!(post_path) + end - def get_posts() do - GenServer.call(__MODULE__, :get_posts) - end + # @posts posts + @posts Enum.sort_by(posts, & &1.date, {:desc, NaiveDateTime}) - def get_files() do - GenServer.call(__MODULE__, :get_files) - end + @tags posts |> Stream.flat_map(& &1.tags) |> Stream.uniq() |> Enum.sort() - # def push(pid, element) do - # GenServer.cast(pid, {:push, element}) - # end - - # def pop(pid) do - # GenServer.call(pid, :pop) - # end - - # def put(server, key, value) do - # GenServer.cast(server, {:put, key, value}) - # end - - # def get(server, key) do - # GenServer.call(server, {:get, key}) - # end - - # - # Server - # - - @impl true - def init(state) do - repo_all_paths = @repo.path |> Path.join("**/*.*") |> Path.wildcard() - repo_post_paths = repo_all_paths |> Enum.filter(fn f -> String.ends_with?(f, ".md") end) - repo_file_paths = repo_all_paths |> Enum.filter(fn f -> !String.ends_with?(f, ".md") end) - {:ok, %{state | posts: repo_post_paths, files: repo_file_paths}} - end - - @impl true - def handle_call(:get_posts, _from, %{posts: posts} = state) do - {:reply, posts, state} - end - - @impl true - def handle_call(:get_files, _from, %{files: files} = state) do - {:reply, files, state} - end - - # @impl true - # def handle_call(:pop, _from, [head | tail]) do - # {:reply, head, tail} - # end - - # @impl true - # def handle_cast({:push, element}, state) do - # {:noreply, [element | state]} - # end - - # def handle_cast({:put, key, value}, state) do - # {:noreply, Map.put(state, key, value)} - # end - - # def handle_call({:get, key}, _from, state) do - # {:reply, Map.fetch!(state, key), state} - # end - - # alias Home73k.Blog.Post - - # @posts_dir Application.compile_env(:home73k, :blog_posts_dir, "posts") - - # posts_paths = - # @posts_dir - # |> Path.join("**/*.md") - # |> Path.wildcard() - # |> Enum.sort() - - # posts = - # for post_path <- posts_paths do - # @external_resource Path.relative_to_cwd(post_path) - # Post.parse!(post_path) - # end - - # @posts Enum.sort_by(posts, & &1.date, {:desc, Date}) - - # @tags posts |> Enum.flat_map(& &1.tags) |> Enum.uniq() |> Enum.sort() - - # def list_posts, do: @posts - # def list_tags, do: @tags + def list_posts, do: @posts + 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 new file mode 100644 index 0000000..89f6c44 --- /dev/null +++ b/lib/home73k/blog/post.ex @@ -0,0 +1,71 @@ +defmodule Home73k.Blog.Post do + @enforce_keys [:title, :slug, :date, :author, :tags, :summary, :body] + defstruct [:title, :slug, :date, :author, :tags, :summary, :body] + + @title_slug_regex ~r/[^a-zA-Z0-9 ]/ + + def parse!(post_path) do + post_path + |> File.read() + |> parse_raw_file_data() + end + + defp parse_raw_file_data({:ok, post_data}) do + post_data + |> String.split("---", parts: 3) + |> parse_split_file_data() + end + + defp parse_raw_file_data(_), do: nil + + defp parse_split_file_data(["", fm, md]) do + Code.eval_string(fm) + |> parse_summary(md) + end + + defp parse_split_file_data(_), do: nil + + defp parse_summary({%{summary: summ} = fm, _}, md) do + Earmark.as_html(md) + |> parse_post(Earmark.as_html(summ), fm) + end + + defp parse_summary({%{} = fm, _}, md) do + String.split(md, "", parts: 2) + |> parse_summary(fm) + end + + defp parse_summary([summ, _] = parts, fm) do + parts + |> Enum.join(" ") + |> Earmark.as_html() + |> parse_post(Earmark.as_html(summ), fm) + end + + defp parse_summary(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_web/controllers/blog_file_controller.ex b/lib/home73k_web/controllers/blog_file_controller.ex deleted file mode 100644 index dc9b910..0000000 --- a/lib/home73k_web/controllers/blog_file_controller.ex +++ /dev/null @@ -1,30 +0,0 @@ -defmodule Home73kWeb.BlogFileController do - use Home73kWeb, :controller - - @moduledoc """ - This controller handles path requests that didn't match an existing route, - including the main Plug.Static for the / root path. - - To handle this situation, we'll check if the requested resource exists as - a file under the blog content repo folder, by querying the Blog genserve. - - If it exists, we'll redirect to the blog content static path under /_ - Otherwise, we'll return 404 not found. - """ - - alias Home73k.Repo - alias Home73k.Blog - - def index(conn, _params) do - # What would be the content path of this requested resource? - content_path = Repo.content_path() |> Path.join(conn.request_path) - - # Check if it exists in the Blog's known files - Blog.get_files() - |> Enum.member?(content_path) - |> case do - true -> redirect(conn, to: "/_#{conn.request_path}") - false -> send_resp(conn, 404, "not found") - end - end -end diff --git a/lib/home73k_web/endpoint.ex b/lib/home73k_web/endpoint.ex index 1559f7c..3e9a938 100644 --- a/lib/home73k_web/endpoint.ex +++ b/lib/home73k_web/endpoint.ex @@ -26,18 +26,6 @@ defmodule Home73kWeb.Endpoint do gzip: true, only: ~w(css fonts images js favicon.ico robots.txt DF185CEE29A3D443_public_key.asc) - # Blog static path handler - if File.dir?(Home73k.Repo.content_path()) do - plug Plug.Static, - at: "/_", - from: Home73k.Repo.content_path(), - gzip: false, - only: - Home73k.Repo.content_path() - |> File.ls!() - |> Enum.filter(fn f -> !String.starts_with?(f, ".") end) - end - # Code reloading can be explicitly enabled under the # :code_reloader configuration of your endpoint. if code_reloading? do diff --git a/lib/home73k_web/router.ex b/lib/home73k_web/router.ex index ef374be..a2bd4cc 100644 --- a/lib/home73k_web/router.ex +++ b/lib/home73k_web/router.ex @@ -43,9 +43,4 @@ defmodule Home73kWeb.Router do live_dashboard "/dashboard", metrics: Home73kWeb.Telemetry end end - - # Wildcard path for handling Blog Files from repo - scope "/", Home73kWeb do - get "/*path", BlogFileController, :index - end end diff --git a/mix.exs b/mix.exs index e440427..a428f46 100644 --- a/mix.exs +++ b/mix.exs @@ -45,8 +45,6 @@ defmodule Home73k.MixProject do {:jason, "~> 1.0"}, {:plug_cowboy, "~> 2.0"}, {:tzdata, "~> 1.1"}, - {:git_cli, "~> 0.3.0"}, - {:yaml_elixir, "~> 2.6"}, {:earmark, "~> 1.4"}, # Additional packages diff --git a/priv/content/2016/2016-05-01_initial-test-post.md b/priv/content/2016/2016-05-01_initial-test-post.md new file mode 100644 index 0000000..18a9b2d --- /dev/null +++ b/priv/content/2016/2016-05-01_initial-test-post.md @@ -0,0 +1,20 @@ +--- +%{ + title: "Markdown for blog posts is nice, says Sebastian", + 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! + """ +} +--- + +All the **Markdown** here... + +```javascript +console.log('hello world!') +``` 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 new file mode 100644 index 0000000..6811354 --- /dev/null +++ b/priv/content/2020/08/2020-08-01_enable_vs_cli_env_in_ps.md @@ -0,0 +1,52 @@ +--- +%{ + title: "Enable Visual Studio CLI environment in PowerShell", + date: ~N[2020-08-01 02:00:00], + author: "Adam Piontek", + tags: ["coding", "tech", "elixir", "windows", "powershell", "scripting"], +} +--- + +# 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. + +Luckily, thanks to [Michael Teter's blog](https://michaelteter.com/2018/07/06/compiling-Elixir-modules-in-Windows.html), and posts on github that mentioned the [VS Setup PS Module](https://github.com/microsoft/vssetup.powershell), I was able to put together a solution that works for me: + +The built-in PowerShell (5.1 right now) will load a [profile](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_profiles?view=powershell-5.1) from (under user Documents folder): `WindowsPowerShell\Microsoft.PowerShell_profile.ps1` — and PS 7 (pwsh.exe) will load its [profile](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_profiles?view=powershell-7) from the slightly different (under user Documents folder): `PowerShell\Microsoft.PowerShell_profile.ps1` + +For each version where I want the environment set up, I place the following code in the profile: + +```powershell +function Invoke-CmdScript { + param( + [String] $scriptName + ) + $cmdLine = """$scriptName"" $args & set" + & $Env:SystemRoot\system32\cmd.exe /c $cmdLine | + select-string '^([^=]*)=(.*)$' | foreach-object { + $varName = $_.Matches[0].Groups[1].Value + $varValue = $_.Matches[0].Groups[2].Value + set-item Env:$varName $varValue + } +} + +$installationPath = (Get-VSSetupInstance -All | Select-Object InstallationPath).InstallationPath +$vcvars64Path = Join-Path -Path $installationPath -ChildPath "VC\Auxiliary\Build\vcvars64.bat" + +if ($installationPath -and (Test-Path -Path $vcvars64Path)) { + Invoke-CmdScript $vcvars64Path +} +``` + +As Michael explains: + +> The function allows you to run a script, the one that makes the build tools visible, but instead of losing the context after that script ends, the settings are propogated back to the calling shell (Powershell). + +> The profile then uses that Invoke-CmdScript function to call the vcvars64.bat script... + +My addition is just to get the VS installation path from that VS Setup PS Module's `Get-VSSetupInstance` and build the script path from that. Hopefully that will survive future VS upgrades? diff --git a/priv/content/2020/2020-12-29_moms-meatloaf.md b/priv/content/2020/2020-12-29_moms-meatloaf.md new file mode 100644 index 0000000..25d63c3 --- /dev/null +++ b/priv/content/2020/2020-12-29_moms-meatloaf.md @@ -0,0 +1,39 @@ +--- +%{ + 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" +} +--- + +Preheat oven to 350 + +So as you know, I just throw my meatloaf together, but here are my estimates based on a bunch of published recipes: + +- Put 2 pounds ground beef (80/20) in a large bowl. +- Squirt about 1/2 cup ketchup over it. +- Sprinkle about a tablespoon of Worcestershire sauce over. +- Throw in one egg (or 2, if you feel the need - I usually just use one). +- Put in about 1 teaspoon horseradish +- Put in about 1 teaspoon mustard +- Sprinkle 1 packet of dry onion soup mix over +- I have used bread (2 slices soaked like you're making meatballs and squeezed out) or oats (about 3/4 cup), or sometimes both depending on what I have or need to use up + +You don't need any extra salt, everything you put in is pretty salty, grind some pepper on top + +If you like you can put in a bit of mild hot sauce like Franks, Crystal, Louisiana hot sauce + +Mix and squish everything together with your hands really well (make sure you wash them first!) (I don't think Justine will like this part). (Some recipes say to only mix loosely, I disagree.) Mixture should be nice and soft, but not so soft that you can't form it into a loaf (not as soft as I do for meatballs). I don't use a loaf pan, I shape it into an oval loaf. Do this in the pan you'll bake it in and then you don't have to grease it. + +Squirt more ketchup over top, and spread all over the loaf (I use the back of a teaspoon to do this). + +I like to bake it in a cast iron skillet, easier to clean and also you can make gravy in it if you like. + +Bake at 350 for about an hour and 15 minutes. Internal temp should be 160. Let it stand a few minutes before slicing and serving with the classic accompaniments of mashed potatoes and corn. + +If you want to make a gravy, pour off most of the fat from the pan, stir in flour to make a roux, add unsalted broth (beef or chicken or vegetable), cook and scrape the good stuff from the bottom of the pan. Drippings are pretty salty so unsalted liquid is essential, keep adding till flavor suits you. + +Well, now you know all my secrets! +