Skip to content

Commit

Permalink
allow for dynamic part run output, catch assertion messages, add --al…
Browse files Browse the repository at this point in the history
…low-crash flag to run and run all commands
  • Loading branch information
TanklesXL committed Dec 6, 2022
1 parent ebc825a commit 540c7b5
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 28 deletions.
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ To add this library to your project run: `gleam add gladvent` and add `import gl
This library provides 2 options to run your advent of code solvers:

1. The easy way: simply add `gladvent.main()` to the end of your project's `main` function.
2. Create your own `Map(Int, #(fn(String) -> Int, fn(String) -> Int))` and pass it to `gladvent.execute`
2. Create your own `Map(Int, #(fn(String) -> Dynamic, fn(String) -> Dynamic))` and pass it to `gladvent.execute`

## Available commands

Expand All @@ -31,23 +31,31 @@ This project provides your application with 2 commands, `new` and `run`:
- flags:
- `--timeout`: `gleam run run --timeout={timeout in ms} a b c ...`
- usage example: `gleam run run --timeout=1000 1 2` with timeout 1000 milliseconds and days 1 and 2, runs and prints the output of running the `run` function of `day_1.gleam` and `day_2.gleam`
- `--allow-crash`: runs days without the use of `rescue` functionality, rendering output text more verbose but also allowing for stacktraces to be printed
- usage example: `gleam run run 1 2 3 --allow-crash`

- `run all`: run all registered days
- format: `gleam run run all`
- flags:
- `--timeout`: `gleam run run --timeout={timeout in ms} a b c ...`
- `--timeout`: `gleam run run all --timeout={timeout in ms}`
- usage example: `gleam run run --timeout=1000 1 2` with timeout 1000 milliseconds and days 1 and 2, runs and prints the output of running the `run` function of `day_1.gleam` and `day_2.gleam`
- `--allow-crash`: runs days without the use of `rescue` functionality, rendering output text more verbose but also allowing for stacktraces to be printed
- usage example: `gleam run run all --allow-crash`

_Note:_

- due to how `gladvent` works, the `pt_1` and `pt_2` functions only need to return `Dynamic` when direction building a `RunnerMap` and using `gladvent.execute`, when using `gladvent.main` they can return anything.
- the `new` command creates source files in `src/days/` and input files in the `input/` directory.
- the `run` command expects input files to be in the `input/` directory.
- using `gladvent.main` expects gleam day runners to be in `src/days/`
- any triggered `assert` will be captured and printed, for example: `error: assert - Assertion pattern match failed in module days/day_1 in function pt_1 at line 2 with value 2`
- any message in a `todo` will be captured and printed, for example: `error: todo - test in module days/day_1 in function pt_2 at line 7`

## Seeing help messages

- To see available subcommands: `gleam run -- --help`
- To see help for the `run` command: `gleam run run --help`
- To see help for the `run` command: `gleam run run all --help`
- To see help for the `new` command: `gleam run new --help`

## General Workflow
Expand Down
2 changes: 1 addition & 1 deletion gleam.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name = "gladvent"
version = "0.4.2"
version = "0.5.0"
repository = { type = "github", user = "TanklesXL", repo = "gladvent" }
description = "An Advent Of Code runner for gleam"
licences = ["Apache-2.0"]
Expand Down
4 changes: 2 additions & 2 deletions src/cmd/new.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,11 @@ fn do(day: Day) -> snag.Result(Nil) {
create_files(day)
}

const gleam_starter = "pub fn pt_1(input: String) -> Int {
const gleam_starter = "pub fn pt_1(input: String) {
todo
}

pub fn pt_2(input: String) -> Int {
pub fn pt_2(input: String) {
todo
}
"
Expand Down
105 changes: 83 additions & 22 deletions src/cmd/run.gleam
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import gleam/list
import gleam/int
import gleam/list
import gleam/result
import gleam/string
import snag.{Result, Snag}
import gleam/erlang/file
import gleam/erlang
import gleam/erlang/charlist.{Charlist}
import gleam/erlang/atom
import gleam/dynamic
import parse.{Day}
import gleam/map
import cmd.{Ending, Endless}
import glint.{CommandInput}
import glint/flag
import gleam
import runners.{RunnerMap}
import gleam/dynamic.{Dynamic}
import gleam/option.{None, Option}

type SolveErr {
Undef
Expand All @@ -38,7 +39,7 @@ fn err_to_snag(err: Err) -> Snag {
}

type RunResult =
gleam.Result(Int, SolveErr)
gleam.Result(Dynamic, SolveErr)

type Direction {
// Leading
Expand All @@ -56,6 +57,7 @@ external fn do_trim(String, Direction, Charlist) -> String =
fn do(
day: Day,
runners: RunnerMap,
allow_crash: Bool,
) -> gleam.Result(#(RunResult, RunResult), Err) {
try #(pt_1, pt_2) =
map.get(runners, day)
Expand All @@ -68,15 +70,21 @@ fn do(
|> file.read()
|> result.map(string_trim(_, Both, "\n"))
|> result.replace_error(FailedToReadInput(input_path))
let pt_1 =
erlang.rescue(fn() { pt_1(input) })
|> result.map_error(run_err_to_string)

let pt_2 =
erlang.rescue(fn() { pt_2(input) })
|> result.map_error(run_err_to_string)

Ok(#(pt_1, pt_2))
case allow_crash {
True -> Ok(#(Ok(pt_1(input)), Ok(pt_2(input))))
False -> {
let pt_1 =
fn() { pt_1(input) }
|> erlang.rescue
|> result.map_error(run_err_to_string)
let pt_2 =
fn() { pt_2(input) }
|> erlang.rescue
|> result.map_error(run_err_to_string)
Ok(#(pt_1, pt_2))
}
}
}

fn crash_to_dyn(err: erlang.Crash) -> dynamic.Dynamic {
Expand All @@ -85,23 +93,69 @@ fn crash_to_dyn(err: erlang.Crash) -> dynamic.Dynamic {
}
}

type GleamErr {
GleamErr(
gleam_error: atom.Atom,
module: String,
function: String,
line: Int,
message: String,
value: Option(Dynamic),
)
}

fn decode_gleam_err() {
dynamic.decode6(
GleamErr,
dynamic.field(atom.create_from_string("gleam_error"), atom.from_dynamic),
dynamic.field(atom.create_from_string("module"), dynamic.string),
dynamic.field(atom.create_from_string("function"), dynamic.string),
dynamic.field(atom.create_from_string("line"), dynamic.int),
dynamic.field(atom.create_from_string("message"), dynamic.string),
dynamic.any([
dynamic.field(
atom.create_from_string("value"),
dynamic.optional(dynamic.dynamic),
),
fn(_) { Ok(None) },
]),
)
}

fn gleam_err_to_string(g: GleamErr) -> String {
string.join(
[
"error:",
atom.to_string(g.gleam_error),
"-",
g.message,
"in module",
g.module,
"in function",
g.function,
"at line",
int.to_string(g.line),
g.value
|> option.map(fn(val) { "with value " <> string.inspect(val) })
|> option.unwrap(""),
],
" ",
)
}

fn run_err_to_string(err: erlang.Crash) -> SolveErr {
let dyn = crash_to_dyn(err)
{
try m = dynamic.map(atom.from_dynamic, dynamic.dynamic)(dyn)
map.get(m, atom.create_from_string("message"))
|> result.replace_error([])
|> result.then(dynamic.string)
}
decode_gleam_err()(dyn)
|> result.map(gleam_err_to_string)
|> result.lazy_unwrap(fn() {
"run failed for some reason: " <> string.inspect(dyn)
"run failed for some reason: " <> string.inspect(err)
})
|> RunFailed
}

fn run_res_to_string(res: RunResult) -> String {
case res {
Ok(res) -> int.to_string(res)
Ok(res) -> string.inspect(res)
Error(err) ->
case err {
Undef -> "function undefined"
Expand All @@ -126,15 +180,21 @@ fn collect(x: #(Day, gleam.Result(#(RunResult, RunResult), Err))) -> String {
}
}

// ----- CLI -----

fn timeout_flag() {
flag.int("timeout", 0, "Run with specified timeout")
}

fn allow_crash_flag() {
flag.bool("allow-crash", False, "Don't catch exceptions thrown by runners")
}

pub fn run_command(runners: RunnerMap) -> glint.Stub(Result(List(String))) {
glint.Stub(
path: ["run"],
run: run(_, runners, False),
flags: [timeout_flag()],
flags: [timeout_flag(), allow_crash_flag()],
description: "Run the specified days",
)
}
Expand All @@ -143,7 +203,7 @@ pub fn run_all_command(runners: RunnerMap) -> glint.Stub(Result(List(String))) {
glint.Stub(
path: ["run", "all"],
run: run(_, runners, True),
flags: [timeout_flag()],
flags: [timeout_flag(), allow_crash_flag()],
description: "Run all registered days",
)
}
Expand All @@ -154,6 +214,7 @@ fn run(
run_all: Bool,
) -> Result(List(String)) {
assert Ok(flag.I(timeout)) = flag.get(input.flags, timeout_flag().0)
assert Ok(flag.B(allow_crash)) = flag.get(input.flags, allow_crash_flag().0)

try timing = case timeout {
0 -> Ok(Endless)
Expand All @@ -175,7 +236,7 @@ fn run(
}

days
|> cmd.exec(timing, do(_, runners), Other, collect)
|> cmd.exec(timing, do(_, runners, allow_crash), Other, collect)
|> Ok
}

Expand Down
3 changes: 2 additions & 1 deletion src/runners.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import parse.{Day}
import gleam/list
import gleam/result
import gleam
import gleam/dynamic.{Dynamic}

pub const input_dir = "input/"

pub const days_dir = "src/days/"

pub type PartRunner =
fn(String) -> Int
fn(String) -> Dynamic

pub type DayRunner =
#(PartRunner, PartRunner)
Expand Down

0 comments on commit 540c7b5

Please sign in to comment.