aboutsummaryrefslogtreecommitdiff
path: root/aoc2023/build/packages/snag/src
diff options
context:
space:
mode:
authorHJ <thechairman@thechairman.info>2024-02-03 15:09:54 -0500
committerHJ <thechairman@thechairman.info>2024-02-03 15:09:54 -0500
commit96a3c5c179d8d3fff24eb2953e45f8dd15e2714c (patch)
tree0a67bc0cfeabe51740bb049c61f16f1ac3bdd4ff /aoc2023/build/packages/snag/src
parent547fe03cf43105f46160e2dd9afff21637eaaf47 (diff)
downloadgleam_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.src8
-rw-r--r--aoc2023/build/packages/snag/src/snag.erl74
-rw-r--r--aoc2023/build/packages/snag/src/snag.gleam141
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(" <- ")
+}