diff options
author | HJ <thechairman@thechairman.info> | 2024-02-03 15:09:54 -0500 |
---|---|---|
committer | HJ <thechairman@thechairman.info> | 2024-02-03 15:09:54 -0500 |
commit | 96a3c5c179d8d3fff24eb2953e45f8dd15e2714c (patch) | |
tree | 0a67bc0cfeabe51740bb049c61f16f1ac3bdd4ff /aoc2023/build/packages/snag/src | |
parent | 547fe03cf43105f46160e2dd9afff21637eaaf47 (diff) | |
download | gleam_aoc-96a3c5c179d8d3fff24eb2953e45f8dd15e2714c.tar.gz gleam_aoc-96a3c5c179d8d3fff24eb2953e45f8dd15e2714c.zip |
cleanup
Diffstat (limited to 'aoc2023/build/packages/snag/src')
-rw-r--r-- | aoc2023/build/packages/snag/src/snag.app.src | 8 | ||||
-rw-r--r-- | aoc2023/build/packages/snag/src/snag.erl | 74 | ||||
-rw-r--r-- | aoc2023/build/packages/snag/src/snag.gleam | 141 |
3 files changed, 223 insertions, 0 deletions
diff --git a/aoc2023/build/packages/snag/src/snag.app.src b/aoc2023/build/packages/snag/src/snag.app.src new file mode 100644 index 0000000..175e326 --- /dev/null +++ b/aoc2023/build/packages/snag/src/snag.app.src @@ -0,0 +1,8 @@ +{application, snag, [ + {vsn, "0.2.1"}, + {applications, [gleam_stdlib, + gleeunit]}, + {description, "A boilerplate-free ad-hoc error type"}, + {modules, [snag]}, + {registered, []} +]}. diff --git a/aoc2023/build/packages/snag/src/snag.erl b/aoc2023/build/packages/snag/src/snag.erl new file mode 100644 index 0000000..a07f242 --- /dev/null +++ b/aoc2023/build/packages/snag/src/snag.erl @@ -0,0 +1,74 @@ +-module(snag). +-compile([no_auto_import, nowarn_unused_vars]). + +-export([new/1, error/1, layer/2, context/2, pretty_print/1, line_print/1]). +-export_type([snag/0]). + +-type snag() :: {snag, binary(), list(binary())}. + +-spec new(binary()) -> snag(). +new(Issue) -> + {snag, Issue, []}. + +-spec error(binary()) -> {ok, any()} | {error, snag()}. +error(Issue) -> + {error, new(Issue)}. + +-spec layer(snag(), binary()) -> snag(). +layer(Snag, Issue) -> + {snag, Issue, [erlang:element(2, Snag) | erlang:element(3, Snag)]}. + +-spec context({ok, EXX} | {error, snag()}, binary()) -> {ok, EXX} | + {error, snag()}. +context(Result, Issue) -> + case Result of + {ok, _} -> + Result; + + {error, Snag} -> + {error, layer(Snag, Issue)} + end. + +-spec pretty_print_cause(list(binary())) -> gleam@string_builder:string_builder(). +pretty_print_cause(Cause) -> + _pipe = Cause, + _pipe@1 = gleam@list:index_map( + _pipe, + fun(Index, Line) -> + gleam@string:concat( + [<<" "/utf8>>, + gleam@int:to_string(Index), + <<": "/utf8>>, + Line, + <<"\n"/utf8>>] + ) + end + ), + gleam@string_builder:from_strings(_pipe@1). + +-spec pretty_print(snag()) -> binary(). +pretty_print(Snag) -> + Builder = gleam@string_builder:from_strings( + [<<"error: "/utf8>>, erlang:element(2, Snag), <<"\n"/utf8>>] + ), + gleam@string_builder:to_string(case erlang:element(3, Snag) of + [] -> + Builder; + + Cause -> + _pipe = Builder, + _pipe@1 = gleam@string_builder:append( + _pipe, + <<"\ncause:\n"/utf8>> + ), + gleam@string_builder:append_builder( + _pipe@1, + pretty_print_cause(Cause) + ) + end). + +-spec line_print(snag()) -> binary(). +line_print(Snag) -> + _pipe = [gleam@string:append(<<"error: "/utf8>>, erlang:element(2, Snag)) | + erlang:element(3, Snag)], + gleam@string:join(_pipe, <<" <- "/utf8>>). diff --git a/aoc2023/build/packages/snag/src/snag.gleam b/aoc2023/build/packages/snag/src/snag.gleam new file mode 100644 index 0000000..8d39537 --- /dev/null +++ b/aoc2023/build/packages/snag/src/snag.gleam @@ -0,0 +1,141 @@ +import gleam +import gleam/string_builder +import gleam/string +import gleam/list +import gleam/int + +/// A Snag is a boilerplate-free error type that can be used to track why an +/// error happened, though does not store as much detail on specific errors as a +/// custom error type would. +/// +/// It is useful in code where it must either pass or fail, and when it fails we +/// want good debugging information to print to the user. i.e. Command line +/// tools, data processing pipelines, etc. +/// +/// If it not suited to code where the application needs to make a decision about +/// what to do in the event of an error, such as whether to give up or to try +/// again. i.e. Libraries, web application backends, API clients, etc. +/// In these situations it is recommended to create a custom type for your errors +/// as it can be pattern matched on and have any additional detail added as +/// fields. +pub type Snag { + Snag(issue: String, cause: List(String)) +} + +/// A concise alias for a `Result` that uses a `Snag` as the error value. +pub type Result(t) = + gleam.Result(t, Snag) + +/// Create a new `Snag` with the given issue text. +/// +/// See also the `error` function for creating a `Snag` wrapped in a `Result`. +/// +/// # Example +/// +/// ```gleam +/// > new("Not enough credit") +/// > |> line_print +/// "error: Not enough credit" +/// ``` +pub fn new(issue: String) -> Snag { + Snag(issue: issue, cause: []) +} + +/// Create a new `Snag` wrapped in a `Result` with the given issue text. +/// +/// # Example +/// +/// ```gleam +/// > error("Not enough credit") +/// Error(new("Not enough credit")) +/// ``` +pub fn error(issue: String) -> Result(success) { + Error(new(issue)) +} + +/// Add additional contextual information to a `Snag`. +/// +/// See also the `context` function for adding contextual information to a `Snag` +/// wrapped in a `Result`. +/// +/// # Example +/// +/// ```gleam +/// > new("Not enough credit") +/// > |> layer("Unable to make purchase") +/// > |> line_print +/// "error: Unable to make purchase <- Not enough credit" +/// ``` +pub fn layer(snag: Snag, issue: String) -> Snag { + Snag(issue: issue, cause: [snag.issue, ..snag.cause]) +} + +/// Add additional contextual information to a `Snag` wrapped in a `Result`. +/// +/// # Example +/// +/// ```gleam +/// > error("Not enough credit") +/// > |> context("Unable to make purchase") +/// > |> result.map_error(line_print) +/// Error("error: Unable to make purchase <- Not enough credit") +/// ``` +pub fn context(result: Result(success), issue: String) -> Result(success) { + case result { + Ok(_) -> result + Error(snag) -> Error(layer(snag, issue)) + } +} + +/// Turn a snag into a multi-line string, optimised for readability. +/// +/// # Example +/// +/// ```gleam +/// > new("Not enough credit") +/// > |> layer("Unable to make purchase") +/// > |> layer("Character creation failed") +/// > |> pretty_print +/// "error: Character creation failed +/// +/// cause: +/// 0: Unable to make purchase +/// 1: Not enough credit +/// " +/// ``` +pub fn pretty_print(snag: Snag) -> String { + let builder = string_builder.from_strings(["error: ", snag.issue, "\n"]) + + string_builder.to_string(case snag.cause { + [] -> builder + cause -> + builder + |> string_builder.append("\ncause:\n") + |> string_builder.append_builder(pretty_print_cause(cause)) + }) +} + +fn pretty_print_cause(cause) { + cause + |> list.index_map(fn(index, line) { + string.concat([" ", int.to_string(index), ": ", line, "\n"]) + }) + |> string_builder.from_strings +} + +/// Turn a snag into a single-line string, optimised for compactness. This may be +/// useful for logging snags. +/// +/// # Example +/// +/// ```gleam +/// > new("Not enough credit") +/// > |> layer("Unable to make purchase") +/// > |> layer("Character creation failed") +/// > |> pretty_print +/// "error: Character creation failed <- Unable to make purchase <- Not enough credit" +/// ``` +pub fn line_print(snag: Snag) -> String { + [string.append("error: ", snag.issue), ..snag.cause] + |> string.join(" <- ") +} |