From 816fdbb484f188a5f684f605e6060d7f8d8c8312 Mon Sep 17 00:00:00 2001 From: Adam Piontek Date: Tue, 6 Apr 2021 14:45:54 -0400 Subject: [PATCH] fixes, styling improvement including inlined code --- lib/home73k/blog/post.ex | 2 +- lib/home73k/highlighter.ex | 42 +++++++- lib/home73k_web/live/blog_live.ex | 2 +- .../content/2018/03/2018-03-24_pihole-love.md | 2 +- .../2021/04/2021-04-05_blog-incorporated.md | 100 ++++++++++++++---- 5 files changed, 121 insertions(+), 27 deletions(-) diff --git a/lib/home73k/blog/post.ex b/lib/home73k/blog/post.ex index c5ef717..6194883 100644 --- a/lib/home73k/blog/post.ex +++ b/lib/home73k/blog/post.ex @@ -63,7 +63,7 @@ defmodule Home73k.Blog.Post do # """ parse_body/1 # Convert body markdown to html, and highlight code fence blocks defp parse_body({fm, md}) do - html = Earmark.as_html!(md) |> Highlighter.highlight_code_blocks() + html = Earmark.as_html!(md) |> Highlighter.highlight_all() Map.put(fm, :body, html) end diff --git a/lib/home73k/highlighter.ex b/lib/home73k/highlighter.ex index 496a347..31bb7b7 100644 --- a/lib/home73k/highlighter.ex +++ b/lib/home73k/highlighter.ex @@ -8,7 +8,14 @@ defmodule Home73k.Highlighter do @chroma_bin Home73k.app_chroma_bin() |> Path.expand() @doc """ - Highlights all code block in an already generated HTML document. + Highlights all code in HTML (fenced code blocks and inlined code) + """ + def highlight_all(html) do + highlight_code_blocks(html) |> highlight_code_inlined() + end + + @doc """ + Highlights fenced code blocks in HTML document """ def highlight_code_blocks(html) do ~r/
([^<]*)<\/code><\/pre>/
@@ -16,6 +23,31 @@ defmodule Home73k.Highlighter do
   end
 
   defp highlight_code_block(_full_block, lang, code) do
+    # perform the code highlighting
+    highlighted = highlight_code(lang, code)
+    # return properly wrapped highlighted code
+    ~s(
#{highlighted}
) + end + + @doc """ + Highlights inlined code in HTML document + """ + def highlight_code_inlined(html) do + ~r/([^<]*)<\/code>/ + |> Regex.replace(html, &highlight_code_inline(&1, &2, &3)) + end + + defp highlight_code_inline(_full_block, lang, code) do + # perform the code highlighting + highlighted = highlight_code(lang, code) + # return properly wrapped highlighted code + ~s(#{highlighted}) + end + + # """ + # Performs code highlighting using chroma + # """ + defp highlight_code(lang, code) do # unescape the code unescaped_code = unescape_html(code) |> IO.iodata_to_binary() @@ -25,12 +57,12 @@ defmodule Home73k.Highlighter do # use chroma to highlight the code via temp file bin_args = ["-l", lang, "-f", "html", "--html-only", "--html-prevent-surrounding-pre", tmp_file] - {highlighted, _} = System.cmd(@chroma_bin, bin_args) - - # return properly wrapped highlighted code - ~s(
#{highlighted}
) + System.cmd(@chroma_bin, bin_args) |> elem(0) end + # """ + # Code below for unescaping html, since it's escaped by Earmark markdown parsing + # """ entities = [{"&", ?&}, {"<", ?<}, {">", ?>}, {""", ?"}, {"'", ?'}] for {encoded, decoded} <- entities do diff --git a/lib/home73k_web/live/blog_live.ex b/lib/home73k_web/live/blog_live.ex index aa97f12..b6e537c 100644 --- a/lib/home73k_web/live/blog_live.ex +++ b/lib/home73k_web/live/blog_live.ex @@ -60,7 +60,7 @@ defmodule Home73kWeb.BlogLive do defp init_per_live_action(:show, socket, %{"id" => id}) do post = Blog.get_post_by_id!(id) socket - |> assign(:page_title, "Blog \\ post.title") + |> assign(:page_title, "Blog \\ #{post.title}") |> assign(:posts, [post]) |> assign(:page_count, nil) |> assign_prev_next(0) diff --git a/priv/content/2018/03/2018-03-24_pihole-love.md b/priv/content/2018/03/2018-03-24_pihole-love.md index ca338b4..545daf7 100644 --- a/priv/content/2018/03/2018-03-24_pihole-love.md +++ b/priv/content/2018/03/2018-03-24_pihole-love.md @@ -35,7 +35,7 @@ I also whitelisted several relevant domains from both the bottom of the Big Bloc We found my partner couldn't use a website related to her profession. It would just get stuck loading and it wasn't clear why. With Chrome (can do this in Firefox, too) I was able to use F12 to open the DevTools, click Network, reload the page, and see in red what was failing to load. -In this case, the Pi-hole was blocking "cdns.gigya.com," a content delivery service hosting a javascript the page wanted to use. Off to the Pi-hole web interface (or cli with `pihole -w`) to quickly whitelist, and the site worked again. +In this case, the Pi-hole was blocking "cdns.gigya.com," a content delivery service hosting a javascript the page wanted to use. Off to the Pi-hole web interface (or cli with ``pihole -w``{:.lang-shell}) to quickly whitelist, and the site worked again. The same for "prod.imgur.map.fastlylb.net," apparently needed for imgur to work. diff --git a/priv/content/2021/04/2021-04-05_blog-incorporated.md b/priv/content/2021/04/2021-04-05_blog-incorporated.md index 77a99c8..7795255 100644 --- a/priv/content/2021/04/2021-04-05_blog-incorporated.md +++ b/priv/content/2021/04/2021-04-05_blog-incorporated.md @@ -18,7 +18,9 @@ I set to incorporating my blog posts and previous static webpack-based design in For one, as much as I love Elxiir, the only server-side syntax-highlighting package out there doesn't support enough languages for my needs. I didn't like the idea of using client-side javascript, so I decided to leverage [Chroma's cli](https://github.com/alecthomas/chroma#command-line-interface). Hooking elixir's System.cmd/3 in with a trimmed & modified version of [the ExDoc syntax highlighter code](https://github.com/elixir-lang/ex_doc/blob/d5cde30f55c7e0cde486ec3878067aee82ccc924/lib/ex_doc/highlighter.ex), after parsing the markdown to html, I'm able to isolate all fenced code in a post, write each code fragment to a temp file, apply highlight classes via chroma, and replace the code in the post. -*(As an aside, I at first tried using [Pygments' `pygmentize` cli](https://pygments.org/docs/cmdline/), which worked fine, but I realized it didn't seem to support vue templates, and I do some work with vue. At some point it might be fun to try to figure out how to implement the lexers I need in the elixir native project [makeup](https://github.com/elixir-makeup/makeup) but that's a big task to tackle some other day!)* +*(As an aside, I at first tried using the [Pygments cli](https://pygments.org/docs/cmdline/), which worked fine, but I realized it didn't seem to support vue templates, and I do some work with vue. At some point it might be fun to try to figure out how to implement the lexers I need in the elixir native project [makeup](https://github.com/elixir-makeup/makeup) but that's a big task to tackle some other day!)* + +Incidentally, [Earmark](https://github.com/pragdave/earmark)'s markdown parsing includes support for setting the language for inline code with a braced suffix --- e.g., `` `console.log('test');`{:.lang-javascript} ``{:.lang-markdown} --- so my highlighter code below includes support for such code elements. I also found code hot-reloading wasn't working great if I added or removed a file, even if I canceled all elixir processes and started it up again. I had to get this fixed because the plan was to deploy new content and other changes via git repository post-receive hook, and while I can script the recompilation, and probably add in a `--force` flag or something, I wanted it cleaner. Plus I just wanted the convenience of writing posts in dev mode with a preview. @@ -35,29 +37,89 @@ end Following that change, new markdown files are recognized and included as expected. -And, FWIW, here's the meat of my modified highlighter using chroma (NOTE: the CSS styles can be exported separately (like so: `~/go/bin/chroma -s base16-snazzy --html-styles > _chroma.css`), or you can use styles from Pygments, including custom styles like [nord_pygments](https://github.com/sbrisard/nord_pygments)). Once included in your app.scss, if you use purgecss like me, you'll need to add the chroma class (or whatever class you're using) to the safelist for the webpack plugin: `safelist: {greedy: [/phx/, /topbar/, /inline/, /chroma/]}` .) +And, FWIW, here's my modified highlighter module using chroma (I've exported the CSS styles separately (like so: `~/go/bin/chroma -s base16-snazzy --html-styles > _chroma.css`{:.lang-shell}) to include in my `app.scss`. If you use purgecss like me, you'll need to add the `.chroma`{:.lang-css} class to the safelist for the webpack plugin: `safelist: {greedy: [/phx/, /topbar/, /inline/, /chroma/]}`{:.lang-javascript} .) ```elixir -def highlight_code_blocks(html) do - ~r/
([^<]*)<\/code><\/pre>/
-  |> Regex.replace(html, &highlight_code_block(&1, &2, &3))
-end
+defmodule Home73k.Highlighter do
+  @moduledoc """
+  Performs code highlighting.
+  """
 
-defp highlight_code_block(_full_block, lang, code) do
-  # unescape the code
-  unescaped_code = unescape_html(code) |> IO.iodata_to_binary()
+  alias Home73k.Temp
 
-  # write code to temp file
-  tmp_file = Temp.file()
-  File.write!(tmp_file, unescaped_code)
+  # This refers to a method that gets the chroma binary location from application config
+  @chroma_bin Home73k.app_chroma_bin() |> Path.expand()
 
-  # use chroma to highlight the code via temp file
-  bin_args = ["-l", lang, "-f", "html", "--html-only", "--html-prevent-surrounding-pre", tmp_file]
-  # The '@chroma_bin' module attribute retrieves the configured
-  # location of the chroma cli binary from the application config.
-  {highlighted, _} = System.cmd(@chroma_bin, bin_args)
+  @doc """
+  Highlights all code in HTML (fenced code blocks and inlined code)
+  """
+  def highlight_all(html) do
+    highlight_code_blocks(html) |> highlight_code_inlined()
+  end
 
-  # return properly wrapped highlighted code
-  ~s(
#{highlighted}
) + @doc """ + Highlights fenced code blocks in HTML document + """ + def highlight_code_blocks(html) do + ~r/
([^<]*)<\/code><\/pre>/
+    |> Regex.replace(html, &highlight_code_block(&1, &2, &3))
+  end
+
+  defp highlight_code_block(_full_block, lang, code) do
+    # perform the code highlighting
+    highlighted = highlight_code(lang, code)
+    # return properly wrapped highlighted code
+    ~s(
#{highlighted}
) + end + + @doc """ + Highlights inlined code in HTML document + """ + def highlight_code_inlined(html) do + ~r/([^<]*)<\/code>/ + |> Regex.replace(html, &highlight_code_inline(&1, &2, &3)) + end + + defp highlight_code_inline(_full_block, lang, code) do + # perform the code highlighting + highlighted = highlight_code(lang, code) + # return properly wrapped highlighted code + ~s(#{highlighted}) + end + + # """ + # Performs code highlighting using chroma + # """ + defp highlight_code(lang, code) do + # unescape the code + unescaped_code = unescape_html(code) |> IO.iodata_to_binary() + + # write code to temp file + tmp_file = Temp.file() + File.write!(tmp_file, unescaped_code) + + # use chroma to highlight the code via temp file + bin_args = ["-l", lang, "-f", "html", "--html-only", "--html-prevent-surrounding-pre", tmp_file] + System.cmd(@chroma_bin, bin_args) |> elem(0) + end + + # """ + # Code below for unescaping html, since it's escaped by Earmark markdown parsing + # """ + entities = [{"&", ?&}, {"<", ?<}, {">", ?>}, {""", ?"}, {"'", ?'}] + + for {encoded, decoded} <- entities do + defp unescape_html(unquote(encoded) <> rest) do + [unquote(decoded) | unescape_html(rest)] + end + end + + defp unescape_html(<>) do + [c | unescape_html(rest)] + end + + defp unescape_html(<<>>) do + [] + end end ``` \ No newline at end of file