aboutsummaryrefslogtreecommitdiff
path: root/aoc2023/build/packages/adglent/src
diff options
context:
space:
mode:
authorJ.J <thechairman@thechairman.info>2024-05-30 21:50:02 -0400
committerJ.J <thechairman@thechairman.info>2024-05-30 21:50:02 -0400
commit612fd986ab1e00b6d34dc1937136250e08e89325 (patch)
treea3c93952040c6afdf348b5831619a45db7ba0a2e /aoc2023/build/packages/adglent/src
parent231c2b688d1e6cf0846d46e883da30e042a9c6cf (diff)
downloadgleam_aoc-612fd986ab1e00b6d34dc1937136250e08e89325.tar.gz
gleam_aoc-612fd986ab1e00b6d34dc1937136250e08e89325.zip
cleanup
Diffstat (limited to 'aoc2023/build/packages/adglent/src')
-rw-r--r--aoc2023/build/packages/adglent/src/adglent.app.src45
-rw-r--r--aoc2023/build/packages/adglent/src/adglent.erl55
-rw-r--r--aoc2023/build/packages/adglent/src/adglent.gleam56
-rw-r--r--aoc2023/build/packages/adglent/src/adglent/day.gleam126
-rw-r--r--aoc2023/build/packages/adglent/src/adglent/init.gleam110
-rw-r--r--aoc2023/build/packages/adglent/src/adglent@day.erl278
-rw-r--r--aoc2023/build/packages/adglent/src/adglent@init.erl142
-rw-r--r--aoc2023/build/packages/adglent/src/adglent_ffi.erl12
-rw-r--r--aoc2023/build/packages/adglent/src/priv/aoc_client.gleam37
-rw-r--r--aoc2023/build/packages/adglent/src/priv/errors.gleam54
-rw-r--r--aoc2023/build/packages/adglent/src/priv/prompt.gleam38
-rw-r--r--aoc2023/build/packages/adglent/src/priv/template.gleam18
-rw-r--r--aoc2023/build/packages/adglent/src/priv/templates/solution.gleam27
-rw-r--r--aoc2023/build/packages/adglent/src/priv/templates/test_main.gleam7
-rw-r--r--aoc2023/build/packages/adglent/src/priv/templates/testfile_gleeunit.gleam41
-rw-r--r--aoc2023/build/packages/adglent/src/priv/templates/testfile_showtime.gleam41
-rw-r--r--aoc2023/build/packages/adglent/src/priv/toml.gleam52
-rw-r--r--aoc2023/build/packages/adglent/src/priv@aoc_client.erl61
-rw-r--r--aoc2023/build/packages/adglent/src/priv@errors.erl74
-rw-r--r--aoc2023/build/packages/adglent/src/priv@prompt.erl53
-rw-r--r--aoc2023/build/packages/adglent/src/priv@template.erl25
-rw-r--r--aoc2023/build/packages/adglent/src/priv@templates@solution.erl1
-rw-r--r--aoc2023/build/packages/adglent/src/priv@templates@test_main.erl1
-rw-r--r--aoc2023/build/packages/adglent/src/priv@templates@testfile_gleeunit.erl1
-rw-r--r--aoc2023/build/packages/adglent/src/priv@templates@testfile_showtime.erl1
-rw-r--r--aoc2023/build/packages/adglent/src/priv@toml.erl83
-rw-r--r--aoc2023/build/packages/adglent/src/showtime.erl155
-rw-r--r--aoc2023/build/packages/adglent/src/showtime.gleam116
-rw-r--r--aoc2023/build/packages/adglent/src/showtime/internal/common/cli.gleam5
-rw-r--r--aoc2023/build/packages/adglent/src/showtime/internal/common/common_event_handler.gleam101
-rw-r--r--aoc2023/build/packages/adglent/src/showtime/internal/common/test_result.gleam119
-rw-r--r--aoc2023/build/packages/adglent/src/showtime/internal/common/test_suite.gleam63
-rw-r--r--aoc2023/build/packages/adglent/src/showtime/internal/erlang/discover.gleam167
-rw-r--r--aoc2023/build/packages/adglent/src/showtime/internal/erlang/event_handler.gleam91
-rw-r--r--aoc2023/build/packages/adglent/src/showtime/internal/erlang/module_handler.gleam43
-rw-r--r--aoc2023/build/packages/adglent/src/showtime/internal/erlang/runner.gleam59
-rw-r--r--aoc2023/build/packages/adglent/src/showtime/internal/reports/compare.gleam42
-rw-r--r--aoc2023/build/packages/adglent/src/showtime/internal/reports/formatter.gleam480
-rw-r--r--aoc2023/build/packages/adglent/src/showtime/internal/reports/styles.gleam84
-rw-r--r--aoc2023/build/packages/adglent/src/showtime/internal/reports/table.gleam148
-rw-r--r--aoc2023/build/packages/adglent/src/showtime/tests/meta.gleam3
-rw-r--r--aoc2023/build/packages/adglent/src/showtime/tests/should.gleam113
-rw-r--r--aoc2023/build/packages/adglent/src/showtime/tests/test.gleam57
-rw-r--r--aoc2023/build/packages/adglent/src/showtime@internal@common@cli.erl8
-rw-r--r--aoc2023/build/packages/adglent/src/showtime@internal@common@common_event_handler.erl131
-rw-r--r--aoc2023/build/packages/adglent/src/showtime@internal@common@test_result.erl54
-rw-r--r--aoc2023/build/packages/adglent/src/showtime@internal@common@test_suite.erl30
-rw-r--r--aoc2023/build/packages/adglent/src/showtime@internal@erlang@discover.erl230
-rw-r--r--aoc2023/build/packages/adglent/src/showtime@internal@erlang@event_handler.erl76
-rw-r--r--aoc2023/build/packages/adglent/src/showtime@internal@erlang@module_handler.erl53
-rw-r--r--aoc2023/build/packages/adglent/src/showtime@internal@erlang@runner.erl46
-rw-r--r--aoc2023/build/packages/adglent/src/showtime@internal@reports@compare.erl61
-rw-r--r--aoc2023/build/packages/adglent/src/showtime@internal@reports@formatter.erl749
-rw-r--r--aoc2023/build/packages/adglent/src/showtime@internal@reports@styles.erl93
-rw-r--r--aoc2023/build/packages/adglent/src/showtime@internal@reports@table.erl229
-rw-r--r--aoc2023/build/packages/adglent/src/showtime@tests@meta.erl8
-rw-r--r--aoc2023/build/packages/adglent/src/showtime@tests@should.erl143
-rw-r--r--aoc2023/build/packages/adglent/src/showtime@tests@test.erl57
-rw-r--r--aoc2023/build/packages/adglent/src/showtime_ffi.erl187
59 files changed, 5440 insertions, 0 deletions
diff --git a/aoc2023/build/packages/adglent/src/adglent.app.src b/aoc2023/build/packages/adglent/src/adglent.app.src
new file mode 100644
index 0000000..aa44f8b
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/adglent.app.src
@@ -0,0 +1,45 @@
+{application, adglent, [
+ {vsn, "1.2.0"},
+ {applications, [gap,
+ gleam_community_ansi,
+ gleam_erlang,
+ gleam_http,
+ gleam_httpc,
+ gleam_otp,
+ gleam_stdlib,
+ gleeunit,
+ glint,
+ simplifile,
+ snag,
+ tom]},
+ {description, "Advent of code helper - automating setup of tests, solution template and problem input"},
+ {modules, [adglent,
+ adglent@day,
+ adglent@init,
+ priv@aoc_client,
+ priv@errors,
+ priv@prompt,
+ priv@template,
+ priv@templates@solution,
+ priv@templates@test_main,
+ priv@templates@testfile_gleeunit,
+ priv@templates@testfile_showtime,
+ priv@toml,
+ showtime,
+ showtime@internal@common@cli,
+ showtime@internal@common@common_event_handler,
+ showtime@internal@common@test_result,
+ showtime@internal@common@test_suite,
+ showtime@internal@erlang@discover,
+ showtime@internal@erlang@event_handler,
+ showtime@internal@erlang@module_handler,
+ showtime@internal@erlang@runner,
+ showtime@internal@reports@compare,
+ showtime@internal@reports@formatter,
+ showtime@internal@reports@styles,
+ showtime@internal@reports@table,
+ showtime@tests@meta,
+ showtime@tests@should,
+ showtime@tests@test]},
+ {registered, []}
+]}.
diff --git a/aoc2023/build/packages/adglent/src/adglent.erl b/aoc2023/build/packages/adglent/src/adglent.erl
new file mode 100644
index 0000000..e9df2b7
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/adglent.erl
@@ -0,0 +1,55 @@
+-module(adglent).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([inspect/1, get_input/1, get_test_folder/1, start_arguments/0, get_part/0]).
+-export_type([example/1, problem/0, charlist/0]).
+
+-type example(OFF) :: {example, binary(), OFF}.
+
+-type problem() :: first | second.
+
+-type charlist() :: any().
+
+-spec inspect(any()) -> binary().
+inspect(Value) ->
+ Inspected_value = gleam@string:inspect(Value),
+ case begin
+ _pipe = Inspected_value,
+ gleam@string:starts_with(_pipe, <<"\""/utf8>>)
+ end of
+ true ->
+ _pipe@1 = Inspected_value,
+ _pipe@2 = gleam@string:drop_left(_pipe@1, 1),
+ gleam@string:drop_right(_pipe@2, 1);
+
+ false ->
+ Inspected_value
+ end.
+
+-spec get_input(binary()) -> {ok, binary()} | {error, simplifile:file_error()}.
+get_input(Day) ->
+ simplifile:read(
+ <<<<"src/day"/utf8, Day/binary>>/binary, "/input.txt"/utf8>>
+ ).
+
+-spec get_test_folder(binary()) -> binary().
+get_test_folder(Day) ->
+ <<"test/day"/utf8, Day/binary>>.
+
+-spec start_arguments() -> list(binary()).
+start_arguments() ->
+ _pipe = init:get_plain_arguments(),
+ gleam@list:map(_pipe, fun unicode:characters_to_binary/1).
+
+-spec get_part() -> {ok, problem()} | {error, nil}.
+get_part() ->
+ case start_arguments() of
+ [<<"1"/utf8>>] ->
+ {ok, first};
+
+ [<<"2"/utf8>>] ->
+ {ok, second};
+
+ _ ->
+ {error, nil}
+ end.
diff --git a/aoc2023/build/packages/adglent/src/adglent.gleam b/aoc2023/build/packages/adglent/src/adglent.gleam
new file mode 100644
index 0000000..077d49d
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/adglent.gleam
@@ -0,0 +1,56 @@
+import simplifile.{type FileError}
+import gleam/list
+import gleam/string
+
+pub type Example(a) {
+ Example(input: String, answer: a)
+}
+
+pub fn inspect(value: a) -> String {
+ let inspected_value = string.inspect(value)
+ case
+ inspected_value
+ |> string.starts_with("\"")
+ {
+ True ->
+ inspected_value
+ |> string.drop_left(1)
+ |> string.drop_right(1)
+ False -> inspected_value
+ }
+}
+
+pub fn get_input(day: String) -> Result(String, FileError) {
+ simplifile.read("src/day" <> day <> "/input.txt")
+}
+
+pub fn get_test_folder(day: String) -> String {
+ "test/day" <> day
+}
+
+pub type Problem {
+ First
+ Second
+}
+
+pub fn get_part() -> Result(Problem, Nil) {
+ case start_arguments() {
+ ["1"] -> Ok(First)
+ ["2"] -> Ok(Second)
+ _ -> Error(Nil)
+ }
+}
+
+pub fn start_arguments() -> List(String) {
+ get_start_arguments()
+ |> list.map(to_string)
+}
+
+type Charlist
+
+/// Transform a charlist to a string
+@external(erlang, "unicode", "characters_to_binary")
+fn to_string(a: Charlist) -> String
+
+@external(erlang, "init", "get_plain_arguments")
+fn get_start_arguments() -> List(Charlist)
diff --git a/aoc2023/build/packages/adglent/src/adglent/day.gleam b/aoc2023/build/packages/adglent/src/adglent/day.gleam
new file mode 100644
index 0000000..69e4ccc
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/adglent/day.gleam
@@ -0,0 +1,126 @@
+import adglent
+import gleam/string
+import gleam/result
+import priv/template
+import priv/templates/testfile_gleeunit
+import priv/templates/testfile_showtime
+import priv/templates/solution
+import priv/toml
+import priv/aoc_client
+import priv/errors
+import simplifile
+
+pub fn main() {
+ let day =
+ case adglent.start_arguments() {
+ [day] -> Ok(day)
+ args -> Error("Expected day - found: " <> string.join(args, ", "))
+ }
+ |> errors.map_error("Error when parsing command args")
+ |> errors.print_error
+ |> errors.assert_ok
+
+ let aoc_toml =
+ simplifile.read("aoc.toml")
+ |> errors.map_error("Could not read aoc.toml")
+ |> errors.print_error
+ |> errors.assert_ok
+
+ let aoc_toml_version = toml.get_int(aoc_toml, ["version"])
+ let year =
+ toml.get_string(aoc_toml, ["year"])
+ |> errors.map_error("Could not read \"year\" from aoc.toml")
+ |> errors.print_error
+ |> errors.assert_ok
+ let session =
+ toml.get_string(aoc_toml, ["session"])
+ |> errors.map_error("Could not read \"session\" from aoc.toml")
+ |> errors.print_error
+ |> errors.assert_ok
+
+ let showtime = case aoc_toml_version {
+ Ok(2) -> {
+ toml.get_bool(aoc_toml, ["showtime"])
+ |> errors.map_error("Could not read \"showtime\" from aoc.toml")
+ |> errors.print_error
+ |> errors.assert_ok
+ }
+ _ ->
+ toml.get_string(aoc_toml, ["showtime"])
+ |> result.map(fn(bool_string) {
+ case bool_string {
+ "True" -> True
+ _ -> False
+ }
+ })
+ |> errors.map_error("Could not read \"showtime\" from aoc.toml")
+ |> errors.print_error
+ |> errors.assert_ok
+ }
+
+ let test_folder = adglent.get_test_folder(day)
+ let test_file = test_folder <> "/day" <> day <> "_test.gleam"
+
+ simplifile.create_directory_all(test_folder)
+ |> errors.map_error("Could not create folder \"" <> test_folder <> "\"")
+ |> errors.print_error
+ |> errors.assert_ok
+
+ let testfile_template = case showtime {
+ True -> testfile_showtime.template
+ False -> testfile_gleeunit.template
+ }
+
+ template.render(testfile_template, [#("day", day)])
+ |> create_file_if_not_present(test_file)
+ |> errors.print_result
+ |> errors.assert_ok
+
+ let solutions_folder = "src/day" <> day
+ let solution_file = solutions_folder <> "/solve.gleam"
+
+ simplifile.create_directory_all(solutions_folder)
+ |> errors.map_error("Could not create folder \"" <> solutions_folder <> "\"")
+ |> errors.print_error
+ |> errors.assert_ok
+
+ template.render(solution.template, [#("day", day)])
+ |> create_file_if_not_present(solution_file)
+ |> errors.print_result
+ |> errors.assert_ok
+
+ create_file_if_not_present("input.txt", solutions_folder <> "/.gitignore")
+ |> errors.print_result
+ |> errors.assert_ok
+
+ let input =
+ aoc_client.get_input(year, day, session)
+ |> errors.map_error("Error when fetching input")
+ |> errors.print_error
+ |> errors.assert_ok
+
+ input
+ |> string.trim
+ |> create_file_if_not_present(solutions_folder <> "/input.txt")
+ |> errors.print_result
+ |> errors.assert_ok
+}
+
+fn create_file_if_not_present(
+ content: String,
+ path: String,
+) -> Result(String, String) {
+ case simplifile.is_file(path) {
+ True -> {
+ Ok(path <> " already exists - skipped")
+ }
+ False -> {
+ use _ <- result.try(
+ simplifile.create_file(path)
+ |> errors.map_messages("Created " <> path, "Could not create " <> path),
+ )
+ simplifile.write(content, to: path)
+ |> errors.map_messages("Wrote " <> path, "Could not write to " <> path)
+ }
+ }
+}
diff --git a/aoc2023/build/packages/adglent/src/adglent/init.gleam b/aoc2023/build/packages/adglent/src/adglent/init.gleam
new file mode 100644
index 0000000..42eb833
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/adglent/init.gleam
@@ -0,0 +1,110 @@
+import priv/prompt
+import priv/template
+import priv/templates/test_main
+import priv/errors
+import priv/toml
+import simplifile
+import gleam/string
+import gleam/list
+import gleam/result
+import gleam/bool
+
+const aoc_toml_template = "
+version = {{ version }}
+year = \"{{ year }}\"
+session = \"{{ session }}\"
+showtime = {{ showtime }}
+"
+
+pub fn main() {
+ let year = prompt.value("Year", "2023", False)
+ let session = prompt.value("Session Cookie", "", False)
+ let use_showtime = prompt.confirm("Use showtime", False)
+
+ let aoc_toml_file = "aoc.toml"
+ let overwrite = case simplifile.create_file(aoc_toml_file) {
+ Ok(_) -> True
+ Error(simplifile.Eexist) ->
+ prompt.confirm("aoc.toml exits - overwrite", False)
+ _ -> panic as "Could not create aoc.toml"
+ }
+ case overwrite {
+ True -> {
+ template.render(
+ aoc_toml_template,
+ [
+ #("version", "2"),
+ #("year", year),
+ #("session", session),
+ #(
+ "showtime",
+ bool.to_string(use_showtime)
+ |> string.lowercase,
+ ),
+ ],
+ )
+ |> simplifile.write(to: aoc_toml_file)
+ |> errors.map_messages(
+ "aoc.toml - written",
+ "Error when writing aoc.toml",
+ )
+ }
+
+ False -> Ok("aoc.toml - skipped")
+ }
+ |> errors.print_result
+
+ let gleam_toml =
+ simplifile.read("gleam.toml")
+ |> errors.map_error("Could not read gleam.toml")
+ |> errors.print_error
+ |> errors.assert_ok
+
+ let name =
+ toml.get_string(gleam_toml, ["name"])
+ |> errors.map_error("Could not read \"name\" from gleam.toml")
+ |> errors.print_error
+ |> errors.assert_ok
+
+ let test_main_file = "test/" <> name <> "_test.gleam"
+
+ case use_showtime {
+ True -> {
+ template.render(test_main.template, [])
+ |> simplifile.write(to: test_main_file)
+ |> errors.map_messages(
+ "Wrote " <> test_main_file,
+ "Could not write to " <> test_main_file,
+ )
+ }
+ False -> Ok("Using existing (gleeunit) " <> test_main_file)
+ }
+ |> errors.print_result
+ |> errors.assert_ok
+
+ case simplifile.is_file(".gitignore") {
+ True -> {
+ use gitignore <- result.try(
+ simplifile.read(".gitignore")
+ |> result.map_error(fn(err) {
+ "Could not read .gitignore: " <> string.inspect(err)
+ }),
+ )
+ let aoc_toml_ignored =
+ string.split(gitignore, "\n")
+ |> list.find(fn(line) { line == "aoc.toml" })
+ case aoc_toml_ignored {
+ Error(_) -> {
+ simplifile.append("\naoc.toml", to: ".gitignore")
+ |> errors.map_messages(
+ ".gitignore written",
+ "Error when writing .gitignore",
+ )
+ }
+ Ok(_) -> Ok(".gitignore - skipped (already configured)")
+ }
+ }
+ False -> Error("Could not find .gitignore")
+ }
+ |> errors.print_result
+}
diff --git a/aoc2023/build/packages/adglent/src/adglent@day.erl b/aoc2023/build/packages/adglent/src/adglent@day.erl
new file mode 100644
index 0000000..b80368f
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/adglent@day.erl
@@ -0,0 +1,278 @@
+-module(adglent@day).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([main/0]).
+
+-spec create_file_if_not_present(binary(), binary()) -> {ok, binary()} |
+ {error, binary()}.
+create_file_if_not_present(Content, Path) ->
+ case simplifile:is_file(Path) of
+ true ->
+ {ok, <<Path/binary, " already exists - skipped"/utf8>>};
+
+ false ->
+ gleam@result:'try'(
+ begin
+ _pipe = simplifile:create_file(Path),
+ priv@errors:map_messages(
+ _pipe,
+ <<"Created "/utf8, Path/binary>>,
+ <<"Could not create "/utf8, Path/binary>>
+ )
+ end,
+ fun(_) -> _pipe@1 = simplifile:write(Path, Content),
+ priv@errors:map_messages(
+ _pipe@1,
+ <<"Wrote "/utf8, Path/binary>>,
+ <<"Could not write to "/utf8, Path/binary>>
+ ) end
+ )
+ end.
+
+-spec main() -> binary().
+main() ->
+ Day@1 = begin
+ _pipe = case adglent:start_arguments() of
+ [Day] ->
+ {ok, Day};
+
+ Args ->
+ {error,
+ <<"Expected day - found: "/utf8,
+ (gleam@string:join(Args, <<", "/utf8>>))/binary>>}
+ end,
+ _pipe@1 = priv@errors:map_error(
+ _pipe,
+ <<"Error when parsing command args"/utf8>>
+ ),
+ _pipe@2 = priv@errors:print_error(_pipe@1),
+ priv@errors:assert_ok(_pipe@2)
+ end,
+ Aoc_toml = begin
+ _pipe@3 = simplifile:read(<<"aoc.toml"/utf8>>),
+ _pipe@4 = priv@errors:map_error(
+ _pipe@3,
+ <<"Could not read aoc.toml"/utf8>>
+ ),
+ _pipe@5 = priv@errors:print_error(_pipe@4),
+ priv@errors:assert_ok(_pipe@5)
+ end,
+ Aoc_toml_version = priv@toml:get_int(Aoc_toml, [<<"version"/utf8>>]),
+ Year = begin
+ _pipe@6 = priv@toml:get_string(Aoc_toml, [<<"year"/utf8>>]),
+ _pipe@7 = priv@errors:map_error(
+ _pipe@6,
+ <<"Could not read \"year\" from aoc.toml"/utf8>>
+ ),
+ _pipe@8 = priv@errors:print_error(_pipe@7),
+ priv@errors:assert_ok(_pipe@8)
+ end,
+ Session = begin
+ _pipe@9 = priv@toml:get_string(Aoc_toml, [<<"session"/utf8>>]),
+ _pipe@10 = priv@errors:map_error(
+ _pipe@9,
+ <<"Could not read \"session\" from aoc.toml"/utf8>>
+ ),
+ _pipe@11 = priv@errors:print_error(_pipe@10),
+ priv@errors:assert_ok(_pipe@11)
+ end,
+ Showtime = case Aoc_toml_version of
+ {ok, 2} ->
+ _pipe@12 = priv@toml:get_bool(Aoc_toml, [<<"showtime"/utf8>>]),
+ _pipe@13 = priv@errors:map_error(
+ _pipe@12,
+ <<"Could not read \"showtime\" from aoc.toml"/utf8>>
+ ),
+ _pipe@14 = priv@errors:print_error(_pipe@13),
+ priv@errors:assert_ok(_pipe@14);
+
+ _ ->
+ _pipe@15 = priv@toml:get_string(Aoc_toml, [<<"showtime"/utf8>>]),
+ _pipe@16 = gleam@result:map(
+ _pipe@15,
+ fun(Bool_string) -> case Bool_string of
+ <<"True"/utf8>> ->
+ true;
+
+ _ ->
+ false
+ end end
+ ),
+ _pipe@17 = priv@errors:map_error(
+ _pipe@16,
+ <<"Could not read \"showtime\" from aoc.toml"/utf8>>
+ ),
+ _pipe@18 = priv@errors:print_error(_pipe@17),
+ priv@errors:assert_ok(_pipe@18)
+ end,
+ Test_folder = adglent:get_test_folder(Day@1),
+ Test_file = <<<<<<Test_folder/binary, "/day"/utf8>>/binary, Day@1/binary>>/binary,
+ "_test.gleam"/utf8>>,
+ _pipe@19 = simplifile:create_directory_all(Test_folder),
+ _pipe@20 = priv@errors:map_error(
+ _pipe@19,
+ <<<<"Could not create folder \""/utf8, Test_folder/binary>>/binary,
+ "\""/utf8>>
+ ),
+ _pipe@21 = priv@errors:print_error(_pipe@20),
+ priv@errors:assert_ok(_pipe@21),
+ Testfile_template = case Showtime of
+ true ->
+ <<"
+import gleam/list
+import showtime/tests/should
+import adglent.{type Example, Example}
+import day{{ day }}/solve
+
+type Problem1AnswerType =
+ String
+
+type Problem2AnswerType =
+ String
+
+/// Add examples for part 1 here:
+/// ```gleam
+///const part1_examples: List(Example(Problem1AnswerType)) = [Example(\"some input\", \"\")]
+/// ```
+const part1_examples: List(Example(Problem1AnswerType)) = []
+
+/// Add examples for part 2 here:
+/// ```gleam
+///const part2_examples: List(Example(Problem2AnswerType)) = [Example(\"some input\", \"\")]
+/// ```
+const part2_examples: List(Example(Problem2AnswerType)) = []
+
+pub fn part1_test() {
+ part1_examples
+ |> should.not_equal([])
+ use example <- list.map(part1_examples)
+ solve.part1(example.input)
+ |> should.equal(example.answer)
+}
+
+pub fn part2_test() {
+ part2_examples
+ |> should.not_equal([])
+ use example <- list.map(part2_examples)
+ solve.part2(example.input)
+ |> should.equal(example.answer)
+}
+
+"/utf8>>;
+
+ false ->
+ <<"
+import gleam/list
+import gleeunit/should
+import adglent.{type Example, Example}
+import day{{ day }}/solve
+
+type Problem1AnswerType =
+ String
+
+type Problem2AnswerType =
+ String
+
+/// Add examples for part 1 here:
+/// ```gleam
+///const part1_examples: List(Example(Problem1AnswerType)) = [Example(\"some input\", \"\")]
+/// ```
+const part1_examples: List(Example(Problem1AnswerType)) = []
+
+/// Add examples for part 2 here:
+/// ```gleam
+///const part2_examples: List(Example(Problem2AnswerType)) = [Example(\"some input\", \"\")]
+/// ```
+const part2_examples: List(Example(Problem2AnswerType)) = []
+
+pub fn part1_test() {
+ part1_examples
+ |> should.not_equal([])
+ use example <- list.map(part1_examples)
+ solve.part1(example.input)
+ |> should.equal(example.answer)
+}
+
+pub fn part2_test() {
+ part2_examples
+ |> should.not_equal([])
+ use example <- list.map(part2_examples)
+ solve.part2(example.input)
+ |> should.equal(example.answer)
+}
+
+"/utf8>>
+ end,
+ _pipe@22 = priv@template:render(
+ Testfile_template,
+ [{<<"day"/utf8>>, Day@1}]
+ ),
+ _pipe@23 = create_file_if_not_present(_pipe@22, Test_file),
+ _pipe@24 = priv@errors:print_result(_pipe@23),
+ priv@errors:assert_ok(_pipe@24),
+ Solutions_folder = <<"src/day"/utf8, Day@1/binary>>,
+ Solution_file = <<Solutions_folder/binary, "/solve.gleam"/utf8>>,
+ _pipe@25 = simplifile:create_directory_all(Solutions_folder),
+ _pipe@26 = priv@errors:map_error(
+ _pipe@25,
+ <<<<"Could not create folder \""/utf8, Solutions_folder/binary>>/binary,
+ "\""/utf8>>
+ ),
+ _pipe@27 = priv@errors:print_error(_pipe@26),
+ priv@errors:assert_ok(_pipe@27),
+ _pipe@28 = priv@template:render(
+ <<"
+import adglent.{First, Second}
+import gleam/io
+
+pub fn part1(input: String) {
+ todo as \"Implement solution to part 1\"
+}
+
+pub fn part2(input: String) {
+ todo as \"Implement solution to part 2\"
+}
+
+pub fn main() {
+ let assert Ok(part) = adglent.get_part()
+ let assert Ok(input) = adglent.get_input(\"{{ day }}\")
+ case part {
+ First ->
+ part1(input)
+ |> adglent.inspect
+ |> io.println
+ Second ->
+ part2(input)
+ |> adglent.inspect
+ |> io.println
+ }
+}
+"/utf8>>,
+ [{<<"day"/utf8>>, Day@1}]
+ ),
+ _pipe@29 = create_file_if_not_present(_pipe@28, Solution_file),
+ _pipe@30 = priv@errors:print_result(_pipe@29),
+ priv@errors:assert_ok(_pipe@30),
+ _pipe@31 = create_file_if_not_present(
+ <<"input.txt"/utf8>>,
+ <<Solutions_folder/binary, "/.gitignore"/utf8>>
+ ),
+ _pipe@32 = priv@errors:print_result(_pipe@31),
+ priv@errors:assert_ok(_pipe@32),
+ Input = begin
+ _pipe@33 = priv@aoc_client:get_input(Year, Day@1, Session),
+ _pipe@34 = priv@errors:map_error(
+ _pipe@33,
+ <<"Error when fetching input"/utf8>>
+ ),
+ _pipe@35 = priv@errors:print_error(_pipe@34),
+ priv@errors:assert_ok(_pipe@35)
+ end,
+ _pipe@36 = Input,
+ _pipe@37 = gleam@string:trim(_pipe@36),
+ _pipe@38 = create_file_if_not_present(
+ _pipe@37,
+ <<Solutions_folder/binary, "/input.txt"/utf8>>
+ ),
+ _pipe@39 = priv@errors:print_result(_pipe@38),
+ priv@errors:assert_ok(_pipe@39).
diff --git a/aoc2023/build/packages/adglent/src/adglent@init.erl b/aoc2023/build/packages/adglent/src/adglent@init.erl
new file mode 100644
index 0000000..fb28101
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/adglent@init.erl
@@ -0,0 +1,142 @@
+-module(adglent@init).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([main/0]).
+
+-spec main() -> {ok, binary()} | {error, binary()}.
+main() ->
+ Year = priv@prompt:value(<<"Year"/utf8>>, <<"2023"/utf8>>, false),
+ Session = priv@prompt:value(<<"Session Cookie"/utf8>>, <<""/utf8>>, false),
+ Use_showtime = priv@prompt:confirm(<<"Use showtime"/utf8>>, false),
+ Aoc_toml_file = <<"aoc.toml"/utf8>>,
+ Overwrite = case simplifile:create_file(Aoc_toml_file) of
+ {ok, _} ->
+ true;
+
+ {error, eexist} ->
+ priv@prompt:confirm(<<"aoc.toml exits - overwrite"/utf8>>, false);
+
+ _ ->
+ erlang:error(#{gleam_error => panic,
+ message => <<"Could not create aoc.toml"/utf8>>,
+ module => <<"adglent/init"/utf8>>,
+ function => <<"main"/utf8>>,
+ line => 29})
+ end,
+ _pipe@3 = case Overwrite of
+ true ->
+ _pipe@1 = priv@template:render(
+ <<"
+version = {{ version }}
+year = \"{{ year }}\"
+session = \"{{ session }}\"
+showtime = {{ showtime }}
+"/utf8>>,
+ [{<<"version"/utf8>>, <<"2"/utf8>>},
+ {<<"year"/utf8>>, Year},
+ {<<"session"/utf8>>, Session},
+ {<<"showtime"/utf8>>,
+ begin
+ _pipe = gleam@bool:to_string(Use_showtime),
+ gleam@string:lowercase(_pipe)
+ end}]
+ ),
+ _pipe@2 = simplifile:write(Aoc_toml_file, _pipe@1),
+ priv@errors:map_messages(
+ _pipe@2,
+ <<"aoc.toml - written"/utf8>>,
+ <<"Error when writing aoc.toml"/utf8>>
+ );
+
+ false ->
+ {ok, <<"aoc.toml - skipped"/utf8>>}
+ end,
+ priv@errors:print_result(_pipe@3),
+ Gleam_toml = begin
+ _pipe@4 = simplifile:read(<<"gleam.toml"/utf8>>),
+ _pipe@5 = priv@errors:map_error(
+ _pipe@4,
+ <<"Could not read gleam.toml"/utf8>>
+ ),
+ _pipe@6 = priv@errors:print_error(_pipe@5),
+ priv@errors:assert_ok(_pipe@6)
+ end,
+ Name = begin
+ _pipe@7 = priv@toml:get_string(Gleam_toml, [<<"name"/utf8>>]),
+ _pipe@8 = priv@errors:map_error(
+ _pipe@7,
+ <<"Could not read \"name\" from gleam.toml"/utf8>>
+ ),
+ _pipe@9 = priv@errors:print_error(_pipe@8),
+ priv@errors:assert_ok(_pipe@9)
+ end,
+ Test_main_file = <<<<"test/"/utf8, Name/binary>>/binary,
+ "_test.gleam"/utf8>>,
+ _pipe@12 = case Use_showtime of
+ true ->
+ _pipe@10 = priv@template:render(
+ <<"
+import showtime
+
+pub fn main() {
+ showtime.main()
+}
+"/utf8>>,
+ []
+ ),
+ _pipe@11 = simplifile:write(Test_main_file, _pipe@10),
+ priv@errors:map_messages(
+ _pipe@11,
+ <<"Wrote "/utf8, Test_main_file/binary>>,
+ <<"Could not write to "/utf8, Test_main_file/binary>>
+ );
+
+ false ->
+ {ok, <<"Using existing (gleeunit) "/utf8, Test_main_file/binary>>}
+ end,
+ _pipe@13 = priv@errors:print_result(_pipe@12),
+ priv@errors:assert_ok(_pipe@13),
+ _pipe@17 = case simplifile:is_file(<<".gitignore"/utf8>>) of
+ true ->
+ gleam@result:'try'(
+ begin
+ _pipe@14 = simplifile:read(<<".gitignore"/utf8>>),
+ gleam@result:map_error(
+ _pipe@14,
+ fun(Err) ->
+ <<"Could not read .gitignore: "/utf8,
+ (gleam@string:inspect(Err))/binary>>
+ end
+ )
+ end,
+ fun(Gitignore) ->
+ Aoc_toml_ignored = begin
+ _pipe@15 = gleam@string:split(Gitignore, <<"\n"/utf8>>),
+ gleam@list:find(
+ _pipe@15,
+ fun(Line) -> Line =:= <<"aoc.toml"/utf8>> end
+ )
+ end,
+ case Aoc_toml_ignored of
+ {error, _} ->
+ _pipe@16 = simplifile:append(
+ <<".gitignore"/utf8>>,
+ <<"\naoc.toml"/utf8>>
+ ),
+ priv@errors:map_messages(
+ _pipe@16,
+ <<".gitignore written"/utf8>>,
+ <<"Error when writing .gitignore"/utf8>>
+ );
+
+ {ok, _} ->
+ {ok,
+ <<".gitignore - skipped (already configured)"/utf8>>}
+ end
+ end
+ );
+
+ false ->
+ {error, <<"Could not find .gitignore"/utf8>>}
+ end,
+ priv@errors:print_result(_pipe@17).
diff --git a/aoc2023/build/packages/adglent/src/adglent_ffi.erl b/aoc2023/build/packages/adglent/src/adglent_ffi.erl
new file mode 100644
index 0000000..a6a92e6
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/adglent_ffi.erl
@@ -0,0 +1,12 @@
+-module(adglent_ffi).
+
+-export([get_line/1]).
+
+-spec get_line(io:prompt()) -> {ok, unicode:unicode_binary()} | {error, eof | no_data}.
+get_line(Prompt) ->
+ case io:get_line(Prompt) of
+ eof -> {error, eof};
+ {error, _} -> {error, no_data};
+ Data when is_binary(Data) -> {ok, Data};
+ Data when is_list(Data) -> {ok, unicode:characters_to_binary(Data)}
+ end.
diff --git a/aoc2023/build/packages/adglent/src/priv/aoc_client.gleam b/aoc2023/build/packages/adglent/src/priv/aoc_client.gleam
new file mode 100644
index 0000000..e18bafa
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/priv/aoc_client.gleam
@@ -0,0 +1,37 @@
+import gleam/result.{try}
+import gleam/httpc
+import gleam/http/request
+import gleam/int
+import gleam/string
+
+pub fn get_input(
+ year: String,
+ day: String,
+ session: String,
+) -> Result(String, String) {
+ let url = "https://adventofcode.com/" <> year <> "/day/" <> day <> "/input"
+ use request <- try(
+ request.to(url)
+ |> result.map_error(fn(error) {
+ "Could not create request for \"" <> url <> "\": " <> string.inspect(
+ error,
+ )
+ }),
+ )
+
+ // Send the HTTP request to the server
+ use response <- try(
+ request
+ |> request.prepend_header("Accept", "application/json")
+ |> request.prepend_header("Cookie", "session=" <> session <> ";")
+ |> httpc.send
+ |> result.map_error(fn(error) {
+ "Error when requesting \"" <> url <> "\": " <> string.inspect(error)
+ }),
+ )
+
+ case response.status {
+ status if status >= 200 && status < 300 -> Ok(response.body)
+ status -> Error(int.to_string(status) <> " - " <> response.body)
+ }
+}
diff --git a/aoc2023/build/packages/adglent/src/priv/errors.gleam b/aoc2023/build/packages/adglent/src/priv/errors.gleam
new file mode 100644
index 0000000..14c35ca
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/priv/errors.gleam
@@ -0,0 +1,54 @@
+import gleam/result
+import gleam/string
+import gleam/io
+
+pub fn map_messages(
+ result: Result(a, b),
+ success_message: String,
+ error_message: String,
+) -> Result(String, String) {
+ result
+ |> result.map_error(fn(error) {
+ "Error - " <> error_message <> ": " <> string.inspect(error)
+ })
+ |> result.replace(success_message)
+}
+
+pub fn map_error(
+ result: Result(a, b),
+ error_message: String,
+) -> Result(a, String) {
+ result
+ |> result.map_error(fn(error) {
+ error_message <> ": " <> string.inspect(error)
+ })
+}
+
+pub fn print_result(result: Result(String, String)) {
+ result
+ |> result.unwrap_both
+ |> io.println
+ result
+}
+
+pub fn print_error(result: Result(a, String)) {
+ result
+ |> result.map_error(fn(err) {
+ io.println(err)
+ err
+ })
+}
+
+pub fn assert_ok(result: Result(a, String)) {
+ let assert Ok(value) =
+ result
+ |> result.map_error(fn(err) {
+ halt(1)
+ err
+ })
+ value
+}
+
+@target(erlang)
+@external(erlang, "erlang", "halt")
+fn halt(a: Int) -> Nil
diff --git a/aoc2023/build/packages/adglent/src/priv/prompt.gleam b/aoc2023/build/packages/adglent/src/priv/prompt.gleam
new file mode 100644
index 0000000..6cee35a
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/priv/prompt.gleam
@@ -0,0 +1,38 @@
+import gleam/result
+import gleam/string
+
+pub fn confirm(message: String, auto_accept: Bool) -> Bool {
+ auto_accept || case
+ get_line(message <> "? (Y/N): ")
+ |> result.unwrap("n")
+ |> string.trim()
+ {
+ "Y" | "y" -> True
+ _ -> False
+ }
+}
+
+pub fn value(message: String, default: String, auto_accept: Bool) -> String {
+ case get_value_of_default(message, default, auto_accept) {
+ "" -> default
+ value -> value
+ }
+}
+
+fn get_value_of_default(message: String, default: String, auto_accept: Bool) {
+ case auto_accept {
+ True -> default
+ False ->
+ get_line(message <> "? (" <> default <> "): ")
+ |> result.unwrap("")
+ |> string.trim()
+ }
+}
+
+pub type GetLineError {
+ Eof
+ NoData
+}
+
+@external(erlang, "adglent_ffi", "get_line")
+pub fn get_line(prompt prompt: String) -> Result(String, GetLineError)
diff --git a/aoc2023/build/packages/adglent/src/priv/template.gleam b/aoc2023/build/packages/adglent/src/priv/template.gleam
new file mode 100644
index 0000000..e946888
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/priv/template.gleam
@@ -0,0 +1,18 @@
+import gleam/list
+import gleam/string
+
+pub fn render(
+ template: String,
+ substitutions: List(#(String, String)),
+) -> String {
+ substitutions
+ |> list.fold(
+ template,
+ fn(template, substitution) {
+ let #(name, value) = substitution
+ template
+ |> string.replace("{{ " <> name <> " }}", value)
+ },
+ )
+ |> string.trim <> "\n"
+}
diff --git a/aoc2023/build/packages/adglent/src/priv/templates/solution.gleam b/aoc2023/build/packages/adglent/src/priv/templates/solution.gleam
new file mode 100644
index 0000000..96085c3
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/priv/templates/solution.gleam
@@ -0,0 +1,27 @@
+pub const template = "
+import adglent.{First, Second}
+import gleam/io
+
+pub fn part1(input: String) {
+ todo as \"Implement solution to part 1\"
+}
+
+pub fn part2(input: String) {
+ todo as \"Implement solution to part 2\"
+}
+
+pub fn main() {
+ let assert Ok(part) = adglent.get_part()
+ let assert Ok(input) = adglent.get_input(\"{{ day }}\")
+ case part {
+ First ->
+ part1(input)
+ |> adglent.inspect
+ |> io.println
+ Second ->
+ part2(input)
+ |> adglent.inspect
+ |> io.println
+ }
+}
+"
diff --git a/aoc2023/build/packages/adglent/src/priv/templates/test_main.gleam b/aoc2023/build/packages/adglent/src/priv/templates/test_main.gleam
new file mode 100644
index 0000000..27548d3
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/priv/templates/test_main.gleam
@@ -0,0 +1,7 @@
+pub const template = "
+import showtime
+
+pub fn main() {
+ showtime.main()
+}
+"
diff --git a/aoc2023/build/packages/adglent/src/priv/templates/testfile_gleeunit.gleam b/aoc2023/build/packages/adglent/src/priv/templates/testfile_gleeunit.gleam
new file mode 100644
index 0000000..a1d56f6
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/priv/templates/testfile_gleeunit.gleam
@@ -0,0 +1,41 @@
+pub const template = "
+import gleam/list
+import gleeunit/should
+import adglent.{type Example, Example}
+import day{{ day }}/solve
+
+type Problem1AnswerType =
+ String
+
+type Problem2AnswerType =
+ String
+
+/// Add examples for part 1 here:
+/// ```gleam
+///const part1_examples: List(Example(Problem1AnswerType)) = [Example(\"some input\", \"\")]
+/// ```
+const part1_examples: List(Example(Problem1AnswerType)) = []
+
+/// Add examples for part 2 here:
+/// ```gleam
+///const part2_examples: List(Example(Problem2AnswerType)) = [Example(\"some input\", \"\")]
+/// ```
+const part2_examples: List(Example(Problem2AnswerType)) = []
+
+pub fn part1_test() {
+ part1_examples
+ |> should.not_equal([])
+ use example <- list.map(part1_examples)
+ solve.part1(example.input)
+ |> should.equal(example.answer)
+}
+
+pub fn part2_test() {
+ part2_examples
+ |> should.not_equal([])
+ use example <- list.map(part2_examples)
+ solve.part2(example.input)
+ |> should.equal(example.answer)
+}
+
+"
diff --git a/aoc2023/build/packages/adglent/src/priv/templates/testfile_showtime.gleam b/aoc2023/build/packages/adglent/src/priv/templates/testfile_showtime.gleam
new file mode 100644
index 0000000..699feb2
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/priv/templates/testfile_showtime.gleam
@@ -0,0 +1,41 @@
+pub const template = "
+import gleam/list
+import showtime/tests/should
+import adglent.{type Example, Example}
+import day{{ day }}/solve
+
+type Problem1AnswerType =
+ String
+
+type Problem2AnswerType =
+ String
+
+/// Add examples for part 1 here:
+/// ```gleam
+///const part1_examples: List(Example(Problem1AnswerType)) = [Example(\"some input\", \"\")]
+/// ```
+const part1_examples: List(Example(Problem1AnswerType)) = []
+
+/// Add examples for part 2 here:
+/// ```gleam
+///const part2_examples: List(Example(Problem2AnswerType)) = [Example(\"some input\", \"\")]
+/// ```
+const part2_examples: List(Example(Problem2AnswerType)) = []
+
+pub fn part1_test() {
+ part1_examples
+ |> should.not_equal([])
+ use example <- list.map(part1_examples)
+ solve.part1(example.input)
+ |> should.equal(example.answer)
+}
+
+pub fn part2_test() {
+ part2_examples
+ |> should.not_equal([])
+ use example <- list.map(part2_examples)
+ solve.part2(example.input)
+ |> should.equal(example.answer)
+}
+
+"
diff --git a/aoc2023/build/packages/adglent/src/priv/toml.gleam b/aoc2023/build/packages/adglent/src/priv/toml.gleam
new file mode 100644
index 0000000..7042833
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/priv/toml.gleam
@@ -0,0 +1,52 @@
+import tom
+import gleam/result
+
+pub type TomError {
+ TomParseError(error: tom.ParseError)
+ TomGetError(error: tom.GetError)
+}
+
+pub fn get_string(
+ toml_content: String,
+ key_path: List(String),
+) -> Result(String, TomError) {
+ use toml <- result.try(
+ tom.parse(toml_content <> "\n")
+ |> result.map_error(TomParseError),
+ )
+ use value <- result.try(
+ tom.get_string(toml, key_path)
+ |> result.map_error(TomGetError),
+ )
+ Ok(value)
+}
+
+pub fn get_bool(
+ toml_content: String,
+ key_path: List(String),
+) -> Result(Bool, TomError) {
+ use toml <- result.try(
+ tom.parse(toml_content <> "\n")
+ |> result.map_error(TomParseError),
+ )
+ use value <- result.try(
+ tom.get_bool(toml, key_path)
+ |> result.map_error(TomGetError),
+ )
+ Ok(value)
+}
+
+pub fn get_int(
+ toml_content: String,
+ key_path: List(String),
+) -> Result(Int, TomError) {
+ use toml <- result.try(
+ tom.parse(toml_content <> "\n")
+ |> result.map_error(TomParseError),
+ )
+ use value <- result.try(
+ tom.get_int(toml, key_path)
+ |> result.map_error(TomGetError),
+ )
+ Ok(value)
+}
diff --git a/aoc2023/build/packages/adglent/src/priv@aoc_client.erl b/aoc2023/build/packages/adglent/src/priv@aoc_client.erl
new file mode 100644
index 0000000..1acb9b5
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/priv@aoc_client.erl
@@ -0,0 +1,61 @@
+-module(priv@aoc_client).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([get_input/3]).
+
+-spec get_input(binary(), binary(), binary()) -> {ok, binary()} |
+ {error, binary()}.
+get_input(Year, Day, Session) ->
+ Url = <<<<<<<<"https://adventofcode.com/"/utf8, Year/binary>>/binary,
+ "/day/"/utf8>>/binary,
+ Day/binary>>/binary,
+ "/input"/utf8>>,
+ gleam@result:'try'(
+ begin
+ _pipe = gleam@http@request:to(Url),
+ gleam@result:map_error(
+ _pipe,
+ fun(Error) ->
+ <<<<<<"Could not create request for \""/utf8, Url/binary>>/binary,
+ "\": "/utf8>>/binary,
+ (gleam@string:inspect(Error))/binary>>
+ end
+ )
+ end,
+ fun(Request) ->
+ gleam@result:'try'(
+ begin
+ _pipe@1 = Request,
+ _pipe@2 = gleam@http@request:prepend_header(
+ _pipe@1,
+ <<"Accept"/utf8>>,
+ <<"application/json"/utf8>>
+ ),
+ _pipe@3 = gleam@http@request:prepend_header(
+ _pipe@2,
+ <<"Cookie"/utf8>>,
+ <<<<"session="/utf8, Session/binary>>/binary, ";"/utf8>>
+ ),
+ _pipe@4 = gleam@httpc:send(_pipe@3),
+ gleam@result:map_error(
+ _pipe@4,
+ fun(Error@1) ->
+ <<<<<<"Error when requesting \""/utf8, Url/binary>>/binary,
+ "\": "/utf8>>/binary,
+ (gleam@string:inspect(Error@1))/binary>>
+ end
+ )
+ end,
+ fun(Response) -> case erlang:element(2, Response) of
+ Status when (Status >= 200) andalso (Status < 300) ->
+ {ok, erlang:element(4, Response)};
+
+ Status@1 ->
+ {error,
+ <<<<(gleam@int:to_string(Status@1))/binary,
+ " - "/utf8>>/binary,
+ (erlang:element(4, Response))/binary>>}
+ end end
+ )
+ end
+ ).
diff --git a/aoc2023/build/packages/adglent/src/priv@errors.erl b/aoc2023/build/packages/adglent/src/priv@errors.erl
new file mode 100644
index 0000000..978c675
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/priv@errors.erl
@@ -0,0 +1,74 @@
+-module(priv@errors).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([map_messages/3, map_error/2, print_result/1, print_error/1, assert_ok/1]).
+
+-spec map_messages({ok, any()} | {error, any()}, binary(), binary()) -> {ok,
+ binary()} |
+ {error, binary()}.
+map_messages(Result, Success_message, Error_message) ->
+ _pipe = Result,
+ _pipe@1 = gleam@result:map_error(
+ _pipe,
+ fun(Error) ->
+ <<<<<<"Error - "/utf8, Error_message/binary>>/binary, ": "/utf8>>/binary,
+ (gleam@string:inspect(Error))/binary>>
+ end
+ ),
+ gleam@result:replace(_pipe@1, Success_message).
+
+-spec map_error({ok, NIB} | {error, any()}, binary()) -> {ok, NIB} |
+ {error, binary()}.
+map_error(Result, Error_message) ->
+ _pipe = Result,
+ gleam@result:map_error(
+ _pipe,
+ fun(Error) ->
+ <<<<Error_message/binary, ": "/utf8>>/binary,
+ (gleam@string:inspect(Error))/binary>>
+ end
+ ).
+
+-spec print_result({ok, binary()} | {error, binary()}) -> {ok, binary()} |
+ {error, binary()}.
+print_result(Result) ->
+ _pipe = Result,
+ _pipe@1 = gleam@result:unwrap_both(_pipe),
+ gleam@io:println(_pipe@1),
+ Result.
+
+-spec print_error({ok, NIK} | {error, binary()}) -> {ok, NIK} |
+ {error, binary()}.
+print_error(Result) ->
+ _pipe = Result,
+ gleam@result:map_error(
+ _pipe,
+ fun(Err) ->
+ gleam@io:println(Err),
+ Err
+ end
+ ).
+
+-spec assert_ok({ok, NIO} | {error, binary()}) -> NIO.
+assert_ok(Result) ->
+ _assert_subject = begin
+ _pipe = Result,
+ gleam@result:map_error(
+ _pipe,
+ fun(Err) ->
+ erlang:halt(1),
+ Err
+ end
+ )
+ end,
+ {ok, Value} = case _assert_subject of
+ {ok, _} -> _assert_subject;
+ _assert_fail ->
+ erlang:error(#{gleam_error => let_assert,
+ message => <<"Assertion pattern match failed"/utf8>>,
+ value => _assert_fail,
+ module => <<"priv/errors"/utf8>>,
+ function => <<"assert_ok"/utf8>>,
+ line => 43})
+ end,
+ Value.
diff --git a/aoc2023/build/packages/adglent/src/priv@prompt.erl b/aoc2023/build/packages/adglent/src/priv@prompt.erl
new file mode 100644
index 0000000..0277f14
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/priv@prompt.erl
@@ -0,0 +1,53 @@
+-module(priv@prompt).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([get_line/1, confirm/2, value/3]).
+-export_type([get_line_error/0]).
+
+-type get_line_error() :: eof | no_data.
+
+-spec get_line(binary()) -> {ok, binary()} | {error, get_line_error()}.
+get_line(Prompt) ->
+ adglent_ffi:get_line(Prompt).
+
+-spec confirm(binary(), boolean()) -> boolean().
+confirm(Message, Auto_accept) ->
+ Auto_accept orelse case begin
+ _pipe = adglent_ffi:get_line(<<Message/binary, "? (Y/N): "/utf8>>),
+ _pipe@1 = gleam@result:unwrap(_pipe, <<"n"/utf8>>),
+ gleam@string:trim(_pipe@1)
+ end of
+ <<"Y"/utf8>> ->
+ true;
+
+ <<"y"/utf8>> ->
+ true;
+
+ _ ->
+ false
+ end.
+
+-spec get_value_of_default(binary(), binary(), boolean()) -> binary().
+get_value_of_default(Message, Default, Auto_accept) ->
+ case Auto_accept of
+ true ->
+ Default;
+
+ false ->
+ _pipe = adglent_ffi:get_line(
+ <<<<<<Message/binary, "? ("/utf8>>/binary, Default/binary>>/binary,
+ "): "/utf8>>
+ ),
+ _pipe@1 = gleam@result:unwrap(_pipe, <<""/utf8>>),
+ gleam@string:trim(_pipe@1)
+ end.
+
+-spec value(binary(), binary(), boolean()) -> binary().
+value(Message, Default, Auto_accept) ->
+ case get_value_of_default(Message, Default, Auto_accept) of
+ <<""/utf8>> ->
+ Default;
+
+ Value ->
+ Value
+ end.
diff --git a/aoc2023/build/packages/adglent/src/priv@template.erl b/aoc2023/build/packages/adglent/src/priv@template.erl
new file mode 100644
index 0000000..6a5d0bf
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/priv@template.erl
@@ -0,0 +1,25 @@
+-module(priv@template).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([render/2]).
+
+-spec render(binary(), list({binary(), binary()})) -> binary().
+render(Template, Substitutions) ->
+ <<(begin
+ _pipe = Substitutions,
+ _pipe@2 = gleam@list:fold(
+ _pipe,
+ Template,
+ fun(Template@1, Substitution) ->
+ {Name, Value} = Substitution,
+ _pipe@1 = Template@1,
+ gleam@string:replace(
+ _pipe@1,
+ <<<<"{{ "/utf8, Name/binary>>/binary, " }}"/utf8>>,
+ Value
+ )
+ end
+ ),
+ gleam@string:trim(_pipe@2)
+ end)/binary,
+ "\n"/utf8>>.
diff --git a/aoc2023/build/packages/adglent/src/priv@templates@solution.erl b/aoc2023/build/packages/adglent/src/priv@templates@solution.erl
new file mode 100644
index 0000000..7e36387
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/priv@templates@solution.erl
@@ -0,0 +1 @@
+-module(priv@templates@solution).
diff --git a/aoc2023/build/packages/adglent/src/priv@templates@test_main.erl b/aoc2023/build/packages/adglent/src/priv@templates@test_main.erl
new file mode 100644
index 0000000..ca6b127
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/priv@templates@test_main.erl
@@ -0,0 +1 @@
+-module(priv@templates@test_main).
diff --git a/aoc2023/build/packages/adglent/src/priv@templates@testfile_gleeunit.erl b/aoc2023/build/packages/adglent/src/priv@templates@testfile_gleeunit.erl
new file mode 100644
index 0000000..2f5a41e
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/priv@templates@testfile_gleeunit.erl
@@ -0,0 +1 @@
+-module(priv@templates@testfile_gleeunit).
diff --git a/aoc2023/build/packages/adglent/src/priv@templates@testfile_showtime.erl b/aoc2023/build/packages/adglent/src/priv@templates@testfile_showtime.erl
new file mode 100644
index 0000000..bbbc8b2
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/priv@templates@testfile_showtime.erl
@@ -0,0 +1 @@
+-module(priv@templates@testfile_showtime).
diff --git a/aoc2023/build/packages/adglent/src/priv@toml.erl b/aoc2023/build/packages/adglent/src/priv@toml.erl
new file mode 100644
index 0000000..6c41fbf
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/priv@toml.erl
@@ -0,0 +1,83 @@
+-module(priv@toml).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([get_string/2, get_bool/2, get_int/2]).
+-export_type([tom_error/0]).
+
+-type tom_error() :: {tom_parse_error, tom:parse_error()} |
+ {tom_get_error, tom:get_error()}.
+
+-spec get_string(binary(), list(binary())) -> {ok, binary()} |
+ {error, tom_error()}.
+get_string(Toml_content, Key_path) ->
+ gleam@result:'try'(
+ begin
+ _pipe = tom:parse(<<Toml_content/binary, "\n"/utf8>>),
+ gleam@result:map_error(
+ _pipe,
+ fun(Field@0) -> {tom_parse_error, Field@0} end
+ )
+ end,
+ fun(Toml) ->
+ gleam@result:'try'(
+ begin
+ _pipe@1 = tom:get_string(Toml, Key_path),
+ gleam@result:map_error(
+ _pipe@1,
+ fun(Field@0) -> {tom_get_error, Field@0} end
+ )
+ end,
+ fun(Value) -> {ok, Value} end
+ )
+ end
+ ).
+
+-spec get_bool(binary(), list(binary())) -> {ok, boolean()} |
+ {error, tom_error()}.
+get_bool(Toml_content, Key_path) ->
+ gleam@result:'try'(
+ begin
+ _pipe = tom:parse(<<Toml_content/binary, "\n"/utf8>>),
+ gleam@result:map_error(
+ _pipe,
+ fun(Field@0) -> {tom_parse_error, Field@0} end
+ )
+ end,
+ fun(Toml) ->
+ gleam@result:'try'(
+ begin
+ _pipe@1 = tom:get_bool(Toml, Key_path),
+ gleam@result:map_error(
+ _pipe@1,
+ fun(Field@0) -> {tom_get_error, Field@0} end
+ )
+ end,
+ fun(Value) -> {ok, Value} end
+ )
+ end
+ ).
+
+-spec get_int(binary(), list(binary())) -> {ok, integer()} |
+ {error, tom_error()}.
+get_int(Toml_content, Key_path) ->
+ gleam@result:'try'(
+ begin
+ _pipe = tom:parse(<<Toml_content/binary, "\n"/utf8>>),
+ gleam@result:map_error(
+ _pipe,
+ fun(Field@0) -> {tom_parse_error, Field@0} end
+ )
+ end,
+ fun(Toml) ->
+ gleam@result:'try'(
+ begin
+ _pipe@1 = tom:get_int(Toml, Key_path),
+ gleam@result:map_error(
+ _pipe@1,
+ fun(Field@0) -> {tom_get_error, Field@0} end
+ )
+ end,
+ fun(Value) -> {ok, Value} end
+ )
+ end
+ ).
diff --git a/aoc2023/build/packages/adglent/src/showtime.erl b/aoc2023/build/packages/adglent/src/showtime.erl
new file mode 100644
index 0000000..721bad4
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime.erl
@@ -0,0 +1,155 @@
+-module(showtime).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([main/0]).
+
+-spec mk_runner(
+ fun((gleam@option:option(list(binary())), list(binary()), showtime@internal@common@cli:capture()) -> OBY),
+ glint:command_input()
+) -> OBY.
+mk_runner(Func, Command) ->
+ _assert_subject = begin
+ _pipe = erlang:element(3, Command),
+ _pipe@1 = glint@flag:get_strings(_pipe, <<"modules"/utf8>>),
+ gleam@result:map(_pipe@1, fun(Modules) -> case Modules of
+ [] ->
+ none;
+
+ Modules@1 ->
+ {some, Modules@1}
+ end end)
+ end,
+ {ok, Module_list} = case _assert_subject of
+ {ok, _} -> _assert_subject;
+ _assert_fail ->
+ erlang:error(#{gleam_error => let_assert,
+ message => <<"Assertion pattern match failed"/utf8>>,
+ value => _assert_fail,
+ module => <<"showtime"/utf8>>,
+ function => <<"mk_runner"/utf8>>,
+ line => 91})
+ end,
+ _assert_subject@1 = begin
+ _pipe@2 = erlang:element(3, Command),
+ glint@flag:get_strings(_pipe@2, <<"ignore"/utf8>>)
+ end,
+ {ok, Ignore_tags} = case _assert_subject@1 of
+ {ok, _} -> _assert_subject@1;
+ _assert_fail@1 ->
+ erlang:error(#{gleam_error => let_assert,
+ message => <<"Assertion pattern match failed"/utf8>>,
+ value => _assert_fail@1,
+ module => <<"showtime"/utf8>>,
+ function => <<"mk_runner"/utf8>>,
+ line => 100})
+ end,
+ _assert_subject@2 = begin
+ _pipe@3 = erlang:element(3, Command),
+ _pipe@4 = glint@flag:get_string(_pipe@3, <<"capture"/utf8>>),
+ _pipe@5 = gleam@result:map(
+ _pipe@4,
+ fun(Arg) -> gleam@string:lowercase(Arg) end
+ ),
+ gleam@result:map(_pipe@5, fun(Arg@1) -> case Arg@1 of
+ <<"no"/utf8>> ->
+ no;
+
+ <<"yes"/utf8>> ->
+ yes;
+
+ <<"mixed"/utf8>> ->
+ mixed
+ end end)
+ end,
+ {ok, Capture_output} = case _assert_subject@2 of
+ {ok, _} -> _assert_subject@2;
+ _assert_fail@2 ->
+ erlang:error(#{gleam_error => let_assert,
+ message => <<"Assertion pattern match failed"/utf8>>,
+ value => _assert_fail@2,
+ module => <<"showtime"/utf8>>,
+ function => <<"mk_runner"/utf8>>,
+ line => 104})
+ end,
+ Func(Module_list, Ignore_tags, Capture_output).
+
+-spec start_with_args(
+ list(binary()),
+ fun((gleam@option:option(list(binary())), list(binary()), showtime@internal@common@cli:capture()) -> any())
+) -> nil.
+start_with_args(Args, Func) ->
+ Modules_flag = begin
+ _pipe = glint@flag:string_list(),
+ _pipe@1 = glint@flag:default(_pipe, []),
+ glint@flag:description(
+ _pipe@1,
+ <<"Run only tests in the modules in this list"/utf8>>
+ )
+ end,
+ Ignore_flag = begin
+ _pipe@2 = glint@flag:string_list(),
+ _pipe@3 = glint@flag:default(_pipe@2, []),
+ glint@flag:description(
+ _pipe@3,
+ <<"Ignore tests that are have tags matching a tag in this list"/utf8>>
+ )
+ end,
+ Capture_flag = begin
+ _pipe@4 = glint@flag:string(),
+ _pipe@5 = glint@flag:default(_pipe@4, <<"no"/utf8>>),
+ _pipe@6 = glint@flag:constraint(
+ _pipe@5,
+ glint@flag@constraint:one_of(
+ [<<"yes"/utf8>>, <<"no"/utf8>>, <<"mixed"/utf8>>]
+ )
+ ),
+ glint@flag:description(
+ _pipe@6,
+ <<"Capture output: no (default) - output when tests are run, yes - output is captured and shown in report, mixed - output when run and in report"/utf8>>
+ )
+ end,
+ _pipe@7 = glint:new(),
+ _pipe@12 = glint:add(
+ _pipe@7,
+ [],
+ begin
+ _pipe@8 = glint:command(
+ fun(_capture) -> mk_runner(Func, _capture) end
+ ),
+ _pipe@9 = glint:flag(_pipe@8, <<"modules"/utf8>>, Modules_flag),
+ _pipe@10 = glint:flag(_pipe@9, <<"ignore"/utf8>>, Ignore_flag),
+ _pipe@11 = glint:flag(_pipe@10, <<"capture"/utf8>>, Capture_flag),
+ glint:description(_pipe@11, <<"Runs test"/utf8>>)
+ end
+ ),
+ _pipe@13 = glint:with_pretty_help(_pipe@12, glint:default_pretty_help()),
+ glint:run(_pipe@13, Args).
+
+-spec main() -> nil.
+main() ->
+ start_with_args(
+ gleam@erlang:start_arguments(),
+ fun(Module_list, Ignore_tags, Capture) ->
+ Test_event_handler = showtime@internal@erlang@event_handler:start(),
+ Test_module_handler = showtime@internal@erlang@module_handler:start(
+ Test_event_handler,
+ fun showtime@internal@erlang@discover:collect_test_functions/1,
+ fun showtime@internal@erlang@runner:run_test_suite/4,
+ Ignore_tags,
+ Capture
+ ),
+ Test_event_handler(start_test_run),
+ Modules = showtime@internal@erlang@discover:collect_modules(
+ Test_module_handler,
+ Module_list
+ ),
+ Test_event_handler(
+ {end_test_run,
+ begin
+ _pipe = Modules,
+ gleam@list:length(_pipe)
+ end}
+ ),
+ nil
+ end
+ ).
diff --git a/aoc2023/build/packages/adglent/src/showtime.gleam b/aoc2023/build/packages/adglent/src/showtime.gleam
new file mode 100644
index 0000000..f0401c9
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime.gleam
@@ -0,0 +1,116 @@
+import glint.{type CommandInput, flag}
+import glint/flag
+import glint/flag/constraint.{one_of}
+import gleam/result
+import gleam/string
+import showtime/internal/common/cli.{Mixed, No, Yes}
+@target(erlang)
+import gleam/list
+@target(erlang)
+import gleam/option.{None, Some}
+@target(erlang)
+import gleam/erlang.{start_arguments}
+@target(erlang)
+import showtime/internal/common/test_suite.{EndTestRun, StartTestRun}
+@target(erlang)
+import showtime/internal/erlang/event_handler
+@target(erlang)
+import showtime/internal/erlang/module_handler
+@target(erlang)
+import showtime/internal/erlang/runner
+@target(erlang)
+import showtime/internal/erlang/discover.{
+ collect_modules, collect_test_functions,
+}
+
+// @target(javascript)
+// import gleam/io
+
+@target(erlang)
+pub fn main() {
+ use module_list, ignore_tags, capture <- start_with_args(start_arguments())
+ // Start event handler which will collect test-results and eventually
+ // print test report
+ let test_event_handler = event_handler.start()
+ // Start module handler which receives msg about modules to test and
+ // runs the test-suite for the module
+ let test_module_handler =
+ module_handler.start(
+ test_event_handler,
+ collect_test_functions,
+ runner.run_test_suite,
+ ignore_tags,
+ capture,
+ )
+
+ test_event_handler(StartTestRun)
+ // Collect modules and notify the module handler to start the test-suites
+ let modules = collect_modules(test_module_handler, module_list)
+ test_event_handler(EndTestRun(
+ modules
+ |> list.length(),
+ ))
+ Nil
+}
+
+fn start_with_args(args, func) {
+ let modules_flag =
+ flag.string_list()
+ |> flag.default([])
+ |> flag.description("Run only tests in the modules in this list")
+
+ let ignore_flag =
+ flag.string_list()
+ |> flag.default([])
+ |> flag.description(
+ "Ignore tests that are have tags matching a tag in this list",
+ )
+
+ let capture_flag =
+ flag.string()
+ |> flag.default("no")
+ |> flag.constraint(one_of(["yes", "no", "mixed"]))
+ |> flag.description(
+ "Capture output: no (default) - output when tests are run, yes - output is captured and shown in report, mixed - output when run and in report",
+ )
+
+ glint.new()
+ |> glint.add(
+ at: [],
+ do: glint.command(mk_runner(func, _))
+ |> glint.flag("modules", modules_flag)
+ |> glint.flag("ignore", ignore_flag)
+ |> glint.flag("capture", capture_flag)
+ |> glint.description("Runs test"),
+ )
+ |> glint.with_pretty_help(glint.default_pretty_help())
+ |> glint.run(args)
+}
+
+fn mk_runner(func, command: CommandInput) {
+ let assert Ok(module_list) =
+ command.flags
+ |> flag.get_strings("modules")
+ |> result.map(fn(modules) {
+ case modules {
+ [] -> None
+ modules -> Some(modules)
+ }
+ })
+ let assert Ok(ignore_tags) =
+ command.flags
+ |> flag.get_strings("ignore")
+
+ let assert Ok(capture_output) =
+ command.flags
+ |> flag.get_string("capture")
+ |> result.map(fn(arg) { string.lowercase(arg) })
+ |> result.map(fn(arg) {
+ case arg {
+ "no" -> No
+ "yes" -> Yes
+ "mixed" -> Mixed
+ }
+ })
+ func(module_list, ignore_tags, capture_output)
+}
diff --git a/aoc2023/build/packages/adglent/src/showtime/internal/common/cli.gleam b/aoc2023/build/packages/adglent/src/showtime/internal/common/cli.gleam
new file mode 100644
index 0000000..1c03211
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime/internal/common/cli.gleam
@@ -0,0 +1,5 @@
+pub type Capture {
+ Yes
+ No
+ Mixed
+}
diff --git a/aoc2023/build/packages/adglent/src/showtime/internal/common/common_event_handler.gleam b/aoc2023/build/packages/adglent/src/showtime/internal/common/common_event_handler.gleam
new file mode 100644
index 0000000..b90af14
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime/internal/common/common_event_handler.gleam
@@ -0,0 +1,101 @@
+import gleam/map.{type Map}
+import showtime/internal/common/test_suite.{
+ type TestEvent, type TestRun, CompletedTestRun, EndTest, EndTestRun,
+ EndTestSuite, OngoingTestRun, StartTest, StartTestRun, StartTestSuite,
+}
+
+pub type TestState {
+ NotStarted
+ Running
+ Finished(num_modules: Int)
+}
+
+pub type HandlerState {
+ HandlerState(
+ test_state: TestState,
+ num_done: Int,
+ events: Map(String, Map(String, TestRun)),
+ )
+}
+
+// This is the common event-handler (shared between erlang/JS targets)
+// The main strategy is to collect the test-results in a map of maps:
+// module_name ->
+// test_name -> test_result
+// It will also keep track of if it is running (i.e. did it receive the EndTestRun)
+// so that the caller can determine when to print test-results
+pub fn handle_event(
+ msg: TestEvent,
+ system_time: fn() -> Int,
+ state: HandlerState,
+) {
+ let test_state = state.test_state
+ let num_done = state.num_done
+ let events = state.events
+ let #(updated_test_state, updated_num_done, updated_events) = case msg {
+ StartTestRun -> #(Running, num_done, events)
+ StartTestSuite(module) -> {
+ let maybe_module_events = map.get(events, module.name)
+ let new_events = case maybe_module_events {
+ Ok(_) -> events
+ Error(_) ->
+ events
+ |> map.insert(module.name, map.new())
+ }
+ #(test_state, num_done, new_events)
+ }
+ StartTest(module, test) -> {
+ let current_time = system_time()
+ let maybe_module_events = map.get(events, module.name)
+ let new_events = case maybe_module_events {
+ Ok(module_events) -> {
+ let maybe_test_event = map.get(module_events, test.name)
+ case maybe_test_event {
+ Error(_) ->
+ events
+ |> map.insert(
+ module.name,
+ module_events
+ |> map.insert(test.name, OngoingTestRun(test, current_time)),
+ )
+ Ok(_) -> events
+ }
+ }
+ Error(_) -> events
+ }
+ #(test_state, num_done, new_events)
+ }
+ EndTest(module, test, result) -> {
+ let current_time = system_time()
+ let maybe_module_events = map.get(events, module.name)
+ let new_events = case maybe_module_events {
+ Ok(module_events) -> {
+ let maybe_test_run =
+ module_events
+ |> map.get(test.name)
+ let updated_module_events = case maybe_test_run {
+ Ok(OngoingTestRun(test_function, started_at)) ->
+ module_events
+ |> map.insert(
+ test.name,
+ CompletedTestRun(
+ test_function,
+ current_time - started_at,
+ result,
+ ),
+ )
+ Error(_) -> module_events
+ }
+ events
+ |> map.insert(module.name, updated_module_events)
+ }
+ Error(_) -> events
+ }
+ #(test_state, num_done, new_events)
+ }
+ EndTestSuite(_) -> #(test_state, num_done + 1, events)
+ EndTestRun(num_modules) -> #(Finished(num_modules), num_done, events)
+ _ -> #(Running, num_done, events)
+ }
+ HandlerState(updated_test_state, updated_num_done, updated_events)
+}
diff --git a/aoc2023/build/packages/adglent/src/showtime/internal/common/test_result.gleam b/aoc2023/build/packages/adglent/src/showtime/internal/common/test_result.gleam
new file mode 100644
index 0000000..a1d6bd9
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime/internal/common/test_result.gleam
@@ -0,0 +1,119 @@
+import gleam/dynamic.{type Dynamic}
+import gleam/map.{type Map}
+
+// These are all the types used for test-results
+// NOTE: These are heavily used in the erlang/js ffi:s
+// so any changes here are likely to break the ffi:s unless
+// the corresponding change is introduced there
+//
+// Futhermore this has some erlang related names that should
+// probably be cleaned up, but it is used by both the ffi:s
+
+// Currently only one reason, but could be possible to support
+// more reasons in the future
+pub type IgnoreReason {
+ Ignore
+}
+
+// This is the return value from running the test-function
+// or ignored if the test was ignored
+pub type TestReturn {
+ TestFunctionReturn(value: Dynamic, output_buffer: List(String))
+ Ignored(reason: IgnoreReason)
+}
+
+// All data about an exception in the test function is captured
+// in this type.
+// This is also where the data about the assertions will end up (in reason)
+pub type Exception {
+ ErlangException(
+ class: Class,
+ reason: Reason,
+ stacktrace: TraceList,
+ output_buffer: List(String),
+ )
+}
+
+// Alias for the test-result which is either a TestResult (passed test, ignored or a test-definition)
+// or an Exception (failed test)
+pub type TestResult =
+ Result(TestReturn, Exception)
+
+// Reason is either an assert equal (which is if the error was produced by gleeunit should)
+// TODO: Add other asserts
+// or it is a gleam error meaning that is was produced by showtime should
+// TODO: Rename GleamError to ShowtimeError
+pub type Reason {
+ AssertEqual(details: List(ReasonDetail))
+ AssertNotEqual(details: List(ReasonDetail))
+ AssertMatch(details: List(ReasonDetail))
+ GleamError(details: GleamErrorDetail)
+ GleamAssert(value: Dynamic, line_no: Int)
+ GenericException(value: Dynamic)
+}
+
+// ReasonDetail is the union-type used in erlang-exceptions where the reason
+// is a list of such details
+pub type ReasonDetail {
+ Module(name: String)
+ ReasonLine(line_no: Int)
+ Expression(expression: String)
+ Expected(value: Dynamic)
+ Value(value: Dynamic)
+ Pattern(pattern: String)
+}
+
+// Gleam error detail is produced by showtime should and will hold all the information
+// about the assertion (both expected and got)
+pub type GleamErrorDetail {
+ LetAssert(
+ module: String,
+ function: String,
+ line_no: Int,
+ message: String,
+ value: Dynamic,
+ )
+}
+
+// Class is a part of standard erlang exceptions, but also used on js-side
+// TODO: Extend to include a JS specific constructor
+pub type Class {
+ ErlangError
+ Exit
+ Throw
+}
+
+// The trace list is part of the standard erlang exception, but is also
+// emulated on js-side.
+// TODO: Maybe we need a js-version that contain some js-specific trace-elements
+pub type TraceList {
+ TraceList(traces: List(Trace))
+}
+
+// Trace are the elements in the trace list in an erlang exception
+// TODO: Maybe add a js-specific trace (since arity is not really a js attribute)
+pub type Trace {
+ Trace(function: String, arity: Arity, extra_info: List(ExtraInfo))
+ TraceModule(
+ module: String,
+ function: String,
+ arity: Arity,
+ extra_info: List(ExtraInfo),
+ )
+}
+
+// Extra info holds information about the file and line
+// as well as some dynamic data in a map
+// This is currently not used in the reporter
+pub type ExtraInfo {
+ ErrorInfo(error_info: Map(Dynamic, Dynamic))
+ File(filename: String)
+ Line(line_no: Int)
+}
+
+// Arity is the erlang type for arity
+// Can be either a number, or a list of arguments
+pub type Arity {
+ Num(arity: Int)
+ ArgList(arg_list: List(Dynamic))
+}
diff --git a/aoc2023/build/packages/adglent/src/showtime/internal/common/test_suite.gleam b/aoc2023/build/packages/adglent/src/showtime/internal/common/test_suite.gleam
new file mode 100644
index 0000000..eb58d64
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime/internal/common/test_suite.gleam
@@ -0,0 +1,63 @@
+import gleam/option.{type Option}
+import showtime/internal/common/test_result.{type TestResult}
+import showtime/internal/common/cli.{type Capture}
+
+// The state (and result) of a test function
+pub type TestRun {
+ OngoingTestRun(test_function: TestFunction, started_at: Int)
+ CompletedTestRun(
+ test_function: TestFunction,
+ total_time: Int,
+ result: TestResult,
+ )
+}
+
+// A test module (found by discovery)
+pub type TestModule {
+ TestModule(name: String, path: Option(String))
+}
+
+// A test function
+pub type TestFunction {
+ TestFunction(name: String)
+}
+
+// A test suite is a test module together with the test functions
+// that were collected from that module
+pub type TestSuite {
+ TestSuite(module: TestModule, tests: List(TestFunction))
+}
+
+// Test event for the event handler
+pub type TestEvent {
+ StartTestRun
+ StartTestSuite(test_module: TestModule)
+ StartTest(test_module: TestModule, test_function: TestFunction)
+ EndTest(
+ test_module: TestModule,
+ test_function: TestFunction,
+ result: TestResult,
+ )
+ EndTestSuite(test_module: TestModule)
+ EndTestRun(num_modules: Int)
+}
+
+// Interface for the module handler
+pub type TestModuleHandler =
+ fn(TestModule) -> Nil
+
+// Interface for the event handler
+pub type TestEventHandler =
+ fn(TestEvent) -> Nil
+
+// Interface for the module collector
+pub type ModuleCollector =
+ fn(TestModuleHandler) -> List(TestModule)
+
+// Interface for the function collector
+pub type TestFunctionCollector =
+ fn(TestModule) -> TestSuite
+
+// Interface for the test runner
+pub type TestRunner =
+ fn(TestSuite, TestEventHandler, List(String), Capture) -> Nil
diff --git a/aoc2023/build/packages/adglent/src/showtime/internal/erlang/discover.gleam b/aoc2023/build/packages/adglent/src/showtime/internal/erlang/discover.gleam
new file mode 100644
index 0000000..ecb752d
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime/internal/erlang/discover.gleam
@@ -0,0 +1,167 @@
+@target(erlang)
+import gleam/io
+@target(erlang)
+import gleam/dynamic.{type Dynamic}
+@target(erlang)
+import gleam/list
+@target(erlang)
+import gleam/string
+@target(erlang)
+import gleam/int
+@target(erlang)
+import gleam/option.{type Option, None, Some}
+@target(erlang)
+import gleam/erlang/atom.{type Atom}
+@target(erlang)
+import showtime/internal/common/test_suite.{
+ type TestModule, type TestModuleHandler, type TestSuite, TestFunction,
+ TestModule, TestSuite,
+}
+import simplifile
+
+// Module collector for erlang
+// Will search the test folder for files ending with _test and notify
+// the module handler about each module it finds
+@target(erlang)
+pub fn collect_modules(
+ test_module_handler: TestModuleHandler,
+ only_modules: Option(List(String)),
+) -> List(TestModule) {
+ collect_modules_in_folder("./test", test_module_handler, only_modules)
+}
+
+@target(erlang)
+fn collect_modules_in_folder(
+ path: String,
+ test_module_handler: TestModuleHandler,
+ only_modules: Option(List(String)),
+) {
+ let module_prefix = get_module_prefix(path)
+ let assert Ok(files) = simplifile.read_directory(path)
+ let test_modules_in_folder =
+ files
+ |> list.filter(string.ends_with(_, "_test.gleam"))
+ |> list.filter_map(fn(test_module_file) {
+ let module_name =
+ module_prefix <> {
+ test_module_file
+ |> string.replace(".gleam", "")
+ }
+ case only_modules {
+ Some(only_modules_list) -> {
+ let module_in_list =
+ only_modules_list
+ |> list.any(fn(only_module_name) {
+ only_module_name == module_name
+ |> string.replace("@", "/")
+ })
+ case module_in_list {
+ True -> {
+ let test_module = TestModule(module_name, Some(test_module_file))
+ test_module_handler(test_module)
+ Ok(test_module)
+ }
+
+ False -> Error(Nil)
+ }
+ }
+ None -> {
+ let test_module = TestModule(module_name, Some(test_module_file))
+ test_module_handler(test_module)
+ Ok(test_module)
+ }
+ }
+ })
+ let test_modules_in_subfolders =
+ files
+ |> list.map(fn(filename) { path <> "/" <> filename })
+ |> list.filter(fn(file) { simplifile.is_directory(file) })
+ |> list.fold(
+ [],
+ fn(modules, subfolder) {
+ modules
+ |> list.append(collect_modules_in_folder(
+ subfolder,
+ test_module_handler,
+ only_modules,
+ ))
+ },
+ )
+ test_modules_in_folder
+ |> list.append(test_modules_in_subfolders)
+}
+
+@target(erlang)
+fn get_module_prefix(path) {
+ let path_without_test =
+ path
+ |> string.replace("./test", "")
+
+ let path_without_leading_slash = case
+ string.starts_with(path_without_test, "/")
+ {
+ True -> string.drop_left(path_without_test, 1)
+ False -> path_without_test
+ }
+ let module_prefix =
+ path_without_leading_slash
+ |> string.replace("/", "@")
+ case string.length(module_prefix) {
+ 0 -> module_prefix
+ _ -> module_prefix <> "@"
+ }
+}
+
+// Test function collector for erlang
+// Uses erlang `apply` to run `module_info` for the test module
+// and collects all the exports ending with _test into a `TestSuite`
+@target(erlang)
+pub fn collect_test_functions(module: TestModule) -> TestSuite {
+ let test_functions: List(#(Atom, Int)) =
+ apply(
+ atom.create_from_string(module.name),
+ atom.create_from_string("module_info"),
+ [dynamic.from(atom.create_from_string("exports"))],
+ )
+ |> dynamic.unsafe_coerce()
+
+ let test_functions_filtered =
+ test_functions
+ |> list.map(fn(entry) {
+ let assert #(name, arity) = entry
+ #(
+ name
+ |> atom.to_string(),
+ arity,
+ )
+ })
+ |> list.filter_map(fn(entry) {
+ let assert #(name, arity) = entry
+ case string.ends_with(name, "_test") {
+ True ->
+ case arity {
+ 0 -> Ok(name)
+ _ -> {
+ io.println(
+ "WARNING: function \"" <> name <> "\" has arity: " <> int.to_string(
+ arity,
+ ) <> " - cannot be used as test (needs to be 0)",
+ )
+ Error("Wrong arity")
+ }
+ }
+ False -> Error("Non matching name")
+ }
+ })
+ |> list.filter(string.ends_with(_, "_test"))
+ |> list.map(fn(function_name) { TestFunction(function_name) })
+ TestSuite(module, test_functions_filtered)
+}
+
+@target(erlang)
+@external(erlang, "erlang", "apply")
+fn apply(
+ module module: Atom,
+ function function: Atom,
+ args args: List(Dynamic),
+) -> Dynamic
diff --git a/aoc2023/build/packages/adglent/src/showtime/internal/erlang/event_handler.gleam b/aoc2023/build/packages/adglent/src/showtime/internal/erlang/event_handler.gleam
new file mode 100644
index 0000000..62a9caf
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime/internal/erlang/event_handler.gleam
@@ -0,0 +1,91 @@
+@target(erlang)
+import gleam/io
+@target(erlang)
+import gleam/otp/actor.{Continue, Stop}
+@target(erlang)
+import gleam/erlang/process.{type Subject, Normal}
+@target(erlang)
+import gleam/map
+@target(erlang)
+import showtime/internal/common/test_suite.{type TestEvent, EndTestRun}
+@target(erlang)
+import showtime/internal/common/common_event_handler.{
+ Finished, HandlerState, NotStarted, handle_event,
+}
+@target(erlang)
+import showtime/internal/reports/formatter.{create_test_report}
+@target(erlang)
+import gleam/erlang.{Millisecond}
+@target(erlang)
+import gleam/option.{None}
+
+@target(erlang)
+type EventHandlerMessage {
+ EventHandlerMessage(test_event: TestEvent, reply_to: Subject(Int))
+}
+
+// Starts an actor that receives test events and forwards the to the event handler
+// When handler updates the state to `Finished` the actor will wait until handler
+// reports that all modules are done and the stop
+@target(erlang)
+pub fn start() {
+ let assert Ok(subject) =
+ actor.start(
+ #(NotStarted, 0, map.new()),
+ fn(msg: EventHandlerMessage, state) {
+ let EventHandlerMessage(test_event, reply_to) = msg
+ let #(test_state, num_done, events) = state
+ let updated_state =
+ handle_event(
+ test_event,
+ system_time,
+ HandlerState(test_state, num_done, events),
+ )
+ case updated_state {
+ HandlerState(Finished(num_modules), num_done, events) if num_done == num_modules -> {
+ let #(test_report, num_failed) = create_test_report(events)
+ io.println(test_report)
+ process.send(reply_to, num_failed)
+ Stop(Normal)
+ }
+ HandlerState(test_state, num_done, events) ->
+ Continue(#(test_state, num_done, events), None)
+ }
+ },
+ )
+ let parent_subject = process.new_subject()
+
+ let selector =
+ process.new_selector()
+ |> process.selecting(parent_subject, fn(x) { x })
+
+ // Returns a callback that can receive test events
+ fn(test_event: TestEvent) {
+ case test_event {
+ EndTestRun(..) -> {
+ // When EndTestRun has been received the callback will wait until the
+ // actor has stopped
+ // TODO: Use a timeout?
+ process.send(subject, EventHandlerMessage(test_event, parent_subject))
+ let num_failed = process.select_forever(selector)
+ case num_failed > 0 {
+ True -> halt(1)
+ False -> halt(0)
+ }
+ }
+
+ // Normally just send the test event to the actor
+ _ ->
+ process.send(subject, EventHandlerMessage(test_event, parent_subject))
+ }
+ }
+}
+
+@target(erlang)
+@external(erlang, "erlang", "halt")
+fn halt(a: Int) -> Nil
+
+@target(erlang)
+fn system_time() {
+ erlang.system_time(Millisecond)
+}
diff --git a/aoc2023/build/packages/adglent/src/showtime/internal/erlang/module_handler.gleam b/aoc2023/build/packages/adglent/src/showtime/internal/erlang/module_handler.gleam
new file mode 100644
index 0000000..88cc251
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime/internal/erlang/module_handler.gleam
@@ -0,0 +1,43 @@
+@target(erlang)
+import gleam/otp/actor.{Continue}
+@target(erlang)
+import gleam/erlang/process
+@target(erlang)
+import showtime/internal/common/test_suite.{
+ type TestEventHandler, type TestFunctionCollector, type TestModule,
+ type TestRunner, EndTestSuite, StartTestSuite,
+}
+@target(erlang)
+import showtime/internal/common/cli.{type Capture}
+@target(erlang)
+import gleam/option.{None}
+
+@target(erlang)
+pub fn start(
+ test_event_handler: TestEventHandler,
+ test_function_collector: TestFunctionCollector,
+ run_test_suite: TestRunner,
+ ignore_tags: List(String),
+ capture: Capture,
+) {
+ let assert Ok(subject) =
+ actor.start(
+ Nil,
+ fn(module: TestModule, state) {
+ process.start(
+ fn() {
+ let test_suite = test_function_collector(module)
+ test_event_handler(StartTestSuite(module))
+ run_test_suite(test_suite, test_event_handler, ignore_tags, capture)
+ test_event_handler(EndTestSuite(module))
+ },
+ False,
+ )
+ Continue(state, None)
+ },
+ )
+ fn(test_module: TestModule) {
+ process.send(subject, test_module)
+ Nil
+ }
+}
diff --git a/aoc2023/build/packages/adglent/src/showtime/internal/erlang/runner.gleam b/aoc2023/build/packages/adglent/src/showtime/internal/erlang/runner.gleam
new file mode 100644
index 0000000..ebbf426
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime/internal/erlang/runner.gleam
@@ -0,0 +1,59 @@
+@target(erlang)
+import gleam/list
+@target(erlang)
+import gleam/erlang/atom.{type Atom}
+@target(erlang)
+import showtime/internal/common/test_suite.{
+ type TestEventHandler, type TestSuite, EndTest, StartTest,
+}
+@target(erlang)
+import showtime/internal/common/test_result.{type TestResult}
+@target(erlang)
+import showtime/internal/common/cli.{type Capture}
+
+// Runs all tests in a test suite
+@target(erlang)
+pub fn run_test_suite(
+ test_suite: TestSuite,
+ test_event_handler: TestEventHandler,
+ ignore_tags: List(String),
+ capture: Capture,
+) {
+ test_suite.tests
+ |> list.each(fn(test) {
+ test_event_handler(StartTest(test_suite.module, test))
+ let result =
+ run_test(test_suite.module.name, test.name, ignore_tags, capture)
+ test_event_handler(EndTest(test_suite.module, test, result))
+ })
+}
+
+// Wrapper around the ffi function that converts names to atoms
+@target(erlang)
+pub fn run_test(
+ module_name: String,
+ test_name: String,
+ ignore_tags: List(String),
+ capture: Capture,
+) -> TestResult {
+ let result =
+ run_test_ffi(
+ atom.create_from_string(module_name),
+ atom.create_from_string(test_name),
+ ignore_tags,
+ capture,
+ )
+ result
+}
+
+// Calls ffi for running a test function
+// The ffi will take care of mapping the result and exception to the data-types
+// used in gleam
+@target(erlang)
+@external(erlang, "showtime_ffi", "run_test")
+fn run_test_ffi(
+ module module: Atom,
+ function function: Atom,
+ ignore_tags ignore_tags: List(String),
+ capture capture: Capture,
+) -> TestResult
diff --git a/aoc2023/build/packages/adglent/src/showtime/internal/reports/compare.gleam b/aoc2023/build/packages/adglent/src/showtime/internal/reports/compare.gleam
new file mode 100644
index 0000000..5ccddee
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime/internal/reports/compare.gleam
@@ -0,0 +1,42 @@
+import gleam/dynamic.{type Dynamic}
+import gleam/string
+import showtime/internal/reports/styles.{expected_highlight, got_highlight}
+import gap.{compare_lists, compare_strings}
+import gap/styling.{from_comparison, highlight, to_styled_comparison}
+
+pub fn compare(expected: Dynamic, got: Dynamic) -> #(String, String) {
+ let expected_as_list =
+ expected
+ |> dynamic.list(dynamic.dynamic)
+ let got_as_list =
+ got
+ |> dynamic.list(dynamic.dynamic)
+ let expected_as_string =
+ expected
+ |> dynamic.string()
+ let got_as_string =
+ got
+ |> dynamic.string()
+ case expected_as_list, got_as_list, expected_as_string, got_as_string {
+ Ok(expected_list), Ok(got_list), _, _ -> {
+ let comparison =
+ compare_lists(expected_list, got_list)
+ |> from_comparison()
+ |> highlight(expected_highlight, got_highlight, fn(item) { item })
+ |> to_styled_comparison()
+ #(comparison.first, comparison.second)
+ }
+ _, _, Ok(expected_string), Ok(got_string) -> {
+ let comparison =
+ compare_strings(expected_string, got_string)
+ |> from_comparison()
+ |> highlight(expected_highlight, got_highlight, fn(item) { item })
+ |> to_styled_comparison()
+ #(comparison.first, comparison.second)
+ }
+ _, _, _, _ -> #(
+ expected_highlight(string.inspect(expected)),
+ got_highlight(string.inspect(got)),
+ )
+ }
+}
diff --git a/aoc2023/build/packages/adglent/src/showtime/internal/reports/formatter.gleam b/aoc2023/build/packages/adglent/src/showtime/internal/reports/formatter.gleam
new file mode 100644
index 0000000..8c1a6ac
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime/internal/reports/formatter.gleam
@@ -0,0 +1,480 @@
+import gleam/io
+import gleam/int
+import gleam/list
+import gleam/string
+import gleam/option.{type Option, None, Some}
+import gleam/map.{type Map}
+import gleam/dynamic.{type Dynamic}
+import showtime/internal/common/test_result.{
+ type GleamErrorDetail, type ReasonDetail, type Trace, AssertEqual, AssertMatch,
+ AssertNotEqual, Expected, Expression, GenericException, GleamAssert,
+ GleamError, Ignored, LetAssert, Pattern, Trace, TraceModule, Value,
+}
+import showtime/internal/common/test_suite.{type TestRun, CompletedTestRun}
+import showtime/tests/should.{type Assertion, Eq, Fail, IsError, IsOk, NotEq}
+import showtime/internal/reports/styles.{
+ error_style, expected_highlight, failed_style, function_style, got_highlight,
+ heading_style, ignored_style, not_style, passed_style, stacktrace_style,
+}
+import showtime/internal/reports/compare.{compare}
+import showtime/internal/reports/table.{
+ AlignLeft, AlignLeftOverflow, AlignRight, Content, Separator, StyledContent,
+ Table, align_table, to_string,
+}
+import showtime/tests/meta.{type Meta}
+
+type GleeUnitAssertionType {
+ GleeUnitAssertEqual(message: String)
+ GleeUnitAssertNotEqual(message: String)
+ GleeUnitAssertMatch(message: String)
+}
+
+type ModuleAndTest {
+ ModuleAndTestRun(module_name: String, test_run: TestRun)
+}
+
+type UnifiedError {
+ UnifiedError(
+ meta: Option(Meta),
+ reason: String,
+ message: String,
+ expected: String,
+ got: String,
+ line: Option(Int),
+ stacktrace: List(Trace),
+ )
+}
+
+pub fn create_test_report(test_results: Map(String, Map(String, TestRun))) {
+ let all_test_runs =
+ test_results
+ |> map.values()
+ |> list.flat_map(map.values)
+ let failed_test_runs =
+ test_results
+ |> map.to_list()
+ |> list.flat_map(fn(entry) {
+ let #(module_name, test_module_results) = entry
+ test_module_results
+ |> map.values()
+ |> list.filter_map(fn(test_run) {
+ case test_run {
+ CompletedTestRun(_test_function, _, result) ->
+ case result {
+ Error(_) -> Ok(ModuleAndTestRun(module_name, test_run))
+ Ok(Ignored(_)) -> Error(Nil)
+ Ok(_) -> Error(Nil)
+ }
+ _ -> {
+ test_run
+ |> io.debug()
+ Error(Nil)
+ }
+ }
+ })
+ })
+
+ let ignored_test_runs =
+ test_results
+ |> map.to_list()
+ |> list.flat_map(fn(entry) {
+ let #(module_name, test_module_results) = entry
+ test_module_results
+ |> map.values()
+ |> list.filter_map(fn(test_run) {
+ case test_run {
+ CompletedTestRun(test_function, _, result) ->
+ case result {
+ Ok(Ignored(reason)) ->
+ Ok(#(module_name <> "." <> test_function.name, reason))
+ _ -> Error(Nil)
+ }
+ _ -> Error(Nil)
+ }
+ })
+ })
+
+ let failed_tests_report =
+ failed_test_runs
+ |> list.filter_map(fn(module_and_test_run) {
+ case module_and_test_run.test_run {
+ CompletedTestRun(test_function, _total_time, result) ->
+ case result {
+ Error(exception) ->
+ case exception.reason {
+ AssertEqual(reason_details) ->
+ Ok(format_reason(
+ erlang_error_to_unified(
+ reason_details,
+ GleeUnitAssertEqual("Assert equal"),
+ exception.stacktrace.traces,
+ ),
+ module_and_test_run.module_name,
+ test_function.name,
+ exception.output_buffer,
+ ))
+ AssertNotEqual(reason_details) ->
+ Ok(format_reason(
+ erlang_error_to_unified(
+ reason_details,
+ GleeUnitAssertNotEqual("Assert not equal"),
+ exception.stacktrace.traces,
+ ),
+ module_and_test_run.module_name,
+ test_function.name,
+ exception.output_buffer,
+ ))
+ AssertMatch(reason_details) ->
+ Ok(format_reason(
+ erlang_error_to_unified(
+ reason_details,
+ GleeUnitAssertMatch("Assert match"),
+ exception.stacktrace.traces,
+ ),
+ module_and_test_run.module_name,
+ test_function.name,
+ exception.output_buffer,
+ ))
+ GleamError(reason) ->
+ Ok(format_reason(
+ gleam_error_to_unified(reason, exception.stacktrace.traces),
+ module_and_test_run.module_name,
+ test_function.name,
+ exception.output_buffer,
+ ))
+ // GleamAssert(value) -> Error(Nil)
+ GleamAssert(value, line_no) ->
+ Ok(format_reason(
+ UnifiedError(
+ None,
+ "gleam assert",
+ "Assert failed",
+ "Patterns should match",
+ error_style(string.inspect(value)),
+ Some(line_no),
+ exception.stacktrace.traces,
+ ),
+ module_and_test_run.module_name,
+ test_function.name,
+ exception.output_buffer,
+ ))
+ GenericException(value) ->
+ Ok(format_reason(
+ UnifiedError(
+ None,
+ "generic exception",
+ "Test function threw an exception",
+ "Exception in test function",
+ error_style(string.inspect(value)),
+ None,
+ exception.stacktrace.traces,
+ ),
+ module_and_test_run.module_name,
+ test_function.name,
+ exception.output_buffer,
+ ))
+ other -> {
+ io.println("Other: " <> string.inspect(other))
+ panic
+ Error(Nil)
+ }
+ }
+ _ -> Error(Nil)
+ }
+ _ -> Error(Nil)
+ }
+ })
+ |> list.fold([], fn(rows, test_rows) { list.append(rows, test_rows) })
+
+ let all_test_execution_time_reports =
+ all_test_runs
+ |> list.filter_map(fn(test_run) {
+ case test_run {
+ CompletedTestRun(test_function, total_time, _) ->
+ Ok(test_function.name <> ": " <> int.to_string(total_time) <> " ms")
+ _ -> Error(Nil)
+ }
+ })
+ let _execution_times_report =
+ all_test_execution_time_reports
+ |> string.join("\n")
+
+ let all_tests_count =
+ all_test_runs
+ |> list.length()
+ let ignored_tests_count =
+ ignored_test_runs
+ |> list.length()
+ let failed_tests_count =
+ failed_test_runs
+ |> list.length()
+
+ let passed =
+ passed_style(
+ int.to_string(all_tests_count - failed_tests_count - ignored_tests_count) <> " passed",
+ )
+ let failed = failed_style(int.to_string(failed_tests_count) <> " failed")
+ let ignored = case ignored_tests_count {
+ 0 -> ""
+ _ -> ", " <> ignored_style(int.to_string(ignored_tests_count) <> " ignored")
+ }
+
+ let failed_tests_table =
+ Table(None, failed_tests_report)
+ |> align_table()
+ |> to_string()
+
+ let test_report =
+ "\n" <> failed_tests_table <> "\n" <> passed <> ", " <> failed <> ignored
+ #(test_report, failed_tests_count)
+}
+
+fn erlang_error_to_unified(
+ error_details: List(ReasonDetail),
+ assertion_type: GleeUnitAssertionType,
+ stacktrace: List(Trace),
+) {
+ error_details
+ |> list.fold(
+ UnifiedError(
+ None,
+ "not_set",
+ assertion_type.message,
+ "",
+ "",
+ None,
+ stacktrace,
+ ),
+ fn(unified, reason) {
+ case reason {
+ Expression(expression) -> UnifiedError(..unified, reason: expression)
+ Expected(value) ->
+ case assertion_type {
+ GleeUnitAssertEqual(_messaged) ->
+ UnifiedError(
+ ..unified,
+ expected: expected_highlight(string.inspect(value)),
+ )
+ _ -> unified
+ }
+ Value(value) ->
+ case assertion_type {
+ GleeUnitAssertNotEqual(_message) ->
+ UnifiedError(
+ ..unified,
+ expected: not_style("not ") <> string.inspect(value),
+ got: got_highlight(string.inspect(value)),
+ )
+ _ ->
+ UnifiedError(..unified, got: got_highlight(string.inspect(value)))
+ }
+ Pattern(pattern) ->
+ case pattern {
+ "{ ok , _ }" ->
+ UnifiedError(..unified, expected: expected_highlight("Ok(_)"))
+ "{ error , _ }" ->
+ UnifiedError(..unified, expected: expected_highlight("Error(_)"))
+ _ -> unified
+ }
+ _ -> unified
+ }
+ },
+ )
+}
+
+fn gleam_error_to_unified(
+ gleam_error: GleamErrorDetail,
+ stacktrace: List(Trace),
+) -> UnifiedError {
+ case gleam_error {
+ LetAssert(_module, _function, _line_no, _message, value) -> {
+ let result: Result(Dynamic, Assertion(Dynamic, Dynamic)) =
+ dynamic.unsafe_coerce(value)
+ let assert Error(assertion) = result
+ case assertion {
+ Eq(got, expected, meta) -> {
+ let #(expected, got) = compare(expected, got)
+ UnifiedError(
+ meta,
+ "assert",
+ "Assert equal",
+ expected,
+ got,
+ None,
+ stacktrace,
+ )
+ }
+ NotEq(got, expected, meta) ->
+ UnifiedError(
+ meta,
+ "assert",
+ "Assert not equal",
+ not_style("not ") <> string.inspect(expected),
+ string.inspect(got),
+ None,
+ stacktrace,
+ )
+ IsOk(got, meta) ->
+ UnifiedError(
+ meta,
+ "assert",
+ "Assert is Ok",
+ expected_highlight("Ok(_)"),
+ got_highlight(string.inspect(got)),
+ None,
+ stacktrace,
+ )
+ IsError(got, meta) ->
+ UnifiedError(
+ meta,
+ "assert",
+ "Assert is Ok",
+ expected_highlight("Error(_)"),
+ got_highlight(string.inspect(got)),
+ None,
+ stacktrace,
+ )
+ Fail(meta) ->
+ UnifiedError(
+ meta,
+ "assert",
+ "Assert is Ok",
+ got_highlight("should.fail()"),
+ got_highlight("N/A - test always expected to fail"),
+ None,
+ stacktrace,
+ )
+ }
+ }
+ }
+}
+
+fn format_reason(
+ error: UnifiedError,
+ module: String,
+ function: String,
+ output_buffer: List(String),
+) {
+ let meta = case error.meta {
+ Some(meta) ->
+ Some([
+ AlignRight(StyledContent(heading_style("Description")), 2),
+ Separator(": "),
+ AlignLeft(Content(meta.description), 0),
+ ])
+
+ None -> None
+ }
+
+ let stacktrace =
+ error.stacktrace
+ |> list.map(fn(trace) {
+ case trace {
+ Trace(function, _, _) if function == "" -> "(anonymous)"
+ TraceModule(module, function, _, _) if function == "" ->
+ module <> "." <> "(anonymous)"
+ Trace(function, _, _) -> function
+ TraceModule(module, function, _, _) -> module <> "." <> function
+ }
+ })
+ let stacktrace_rows = case stacktrace {
+ [] -> []
+ [first, ..rest] -> {
+ let first_row =
+ Some([
+ AlignRight(StyledContent(heading_style("Stacktrace")), 2),
+ Separator(": "),
+ AlignLeft(StyledContent(stacktrace_style(first)), 0),
+ ])
+ let rest_rows =
+ rest
+ |> list.map(fn(row) {
+ Some([
+ AlignRight(Content(""), 2),
+ Separator(" "),
+ AlignLeft(StyledContent(stacktrace_style(row)), 0),
+ ])
+ })
+ [first_row, ..rest_rows]
+ }
+ }
+
+ let output_rows = case
+ output_buffer
+ |> list.reverse()
+ |> list.map(fn(row) { string.trim_right(row) })
+ {
+ [] -> []
+ [first, ..rest] -> {
+ let first_row =
+ Some([
+ AlignRight(StyledContent(heading_style("Output")), 2),
+ Separator(": "),
+ AlignLeftOverflow(StyledContent(stacktrace_style(first)), 0),
+ ])
+ let rest_rows =
+ rest
+ |> list.map(fn(row) {
+ Some([
+ AlignRight(Content(""), 2),
+ Separator(" "),
+ AlignLeftOverflow(StyledContent(stacktrace_style(row)), 0),
+ ])
+ })
+ [first_row, ..rest_rows]
+ }
+ }
+
+ let line =
+ error.line
+ |> option.map(fn(line) { ":" <> int.to_string(line) })
+ |> option.unwrap("")
+
+ let arrow =
+ string.join(
+ list.repeat(
+ "-",
+ string.length(module) + 1 + {
+ string.length(function) + string.length(line)
+ } / 2,
+ ),
+ "",
+ ) <> "⌄"
+ let standard_table_rows = [
+ Some([
+ AlignRight(StyledContent(error_style("Failed")), 2),
+ Separator(": "),
+ AlignLeft(Content(arrow), 0),
+ ]),
+ Some([
+ AlignRight(StyledContent(heading_style("Test")), 2),
+ Separator(": "),
+ AlignLeft(
+ StyledContent(module <> "." <> function_style(function <> line)),
+ 0,
+ ),
+ ]),
+ meta,
+ Some([
+ AlignRight(StyledContent(heading_style("Expected")), 2),
+ Separator(": "),
+ AlignLeftOverflow(StyledContent(error.expected), 0),
+ ]),
+ Some([
+ AlignRight(StyledContent(heading_style("Got")), 2),
+ Separator(": "),
+ AlignLeftOverflow(StyledContent(error.got), 0),
+ ]),
+ ]
+ standard_table_rows
+ |> list.append(stacktrace_rows)
+ |> list.append(output_rows)
+ |> list.append([
+ Some([
+ AlignRight(Content(""), 0),
+ AlignRight(Content(""), 0),
+ AlignRight(Content(""), 0),
+ ]),
+ ])
+ |> list.filter_map(fn(row) { option.to_result(row, Nil) })
+}
diff --git a/aoc2023/build/packages/adglent/src/showtime/internal/reports/styles.gleam b/aoc2023/build/packages/adglent/src/showtime/internal/reports/styles.gleam
new file mode 100644
index 0000000..b051dd3
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime/internal/reports/styles.gleam
@@ -0,0 +1,84 @@
+import gleam_community/ansi
+import gleam/list
+import gleam/string
+import gleam/bit_array
+
+pub fn passed_style(text) {
+ bold_green(text)
+}
+
+pub fn failed_style(text) {
+ bold_red(text)
+}
+
+pub fn ignored_style(text) {
+ bold_yellow(text)
+}
+
+pub fn error_style(text) {
+ bold_red(text)
+}
+
+pub fn expected_highlight(text) {
+ bold_green(text)
+}
+
+pub fn got_highlight(text) {
+ bold_red(text)
+}
+
+pub fn not_style(text) {
+ ansi.bold(text)
+}
+
+pub fn module_style(text: String) {
+ ansi.cyan(text)
+}
+
+pub fn heading_style(text: String) {
+ ansi.cyan(text)
+}
+
+pub fn function_style(text: String) {
+ bold_cyan(text)
+}
+
+pub fn stacktrace_style(text: String) {
+ text
+}
+
+fn bold_red(text: String) {
+ ansi.bold(ansi.red(text))
+}
+
+fn bold_green(text) {
+ ansi.bold(ansi.green(text))
+}
+
+fn bold_yellow(text) {
+ ansi.bold(ansi.yellow(text))
+}
+
+fn bold_cyan(text) {
+ ansi.bold(ansi.cyan(text))
+}
+
+pub fn strip_style(text) {
+ let #(new_text, _) =
+ text
+ |> string.to_graphemes()
+ |> list.fold(
+ #("", False),
+ fn(acc, char) {
+ let #(str, removing) = acc
+ let bit_char = bit_array.from_string(char)
+ case bit_char, removing {
+ <<0x1b>>, _ -> #(str, True)
+ <<0x6d>>, True -> #(str, False)
+ _, True -> #(str, True)
+ _, False -> #(str <> char, False)
+ }
+ },
+ )
+ new_text
+}
diff --git a/aoc2023/build/packages/adglent/src/showtime/internal/reports/table.gleam b/aoc2023/build/packages/adglent/src/showtime/internal/reports/table.gleam
new file mode 100644
index 0000000..f8bc00c
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime/internal/reports/table.gleam
@@ -0,0 +1,148 @@
+import gleam/list
+import gleam/string
+import gleam/int
+import gleam/option.{type Option}
+import showtime/internal/reports/styles.{strip_style}
+
+pub type Content {
+ Content(unstyled_text: String)
+ StyledContent(styled_text: String)
+}
+
+pub type Col {
+ AlignRight(content: Content, margin: Int)
+ AlignLeft(content: Content, margin: Int)
+ AlignRightOverflow(content: Content, margin: Int)
+ AlignLeftOverflow(content: Content, margin: Int)
+ Separator(char: String)
+ Aligned(content: String)
+}
+
+pub type Table {
+ Table(header: Option(String), rows: List(List(Col)))
+}
+
+pub fn to_string(table: Table) -> String {
+ let rows =
+ table.rows
+ |> list.map(fn(row) {
+ row
+ |> list.filter_map(fn(col) {
+ case col {
+ Separator(char) -> Ok(char)
+ Aligned(content) -> Ok(content)
+ _ -> Error(Nil)
+ }
+ })
+ |> string.join("")
+ })
+ |> string.join("\n")
+ let header =
+ table.header
+ |> option.map(fn(header) { header <> "\n" })
+ |> option.unwrap("")
+ header <> rows
+}
+
+pub fn align_table(table: Table) -> Table {
+ let cols =
+ table.rows
+ |> list.transpose()
+ let col_width =
+ cols
+ |> list.map(fn(col) {
+ col
+ |> list.map(fn(content) {
+ case content {
+ AlignRight(Content(unstyled), _) -> unstyled
+ AlignRight(StyledContent(styled), _) -> strip_style(styled)
+ AlignLeft(Content(unstyled), _) -> unstyled
+ AlignLeft(StyledContent(styled), _) -> strip_style(styled)
+ AlignLeftOverflow(_, _) -> ""
+ AlignRightOverflow(_, _) -> ""
+ Separator(char) -> char
+ Aligned(content) -> content
+ }
+ })
+ |> list.fold(0, fn(max, str) { int.max(max, string.length(str)) })
+ })
+ let aligned_col =
+ cols
+ |> list.zip(col_width)
+ |> list.map(fn(col_and_width) {
+ let #(col, width) = col_and_width
+ col
+ |> list.map(fn(content) {
+ case content {
+ AlignRight(Content(unstyled), margin) ->
+ Aligned(pad_left(
+ unstyled,
+ width + margin - string.length(unstyled),
+ " ",
+ ))
+ AlignRight(StyledContent(styled), margin) ->
+ Aligned(pad_left(
+ styled,
+ width + margin - string.length(strip_style(styled)),
+ " ",
+ ))
+ AlignRightOverflow(Content(unstyled), margin) ->
+ Aligned(pad_left(
+ unstyled,
+ width + margin - string.length(unstyled),
+ " ",
+ ))
+ AlignRightOverflow(StyledContent(styled), margin) ->
+ Aligned(pad_left(
+ styled,
+ width + margin - string.length(strip_style(styled)),
+ " ",
+ ))
+ AlignLeft(Content(unstyled), margin) ->
+ Aligned(pad_right(
+ unstyled,
+ width + margin - string.length(unstyled),
+ " ",
+ ))
+ AlignLeft(StyledContent(styled), margin) ->
+ Aligned(pad_right(
+ styled,
+ width + margin - string.length(strip_style(styled)),
+ " ",
+ ))
+ AlignLeftOverflow(Content(unstyled), margin) ->
+ Aligned(pad_right(
+ unstyled,
+ width + margin - string.length(unstyled),
+ " ",
+ ))
+ AlignLeftOverflow(StyledContent(styled), margin) ->
+ Aligned(pad_right(
+ styled,
+ width + margin - string.length(strip_style(styled)),
+ " ",
+ ))
+ Separator(char) -> Separator(char)
+ Aligned(content) -> Aligned(content)
+ }
+ })
+ })
+ let aligned_rows =
+ aligned_col
+ |> list.transpose()
+ Table(..table, rows: aligned_rows)
+}
+
+fn pad_left(str: String, num: Int, char: String) {
+ let padding =
+ list.repeat(char, num)
+ |> string.join("")
+ padding <> str
+}
+
+fn pad_right(str: String, num: Int, char: String) {
+ let padding =
+ list.repeat(char, num)
+ |> string.join("")
+ str <> padding
+}
diff --git a/aoc2023/build/packages/adglent/src/showtime/tests/meta.gleam b/aoc2023/build/packages/adglent/src/showtime/tests/meta.gleam
new file mode 100644
index 0000000..cbba414
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime/tests/meta.gleam
@@ -0,0 +1,3 @@
+pub type Meta {
+ Meta(description: String, tags: List(String))
+}
diff --git a/aoc2023/build/packages/adglent/src/showtime/tests/should.gleam b/aoc2023/build/packages/adglent/src/showtime/tests/should.gleam
new file mode 100644
index 0000000..71578c7
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime/tests/should.gleam
@@ -0,0 +1,113 @@
+import gleam/option.{type Option, None, Some}
+import showtime/tests/meta.{type Meta}
+
+pub type Assertion(t, e) {
+ Eq(a: t, b: t, meta: Option(Meta))
+ NotEq(a: t, b: t, meta: Option(Meta))
+ IsOk(a: Result(t, e), meta: Option(Meta))
+ IsError(a: Result(t, e), meta: Option(Meta))
+ Fail(meta: Option(Meta))
+}
+
+pub fn equal(a: t, b: t) {
+ evaluate(Eq(a, b, None))
+}
+
+pub fn equal_meta(a: t, b: t, meta: Meta) {
+ evaluate(Eq(a, b, Some(meta)))
+}
+
+pub fn not_equal(a: t, b: t) {
+ evaluate(NotEq(a, b, None))
+}
+
+pub fn not_equal_meta(a: t, b: t, meta: Meta) {
+ evaluate(NotEq(a, b, Some(meta)))
+}
+
+pub fn be_ok(a: Result(o, e)) {
+ evaluate(IsOk(a, None))
+ let assert Ok(value) = a
+ value
+}
+
+pub fn be_ok_meta(a: Result(o, e), meta: Meta) {
+ evaluate(IsOk(a, Some(meta)))
+}
+
+pub fn be_error(a: Result(o, e)) {
+ evaluate(IsError(a, None))
+ let assert Error(value) = a
+ value
+}
+
+pub fn be_error_meta(a: Result(o, e), meta: Meta) {
+ evaluate(IsError(a, Some(meta)))
+}
+
+pub fn fail() {
+ evaluate(Fail(None))
+}
+
+pub fn fail_meta(meta: Meta) {
+ evaluate(Fail(Some(meta)))
+}
+
+pub fn be_true(a: Bool) {
+ a
+ |> equal(True)
+}
+
+pub fn be_true_meta(a: Bool, meta: Meta) {
+ a
+ |> equal_meta(True, meta)
+}
+
+pub fn be_false(a: Bool) {
+ a
+ |> equal(False)
+}
+
+pub fn be_false_meta(a: Bool, meta: Meta) {
+ a
+ |> equal_meta(False, meta)
+}
+
+@external(erlang, "showtime_ffi", "gleam_error")
+fn gleam_error(value: Result(Nil, Assertion(a, b))) -> Nil
+
+pub fn evaluate(assertion) -> Nil {
+ case assertion {
+ Eq(a, b, _meta) ->
+ case a == b {
+ True -> Nil
+ False -> {
+ gleam_error(Error(assertion))
+ }
+ }
+ NotEq(a, b, _meta) ->
+ case a != b {
+ True -> Nil
+ False -> {
+ gleam_error(Error(assertion))
+ }
+ }
+ IsOk(a, _meta) ->
+ case a {
+ Ok(_) -> Nil
+ Error(_) -> {
+ gleam_error(Error(assertion))
+ }
+ }
+ IsError(a, _meta) ->
+ case a {
+ Error(_) -> Nil
+ Ok(_) -> {
+ gleam_error(Error(assertion))
+ }
+ }
+ Fail(_meta) -> {
+ gleam_error(Error(assertion))
+ }
+ }
+}
diff --git a/aoc2023/build/packages/adglent/src/showtime/tests/test.gleam b/aoc2023/build/packages/adglent/src/showtime/tests/test.gleam
new file mode 100644
index 0000000..730f943
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime/tests/test.gleam
@@ -0,0 +1,57 @@
+import showtime/tests/should
+import showtime/tests/meta.{type Meta}
+import gleam/io
+
+pub type Test {
+ Test(meta: Meta, test_function: fn() -> Nil)
+}
+
+pub type MetaShould(t) {
+ MetaShould(equal: fn(t, t) -> Nil, not_equal: fn(t, t) -> Nil)
+}
+
+pub fn test(meta: Meta, test_function: fn(Meta) -> Nil) {
+ Test(meta, fn() { test_function(meta) })
+}
+
+pub fn with_meta(meta: Meta, test_function: fn(MetaShould(a)) -> Nil) {
+ Test(
+ meta,
+ fn() {
+ test_function(MetaShould(
+ fn(a, b) { equal(a, b, meta) },
+ fn(a, b) { not_equal(a, b, meta) },
+ ))
+ },
+ )
+}
+
+pub fn equal(a: t, b: t, meta: Meta) {
+ io.debug(a)
+ io.debug(b)
+ should.equal_meta(a, b, meta)
+}
+
+pub fn not_equal(a: t, b: t, meta: Meta) {
+ should.equal_meta(a, b, meta)
+}
+
+pub fn be_ok(a: Result(o, e), meta: Meta) {
+ should.be_ok_meta(a, meta)
+}
+
+pub fn be_error(a: Result(o, e), meta: Meta) {
+ should.be_error_meta(a, meta)
+}
+
+pub fn fail(meta: Meta) {
+ should.fail_meta(meta)
+}
+
+pub fn be_true(a: Bool, meta: Meta) {
+ should.be_true_meta(a, meta)
+}
+
+pub fn be_false(a: Bool, meta: Meta) {
+ should.be_false_meta(a, meta)
+}
diff --git a/aoc2023/build/packages/adglent/src/showtime@internal@common@cli.erl b/aoc2023/build/packages/adglent/src/showtime@internal@common@cli.erl
new file mode 100644
index 0000000..f2d2396
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime@internal@common@cli.erl
@@ -0,0 +1,8 @@
+-module(showtime@internal@common@cli).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export_type([capture/0]).
+
+-type capture() :: yes | no | mixed.
+
+
diff --git a/aoc2023/build/packages/adglent/src/showtime@internal@common@common_event_handler.erl b/aoc2023/build/packages/adglent/src/showtime@internal@common@common_event_handler.erl
new file mode 100644
index 0000000..b0a6d7a
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime@internal@common@common_event_handler.erl
@@ -0,0 +1,131 @@
+-module(showtime@internal@common@common_event_handler).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([handle_event/3]).
+-export_type([test_state/0, handler_state/0]).
+
+-type test_state() :: not_started | running | {finished, integer()}.
+
+-type handler_state() :: {handler_state,
+ test_state(),
+ integer(),
+ gleam@map:map_(binary(), gleam@map:map_(binary(), showtime@internal@common@test_suite:test_run()))}.
+
+-spec handle_event(
+ showtime@internal@common@test_suite:test_event(),
+ fun(() -> integer()),
+ handler_state()
+) -> handler_state().
+handle_event(Msg, System_time, State) ->
+ Test_state = erlang:element(2, State),
+ Num_done = erlang:element(3, State),
+ Events = erlang:element(4, State),
+ {Updated_test_state, Updated_num_done, Updated_events} = case Msg of
+ start_test_run ->
+ {running, Num_done, Events};
+
+ {start_test_suite, Module} ->
+ Maybe_module_events = gleam@map:get(
+ Events,
+ erlang:element(2, Module)
+ ),
+ New_events = case Maybe_module_events of
+ {ok, _} ->
+ Events;
+
+ {error, _} ->
+ _pipe = Events,
+ gleam@map:insert(
+ _pipe,
+ erlang:element(2, Module),
+ gleam@map:new()
+ )
+ end,
+ {Test_state, Num_done, New_events};
+
+ {start_test, Module@1, Test} ->
+ Current_time = System_time(),
+ Maybe_module_events@1 = gleam@map:get(
+ Events,
+ erlang:element(2, Module@1)
+ ),
+ New_events@1 = case Maybe_module_events@1 of
+ {ok, Module_events} ->
+ Maybe_test_event = gleam@map:get(
+ Module_events,
+ erlang:element(2, Test)
+ ),
+ case Maybe_test_event of
+ {error, _} ->
+ _pipe@1 = Events,
+ gleam@map:insert(
+ _pipe@1,
+ erlang:element(2, Module@1),
+ begin
+ _pipe@2 = Module_events,
+ gleam@map:insert(
+ _pipe@2,
+ erlang:element(2, Test),
+ {ongoing_test_run, Test, Current_time}
+ )
+ end
+ );
+
+ {ok, _} ->
+ Events
+ end;
+
+ {error, _} ->
+ Events
+ end,
+ {Test_state, Num_done, New_events@1};
+
+ {end_test, Module@2, Test@1, Result} ->
+ Current_time@1 = System_time(),
+ Maybe_module_events@2 = gleam@map:get(
+ Events,
+ erlang:element(2, Module@2)
+ ),
+ New_events@2 = case Maybe_module_events@2 of
+ {ok, Module_events@1} ->
+ Maybe_test_run = begin
+ _pipe@3 = Module_events@1,
+ gleam@map:get(_pipe@3, erlang:element(2, Test@1))
+ end,
+ Updated_module_events = case Maybe_test_run of
+ {ok, {ongoing_test_run, Test_function, Started_at}} ->
+ _pipe@4 = Module_events@1,
+ gleam@map:insert(
+ _pipe@4,
+ erlang:element(2, Test@1),
+ {completed_test_run,
+ Test_function,
+ Current_time@1 - Started_at,
+ Result}
+ );
+
+ {error, _} ->
+ Module_events@1
+ end,
+ _pipe@5 = Events,
+ gleam@map:insert(
+ _pipe@5,
+ erlang:element(2, Module@2),
+ Updated_module_events
+ );
+
+ {error, _} ->
+ Events
+ end,
+ {Test_state, Num_done, New_events@2};
+
+ {end_test_suite, _} ->
+ {Test_state, Num_done + 1, Events};
+
+ {end_test_run, Num_modules} ->
+ {{finished, Num_modules}, Num_done, Events};
+
+ _ ->
+ {running, Num_done, Events}
+ end,
+ {handler_state, Updated_test_state, Updated_num_done, Updated_events}.
diff --git a/aoc2023/build/packages/adglent/src/showtime@internal@common@test_result.erl b/aoc2023/build/packages/adglent/src/showtime@internal@common@test_result.erl
new file mode 100644
index 0000000..b7b73be
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime@internal@common@test_result.erl
@@ -0,0 +1,54 @@
+-module(showtime@internal@common@test_result).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export_type([ignore_reason/0, test_return/0, exception/0, reason/0, reason_detail/0, gleam_error_detail/0, class/0, trace_list/0, trace/0, extra_info/0, arity_/0]).
+
+-type ignore_reason() :: ignore.
+
+-type test_return() :: {test_function_return,
+ gleam@dynamic:dynamic_(),
+ list(binary())} |
+ {ignored, ignore_reason()}.
+
+-type exception() :: {erlang_exception,
+ class(),
+ reason(),
+ trace_list(),
+ list(binary())}.
+
+-type reason() :: {assert_equal, list(reason_detail())} |
+ {assert_not_equal, list(reason_detail())} |
+ {assert_match, list(reason_detail())} |
+ {gleam_error, gleam_error_detail()} |
+ {gleam_assert, gleam@dynamic:dynamic_(), integer()} |
+ {generic_exception, gleam@dynamic:dynamic_()}.
+
+-type reason_detail() :: {module, binary()} |
+ {reason_line, integer()} |
+ {expression, binary()} |
+ {expected, gleam@dynamic:dynamic_()} |
+ {value, gleam@dynamic:dynamic_()} |
+ {pattern, binary()}.
+
+-type gleam_error_detail() :: {let_assert,
+ binary(),
+ binary(),
+ integer(),
+ binary(),
+ gleam@dynamic:dynamic_()}.
+
+-type class() :: erlang_error | exit | throw.
+
+-type trace_list() :: {trace_list, list(trace())}.
+
+-type trace() :: {trace, binary(), arity_(), list(extra_info())} |
+ {trace_module, binary(), binary(), arity_(), list(extra_info())}.
+
+-type extra_info() :: {error_info,
+ gleam@map:map_(gleam@dynamic:dynamic_(), gleam@dynamic:dynamic_())} |
+ {file, binary()} |
+ {line, integer()}.
+
+-type arity_() :: {num, integer()} | {arg_list, list(gleam@dynamic:dynamic_())}.
+
+
diff --git a/aoc2023/build/packages/adglent/src/showtime@internal@common@test_suite.erl b/aoc2023/build/packages/adglent/src/showtime@internal@common@test_suite.erl
new file mode 100644
index 0000000..6a56de8
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime@internal@common@test_suite.erl
@@ -0,0 +1,30 @@
+-module(showtime@internal@common@test_suite).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export_type([test_run/0, test_module/0, test_function/0, test_suite/0, test_event/0]).
+
+-type test_run() :: {ongoing_test_run, test_function(), integer()} |
+ {completed_test_run,
+ test_function(),
+ integer(),
+ {ok, showtime@internal@common@test_result:test_return()} |
+ {error, showtime@internal@common@test_result:exception()}}.
+
+-type test_module() :: {test_module, binary(), gleam@option:option(binary())}.
+
+-type test_function() :: {test_function, binary()}.
+
+-type test_suite() :: {test_suite, test_module(), list(test_function())}.
+
+-type test_event() :: start_test_run |
+ {start_test_suite, test_module()} |
+ {start_test, test_module(), test_function()} |
+ {end_test,
+ test_module(),
+ test_function(),
+ {ok, showtime@internal@common@test_result:test_return()} |
+ {error, showtime@internal@common@test_result:exception()}} |
+ {end_test_suite, test_module()} |
+ {end_test_run, integer()}.
+
+
diff --git a/aoc2023/build/packages/adglent/src/showtime@internal@erlang@discover.erl b/aoc2023/build/packages/adglent/src/showtime@internal@erlang@discover.erl
new file mode 100644
index 0000000..f0548aa
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime@internal@erlang@discover.erl
@@ -0,0 +1,230 @@
+-module(showtime@internal@erlang@discover).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([collect_modules/2, collect_test_functions/1]).
+
+-spec get_module_prefix(binary()) -> binary().
+get_module_prefix(Path) ->
+ Path_without_test = begin
+ _pipe = Path,
+ gleam@string:replace(_pipe, <<"./test"/utf8>>, <<""/utf8>>)
+ end,
+ Path_without_leading_slash = case gleam@string:starts_with(
+ Path_without_test,
+ <<"/"/utf8>>
+ ) of
+ true ->
+ gleam@string:drop_left(Path_without_test, 1);
+
+ false ->
+ Path_without_test
+ end,
+ Module_prefix = begin
+ _pipe@1 = Path_without_leading_slash,
+ gleam@string:replace(_pipe@1, <<"/"/utf8>>, <<"@"/utf8>>)
+ end,
+ case gleam@string:length(Module_prefix) of
+ 0 ->
+ Module_prefix;
+
+ _ ->
+ <<Module_prefix/binary, "@"/utf8>>
+ end.
+
+-spec collect_modules_in_folder(
+ binary(),
+ fun((showtime@internal@common@test_suite:test_module()) -> nil),
+ gleam@option:option(list(binary()))
+) -> list(showtime@internal@common@test_suite:test_module()).
+collect_modules_in_folder(Path, Test_module_handler, Only_modules) ->
+ Module_prefix = get_module_prefix(Path),
+ _assert_subject = simplifile:read_directory(Path),
+ {ok, Files} = case _assert_subject of
+ {ok, _} -> _assert_subject;
+ _assert_fail ->
+ erlang:error(#{gleam_error => let_assert,
+ message => <<"Assertion pattern match failed"/utf8>>,
+ value => _assert_fail,
+ module => <<"showtime/internal/erlang/discover"/utf8>>,
+ function => <<"collect_modules_in_folder"/utf8>>,
+ line => 40})
+ end,
+ Test_modules_in_folder = begin
+ _pipe = Files,
+ _pipe@1 = gleam@list:filter(
+ _pipe,
+ fun(_capture) ->
+ gleam@string:ends_with(_capture, <<"_test.gleam"/utf8>>)
+ end
+ ),
+ gleam@list:filter_map(
+ _pipe@1,
+ fun(Test_module_file) ->
+ Module_name = <<Module_prefix/binary,
+ (begin
+ _pipe@2 = Test_module_file,
+ gleam@string:replace(
+ _pipe@2,
+ <<".gleam"/utf8>>,
+ <<""/utf8>>
+ )
+ end)/binary>>,
+ case Only_modules of
+ {some, Only_modules_list} ->
+ Module_in_list = begin
+ _pipe@3 = Only_modules_list,
+ gleam@list:any(
+ _pipe@3,
+ fun(Only_module_name) ->
+ Only_module_name =:= begin
+ _pipe@4 = Module_name,
+ gleam@string:replace(
+ _pipe@4,
+ <<"@"/utf8>>,
+ <<"/"/utf8>>
+ )
+ end
+ end
+ )
+ end,
+ case Module_in_list of
+ true ->
+ Test_module = {test_module,
+ Module_name,
+ {some, Test_module_file}},
+ Test_module_handler(Test_module),
+ {ok, Test_module};
+
+ false ->
+ {error, nil}
+ end;
+
+ none ->
+ Test_module@1 = {test_module,
+ Module_name,
+ {some, Test_module_file}},
+ Test_module_handler(Test_module@1),
+ {ok, Test_module@1}
+ end
+ end
+ )
+ end,
+ Test_modules_in_subfolders = begin
+ _pipe@5 = Files,
+ _pipe@6 = gleam@list:map(
+ _pipe@5,
+ fun(Filename) ->
+ <<<<Path/binary, "/"/utf8>>/binary, Filename/binary>>
+ end
+ ),
+ _pipe@7 = gleam@list:filter(
+ _pipe@6,
+ fun(File) -> simplifile:is_directory(File) end
+ ),
+ gleam@list:fold(
+ _pipe@7,
+ [],
+ fun(Modules, Subfolder) -> _pipe@8 = Modules,
+ gleam@list:append(
+ _pipe@8,
+ collect_modules_in_folder(
+ Subfolder,
+ Test_module_handler,
+ Only_modules
+ )
+ ) end
+ )
+ end,
+ _pipe@9 = Test_modules_in_folder,
+ gleam@list:append(_pipe@9, Test_modules_in_subfolders).
+
+-spec collect_modules(
+ fun((showtime@internal@common@test_suite:test_module()) -> nil),
+ gleam@option:option(list(binary()))
+) -> list(showtime@internal@common@test_suite:test_module()).
+collect_modules(Test_module_handler, Only_modules) ->
+ collect_modules_in_folder(
+ <<"./test"/utf8>>,
+ Test_module_handler,
+ Only_modules
+ ).
+
+-spec collect_test_functions(showtime@internal@common@test_suite:test_module()) -> showtime@internal@common@test_suite:test_suite().
+collect_test_functions(Module) ->
+ Test_functions = begin
+ _pipe = erlang:apply(
+ erlang:binary_to_atom(erlang:element(2, Module)),
+ erlang:binary_to_atom(<<"module_info"/utf8>>),
+ [gleam@dynamic:from(erlang:binary_to_atom(<<"exports"/utf8>>))]
+ ),
+ gleam@dynamic:unsafe_coerce(_pipe)
+ end,
+ Test_functions_filtered = begin
+ _pipe@1 = Test_functions,
+ _pipe@3 = gleam@list:map(
+ _pipe@1,
+ fun(Entry) ->
+ {Name, Arity} = case Entry of
+ {_, _} -> Entry;
+ _assert_fail ->
+ erlang:error(#{gleam_error => let_assert,
+ message => <<"Assertion pattern match failed"/utf8>>,
+ value => _assert_fail,
+ module => <<"showtime/internal/erlang/discover"/utf8>>,
+ function => <<"collect_test_functions"/utf8>>,
+ line => 131})
+ end,
+ {begin
+ _pipe@2 = Name,
+ erlang:atom_to_binary(_pipe@2)
+ end,
+ Arity}
+ end
+ ),
+ _pipe@4 = gleam@list:filter_map(
+ _pipe@3,
+ fun(Entry@1) ->
+ {Name@1, Arity@1} = case Entry@1 of
+ {_, _} -> Entry@1;
+ _assert_fail@1 ->
+ erlang:error(#{gleam_error => let_assert,
+ message => <<"Assertion pattern match failed"/utf8>>,
+ value => _assert_fail@1,
+ module => <<"showtime/internal/erlang/discover"/utf8>>,
+ function => <<"collect_test_functions"/utf8>>,
+ line => 139})
+ end,
+ case gleam@string:ends_with(Name@1, <<"_test"/utf8>>) of
+ true ->
+ case Arity@1 of
+ 0 ->
+ {ok, Name@1};
+
+ _ ->
+ gleam@io:println(
+ <<<<<<<<"WARNING: function \""/utf8,
+ Name@1/binary>>/binary,
+ "\" has arity: "/utf8>>/binary,
+ (gleam@int:to_string(Arity@1))/binary>>/binary,
+ " - cannot be used as test (needs to be 0)"/utf8>>
+ ),
+ {error, <<"Wrong arity"/utf8>>}
+ end;
+
+ false ->
+ {error, <<"Non matching name"/utf8>>}
+ end
+ end
+ ),
+ _pipe@5 = gleam@list:filter(
+ _pipe@4,
+ fun(_capture) ->
+ gleam@string:ends_with(_capture, <<"_test"/utf8>>)
+ end
+ ),
+ gleam@list:map(
+ _pipe@5,
+ fun(Function_name) -> {test_function, Function_name} end
+ )
+ end,
+ {test_suite, Module, Test_functions_filtered}.
diff --git a/aoc2023/build/packages/adglent/src/showtime@internal@erlang@event_handler.erl b/aoc2023/build/packages/adglent/src/showtime@internal@erlang@event_handler.erl
new file mode 100644
index 0000000..d72ce2c
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime@internal@erlang@event_handler.erl
@@ -0,0 +1,76 @@
+-module(showtime@internal@erlang@event_handler).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([start/0]).
+-export_type([event_handler_message/0]).
+
+-type event_handler_message() :: {event_handler_message,
+ showtime@internal@common@test_suite:test_event(),
+ gleam@erlang@process:subject(integer())}.
+
+-spec system_time() -> integer().
+system_time() ->
+ os:system_time(millisecond).
+
+-spec start() -> fun((showtime@internal@common@test_suite:test_event()) -> nil).
+start() ->
+ _assert_subject = gleam@otp@actor:start(
+ {not_started, 0, gleam@map:new()},
+ fun(Msg, State) ->
+ {event_handler_message, Test_event, Reply_to} = Msg,
+ {Test_state, Num_done, Events} = State,
+ Updated_state = showtime@internal@common@common_event_handler:handle_event(
+ Test_event,
+ fun system_time/0,
+ {handler_state, Test_state, Num_done, Events}
+ ),
+ case Updated_state of
+ {handler_state, {finished, Num_modules}, Num_done@1, Events@1} when Num_done@1 =:= Num_modules ->
+ {Test_report, Num_failed} = showtime@internal@reports@formatter:create_test_report(
+ Events@1
+ ),
+ gleam@io:println(Test_report),
+ gleam@erlang@process:send(Reply_to, Num_failed),
+ {stop, normal};
+
+ {handler_state, Test_state@1, Num_done@2, Events@2} ->
+ {continue, {Test_state@1, Num_done@2, Events@2}, none}
+ end
+ end
+ ),
+ {ok, Subject} = case _assert_subject of
+ {ok, _} -> _assert_subject;
+ _assert_fail ->
+ erlang:error(#{gleam_error => let_assert,
+ message => <<"Assertion pattern match failed"/utf8>>,
+ value => _assert_fail,
+ module => <<"showtime/internal/erlang/event_handler"/utf8>>,
+ function => <<"start"/utf8>>,
+ line => 32})
+ end,
+ Parent_subject = gleam@erlang@process:new_subject(),
+ Selector = begin
+ _pipe = gleam_erlang_ffi:new_selector(),
+ gleam@erlang@process:selecting(_pipe, Parent_subject, fun(X) -> X end)
+ end,
+ fun(Test_event@1) -> case Test_event@1 of
+ {end_test_run, _} ->
+ gleam@erlang@process:send(
+ Subject,
+ {event_handler_message, Test_event@1, Parent_subject}
+ ),
+ Num_failed@1 = gleam_erlang_ffi:select(Selector),
+ case Num_failed@1 > 0 of
+ true ->
+ erlang:halt(1);
+
+ false ->
+ erlang:halt(0)
+ end;
+
+ _ ->
+ gleam@erlang@process:send(
+ Subject,
+ {event_handler_message, Test_event@1, Parent_subject}
+ )
+ end end.
diff --git a/aoc2023/build/packages/adglent/src/showtime@internal@erlang@module_handler.erl b/aoc2023/build/packages/adglent/src/showtime@internal@erlang@module_handler.erl
new file mode 100644
index 0000000..a6959f5
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime@internal@erlang@module_handler.erl
@@ -0,0 +1,53 @@
+-module(showtime@internal@erlang@module_handler).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([start/5]).
+
+-spec start(
+ fun((showtime@internal@common@test_suite:test_event()) -> nil),
+ fun((showtime@internal@common@test_suite:test_module()) -> showtime@internal@common@test_suite:test_suite()),
+ fun((showtime@internal@common@test_suite:test_suite(), fun((showtime@internal@common@test_suite:test_event()) -> nil), list(binary()), showtime@internal@common@cli:capture()) -> nil),
+ list(binary()),
+ showtime@internal@common@cli:capture()
+) -> fun((showtime@internal@common@test_suite:test_module()) -> nil).
+start(
+ Test_event_handler,
+ Test_function_collector,
+ Run_test_suite,
+ Ignore_tags,
+ Capture
+) ->
+ _assert_subject = gleam@otp@actor:start(
+ nil,
+ fun(Module, State) ->
+ gleam@erlang@process:start(
+ fun() ->
+ Test_suite = Test_function_collector(Module),
+ Test_event_handler({start_test_suite, Module}),
+ Run_test_suite(
+ Test_suite,
+ Test_event_handler,
+ Ignore_tags,
+ Capture
+ ),
+ Test_event_handler({end_test_suite, Module})
+ end,
+ false
+ ),
+ {continue, State, none}
+ end
+ ),
+ {ok, Subject} = case _assert_subject of
+ {ok, _} -> _assert_subject;
+ _assert_fail ->
+ erlang:error(#{gleam_error => let_assert,
+ message => <<"Assertion pattern match failed"/utf8>>,
+ value => _assert_fail,
+ module => <<"showtime/internal/erlang/module_handler"/utf8>>,
+ function => <<"start"/utf8>>,
+ line => 23})
+ end,
+ fun(Test_module) ->
+ gleam@erlang@process:send(Subject, Test_module),
+ nil
+ end.
diff --git a/aoc2023/build/packages/adglent/src/showtime@internal@erlang@runner.erl b/aoc2023/build/packages/adglent/src/showtime@internal@erlang@runner.erl
new file mode 100644
index 0000000..702fb75
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime@internal@erlang@runner.erl
@@ -0,0 +1,46 @@
+-module(showtime@internal@erlang@runner).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([run_test/4, run_test_suite/4]).
+
+-spec run_test(
+ binary(),
+ binary(),
+ list(binary()),
+ showtime@internal@common@cli:capture()
+) -> {ok, showtime@internal@common@test_result:test_return()} |
+ {error, showtime@internal@common@test_result:exception()}.
+run_test(Module_name, Test_name, Ignore_tags, Capture) ->
+ Result = showtime_ffi:run_test(
+ erlang:binary_to_atom(Module_name),
+ erlang:binary_to_atom(Test_name),
+ Ignore_tags,
+ Capture
+ ),
+ Result.
+
+-spec run_test_suite(
+ showtime@internal@common@test_suite:test_suite(),
+ fun((showtime@internal@common@test_suite:test_event()) -> nil),
+ list(binary()),
+ showtime@internal@common@cli:capture()
+) -> nil.
+run_test_suite(Test_suite, Test_event_handler, Ignore_tags, Capture) ->
+ _pipe = erlang:element(3, Test_suite),
+ gleam@list:each(
+ _pipe,
+ fun(Test) ->
+ Test_event_handler(
+ {start_test, erlang:element(2, Test_suite), Test}
+ ),
+ Result = run_test(
+ erlang:element(2, erlang:element(2, Test_suite)),
+ erlang:element(2, Test),
+ Ignore_tags,
+ Capture
+ ),
+ Test_event_handler(
+ {end_test, erlang:element(2, Test_suite), Test, Result}
+ )
+ end
+ ).
diff --git a/aoc2023/build/packages/adglent/src/showtime@internal@reports@compare.erl b/aoc2023/build/packages/adglent/src/showtime@internal@reports@compare.erl
new file mode 100644
index 0000000..d2969b2
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime@internal@reports@compare.erl
@@ -0,0 +1,61 @@
+-module(showtime@internal@reports@compare).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([compare/2]).
+
+-spec compare(gleam@dynamic:dynamic_(), gleam@dynamic:dynamic_()) -> {binary(),
+ binary()}.
+compare(Expected, Got) ->
+ Expected_as_list = begin
+ _pipe = Expected,
+ (gleam@dynamic:list(fun gleam@dynamic:dynamic/1))(_pipe)
+ end,
+ Got_as_list = begin
+ _pipe@1 = Got,
+ (gleam@dynamic:list(fun gleam@dynamic:dynamic/1))(_pipe@1)
+ end,
+ Expected_as_string = begin
+ _pipe@2 = Expected,
+ gleam@dynamic:string(_pipe@2)
+ end,
+ Got_as_string = begin
+ _pipe@3 = Got,
+ gleam@dynamic:string(_pipe@3)
+ end,
+ case {Expected_as_list, Got_as_list, Expected_as_string, Got_as_string} of
+ {{ok, Expected_list}, {ok, Got_list}, _, _} ->
+ Comparison = begin
+ _pipe@4 = gap:compare_lists(Expected_list, Got_list),
+ _pipe@5 = gap@styling:from_comparison(_pipe@4),
+ _pipe@6 = gap@styling:highlight(
+ _pipe@5,
+ fun showtime@internal@reports@styles:expected_highlight/1,
+ fun showtime@internal@reports@styles:got_highlight/1,
+ fun(Item) -> Item end
+ ),
+ gap@styling:to_styled_comparison(_pipe@6)
+ end,
+ {erlang:element(2, Comparison), erlang:element(3, Comparison)};
+
+ {_, _, {ok, Expected_string}, {ok, Got_string}} ->
+ Comparison@1 = begin
+ _pipe@7 = gap:compare_strings(Expected_string, Got_string),
+ _pipe@8 = gap@styling:from_comparison(_pipe@7),
+ _pipe@9 = gap@styling:highlight(
+ _pipe@8,
+ fun showtime@internal@reports@styles:expected_highlight/1,
+ fun showtime@internal@reports@styles:got_highlight/1,
+ fun(Item@1) -> Item@1 end
+ ),
+ gap@styling:to_styled_comparison(_pipe@9)
+ end,
+ {erlang:element(2, Comparison@1), erlang:element(3, Comparison@1)};
+
+ {_, _, _, _} ->
+ {showtime@internal@reports@styles:expected_highlight(
+ gleam@string:inspect(Expected)
+ ),
+ showtime@internal@reports@styles:got_highlight(
+ gleam@string:inspect(Got)
+ )}
+ end.
diff --git a/aoc2023/build/packages/adglent/src/showtime@internal@reports@formatter.erl b/aoc2023/build/packages/adglent/src/showtime@internal@reports@formatter.erl
new file mode 100644
index 0000000..faea091
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime@internal@reports@formatter.erl
@@ -0,0 +1,749 @@
+-module(showtime@internal@reports@formatter).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([create_test_report/1]).
+-export_type([glee_unit_assertion_type/0, module_and_test/0, unified_error/0]).
+
+-type glee_unit_assertion_type() :: {glee_unit_assert_equal, binary()} |
+ {glee_unit_assert_not_equal, binary()} |
+ {glee_unit_assert_match, binary()}.
+
+-type module_and_test() :: {module_and_test_run,
+ binary(),
+ showtime@internal@common@test_suite:test_run()}.
+
+-type unified_error() :: {unified_error,
+ gleam@option:option(showtime@tests@meta:meta()),
+ binary(),
+ binary(),
+ binary(),
+ binary(),
+ gleam@option:option(integer()),
+ list(showtime@internal@common@test_result:trace())}.
+
+-spec erlang_error_to_unified(
+ list(showtime@internal@common@test_result:reason_detail()),
+ glee_unit_assertion_type(),
+ list(showtime@internal@common@test_result:trace())
+) -> unified_error().
+erlang_error_to_unified(Error_details, Assertion_type, Stacktrace) ->
+ _pipe = Error_details,
+ gleam@list:fold(
+ _pipe,
+ {unified_error,
+ none,
+ <<"not_set"/utf8>>,
+ erlang:element(2, Assertion_type),
+ <<""/utf8>>,
+ <<""/utf8>>,
+ none,
+ Stacktrace},
+ fun(Unified, Reason) -> case Reason of
+ {expression, Expression} ->
+ erlang:setelement(3, Unified, Expression);
+
+ {expected, Value} ->
+ case Assertion_type of
+ {glee_unit_assert_equal, _} ->
+ erlang:setelement(
+ 5,
+ Unified,
+ showtime@internal@reports@styles:expected_highlight(
+ gleam@string:inspect(Value)
+ )
+ );
+
+ _ ->
+ Unified
+ end;
+
+ {value, Value@1} ->
+ case Assertion_type of
+ {glee_unit_assert_not_equal, _} ->
+ erlang:setelement(
+ 6,
+ erlang:setelement(
+ 5,
+ Unified,
+ <<(showtime@internal@reports@styles:not_style(
+ <<"not "/utf8>>
+ ))/binary,
+ (gleam@string:inspect(Value@1))/binary>>
+ ),
+ showtime@internal@reports@styles:got_highlight(
+ gleam@string:inspect(Value@1)
+ )
+ );
+
+ _ ->
+ erlang:setelement(
+ 6,
+ Unified,
+ showtime@internal@reports@styles:got_highlight(
+ gleam@string:inspect(Value@1)
+ )
+ )
+ end;
+
+ {pattern, Pattern} ->
+ case Pattern of
+ <<"{ ok , _ }"/utf8>> ->
+ erlang:setelement(
+ 5,
+ Unified,
+ showtime@internal@reports@styles:expected_highlight(
+ <<"Ok(_)"/utf8>>
+ )
+ );
+
+ <<"{ error , _ }"/utf8>> ->
+ erlang:setelement(
+ 5,
+ Unified,
+ showtime@internal@reports@styles:expected_highlight(
+ <<"Error(_)"/utf8>>
+ )
+ );
+
+ _ ->
+ Unified
+ end;
+
+ _ ->
+ Unified
+ end end
+ ).
+
+-spec gleam_error_to_unified(
+ showtime@internal@common@test_result:gleam_error_detail(),
+ list(showtime@internal@common@test_result:trace())
+) -> unified_error().
+gleam_error_to_unified(Gleam_error, Stacktrace) ->
+ case Gleam_error of
+ {let_assert, _, _, _, _, Value} ->
+ Result = gleam@dynamic:unsafe_coerce(Value),
+ {error, Assertion} = case Result of
+ {error, _} -> Result;
+ _assert_fail ->
+ erlang:error(#{gleam_error => let_assert,
+ message => <<"Assertion pattern match failed"/utf8>>,
+ value => _assert_fail,
+ module => <<"showtime/internal/reports/formatter"/utf8>>,
+ function => <<"gleam_error_to_unified"/utf8>>,
+ line => 293})
+ end,
+ case Assertion of
+ {eq, Got, Expected, Meta} ->
+ {Expected@1, Got@1} = showtime@internal@reports@compare:compare(
+ Expected,
+ Got
+ ),
+ {unified_error,
+ Meta,
+ <<"assert"/utf8>>,
+ <<"Assert equal"/utf8>>,
+ Expected@1,
+ Got@1,
+ none,
+ Stacktrace};
+
+ {not_eq, Got@2, Expected@2, Meta@1} ->
+ {unified_error,
+ Meta@1,
+ <<"assert"/utf8>>,
+ <<"Assert not equal"/utf8>>,
+ <<(showtime@internal@reports@styles:not_style(
+ <<"not "/utf8>>
+ ))/binary,
+ (gleam@string:inspect(Expected@2))/binary>>,
+ gleam@string:inspect(Got@2),
+ none,
+ Stacktrace};
+
+ {is_ok, Got@3, Meta@2} ->
+ {unified_error,
+ Meta@2,
+ <<"assert"/utf8>>,
+ <<"Assert is Ok"/utf8>>,
+ showtime@internal@reports@styles:expected_highlight(
+ <<"Ok(_)"/utf8>>
+ ),
+ showtime@internal@reports@styles:got_highlight(
+ gleam@string:inspect(Got@3)
+ ),
+ none,
+ Stacktrace};
+
+ {is_error, Got@4, Meta@3} ->
+ {unified_error,
+ Meta@3,
+ <<"assert"/utf8>>,
+ <<"Assert is Ok"/utf8>>,
+ showtime@internal@reports@styles:expected_highlight(
+ <<"Error(_)"/utf8>>
+ ),
+ showtime@internal@reports@styles:got_highlight(
+ gleam@string:inspect(Got@4)
+ ),
+ none,
+ Stacktrace};
+
+ {fail, Meta@4} ->
+ {unified_error,
+ Meta@4,
+ <<"assert"/utf8>>,
+ <<"Assert is Ok"/utf8>>,
+ showtime@internal@reports@styles:got_highlight(
+ <<"should.fail()"/utf8>>
+ ),
+ showtime@internal@reports@styles:got_highlight(
+ <<"N/A - test always expected to fail"/utf8>>
+ ),
+ none,
+ Stacktrace}
+ end
+ end.
+
+-spec format_reason(unified_error(), binary(), binary(), list(binary())) -> list(list(showtime@internal@reports@table:col())).
+format_reason(Error, Module, Function, Output_buffer) ->
+ Meta@1 = case erlang:element(2, Error) of
+ {some, Meta} ->
+ {some,
+ [{align_right,
+ {styled_content,
+ showtime@internal@reports@styles:heading_style(
+ <<"Description"/utf8>>
+ )},
+ 2},
+ {separator, <<": "/utf8>>},
+ {align_left, {content, erlang:element(2, Meta)}, 0}]};
+
+ none ->
+ none
+ end,
+ Stacktrace = begin
+ _pipe = erlang:element(8, Error),
+ gleam@list:map(_pipe, fun(Trace) -> case Trace of
+ {trace, Function@1, _, _} when Function@1 =:= <<""/utf8>> ->
+ <<"(anonymous)"/utf8>>;
+
+ {trace_module, Module@1, Function@2, _, _} when Function@2 =:= <<""/utf8>> ->
+ <<<<Module@1/binary, "."/utf8>>/binary,
+ "(anonymous)"/utf8>>;
+
+ {trace, Function@3, _, _} ->
+ Function@3;
+
+ {trace_module, Module@2, Function@4, _, _} ->
+ <<<<Module@2/binary, "."/utf8>>/binary,
+ Function@4/binary>>
+ end end)
+ end,
+ Stacktrace_rows = case Stacktrace of
+ [] ->
+ [];
+
+ [First | Rest] ->
+ First_row = {some,
+ [{align_right,
+ {styled_content,
+ showtime@internal@reports@styles:heading_style(
+ <<"Stacktrace"/utf8>>
+ )},
+ 2},
+ {separator, <<": "/utf8>>},
+ {align_left,
+ {styled_content,
+ showtime@internal@reports@styles:stacktrace_style(
+ First
+ )},
+ 0}]},
+ Rest_rows = begin
+ _pipe@1 = Rest,
+ gleam@list:map(
+ _pipe@1,
+ fun(Row) ->
+ {some,
+ [{align_right, {content, <<""/utf8>>}, 2},
+ {separator, <<" "/utf8>>},
+ {align_left,
+ {styled_content,
+ showtime@internal@reports@styles:stacktrace_style(
+ Row
+ )},
+ 0}]}
+ end
+ )
+ end,
+ [First_row | Rest_rows]
+ end,
+ Output_rows = case begin
+ _pipe@2 = Output_buffer,
+ _pipe@3 = gleam@list:reverse(_pipe@2),
+ gleam@list:map(
+ _pipe@3,
+ fun(Row@1) -> gleam@string:trim_right(Row@1) end
+ )
+ end of
+ [] ->
+ [];
+
+ [First@1 | Rest@1] ->
+ First_row@1 = {some,
+ [{align_right,
+ {styled_content,
+ showtime@internal@reports@styles:heading_style(
+ <<"Output"/utf8>>
+ )},
+ 2},
+ {separator, <<": "/utf8>>},
+ {align_left_overflow,
+ {styled_content,
+ showtime@internal@reports@styles:stacktrace_style(
+ First@1
+ )},
+ 0}]},
+ Rest_rows@1 = begin
+ _pipe@4 = Rest@1,
+ gleam@list:map(
+ _pipe@4,
+ fun(Row@2) ->
+ {some,
+ [{align_right, {content, <<""/utf8>>}, 2},
+ {separator, <<" "/utf8>>},
+ {align_left_overflow,
+ {styled_content,
+ showtime@internal@reports@styles:stacktrace_style(
+ Row@2
+ )},
+ 0}]}
+ end
+ )
+ end,
+ [First_row@1 | Rest_rows@1]
+ end,
+ Line@1 = begin
+ _pipe@5 = erlang:element(7, Error),
+ _pipe@6 = gleam@option:map(
+ _pipe@5,
+ fun(Line) -> <<":"/utf8, (gleam@int:to_string(Line))/binary>> end
+ ),
+ gleam@option:unwrap(_pipe@6, <<""/utf8>>)
+ end,
+ Arrow = <<(gleam@string:join(
+ gleam@list:repeat(
+ <<"-"/utf8>>,
+ (gleam@string:length(Module) + 1) + ((gleam@string:length(
+ Function
+ )
+ + gleam@string:length(Line@1))
+ div 2)
+ ),
+ <<""/utf8>>
+ ))/binary,
+ "⌄"/utf8>>,
+ Standard_table_rows = [{some,
+ [{align_right,
+ {styled_content,
+ showtime@internal@reports@styles:error_style(
+ <<"Failed"/utf8>>
+ )},
+ 2},
+ {separator, <<": "/utf8>>},
+ {align_left, {content, Arrow}, 0}]},
+ {some,
+ [{align_right,
+ {styled_content,
+ showtime@internal@reports@styles:heading_style(
+ <<"Test"/utf8>>
+ )},
+ 2},
+ {separator, <<": "/utf8>>},
+ {align_left,
+ {styled_content,
+ <<<<Module/binary, "."/utf8>>/binary,
+ (showtime@internal@reports@styles:function_style(
+ <<Function/binary, Line@1/binary>>
+ ))/binary>>},
+ 0}]},
+ Meta@1,
+ {some,
+ [{align_right,
+ {styled_content,
+ showtime@internal@reports@styles:heading_style(
+ <<"Expected"/utf8>>
+ )},
+ 2},
+ {separator, <<": "/utf8>>},
+ {align_left_overflow,
+ {styled_content, erlang:element(5, Error)},
+ 0}]},
+ {some,
+ [{align_right,
+ {styled_content,
+ showtime@internal@reports@styles:heading_style(
+ <<"Got"/utf8>>
+ )},
+ 2},
+ {separator, <<": "/utf8>>},
+ {align_left_overflow,
+ {styled_content, erlang:element(6, Error)},
+ 0}]}],
+ _pipe@7 = Standard_table_rows,
+ _pipe@8 = gleam@list:append(_pipe@7, Stacktrace_rows),
+ _pipe@9 = gleam@list:append(_pipe@8, Output_rows),
+ _pipe@10 = gleam@list:append(
+ _pipe@9,
+ [{some,
+ [{align_right, {content, <<""/utf8>>}, 0},
+ {align_right, {content, <<""/utf8>>}, 0},
+ {align_right, {content, <<""/utf8>>}, 0}]}]
+ ),
+ gleam@list:filter_map(
+ _pipe@10,
+ fun(Row@3) -> gleam@option:to_result(Row@3, nil) end
+ ).
+
+-spec create_test_report(
+ gleam@map:map_(binary(), gleam@map:map_(binary(), showtime@internal@common@test_suite:test_run()))
+) -> {binary(), integer()}.
+create_test_report(Test_results) ->
+ All_test_runs = begin
+ _pipe = Test_results,
+ _pipe@1 = gleam@map:values(_pipe),
+ gleam@list:flat_map(_pipe@1, fun gleam@map:values/1)
+ end,
+ Failed_test_runs = begin
+ _pipe@2 = Test_results,
+ _pipe@3 = gleam@map:to_list(_pipe@2),
+ gleam@list:flat_map(
+ _pipe@3,
+ fun(Entry) ->
+ {Module_name, Test_module_results} = Entry,
+ _pipe@4 = Test_module_results,
+ _pipe@5 = gleam@map:values(_pipe@4),
+ gleam@list:filter_map(_pipe@5, fun(Test_run) -> case Test_run of
+ {completed_test_run, _, _, Result} ->
+ case Result of
+ {error, _} ->
+ {ok,
+ {module_and_test_run,
+ Module_name,
+ Test_run}};
+
+ {ok, {ignored, _}} ->
+ {error, nil};
+
+ {ok, _} ->
+ {error, nil}
+ end;
+
+ _ ->
+ _pipe@6 = Test_run,
+ gleam@io:debug(_pipe@6),
+ {error, nil}
+ end end)
+ end
+ )
+ end,
+ Ignored_test_runs = begin
+ _pipe@7 = Test_results,
+ _pipe@8 = gleam@map:to_list(_pipe@7),
+ gleam@list:flat_map(
+ _pipe@8,
+ fun(Entry@1) ->
+ {Module_name@1, Test_module_results@1} = Entry@1,
+ _pipe@9 = Test_module_results@1,
+ _pipe@10 = gleam@map:values(_pipe@9),
+ gleam@list:filter_map(
+ _pipe@10,
+ fun(Test_run@1) -> case Test_run@1 of
+ {completed_test_run, Test_function, _, Result@1} ->
+ case Result@1 of
+ {ok, {ignored, Reason}} ->
+ {ok,
+ {<<<<Module_name@1/binary,
+ "."/utf8>>/binary,
+ (erlang:element(
+ 2,
+ Test_function
+ ))/binary>>,
+ Reason}};
+
+ _ ->
+ {error, nil}
+ end;
+
+ _ ->
+ {error, nil}
+ end end
+ )
+ end
+ )
+ end,
+ Failed_tests_report = begin
+ _pipe@11 = Failed_test_runs,
+ _pipe@12 = gleam@list:filter_map(
+ _pipe@11,
+ fun(Module_and_test_run) ->
+ case erlang:element(3, Module_and_test_run) of
+ {completed_test_run, Test_function@1, _, Result@2} ->
+ case Result@2 of
+ {error, Exception} ->
+ case erlang:element(3, Exception) of
+ {assert_equal, Reason_details} ->
+ {ok,
+ format_reason(
+ erlang_error_to_unified(
+ Reason_details,
+ {glee_unit_assert_equal,
+ <<"Assert equal"/utf8>>},
+ erlang:element(
+ 2,
+ erlang:element(
+ 4,
+ Exception
+ )
+ )
+ ),
+ erlang:element(
+ 2,
+ Module_and_test_run
+ ),
+ erlang:element(
+ 2,
+ Test_function@1
+ ),
+ erlang:element(5, Exception)
+ )};
+
+ {assert_not_equal, Reason_details@1} ->
+ {ok,
+ format_reason(
+ erlang_error_to_unified(
+ Reason_details@1,
+ {glee_unit_assert_not_equal,
+ <<"Assert not equal"/utf8>>},
+ erlang:element(
+ 2,
+ erlang:element(
+ 4,
+ Exception
+ )
+ )
+ ),
+ erlang:element(
+ 2,
+ Module_and_test_run
+ ),
+ erlang:element(
+ 2,
+ Test_function@1
+ ),
+ erlang:element(5, Exception)
+ )};
+
+ {assert_match, Reason_details@2} ->
+ {ok,
+ format_reason(
+ erlang_error_to_unified(
+ Reason_details@2,
+ {glee_unit_assert_match,
+ <<"Assert match"/utf8>>},
+ erlang:element(
+ 2,
+ erlang:element(
+ 4,
+ Exception
+ )
+ )
+ ),
+ erlang:element(
+ 2,
+ Module_and_test_run
+ ),
+ erlang:element(
+ 2,
+ Test_function@1
+ ),
+ erlang:element(5, Exception)
+ )};
+
+ {gleam_error, Reason@1} ->
+ {ok,
+ format_reason(
+ gleam_error_to_unified(
+ Reason@1,
+ erlang:element(
+ 2,
+ erlang:element(
+ 4,
+ Exception
+ )
+ )
+ ),
+ erlang:element(
+ 2,
+ Module_and_test_run
+ ),
+ erlang:element(
+ 2,
+ Test_function@1
+ ),
+ erlang:element(5, Exception)
+ )};
+
+ {gleam_assert, Value, Line_no} ->
+ {ok,
+ format_reason(
+ {unified_error,
+ none,
+ <<"gleam assert"/utf8>>,
+ <<"Assert failed"/utf8>>,
+ <<"Patterns should match"/utf8>>,
+ showtime@internal@reports@styles:error_style(
+ gleam@string:inspect(
+ Value
+ )
+ ),
+ {some, Line_no},
+ erlang:element(
+ 2,
+ erlang:element(
+ 4,
+ Exception
+ )
+ )},
+ erlang:element(
+ 2,
+ Module_and_test_run
+ ),
+ erlang:element(
+ 2,
+ Test_function@1
+ ),
+ erlang:element(5, Exception)
+ )};
+
+ {generic_exception, Value@1} ->
+ {ok,
+ format_reason(
+ {unified_error,
+ none,
+ <<"generic exception"/utf8>>,
+ <<"Test function threw an exception"/utf8>>,
+ <<"Exception in test function"/utf8>>,
+ showtime@internal@reports@styles:error_style(
+ gleam@string:inspect(
+ Value@1
+ )
+ ),
+ none,
+ erlang:element(
+ 2,
+ erlang:element(
+ 4,
+ Exception
+ )
+ )},
+ erlang:element(
+ 2,
+ Module_and_test_run
+ ),
+ erlang:element(
+ 2,
+ Test_function@1
+ ),
+ erlang:element(5, Exception)
+ )};
+
+ Other ->
+ gleam@io:println(
+ <<"Other: "/utf8,
+ (gleam@string:inspect(Other))/binary>>
+ ),
+ erlang:error(#{gleam_error => panic,
+ message => <<"panic expression evaluated"/utf8>>,
+ module => <<"showtime/internal/reports/formatter"/utf8>>,
+ function => <<"create_test_report"/utf8>>,
+ line => 178}),
+ {error, nil}
+ end;
+
+ _ ->
+ {error, nil}
+ end;
+
+ _ ->
+ {error, nil}
+ end
+ end
+ ),
+ gleam@list:fold(
+ _pipe@12,
+ [],
+ fun(Rows, Test_rows) -> gleam@list:append(Rows, Test_rows) end
+ )
+ end,
+ All_test_execution_time_reports = begin
+ _pipe@13 = All_test_runs,
+ gleam@list:filter_map(_pipe@13, fun(Test_run@2) -> case Test_run@2 of
+ {completed_test_run, Test_function@2, Total_time, _} ->
+ {ok,
+ <<<<<<(erlang:element(2, Test_function@2))/binary,
+ ": "/utf8>>/binary,
+ (gleam@int:to_string(Total_time))/binary>>/binary,
+ " ms"/utf8>>};
+
+ _ ->
+ {error, nil}
+ end end)
+ end,
+ _ = begin
+ _pipe@14 = All_test_execution_time_reports,
+ gleam@string:join(_pipe@14, <<"\n"/utf8>>)
+ end,
+ All_tests_count = begin
+ _pipe@15 = All_test_runs,
+ gleam@list:length(_pipe@15)
+ end,
+ Ignored_tests_count = begin
+ _pipe@16 = Ignored_test_runs,
+ gleam@list:length(_pipe@16)
+ end,
+ Failed_tests_count = begin
+ _pipe@17 = Failed_test_runs,
+ gleam@list:length(_pipe@17)
+ end,
+ Passed = showtime@internal@reports@styles:passed_style(
+ <<(gleam@int:to_string(
+ (All_tests_count - Failed_tests_count) - Ignored_tests_count
+ ))/binary,
+ " passed"/utf8>>
+ ),
+ Failed = showtime@internal@reports@styles:failed_style(
+ <<(gleam@int:to_string(Failed_tests_count))/binary, " failed"/utf8>>
+ ),
+ Ignored = case Ignored_tests_count of
+ 0 ->
+ <<""/utf8>>;
+
+ _ ->
+ <<", "/utf8,
+ (showtime@internal@reports@styles:ignored_style(
+ <<(gleam@int:to_string(Ignored_tests_count))/binary,
+ " ignored"/utf8>>
+ ))/binary>>
+ end,
+ Failed_tests_table = begin
+ _pipe@18 = {table, none, Failed_tests_report},
+ _pipe@19 = showtime@internal@reports@table:align_table(_pipe@18),
+ showtime@internal@reports@table:to_string(_pipe@19)
+ end,
+ Test_report = <<<<<<<<<<<<"\n"/utf8, Failed_tests_table/binary>>/binary,
+ "\n"/utf8>>/binary,
+ Passed/binary>>/binary,
+ ", "/utf8>>/binary,
+ Failed/binary>>/binary,
+ Ignored/binary>>,
+ {Test_report, Failed_tests_count}.
diff --git a/aoc2023/build/packages/adglent/src/showtime@internal@reports@styles.erl b/aoc2023/build/packages/adglent/src/showtime@internal@reports@styles.erl
new file mode 100644
index 0000000..ec6230c
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime@internal@reports@styles.erl
@@ -0,0 +1,93 @@
+-module(showtime@internal@reports@styles).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([not_style/1, module_style/1, heading_style/1, stacktrace_style/1, failed_style/1, error_style/1, got_highlight/1, passed_style/1, expected_highlight/1, ignored_style/1, function_style/1, strip_style/1]).
+
+-spec not_style(binary()) -> binary().
+not_style(Text) ->
+ gleam_community@ansi:bold(Text).
+
+-spec module_style(binary()) -> binary().
+module_style(Text) ->
+ gleam_community@ansi:cyan(Text).
+
+-spec heading_style(binary()) -> binary().
+heading_style(Text) ->
+ gleam_community@ansi:cyan(Text).
+
+-spec stacktrace_style(binary()) -> binary().
+stacktrace_style(Text) ->
+ Text.
+
+-spec bold_red(binary()) -> binary().
+bold_red(Text) ->
+ gleam_community@ansi:bold(gleam_community@ansi:red(Text)).
+
+-spec failed_style(binary()) -> binary().
+failed_style(Text) ->
+ bold_red(Text).
+
+-spec error_style(binary()) -> binary().
+error_style(Text) ->
+ bold_red(Text).
+
+-spec got_highlight(binary()) -> binary().
+got_highlight(Text) ->
+ bold_red(Text).
+
+-spec bold_green(binary()) -> binary().
+bold_green(Text) ->
+ gleam_community@ansi:bold(gleam_community@ansi:green(Text)).
+
+-spec passed_style(binary()) -> binary().
+passed_style(Text) ->
+ bold_green(Text).
+
+-spec expected_highlight(binary()) -> binary().
+expected_highlight(Text) ->
+ bold_green(Text).
+
+-spec bold_yellow(binary()) -> binary().
+bold_yellow(Text) ->
+ gleam_community@ansi:bold(gleam_community@ansi:yellow(Text)).
+
+-spec ignored_style(binary()) -> binary().
+ignored_style(Text) ->
+ bold_yellow(Text).
+
+-spec bold_cyan(binary()) -> binary().
+bold_cyan(Text) ->
+ gleam_community@ansi:bold(gleam_community@ansi:cyan(Text)).
+
+-spec function_style(binary()) -> binary().
+function_style(Text) ->
+ bold_cyan(Text).
+
+-spec strip_style(binary()) -> binary().
+strip_style(Text) ->
+ {New_text, _} = begin
+ _pipe = Text,
+ _pipe@1 = gleam@string:to_graphemes(_pipe),
+ gleam@list:fold(
+ _pipe@1,
+ {<<""/utf8>>, false},
+ fun(Acc, Char) ->
+ {Str, Removing} = Acc,
+ Bit_char = gleam_stdlib:identity(Char),
+ case {Bit_char, Removing} of
+ {<<16#1b>>, _} ->
+ {Str, true};
+
+ {<<16#6d>>, true} ->
+ {Str, false};
+
+ {_, true} ->
+ {Str, true};
+
+ {_, false} ->
+ {<<Str/binary, Char/binary>>, false}
+ end
+ end
+ )
+ end,
+ New_text.
diff --git a/aoc2023/build/packages/adglent/src/showtime@internal@reports@table.erl b/aoc2023/build/packages/adglent/src/showtime@internal@reports@table.erl
new file mode 100644
index 0000000..35dbba3
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime@internal@reports@table.erl
@@ -0,0 +1,229 @@
+-module(showtime@internal@reports@table).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([to_string/1, align_table/1]).
+-export_type([content/0, col/0, table/0]).
+
+-type content() :: {content, binary()} | {styled_content, binary()}.
+
+-type col() :: {align_right, content(), integer()} |
+ {align_left, content(), integer()} |
+ {align_right_overflow, content(), integer()} |
+ {align_left_overflow, content(), integer()} |
+ {separator, binary()} |
+ {aligned, binary()}.
+
+-type table() :: {table, gleam@option:option(binary()), list(list(col()))}.
+
+-spec to_string(table()) -> binary().
+to_string(Table) ->
+ Rows = begin
+ _pipe = erlang:element(3, Table),
+ _pipe@3 = gleam@list:map(_pipe, fun(Row) -> _pipe@1 = Row,
+ _pipe@2 = gleam@list:filter_map(_pipe@1, fun(Col) -> case Col of
+ {separator, Char} ->
+ {ok, Char};
+
+ {aligned, Content} ->
+ {ok, Content};
+
+ _ ->
+ {error, nil}
+ end end),
+ gleam@string:join(_pipe@2, <<""/utf8>>) end),
+ gleam@string:join(_pipe@3, <<"\n"/utf8>>)
+ end,
+ Header@1 = begin
+ _pipe@4 = erlang:element(2, Table),
+ _pipe@5 = gleam@option:map(
+ _pipe@4,
+ fun(Header) -> <<Header/binary, "\n"/utf8>> end
+ ),
+ gleam@option:unwrap(_pipe@5, <<""/utf8>>)
+ end,
+ <<Header@1/binary, Rows/binary>>.
+
+-spec pad_left(binary(), integer(), binary()) -> binary().
+pad_left(Str, Num, Char) ->
+ Padding = begin
+ _pipe = gleam@list:repeat(Char, Num),
+ gleam@string:join(_pipe, <<""/utf8>>)
+ end,
+ <<Padding/binary, Str/binary>>.
+
+-spec pad_right(binary(), integer(), binary()) -> binary().
+pad_right(Str, Num, Char) ->
+ Padding = begin
+ _pipe = gleam@list:repeat(Char, Num),
+ gleam@string:join(_pipe, <<""/utf8>>)
+ end,
+ <<Str/binary, Padding/binary>>.
+
+-spec align_table(table()) -> table().
+align_table(Table) ->
+ Cols = begin
+ _pipe = erlang:element(3, Table),
+ gleam@list:transpose(_pipe)
+ end,
+ Col_width = begin
+ _pipe@1 = Cols,
+ gleam@list:map(_pipe@1, fun(Col) -> _pipe@2 = Col,
+ _pipe@3 = gleam@list:map(
+ _pipe@2,
+ fun(Content) -> case Content of
+ {align_right, {content, Unstyled}, _} ->
+ Unstyled;
+
+ {align_right, {styled_content, Styled}, _} ->
+ showtime@internal@reports@styles:strip_style(
+ Styled
+ );
+
+ {align_left, {content, Unstyled@1}, _} ->
+ Unstyled@1;
+
+ {align_left, {styled_content, Styled@1}, _} ->
+ showtime@internal@reports@styles:strip_style(
+ Styled@1
+ );
+
+ {align_left_overflow, _, _} ->
+ <<""/utf8>>;
+
+ {align_right_overflow, _, _} ->
+ <<""/utf8>>;
+
+ {separator, Char} ->
+ Char;
+
+ {aligned, Content@1} ->
+ Content@1
+ end end
+ ),
+ gleam@list:fold(
+ _pipe@3,
+ 0,
+ fun(Max, Str) ->
+ gleam@int:max(Max, gleam@string:length(Str))
+ end
+ ) end)
+ end,
+ Aligned_col = begin
+ _pipe@4 = Cols,
+ _pipe@5 = gleam@list:zip(_pipe@4, Col_width),
+ gleam@list:map(
+ _pipe@5,
+ fun(Col_and_width) ->
+ {Col@1, Width} = Col_and_width,
+ _pipe@6 = Col@1,
+ gleam@list:map(_pipe@6, fun(Content@2) -> case Content@2 of
+ {align_right, {content, Unstyled@2}, Margin} ->
+ {aligned,
+ pad_left(
+ Unstyled@2,
+ (Width + Margin) - gleam@string:length(
+ Unstyled@2
+ ),
+ <<" "/utf8>>
+ )};
+
+ {align_right, {styled_content, Styled@2}, Margin@1} ->
+ {aligned,
+ pad_left(
+ Styled@2,
+ (Width + Margin@1) - gleam@string:length(
+ showtime@internal@reports@styles:strip_style(
+ Styled@2
+ )
+ ),
+ <<" "/utf8>>
+ )};
+
+ {align_right_overflow,
+ {content, Unstyled@3},
+ Margin@2} ->
+ {aligned,
+ pad_left(
+ Unstyled@3,
+ (Width + Margin@2) - gleam@string:length(
+ Unstyled@3
+ ),
+ <<" "/utf8>>
+ )};
+
+ {align_right_overflow,
+ {styled_content, Styled@3},
+ Margin@3} ->
+ {aligned,
+ pad_left(
+ Styled@3,
+ (Width + Margin@3) - gleam@string:length(
+ showtime@internal@reports@styles:strip_style(
+ Styled@3
+ )
+ ),
+ <<" "/utf8>>
+ )};
+
+ {align_left, {content, Unstyled@4}, Margin@4} ->
+ {aligned,
+ pad_right(
+ Unstyled@4,
+ (Width + Margin@4) - gleam@string:length(
+ Unstyled@4
+ ),
+ <<" "/utf8>>
+ )};
+
+ {align_left, {styled_content, Styled@4}, Margin@5} ->
+ {aligned,
+ pad_right(
+ Styled@4,
+ (Width + Margin@5) - gleam@string:length(
+ showtime@internal@reports@styles:strip_style(
+ Styled@4
+ )
+ ),
+ <<" "/utf8>>
+ )};
+
+ {align_left_overflow,
+ {content, Unstyled@5},
+ Margin@6} ->
+ {aligned,
+ pad_right(
+ Unstyled@5,
+ (Width + Margin@6) - gleam@string:length(
+ Unstyled@5
+ ),
+ <<" "/utf8>>
+ )};
+
+ {align_left_overflow,
+ {styled_content, Styled@5},
+ Margin@7} ->
+ {aligned,
+ pad_right(
+ Styled@5,
+ (Width + Margin@7) - gleam@string:length(
+ showtime@internal@reports@styles:strip_style(
+ Styled@5
+ )
+ ),
+ <<" "/utf8>>
+ )};
+
+ {separator, Char@1} ->
+ {separator, Char@1};
+
+ {aligned, Content@3} ->
+ {aligned, Content@3}
+ end end)
+ end
+ )
+ end,
+ Aligned_rows = begin
+ _pipe@7 = Aligned_col,
+ gleam@list:transpose(_pipe@7)
+ end,
+ erlang:setelement(3, Table, Aligned_rows).
diff --git a/aoc2023/build/packages/adglent/src/showtime@tests@meta.erl b/aoc2023/build/packages/adglent/src/showtime@tests@meta.erl
new file mode 100644
index 0000000..c975467
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime@tests@meta.erl
@@ -0,0 +1,8 @@
+-module(showtime@tests@meta).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export_type([meta/0]).
+
+-type meta() :: {meta, binary(), list(binary())}.
+
+
diff --git a/aoc2023/build/packages/adglent/src/showtime@tests@should.erl b/aoc2023/build/packages/adglent/src/showtime@tests@should.erl
new file mode 100644
index 0000000..29906b4
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime@tests@should.erl
@@ -0,0 +1,143 @@
+-module(showtime@tests@should).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([evaluate/1, equal/2, equal_meta/3, not_equal/2, not_equal_meta/3, be_ok/1, be_ok_meta/2, be_error/1, be_error_meta/2, fail/0, fail_meta/1, be_true/1, be_true_meta/2, be_false/1, be_false_meta/2]).
+-export_type([assertion/2]).
+
+-type assertion(MXP, MXQ) :: {eq,
+ MXP,
+ MXP,
+ gleam@option:option(showtime@tests@meta:meta())} |
+ {not_eq, MXP, MXP, gleam@option:option(showtime@tests@meta:meta())} |
+ {is_ok,
+ {ok, MXP} | {error, MXQ},
+ gleam@option:option(showtime@tests@meta:meta())} |
+ {is_error,
+ {ok, MXP} | {error, MXQ},
+ gleam@option:option(showtime@tests@meta:meta())} |
+ {fail, gleam@option:option(showtime@tests@meta:meta())}.
+
+-spec evaluate(assertion(any(), any())) -> nil.
+evaluate(Assertion) ->
+ case Assertion of
+ {eq, A, B, _} ->
+ case A =:= B of
+ true ->
+ nil;
+
+ false ->
+ showtime_ffi:gleam_error({error, Assertion})
+ end;
+
+ {not_eq, A@1, B@1, _} ->
+ case A@1 /= B@1 of
+ true ->
+ nil;
+
+ false ->
+ showtime_ffi:gleam_error({error, Assertion})
+ end;
+
+ {is_ok, A@2, _} ->
+ case A@2 of
+ {ok, _} ->
+ nil;
+
+ {error, _} ->
+ showtime_ffi:gleam_error({error, Assertion})
+ end;
+
+ {is_error, A@3, _} ->
+ case A@3 of
+ {error, _} ->
+ nil;
+
+ {ok, _} ->
+ showtime_ffi:gleam_error({error, Assertion})
+ end;
+
+ {fail, _} ->
+ showtime_ffi:gleam_error({error, Assertion})
+ end.
+
+-spec equal(MXR, MXR) -> nil.
+equal(A, B) ->
+ evaluate({eq, A, B, none}).
+
+-spec equal_meta(MXT, MXT, showtime@tests@meta:meta()) -> nil.
+equal_meta(A, B, Meta) ->
+ evaluate({eq, A, B, {some, Meta}}).
+
+-spec not_equal(MXV, MXV) -> nil.
+not_equal(A, B) ->
+ evaluate({not_eq, A, B, none}).
+
+-spec not_equal_meta(MXX, MXX, showtime@tests@meta:meta()) -> nil.
+not_equal_meta(A, B, Meta) ->
+ evaluate({not_eq, A, B, {some, Meta}}).
+
+-spec be_ok({ok, MXZ} | {error, any()}) -> MXZ.
+be_ok(A) ->
+ evaluate({is_ok, A, none}),
+ {ok, Value} = case A of
+ {ok, _} -> A;
+ _assert_fail ->
+ erlang:error(#{gleam_error => let_assert,
+ message => <<"Assertion pattern match failed"/utf8>>,
+ value => _assert_fail,
+ module => <<"showtime/tests/should"/utf8>>,
+ function => <<"be_ok"/utf8>>,
+ line => 30})
+ end,
+ Value.
+
+-spec be_ok_meta({ok, any()} | {error, any()}, showtime@tests@meta:meta()) -> nil.
+be_ok_meta(A, Meta) ->
+ evaluate({is_ok, A, {some, Meta}}).
+
+-spec be_error({ok, any()} | {error, MYK}) -> MYK.
+be_error(A) ->
+ evaluate({is_error, A, none}),
+ {error, Value} = case A of
+ {error, _} -> A;
+ _assert_fail ->
+ erlang:error(#{gleam_error => let_assert,
+ message => <<"Assertion pattern match failed"/utf8>>,
+ value => _assert_fail,
+ module => <<"showtime/tests/should"/utf8>>,
+ function => <<"be_error"/utf8>>,
+ line => 40})
+ end,
+ Value.
+
+-spec be_error_meta({ok, any()} | {error, any()}, showtime@tests@meta:meta()) -> nil.
+be_error_meta(A, Meta) ->
+ evaluate({is_error, A, {some, Meta}}).
+
+-spec fail() -> nil.
+fail() ->
+ evaluate({fail, none}).
+
+-spec fail_meta(showtime@tests@meta:meta()) -> nil.
+fail_meta(Meta) ->
+ evaluate({fail, {some, Meta}}).
+
+-spec be_true(boolean()) -> nil.
+be_true(A) ->
+ _pipe = A,
+ equal(_pipe, true).
+
+-spec be_true_meta(boolean(), showtime@tests@meta:meta()) -> nil.
+be_true_meta(A, Meta) ->
+ _pipe = A,
+ equal_meta(_pipe, true, Meta).
+
+-spec be_false(boolean()) -> nil.
+be_false(A) ->
+ _pipe = A,
+ equal(_pipe, false).
+
+-spec be_false_meta(boolean(), showtime@tests@meta:meta()) -> nil.
+be_false_meta(A, Meta) ->
+ _pipe = A,
+ equal_meta(_pipe, false, Meta).
diff --git a/aoc2023/build/packages/adglent/src/showtime@tests@test.erl b/aoc2023/build/packages/adglent/src/showtime@tests@test.erl
new file mode 100644
index 0000000..2f23b9f
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime@tests@test.erl
@@ -0,0 +1,57 @@
+-module(showtime@tests@test).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([test/2, equal/3, not_equal/3, with_meta/2, be_ok/2, be_error/2, fail/1, be_true/2, be_false/2]).
+-export_type([test/0, meta_should/1]).
+
+-type test() :: {test, showtime@tests@meta:meta(), fun(() -> nil)}.
+
+-type meta_should(NDO) :: {meta_should,
+ fun((NDO, NDO) -> nil),
+ fun((NDO, NDO) -> nil)}.
+
+-spec test(showtime@tests@meta:meta(), fun((showtime@tests@meta:meta()) -> nil)) -> test().
+test(Meta, Test_function) ->
+ {test, Meta, fun() -> Test_function(Meta) end}.
+
+-spec equal(NDT, NDT, showtime@tests@meta:meta()) -> nil.
+equal(A, B, Meta) ->
+ gleam@io:debug(A),
+ gleam@io:debug(B),
+ showtime@tests@should:equal_meta(A, B, Meta).
+
+-spec not_equal(NDV, NDV, showtime@tests@meta:meta()) -> nil.
+not_equal(A, B, Meta) ->
+ showtime@tests@should:equal_meta(A, B, Meta).
+
+-spec with_meta(showtime@tests@meta:meta(), fun((meta_should(any())) -> nil)) -> test().
+with_meta(Meta, Test_function) ->
+ {test,
+ Meta,
+ fun() ->
+ Test_function(
+ {meta_should,
+ fun(A, B) -> equal(A, B, Meta) end,
+ fun(A@1, B@1) -> not_equal(A@1, B@1, Meta) end}
+ )
+ end}.
+
+-spec be_ok({ok, any()} | {error, any()}, showtime@tests@meta:meta()) -> nil.
+be_ok(A, Meta) ->
+ showtime@tests@should:be_ok_meta(A, Meta).
+
+-spec be_error({ok, any()} | {error, any()}, showtime@tests@meta:meta()) -> nil.
+be_error(A, Meta) ->
+ showtime@tests@should:be_error_meta(A, Meta).
+
+-spec fail(showtime@tests@meta:meta()) -> nil.
+fail(Meta) ->
+ showtime@tests@should:fail_meta(Meta).
+
+-spec be_true(boolean(), showtime@tests@meta:meta()) -> nil.
+be_true(A, Meta) ->
+ showtime@tests@should:be_true_meta(A, Meta).
+
+-spec be_false(boolean(), showtime@tests@meta:meta()) -> nil.
+be_false(A, Meta) ->
+ showtime@tests@should:be_false_meta(A, Meta).
diff --git a/aoc2023/build/packages/adglent/src/showtime_ffi.erl b/aoc2023/build/packages/adglent/src/showtime_ffi.erl
new file mode 100644
index 0000000..3259623
--- /dev/null
+++ b/aoc2023/build/packages/adglent/src/showtime_ffi.erl
@@ -0,0 +1,187 @@
+-module(showtime_ffi).
+
+-export([run_test/4, functions/0, capture_output/1, gleam_error/1]).
+
+gleam_error(Value) ->
+ erlang:error(#{
+ gleam_error => let_assert,
+ message => <<"Assertion pattern match failed"/utf8>>,
+ value => Value,
+ module => <<"this/is/not/used"/utf8>>,
+ function => <<"gleam_error"/utf8>>,
+ % Not used
+ line => 0
+ }).
+
+start_output_capture(Capture) ->
+ OldGroupLeader = group_leader(),
+ CapturePid = spawn(showtime_ffi, capture_output, [{[], {OldGroupLeader, Capture}}]),
+ group_leader(CapturePid, self()),
+ {CapturePid, OldGroupLeader}.
+
+stop_output_capture({CapturePid, OldGroupLeader}) ->
+ group_leader(OldGroupLeader, self()),
+ CapturePid ! {capture_done, self()},
+ receive
+ Buffer ->
+ Buffer
+ end.
+
+capture_output({Buffer, {OldGroupLeader, Capture}}) ->
+ receive
+ {io_request, From, ReplyAs, {put_chars, unicode, BitString}} ->
+ case Capture of
+ yes ->
+ From ! {io_reply, ReplyAs, ok},
+ capture_output({[BitString | Buffer], {OldGroupLeader, Capture}});
+ mixed ->
+ OldGroupLeader ! {io_request, From, ReplyAs, {put_chars, unicode, BitString}},
+ capture_output({[BitString | Buffer], {OldGroupLeader, Capture}});
+ no ->
+ OldGroupLeader ! {io_request, From, ReplyAs, {put_chars, unicode, BitString}},
+ capture_output({Buffer, {OldGroupLeader, Capture}})
+ end;
+ {capture_done, SenderPid} ->
+ SenderPid ! Buffer;
+ OtherMessage ->
+ OldGroupLeader ! OtherMessage,
+ capture_output({Buffer, {OldGroupLeader, Capture}})
+ end.
+
+run_test(Module, Function, IgnoreTags, Capture) ->
+ OutputCapture = start_output_capture(Capture),
+ try
+ Result = apply(Module, Function, []),
+ {ResultType, FinalResult} =
+ case Result of
+ {test, {meta, _Description, Tags}, TestFun} ->
+ case
+ lists:any(
+ fun(Tag) ->
+ lists:any(fun(IgnoreTag) -> IgnoreTag == Tag end, IgnoreTags)
+ end,
+ Tags
+ )
+ of
+ true ->
+ {ignored, ignore};
+ false ->
+ {test_function_return, TestFun()}
+ end;
+ DirectResult ->
+ {test_function_return, DirectResult}
+ end,
+ OutputCaptureBuffer = stop_output_capture(OutputCapture),
+ case ResultType of
+ ignored -> {ok, {ResultType, FinalResult}};
+ _ -> {ok, {ResultType, FinalResult, OutputCaptureBuffer}}
+ end
+ catch
+ Class:Reason:Stacktrace ->
+ GleamReason =
+ case Reason of
+ {Assertion, ReasonList} ->
+ ErlangReasonList =
+ lists:map(
+ fun(ReasonDetail) ->
+ case ReasonDetail of
+ {line, LineNo} ->
+ {reason_line, LineNo};
+ {expression, List} ->
+ {expression, list_to_binary(List)};
+ {module, ModuleAtom} ->
+ {module, atom_to_binary(ModuleAtom)};
+ {pattern, Pattern} ->
+ {pattern, list_to_binary(Pattern)};
+ Other ->
+ Other
+ end
+ end,
+ ReasonList
+ ),
+ GleamAssertionType =
+ case Assertion of
+ assertEqual ->
+ assert_equal;
+ assertNotEqual ->
+ assert_not_equal;
+ assertMatch ->
+ assert_match;
+ OtherAssertionType ->
+ OtherAssertionType
+ end,
+ {GleamAssertionType, ErlangReasonList};
+ #{
+ function := GleamFunction,
+ gleam_error := GleamError,
+ line := Line,
+ message := Message,
+ module := GleamModule,
+ value := Value
+ } ->
+ case Value of
+ {error, {OkValue, _, _, _}} when OkValue == not_eq; OkValue == eq ->
+ {gleam_error,
+ {GleamError, GleamModule, GleamFunction, Line, Message, Value}};
+ {error, {OkValue, _, _}} when OkValue == is_ok; OkValue == is_error ->
+ {gleam_error,
+ {GleamError, GleamModule, GleamFunction, Line, Message, Value}};
+ {error, {OkValue, _}} when OkValue == fail ->
+ {gleam_error,
+ {GleamError, GleamModule, GleamFunction, Line, Message, Value}};
+ _ ->
+ {gleam_assert, Value, Line}
+ end;
+ OtherReason ->
+ {generic_exception, OtherReason}
+ end,
+ GleamClass =
+ case Class of
+ error ->
+ erlang_error;
+ Other ->
+ Other
+ end,
+ GleamTraceList =
+ lists:map(
+ fun(Trace) ->
+ case Trace of
+ {ModuleName, FunctionName, Arity, ExtraInfoList} ->
+ {trace_module, atom_to_binary(ModuleName),
+ atom_to_binary(FunctionName), map_arity(Arity),
+ map_extra_info_list(ExtraInfoList)};
+ {FunctionName, Arity, ExtraInfoList} ->
+ {trace, atom_to_binary(FunctionName), map_arity(Arity),
+ map_extra_info_list(ExtraInfoList)}
+ end
+ end,
+ Stacktrace
+ ),
+ OutputCaptureBufferCatch = stop_output_capture(OutputCapture),
+ {error,
+ {erlang_exception, GleamClass, GleamReason, {trace_list, GleamTraceList},
+ OutputCaptureBufferCatch}}
+ end.
+
+map_extra_info_list(ExtraInfoList) ->
+ lists:map(
+ fun(ExtraInfo) ->
+ case ExtraInfo of
+ {file, FileNameList} -> {file, list_to_binary(FileNameList)};
+ Other -> Other
+ end
+ end,
+ ExtraInfoList
+ ).
+
+map_arity(Arity) ->
+ if
+ is_list(Arity) ->
+ {arg_list, Arity};
+ is_integer(Arity) ->
+ {num, Arity}
+ end.
+
+functions() ->
+ Funs = module_info(exports),
+ Funs.