Skip to content

Commit

Permalink
Add file watcher for development with live reload (#7)
Browse files Browse the repository at this point in the history
* Add file watcher for development with live reload
* add linter (credo)
* upgrade Erlang and Elixir versions in CI
  • Loading branch information
goncalotomas authored Dec 13, 2023
1 parent 767b6d4 commit f1a2b0a
Show file tree
Hide file tree
Showing 17 changed files with 201 additions and 165 deletions.
16 changes: 6 additions & 10 deletions .github/workflows/elixir.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ jobs:
# Specify the OTP and Elixir versions to use when building
# and running the workflow steps.
matrix:
otp: ['25.3'] # Define the OTP version [required]
elixir: ['1.14'] # Define the elixir version [required]
otp: ['26.1'] # Define the OTP version [required]
elixir: ['1.15'] # Define the elixir version [required]
steps:
# Step: Setup Elixir + Erlang image as the base.
- name: Set up Elixir
Expand Down Expand Up @@ -65,22 +65,18 @@ jobs:
${{ runner.os }}-mix-${{ env.cache-name }}-
${{ runner.os }}-mix-
# Step: Download project dependencies. If unchanged, uses
# the cached version.
# If unchanged, uses the cached version.
- name: Install dependencies
run: mix deps.get

# Step: Compile the project treating any warnings as errors.
# Customize this step if a different behavior is desired.
- name: Compiles without warnings
run: mix compile --warnings-as-errors

# Step: Check that the checked in code has already been formatted.
# This step fails if something was found unformatted.
# Customize this step as desired.
- name: Check Formatting
run: mix format --check-formatted

# Step: Execute the tests.
- name: Lint
run: mix credo

- name: Run tests
run: mix test --no-start
6 changes: 3 additions & 3 deletions installer/lib/grf_new/generator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ defmodule Grf.New.Generator do
{"/config/config.exs", "/config/config.exs"},

{"/lib/app_name.ex", "/lib/#{project.app_name}.ex"},
{"/priv/content/index.md", "/priv/content/index.md"},
{"/priv/content/index.md", "/src/index.md"},
{"/priv/layouts/default.html.eex", "/lib/layouts/default.html.eex"},

{"/gitignore", "/.gitignore"},
Expand Down Expand Up @@ -54,10 +54,10 @@ defmodule Grf.New.Generator do

defp eex_metadata(project) do
[
griffin_dep: "{:griffin_ssg, \"~> 0.1\"}",
griffin_dep: "{:griffin_ssg, \"~> 0.3\"}",
griffin_github_version_tag: @version,
output_path: "_site",
input_path: "priv",
input_path: "src",
app_module: project.module,
app_name: project.app_name,
version: project.version
Expand Down
3 changes: 2 additions & 1 deletion installer/priv/templates/config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import Config

config :griffin_ssg,
input_path: "<%= @input_path %>",
output_path: "<%= @output_path %>"
output_path: "<%= @output_path %>",
passthrough_copies: ["assets/*"]

# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
Expand Down
3 changes: 2 additions & 1 deletion installer/priv/templates/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ defmodule <%= @app_module %>.MixProject do
defp deps do
[
<%= @griffin_dep %>,
{:plug_cowboy, "~> 2.5"}
{:plug_live_reload, "~> 0.1"},
{:file_system, "~> 1.0", override: true},
]
end

Expand Down
2 changes: 1 addition & 1 deletion installer/priv/templates/priv/content/index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: "<%= @app_name %>"
layout: "default"
layout: "default.html"
---

Quick static site generation with Elixir templates 🧡
2 changes: 1 addition & 1 deletion installer/priv/templates/priv/layouts/default.html.eex
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<h1 class="display-5 fw-bold">Welcome to Griffin!</h1>
<div class="col-lg-6 mx-auto">
<p class="lead mb-4">
Quick static site generation with Elixir templates 🧡
<%%= @content %>
</p>
</div>
</div>
Expand Down
4 changes: 3 additions & 1 deletion lib/griffin_ssg.ex
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,12 @@ defmodule GriffinSSG do
assigns
|> Map.put(:content, content)
# here we're re-rendering all existing partials when we might only need a very small subset.
# TODO render only required partials by looking at args in the quoted expression for `layout`
# refactor: render only required partials by looking at args in the quoted expression for `layout`
|> then(fn current_assigns ->
if rerender_partials do
Map.update(current_assigns, :partials, %{}, fn partials ->
# refactor: reduce nesting level by pulling parts into separate functions.
# credo:disable-for-lines:3
partials
|> Enum.map(fn partial ->
{compiled, _bindings} = Code.eval_quoted(partial, assigns: current_assigns)
Expand Down
3 changes: 1 addition & 2 deletions lib/griffin_ssg/filesystem.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ defmodule GriffinSSG.Filesystem do
and only some of the files might have been copied.
"""
def copy_all(files, destination) do
## TODO LOOP OVER FILES AND EITHER CALL CP_R OR CP,
## THIS CODE DOES NOT WORK WITH RELATIVE AND ABSOLUTE PATHS
# refactor: this code does not work with both relative and absolute paths.
files
|> Enum.flat_map(&list_all(&1))
|> Enum.reduce({:ok, 0}, fn path, acc ->
Expand Down
32 changes: 32 additions & 0 deletions lib/griffin_ssg/filesystem/watcher.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
defmodule GriffinSSG.Filesystem.Watcher do
@moduledoc """
Module for non-named GenServer responsible for watching for changes.
Executes a generic callback when file changes are detected.
Used for the LiveReload HTTPServer that is launched as part of `mix grf.server`.
"""
use GenServer

@swap_file_extnames [".swp", ".swx"]

def start_link([directories, callback]) do
GenServer.start_link(__MODULE__, {directories, callback})
end

def init({directories, callback}) do
{:ok, pid} = FileSystem.start_link(dirs: directories)
FileSystem.subscribe(pid)
{:ok, %{callback: callback}}
end

def handle_info({:file_event, _watcher_pid, {file_path, [:modified, :closed]}}, state) do
unless Path.extname(file_path) in @swap_file_extnames do
state.callback.()
end

{:noreply, state}
end

def handle_info({:file_event, _, _}, state) do
{:noreply, state}
end
end
50 changes: 7 additions & 43 deletions lib/griffin_ssg/layouts.ex
Original file line number Diff line number Diff line change
@@ -1,52 +1,14 @@
defmodule GriffinSSG.Layouts do
@moduledoc """
Module responsible for compiling layouts. Stores compiled layouts in an ETS table.
Supports nested layouts with a maximum nesting depth of 10.
"""

@layout_extnames [".eex"]
@layouts_max_nesting_level 10
@compiled_layouts_table :griffin_build_layouts
@layout_strings_table :griffin_build_layout_strings

# def render(layout, options) do
# assigns = Map.get(options, :assigns, %{})
# rerender_partials = Map.get(options, :rerender_partials, true)

# content =
# options
# |> Map.fetch!(:content)
# |> EEx.eval_string(assigns: assigns)
# |> then(fn content_string ->
# case Map.get(options, :content_type, ".md") do
# md when md in [".md", ".markdown"] ->
# Earmark.as_html!(content_string)

# ".eex" ->
# content_string
# end
# end)

# layout_assigns =
# assigns
# |> Map.put(:content, content)
# # here we're re-rendering all existing partials when we might only need a very small subset.
# # TODO render only required partials by looking at args in the quoted expression for `layout`
# |> then(fn current_assigns ->
# if rerender_partials do
# Map.update(current_assigns, :partials, %{}, fn partials ->
# partials
# |> Enum.map(fn partial ->
# {compiled, _bindings} = Code.eval_quoted(partial, assigns: current_assigns)
# compiled
# end)
# |> Enum.into(%{})
# end)
# else
# current_assigns
# end
# end)
# |> Enum.to_list()

# {result, _bindings} = Code.eval_quoted(layout, assigns: layout_assigns)
# result
# end

def compile_layouts(layouts_dir) do
try do
:ets.new(@compiled_layouts_table, [:ordered_set, :public, :named_table])
Expand Down Expand Up @@ -223,6 +185,8 @@ defmodule GriffinSSG.Layouts do

pattern =
Enum.reduce(content_patterns, "<%= @content %>", fn pattern, acc ->
# refactor: reduce nesting level by pulling parts into separate functions.
# credo:disable-for-next-line
if String.contains?(parent_layout, pattern) do
pattern
else
Expand Down
24 changes: 0 additions & 24 deletions lib/griffin_ssg_app.ex

This file was deleted.

18 changes: 9 additions & 9 deletions lib/mix/tasks/grf.build.ex
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ defmodule Mix.Tasks.Grf.Build do
collections =
opts.collections
|> Enum.map(fn {collection_name, config} ->
# terrible efficiency, we're traversing the parsed list files
# refactor: terrible efficiency, we're traversing the parsed list files
# once per collection. Since most sites will have 1-2 collections max,
# we're fine with this for now.
{collection_name,
Expand All @@ -404,7 +404,7 @@ defmodule Mix.Tasks.Grf.Build do

tasks =
for metadata <- parsed_files do
# TODO consider setting collections globally on ETS or persistent term
# refactor: consider setting collections globally on ETS or persistent term
Task.Supervisor.async_nolink(
sup,
__MODULE__,
Expand Down Expand Up @@ -547,8 +547,9 @@ defmodule Mix.Tasks.Grf.Build do
output
end

# refactor: this function shares much of the logic of render_file.
@doc false
def render_file22222(
def render_collection_file(
file,
%{
page: page,
Expand Down Expand Up @@ -609,7 +610,7 @@ defmodule Mix.Tasks.Grf.Build do
# render /tags/ page listing all tags
# render /tags/:tag page listing all pages with that tag
for {collection_name, collection_values} <- collections do
render_file22222(
render_collection_file(
opts.output <> "/#{collection_name}/index.html",
%{
page: nil,
Expand All @@ -627,7 +628,7 @@ defmodule Mix.Tasks.Grf.Build do
for {collection_value, collection_value_pages} <- collection_values do
collection_value = collection_value |> Atom.to_string() |> Slug.slugify()

render_file22222(
render_collection_file(
opts.output <> "/#{collection_name}/#{collection_value}/index.html",
%{
page: nil,
Expand Down Expand Up @@ -700,6 +701,8 @@ defmodule Mix.Tasks.Grf.Build do
|> maybe_parse_csv()
|> Filesystem.copy_all(opts.output)
|> then(fn result ->
# refactor: reduce nesting level by pulling parts into separate functions.
# credo:disable-for-next-line
case result do
{:ok, count} -> count
{:errors, list_errors} -> Mix.raise(hd(list_errors))
Expand Down Expand Up @@ -747,10 +750,7 @@ defmodule Mix.Tasks.Grf.Build do
Mix.shell().info("#{string_col_name}: #{collections_pretty_print}")

for {value, files} <- collections do
files_pretty_print =
files
|> Enum.map(fn metadata -> metadata.input end)
|> Enum.join(",")
files_pretty_print = Enum.map_join(files, ",", fn metadata -> metadata.input end)

Mix.shell().info("#{string_col_name} #{value}: #{files_pretty_print}")
end
Expand Down
Loading

0 comments on commit f1a2b0a

Please sign in to comment.