Current section
Files
Jump to
Current section
Files
lib/belodon.ex
defmodule Belodon do
@moduledoc """
**Belodon** is a utility module designed to process inputs and
dynamically execute problem-solving functions based on provided modules.
It supports two flows:
- **Direct Input Flow:** Takes a raw input string along with a part indicator (`:part1` or `:part2`),
processes the input, and then invokes the corresponding `execute/1` function in the submodule.
- **Auto Input Flow:** Extracts numerical identifiers (typically representing a year and day)
from the module's name, fetches input data via `Belodon.Input.get/2`, and execute the solution.
## Usage
To use this module, create your own module using `mix belodon.create`. Code will be generated in the
test folder using the module
"""
@doc """
Hello world.
## Examples
iex> Belodon.hello()
:world
"""
@spec hello() :: :world
def hello do
:world
end
# Internal helper to prepare the input by splitting into lines and dropping the last one
@doc false
defp prepare(input) do
input
|> String.split("\n")
|> Enum.drop(-1)
end
@doc """
Processes a raw input string and executes the corresponding solution function.
The process involves:
1. **Preparation:** The input is split into a list of lines and the last (potentially empty) line is removed.
2. **Execution:** Based on the `part` parameter, it dynamically quotes and evaluates code to call
the `execute/1` function from the given module's respective nested module (`Part1` or `Part2`).
### Parameters
- `module`: The module that contains nested submodules (`Part1` and `Part2`) with an `execute/1` function.
- `input`: A multi-line string with the problem input.
- `part`: An atom: either `:part1` or `:part2`, which selects the corresponding solution.
"""
@spec solve(module(), binary(), :part1 | :part2) :: any()
def solve(module, input, part) do
input
|> prepare()
|> call(module, part)
end
# These functions create a quoted expression that dynamically calls the `execute/1` function
# on the respective part of the provided module
@doc false
defp execute(module, :part1) do
quote do
input
|> var!()
|> unquote(module).Part1.execute()
end
end
@doc false
defp execute(module, :part2) do
quote do
input
|> var!()
|> unquote(module).Part2.execute()
end
end
# Internal function that evaluates the quoted expression to execute the code
@doc false
defp call(input, module, part) do
module
|> execute(part)
|> Code.eval_quoted(input: input)
|> elem(0)
end
@doc """
Automatically retrieves the input based on the module's name and executes the solution.
This function is tailored for scenarios where your module's name includes numerical identifiers
(typically a year and a day). It performs the following steps:
1. **Extract Identifiers:** Uses a regular expression to find numbers in the module name.
2. **Clean Up:** Trims any leading zeros from the extracted year and day.
3. **Fetch Input:** Retrieves the problem input using `Belodon.Input.get/2`, passing the day.
4. **Execution:** Processes the input similar to the direct flow and calls the appropriate solution.
## Parameters
- `module`: The module containing the solution submodules (`Part1` and `Part2`) with embedded year and day.
- `part`: An atom, either `:part1` or `:part2`, to select which solution to run.
"""
@spec solve(module(), :part1 | :part2) :: any()
def solve(module, part) do
[[dirty_year], [dirty_day]] = Regex.scan(~r/\d+/, to_string(module), capture: :first)
[year, day] = Enum.map([dirty_year, dirty_day], &String.trim_leading(&1, "0"))
year
|> Belodon.Input.get(day)
|> String.split("\n")
|> call(module, part)
end
end