From bce19303cd873fea1ee082f7c87b407300d38e23 Mon Sep 17 00:00:00 2001 From: m1kadev Date: Sun, 11 Jan 2026 15:32:33 +0100 Subject: [PATCH] kethel: add support for pages/ --- kethel/lib/bricks.ex | 5 ++--- kethel/lib/common.ex | 3 +++ kethel/lib/compilers.ex | 12 ----------- kethel/lib/kernelx.ex | 8 ------- kethel/lib/pages.ex | 47 ++++++++++++++++++++++++++++++++++++++--- kethel/main.exs | 13 +++++++++--- kethel/mix.exs | 4 ++-- kethel/mix.lock | 1 + 8 files changed, 62 insertions(+), 31 deletions(-) create mode 100644 kethel/lib/common.ex delete mode 100644 kethel/lib/compilers.ex delete mode 100644 kethel/lib/kernelx.ex diff --git a/kethel/lib/bricks.ex b/kethel/lib/bricks.ex index e2f57a3264ba25a07031ad68a995428c4cfe916a..da7440c3dbb1fe1b80a47d515bc6a6a86a3fbe67 100644 --- a/kethel/lib/bricks.ex +++ b/kethel/lib/bricks.ex @@ -8,10 +8,9 @@ defmodule Bricks do @typedoc "A valid mustache template" @type mustache() :: binary() - @spec collect(relative_path()) :: [ { brick_name(), mustache() } ] + @spec collect(relative_path()) :: %{ brick_name() => mustache() } def collect(folder) do Path.wildcard(folder <> "/bricks/*.thtml") - |> Enum.map(fn brick -> { brick, File.read!(brick) } end) - |> Enum.map(fn { brick, contents } -> { FileX.trimmed_filename(brick), contents } end) # "../bricks/base_header.html" would beccome just "base_header" + |> Map.new(fn x -> { FileX.trimmed_filename(x), File.read!(x) } end) end end diff --git a/kethel/lib/common.ex b/kethel/lib/common.ex new file mode 100644 index 0000000000000000000000000000000000000000..278247d42c5e37f9f7b601f5c05a8e9f950aa68b --- /dev/null +++ b/kethel/lib/common.ex @@ -0,0 +1,3 @@ +defmodule Context do + defstruct project_root: "/", bricks: %{}, fxg: "fxg" +end diff --git a/kethel/lib/compilers.ex b/kethel/lib/compilers.ex deleted file mode 100644 index 20459beef44760c3d9fd56ba12605500f26c7006..0000000000000000000000000000000000000000 --- a/kethel/lib/compilers.ex +++ /dev/null @@ -1,12 +0,0 @@ -defmodule Compilers do - # TODO - @doc "Creates a random file in /tmp/kethel-TIME/, which functions as a pipe writing the given `data`" - @spec tempdata(binary()) :: binary() - defp tempdata(data) do - end - - - @doc "Invokes the fxg compiler on `data`. provides the output as a binary" - def fxg(path) do - end -end diff --git a/kethel/lib/kernelx.ex b/kethel/lib/kernelx.ex deleted file mode 100644 index 61d513dab459b057b10fff8677a8772e408658cf..0000000000000000000000000000000000000000 --- a/kethel/lib/kernelx.ex +++ /dev/null @@ -1,8 +0,0 @@ -defmodule KernelX do - - @doc "Returns all bytes from `binary`, excluding those before `from`" - @spec binary_slice_from(binary(), pos_integer()) :: binary() - def binary_slice_from(binary, from) do - binary_part(binary, from, byte_size(binary) - from) - end -end diff --git a/kethel/lib/pages.ex b/kethel/lib/pages.ex index a960bce64fabc5661b2a13c70b0bc4c687a35780..c381e476986094854a1c80c240f19e2539b17eea 100644 --- a/kethel/lib/pages.ex +++ b/kethel/lib/pages.ex @@ -1,8 +1,49 @@ +require Rambo + defmodule Pages do + defstruct [ :source_files, :main_template, :project_root, :bricks ] + + def trim_abs_path(path, context) do + begin = byte_size(context.project_root) + byte_size("pages/") # ROOT + pages/ + binary_part(path, begin, byte_size(path) - begin - byte_size(".fxg")) # remove extension + end + + @spec collect(%Context{}) :: %Pages{} def collect(context) do pages_root = context.project_root <> "/pages" - pages = Path.wildcard(pages_root <> "/**/*.fxg") - |> Enum.map(fn path -> { path, File.read!(path) } end) - |> Enum.map(fn { path, data } -> { KernelX.binary_slice_from(path, byte_size(pages_root)), data } end) + %Pages{ + project_root: context.project_root, + source_files: Path.wildcard(pages_root <> "/**/*.fxg") + |> Enum.map(fn path -> { path, File.read!(path) } end) + |> Enum.map(fn { path, data } -> { trim_abs_path(path, context), data } end), + main_template: File.read!(context.project_root <> "/templates/base.thtml"), + bricks: context.bricks, + } + end + + @spec compile(%Pages{}) :: none() + def compile(pages) do + Task.async_stream(pages.source_files, fn page -> compile_file(pages, page) end) |> Enum.to_list() + end + + defp compile_file(pages, { page_path, page_data }) do + output_path = "#{pages.project_root}/build#{page_path}.html" + IO.puts("#{pages.project_root}/pages#{page_path}.fxg -> #{output_path}") + # check for a custom template + custom_template_path = "#{pages.project_root}/templates/pages#{page_path}.thtml" + template = case File.read(custom_template_path) do + {:ok, custom_template} -> custom_template + {:error, :enoent} -> pages.main_template + {:error, err } -> raise err + end + mustache_context = Map.merge(%{fxg_content: invoke_fxg(page_data), location: page_path}, pages.bricks) + page_rendered = Mustache.render(template, mustache_context) + File.write!(output_path, page_rendered) + end + + # TODO: switch this to Rambo + defp invoke_fxg(data) do + {:ok, res} = Rambo.run("fxg", ["-"], in: data) + res.out end end diff --git a/kethel/main.exs b/kethel/main.exs index 7a8b960b0bb84a5214cd1d1f50fb2bd2b4544837..ba3b05782b109b04cb853731b0b69dfb2acd97b0 100644 --- a/kethel/main.exs +++ b/kethel/main.exs @@ -4,11 +4,18 @@ folder = Enum.at(args, 1) if folder != nil do project_root = Path.expand(folder) - context = %{ - project_root: project_root, - bricks: Bricks.collect(project_root) + context = %Context{ + project_root: project_root, + bricks: Bricks.collect(project_root), + fxg: System.find_executable("fxg") } + + File.rm_rf!("#{project_root}/build") + File.mkdir_p("#{project_root}/build") + pages = Pages.collect(context) + + Pages.compile(pages) else IO.puts(:stderr, "The root project folder should be provided on the command line") end diff --git a/kethel/mix.exs b/kethel/mix.exs index 82ffd4011bf0591db0e702eb82b02156e4d1e4f7..19ae81ac927c9c81fc10092239b18c418cebac5f 100644 --- a/kethel/mix.exs +++ b/kethel/mix.exs @@ -13,8 +13,8 @@ defmodule Kethel.MixProject do defp deps do [ - { :tzdata, "~> 1.1" }, - { :mustache, "~> 0.5.0" } + { :mustache, "~> 0.5.0" }, + {:rambo, "~> 0.3.4" }, ] end end diff --git a/kethel/mix.lock b/kethel/mix.lock index 9ddcd5613d09cc2d5bd542d33a83762118125347..633e3d3b24e00c1af57d074ea0b7b05f1f6b625e 100644 --- a/kethel/mix.lock +++ b/kethel/mix.lock @@ -6,6 +6,7 @@ "mimerl": {:hex, :mimerl, "1.4.0", "3882a5ca67fbbe7117ba8947f27643557adec38fa2307490c4c4207624cb213b", [:rebar3], [], "hexpm", "13af15f9f68c65884ecca3a3891d50a7b57d82152792f3e19d88650aa126b144"}, "mustache": {:hex, :mustache, "0.5.1", "1bfee8a68a8e72d47616541a93a632ae1684c1abc17c9eb5f7bfecb78d7496e5", [:mix], [], "hexpm", "524c9bbb6080a52d7b6806436b4e269e0224c785a228faf3293ef30a75016bfa"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, + "rambo": {:hex, :rambo, "0.3.4", "8962ac3bd1a633ee9d0e8b44373c7913e3ce3d875b4151dcd060886092d2dce7", [:mix], [], "hexpm", "0cc54ed089fbbc84b65f4b8a774224ebfe60e5c80186fafc7910b3e379ad58f1"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "tzdata": {:hex, :tzdata, "1.1.3", "b1cef7bb6de1de90d4ddc25d33892b32830f907e7fc2fccd1e7e22778ab7dfbc", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "d4ca85575a064d29d4e94253ee95912edfb165938743dbf002acdf0dcecb0c28"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"},