Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 8 additions & 15 deletions .github/workflows/auto-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
- master

permissions:
contents: write
contents: read
pull-requests: write

jobs:
Expand All @@ -18,21 +18,14 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Create Pull Request
id: cpr
uses: peter-evans/create-pull-request@v7
uses: repo-sync/pull-request@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "Auto-created PR from ${{ github.ref_name }}"
title: "${{ github.ref_name }}"
body: |
This PR was automatically created from branch `${{ github.ref_name }}`.
branch: ${{ github.ref_name }}
base: main
assignees: ${{ github.actor }}
draft: false
delete-branch: false
source_branch: ${{ github.ref_name }}
destination_branch: main
pr_title: ${{ github.ref_name }}
pr_body: "This PR was automatically created from branch `${{ github.ref_name }}`."
pr_assignee: ${{ github.actor }}
github_token: ${{ secrets.GITHUB_TOKEN }}

75 changes: 56 additions & 19 deletions lib/then.ex
Original file line number Diff line number Diff line change
Expand Up @@ -89,46 +89,83 @@ defmodule Then do
Module.delete_attribute(env.module, :then)
arity = length(args)

existing_functions = Module.get_attribute(env.module, :functions_with_then) || []
function_key = {function_name, arity}
validated_callback = validate_callback_format(then_callback, env)

if Enum.any?(existing_functions, fn {name, ar, _callback} -> {name, ar} == function_key end) do
existing_functions = Module.get_attribute(env.module, :functions_with_then, [])

if function_already_has_then?(existing_functions, function_name, arity) do
raise CompileError,
file: env.file,
line: env.line,
description: "Multiple @then attributes for function #{function_name}/#{arity}. " <>
"Only one @then per function is allowed."
end

Module.put_attribute(env.module, :functions_with_then, {function_name, arity, then_callback})
Module.put_attribute(env.module, :functions_with_then, {function_name, arity, validated_callback})
end
end

def __on_definition__(_env, _kind, _function_name, _args, _guards, _body) do
:ok
end

defmacro __before_compile__(env) do
functions_with_then = Module.get_attribute(env.module, :functions_with_then) || []
defp function_already_has_then?(existing_functions, function_name, arity) do
Enum.any?(existing_functions, fn {name, ar, _callback} ->
{name, ar} == {function_name, arity}
end)
end

wrapped_functions =
functions_with_then
|> Enum.map(fn {function_name, arity, callback_name} ->
args = Macro.generate_arguments(arity, __MODULE__)
defp validate_callback_format(callback, env) do
case callback do
atom when is_atom(atom) ->
{:local, atom}

quote do
defoverridable [{unquote(function_name), unquote(arity)}]
{module, function} when is_atom(module) and is_atom(function) ->
{:external, module, function}

def unquote(function_name)(unquote_splicing(args)) do
result = super(unquote_splicing(args))
unquote(callback_name)(result)
result
end
end
end)
_ ->
raise CompileError,
file: env.file,
line: env.line,
description: "Invalid @then format. Expected :function_name or {Module, :function_name}, got: #{inspect(callback)}"
end
end

defmacro __before_compile__(env) do
wrapped_functions =
env.module
|> Module.get_attribute(:functions_with_then, [])
|> Enum.map(&generate_wrapped_function/1)

quote do
unquote_splicing(wrapped_functions)
end
end

defp generate_wrapped_function({function_name, arity, callback_spec}) do
args = Macro.generate_arguments(arity, __MODULE__)
callback_call = generate_callback_call(callback_spec)

quote do
defoverridable [{unquote(function_name), unquote(arity)}]

def unquote(function_name)(unquote_splicing(args)) do
result = super(unquote_splicing(args))
unquote(callback_call)
result
end
end
end

defp generate_callback_call({:local, function_name}) do
quote do
unquote(function_name)(result)
end
end

defp generate_callback_call({:external, module, function_name}) do
quote do
unquote(module).unquote(function_name)(result)
end
end
end