aboutsummaryrefslogtreecommitdiff
path: root/aoc2023/build/packages/glint
diff options
context:
space:
mode:
Diffstat (limited to 'aoc2023/build/packages/glint')
-rw-r--r--aoc2023/build/packages/glint/LICENSE201
-rw-r--r--aoc2023/build/packages/glint/README.md104
-rw-r--r--aoc2023/build/packages/glint/gleam.toml23
-rw-r--r--aoc2023/build/packages/glint/include/glint@flag_Flag.hrl1
-rw-r--r--aoc2023/build/packages/glint/include/glint@flag_FlagBuilder.hrl6
-rw-r--r--aoc2023/build/packages/glint/include/glint@flag_Internal.hrl4
-rw-r--r--aoc2023/build/packages/glint/include/glint_Command.hrl5
-rw-r--r--aoc2023/build/packages/glint/include/glint_CommandInput.hrl4
-rw-r--r--aoc2023/build/packages/glint/include/glint_Config.hrl4
-rw-r--r--aoc2023/build/packages/glint/include/glint_Glint.hrl5
-rw-r--r--aoc2023/build/packages/glint/include/glint_PrettyHelp.hrl5
-rw-r--r--aoc2023/build/packages/glint/include/glint_Stub.hrl6
-rw-r--r--aoc2023/build/packages/glint/src/glint.app.src13
-rw-r--r--aoc2023/build/packages/glint/src/glint.erl513
-rw-r--r--aoc2023/build/packages/glint/src/glint.gleam588
-rw-r--r--aoc2023/build/packages/glint/src/glint/flag.gleam478
-rw-r--r--aoc2023/build/packages/glint/src/glint/flag/constraint.gleam66
-rw-r--r--aoc2023/build/packages/glint/src/glint@flag.erl523
-rw-r--r--aoc2023/build/packages/glint/src/glint@flag@constraint.erl68
19 files changed, 2617 insertions, 0 deletions
diff --git a/aoc2023/build/packages/glint/LICENSE b/aoc2023/build/packages/glint/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/aoc2023/build/packages/glint/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/aoc2023/build/packages/glint/README.md b/aoc2023/build/packages/glint/README.md
new file mode 100644
index 0000000..d6d5821
--- /dev/null
+++ b/aoc2023/build/packages/glint/README.md
@@ -0,0 +1,104 @@
+# glint
+
+[![Hex Package](https://img.shields.io/hexpm/v/glint?color=ffaff3&label=%F0%9F%93%A6)](https://hex.pm/packages/glint)
+[![Hex.pm](https://img.shields.io/hexpm/dt/glint?color=ffaff3)](https://hex.pm/packages/glint)
+[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3?label=%F0%9F%93%9A)](https://hexdocs.pm/glint/)
+[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/tanklesxl/glint/main)](https://github.com/tanklesxl/glint/actions)
+
+Gleam command line argument parsing with basic flag support.
+
+## Installation
+
+To install from hex:
+
+```sh
+gleam add glint
+```
+
+## Usage
+
+### Glint's Core
+
+`glint` is conceptually quite small, your general flow will be:
+
+1. create a new glint instance with `glint.new`
+1. configure it with `glint.with_pretty_help` and other configuration functions
+1. add commands with `glint.add`
+ 1. create a new command with `glint.cmd`
+ 1. assign that command any flags required
+ 1. assign the command a custom description
+1. run your cli with `glnt.run`, run with a function to handle command output with `glint.run_and_handle`
+
+### Mini Example
+
+You can import `glint` as a dependency and use it to build simple command-line applications like the following simplified version of the [the hello world example](https://github.com/TanklesXL/glint/tree/main/examples/hello/README.md)
+
+```gleam
+// stdlib imports
+import gleam/io
+import gleam/list
+import gleam/result
+import gleam/string.{uppercase}
+// external dep imports
+import snag
+// glint imports
+import glint
+import glint/flag
+// erlang-specific imports
+
+@target(erlang)
+import gleam/erlang.{start_arguments}
+
+/// the key for the caps flag
+const caps = "caps"
+
+/// a boolean flag with default False to control message capitalization.
+///
+fn caps_flag() -> flag.FlagBuilder(Bool) {
+ flag.bool()
+ |> flag.default(False)
+ |> flag.description("Capitalize the provided name")
+}
+
+/// the command function that will be executed
+///
+fn hello(input: glint.CommandInput) -> Nil {
+ let assert Ok(caps) = flag.get_bool(from: input.flags, for: caps)
+
+ let name =
+ case input.args {
+ [] -> "Joe"
+ [name,..] -> name
+ }
+
+ let msg = "Hello, " <> name <> "!"
+
+
+ case caps {
+ True -> uppercase(msg)
+ False -> msg
+ }
+ |> io.println
+}
+
+pub fn main() {
+ // create a new glint instance
+ glint.new()
+ // with an app name of "hello", this is used when printing help text
+ |> glint.with_name("hello")
+ // with pretty help enabled, using the built-in colours
+ |> glint.with_pretty_help(glint.default_pretty_help())
+ // with a root command that executes the `hello` function
+ |> glint.add(
+ // add the command to the root
+ at: [],
+ // create the command, add any flags
+ do: glint.command(hello)
+ // with flag `caps`
+ |> glint.flag(caps, caps_flag())
+ // with a short description
+ |> glint.description("Prints Hello, <NAME>!"),
+ )
+ |> glint.run(start_arguments())
+}
+```
diff --git a/aoc2023/build/packages/glint/gleam.toml b/aoc2023/build/packages/glint/gleam.toml
new file mode 100644
index 0000000..e8ac4ae
--- /dev/null
+++ b/aoc2023/build/packages/glint/gleam.toml
@@ -0,0 +1,23 @@
+name = "glint"
+version = "0.13.0"
+
+# Fill out these fields if you intend to generate HTML documentation or publishname = "glint"
+# your project to the Hex package manager.
+#
+licences = ["Apache-2.0"]
+description = "Gleam command line argument parsing with basic flag support."
+repository = { type = "github", user = "TanklesXL", repo = "glint" }
+links = [
+ { title = "Hex", href = "https://hex.pm/packages/glint" },
+ { title = "Docs", href = "https://hexdocs.pm/glint/" },
+]
+gleam = ">= 0.32.0"
+
+[dependencies]
+gleam_stdlib = "~> 0.19"
+snag = "~> 0.2"
+gleam_community_ansi = "~> 1.0"
+gleam_community_colour = "~> 1.0"
+
+[dev-dependencies]
+gleeunit = "~> 0.5"
diff --git a/aoc2023/build/packages/glint/include/glint@flag_Flag.hrl b/aoc2023/build/packages/glint/include/glint@flag_Flag.hrl
new file mode 100644
index 0000000..645cb12
--- /dev/null
+++ b/aoc2023/build/packages/glint/include/glint@flag_Flag.hrl
@@ -0,0 +1 @@
+-record(flag, {value :: glint@flag:value(), description :: binary()}).
diff --git a/aoc2023/build/packages/glint/include/glint@flag_FlagBuilder.hrl b/aoc2023/build/packages/glint/include/glint@flag_FlagBuilder.hrl
new file mode 100644
index 0000000..b5e21a2
--- /dev/null
+++ b/aoc2023/build/packages/glint/include/glint@flag_FlagBuilder.hrl
@@ -0,0 +1,6 @@
+-record(flag_builder, {
+ desc :: binary(),
+ parser :: fun((binary()) -> {ok, any()} | {error, snag:snag()}),
+ value :: fun((glint@flag:internal(any())) -> glint@flag:value()),
+ default :: gleam@option:option(any())
+}).
diff --git a/aoc2023/build/packages/glint/include/glint@flag_Internal.hrl b/aoc2023/build/packages/glint/include/glint@flag_Internal.hrl
new file mode 100644
index 0000000..281bbd0
--- /dev/null
+++ b/aoc2023/build/packages/glint/include/glint@flag_Internal.hrl
@@ -0,0 +1,4 @@
+-record(internal, {
+ value :: gleam@option:option(any()),
+ parser :: fun((binary()) -> {ok, any()} | {error, snag:snag()})
+}).
diff --git a/aoc2023/build/packages/glint/include/glint_Command.hrl b/aoc2023/build/packages/glint/include/glint_Command.hrl
new file mode 100644
index 0000000..00a03e3
--- /dev/null
+++ b/aoc2023/build/packages/glint/include/glint_Command.hrl
@@ -0,0 +1,5 @@
+-record(command, {
+ do :: fun((glint:command_input()) -> any()),
+ flags :: gleam@map:map_(binary(), glint@flag:flag()),
+ description :: binary()
+}).
diff --git a/aoc2023/build/packages/glint/include/glint_CommandInput.hrl b/aoc2023/build/packages/glint/include/glint_CommandInput.hrl
new file mode 100644
index 0000000..72c9641
--- /dev/null
+++ b/aoc2023/build/packages/glint/include/glint_CommandInput.hrl
@@ -0,0 +1,4 @@
+-record(command_input, {
+ args :: list(binary()),
+ flags :: gleam@map:map_(binary(), glint@flag:flag())
+}).
diff --git a/aoc2023/build/packages/glint/include/glint_Config.hrl b/aoc2023/build/packages/glint/include/glint_Config.hrl
new file mode 100644
index 0000000..70cf645
--- /dev/null
+++ b/aoc2023/build/packages/glint/include/glint_Config.hrl
@@ -0,0 +1,4 @@
+-record(config, {
+ pretty_help :: gleam@option:option(glint:pretty_help()),
+ name :: gleam@option:option(binary())
+}).
diff --git a/aoc2023/build/packages/glint/include/glint_Glint.hrl b/aoc2023/build/packages/glint/include/glint_Glint.hrl
new file mode 100644
index 0000000..7ece11d
--- /dev/null
+++ b/aoc2023/build/packages/glint/include/glint_Glint.hrl
@@ -0,0 +1,5 @@
+-record(glint, {
+ config :: glint:config(),
+ cmd :: glint:command_node(any()),
+ global_flags :: gleam@map:map_(binary(), glint@flag:flag())
+}).
diff --git a/aoc2023/build/packages/glint/include/glint_PrettyHelp.hrl b/aoc2023/build/packages/glint/include/glint_PrettyHelp.hrl
new file mode 100644
index 0000000..79bd887
--- /dev/null
+++ b/aoc2023/build/packages/glint/include/glint_PrettyHelp.hrl
@@ -0,0 +1,5 @@
+-record(pretty_help, {
+ usage :: gleam_community@colour:colour(),
+ flags :: gleam_community@colour:colour(),
+ subcommands :: gleam_community@colour:colour()
+}).
diff --git a/aoc2023/build/packages/glint/include/glint_Stub.hrl b/aoc2023/build/packages/glint/include/glint_Stub.hrl
new file mode 100644
index 0000000..5aa5d83
--- /dev/null
+++ b/aoc2023/build/packages/glint/include/glint_Stub.hrl
@@ -0,0 +1,6 @@
+-record(stub, {
+ path :: list(binary()),
+ run :: fun((glint:command_input()) -> any()),
+ flags :: list({binary(), glint@flag:flag()}),
+ description :: binary()
+}).
diff --git a/aoc2023/build/packages/glint/src/glint.app.src b/aoc2023/build/packages/glint/src/glint.app.src
new file mode 100644
index 0000000..7eb7649
--- /dev/null
+++ b/aoc2023/build/packages/glint/src/glint.app.src
@@ -0,0 +1,13 @@
+{application, glint, [
+ {vsn, "0.13.0"},
+ {applications, [gleam_community_ansi,
+ gleam_community_colour,
+ gleam_stdlib,
+ gleeunit,
+ snag]},
+ {description, "Gleam command line argument parsing with basic flag support."},
+ {modules, [glint,
+ glint@flag,
+ glint@flag@constraint]},
+ {registered, []}
+]}.
diff --git a/aoc2023/build/packages/glint/src/glint.erl b/aoc2023/build/packages/glint/src/glint.erl
new file mode 100644
index 0000000..0501cc6
--- /dev/null
+++ b/aoc2023/build/packages/glint/src/glint.erl
@@ -0,0 +1,513 @@
+-module(glint).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([with_config/2, with_pretty_help/2, without_pretty_help/1, with_name/2, new/0, command/1, description/2, flag/3, flag_tuple/2, flags/2, global_flag/3, global_flag_tuple/2, global_flags/2, default_pretty_help/0, add/3, help_flag/0, execute/2, run_and_handle/3, run/2, add_command_from_stub/2]).
+-export_type([config/0, pretty_help/0, glint/1, command/1, command_input/0, command_node/1, out/1, stub/1]).
+
+-type config() :: {config,
+ gleam@option:option(pretty_help()),
+ gleam@option:option(binary())}.
+
+-type pretty_help() :: {pretty_help,
+ gleam_community@colour:colour(),
+ gleam_community@colour:colour(),
+ gleam_community@colour:colour()}.
+
+-opaque glint(GHR) :: {glint,
+ config(),
+ command_node(GHR),
+ gleam@map:map_(binary(), glint@flag:flag())}.
+
+-opaque command(GHS) :: {command,
+ fun((command_input()) -> GHS),
+ gleam@map:map_(binary(), glint@flag:flag()),
+ binary()}.
+
+-type command_input() :: {command_input,
+ list(binary()),
+ gleam@map:map_(binary(), glint@flag:flag())}.
+
+-type command_node(GHT) :: {command_node,
+ gleam@option:option(command(GHT)),
+ gleam@map:map_(binary(), command_node(GHT))}.
+
+-type out(GHU) :: {out, GHU} | {help, binary()}.
+
+-type stub(GHV) :: {stub,
+ list(binary()),
+ fun((command_input()) -> GHV),
+ list({binary(), glint@flag:flag()}),
+ binary()}.
+
+-spec with_config(glint(GIA), config()) -> glint(GIA).
+with_config(Glint, Config) ->
+ erlang:setelement(2, Glint, Config).
+
+-spec with_pretty_help(glint(GID), pretty_help()) -> glint(GID).
+with_pretty_help(Glint, Pretty) ->
+ _pipe = erlang:setelement(2, erlang:element(2, Glint), {some, Pretty}),
+ with_config(Glint, _pipe).
+
+-spec without_pretty_help(glint(GIG)) -> glint(GIG).
+without_pretty_help(Glint) ->
+ _pipe = erlang:setelement(2, erlang:element(2, Glint), none),
+ with_config(Glint, _pipe).
+
+-spec with_name(glint(GIJ), binary()) -> glint(GIJ).
+with_name(Glint, Name) ->
+ _pipe = erlang:setelement(3, erlang:element(2, Glint), {some, Name}),
+ with_config(Glint, _pipe).
+
+-spec empty_command() -> command_node(any()).
+empty_command() ->
+ {command_node, none, gleam@map:new()}.
+
+-spec new() -> glint(any()).
+new() ->
+ {glint, {config, none, none}, empty_command(), gleam@map:new()}.
+
+-spec do_add(command_node(GIT), list(binary()), command(GIT)) -> command_node(GIT).
+do_add(Root, Path, Contents) ->
+ case Path of
+ [] ->
+ erlang:setelement(2, Root, {some, Contents});
+
+ [X | Xs] ->
+ erlang:setelement(
+ 3,
+ Root,
+ (gleam@map:update(
+ erlang:element(3, Root),
+ X,
+ fun(Node) -> _pipe = Node,
+ _pipe@1 = gleam@option:lazy_unwrap(
+ _pipe,
+ fun empty_command/0
+ ),
+ do_add(_pipe@1, Xs, Contents) end
+ ))
+ )
+ end.
+
+-spec command(fun((command_input()) -> GJC)) -> command(GJC).
+command(Runner) ->
+ {command, Runner, gleam@map:new(), <<""/utf8>>}.
+
+-spec description(command(GJF), binary()) -> command(GJF).
+description(Cmd, Description) ->
+ erlang:setelement(4, Cmd, Description).
+
+-spec flag(command(GJI), binary(), glint@flag:flag_builder(any())) -> command(GJI).
+flag(Cmd, Key, Flag) ->
+ erlang:setelement(
+ 3,
+ Cmd,
+ gleam@map:insert(erlang:element(3, Cmd), Key, glint@flag:build(Flag))
+ ).
+
+-spec flag_tuple(command(GJN), {binary(), glint@flag:flag_builder(any())}) -> command(GJN).
+flag_tuple(Cmd, Tup) ->
+ flag(Cmd, erlang:element(1, Tup), erlang:element(2, Tup)).
+
+-spec flags(command(GJS), list({binary(), glint@flag:flag()})) -> command(GJS).
+flags(Cmd, Flags) ->
+ gleam@list:fold(
+ Flags,
+ Cmd,
+ fun(Cmd@1, _use1) ->
+ {Key, Flag} = _use1,
+ erlang:setelement(
+ 3,
+ Cmd@1,
+ gleam@map:insert(erlang:element(3, Cmd@1), Key, Flag)
+ )
+ end
+ ).
+
+-spec global_flag(glint(GJW), binary(), glint@flag:flag_builder(any())) -> glint(GJW).
+global_flag(Glint, Key, Flag) ->
+ erlang:setelement(
+ 4,
+ Glint,
+ gleam@map:insert(erlang:element(4, Glint), Key, glint@flag:build(Flag))
+ ).
+
+-spec global_flag_tuple(glint(GKB), {binary(), glint@flag:flag_builder(any())}) -> glint(GKB).
+global_flag_tuple(Glint, Tup) ->
+ global_flag(Glint, erlang:element(1, Tup), erlang:element(2, Tup)).
+
+-spec global_flags(glint(GKG), list({binary(), glint@flag:flag()})) -> glint(GKG).
+global_flags(Glint, Flags) ->
+ erlang:setelement(
+ 4,
+ Glint,
+ (gleam@list:fold(
+ Flags,
+ erlang:element(4, Glint),
+ fun(Acc, Tup) ->
+ gleam@map:insert(
+ Acc,
+ erlang:element(1, Tup),
+ erlang:element(2, Tup)
+ )
+ end
+ ))
+ ).
+
+-spec execute_root(
+ command_node(GKU),
+ gleam@map:map_(binary(), glint@flag:flag()),
+ list(binary()),
+ list(binary())
+) -> {ok, out(GKU)} | {error, snag:snag()}.
+execute_root(Cmd, Global_flags, Args, Flag_inputs) ->
+ _pipe@3 = case erlang:element(2, Cmd) of
+ {some, Contents} ->
+ gleam@result:'try'(
+ gleam@list:try_fold(
+ Flag_inputs,
+ gleam@map:merge(Global_flags, erlang:element(3, Contents)),
+ fun glint@flag:update_flags/2
+ ),
+ fun(New_flags) -> _pipe = {command_input, Args, New_flags},
+ _pipe@1 = (erlang:element(2, Contents))(_pipe),
+ _pipe@2 = {out, _pipe@1},
+ {ok, _pipe@2} end
+ );
+
+ none ->
+ snag:error(<<"command not found"/utf8>>)
+ end,
+ snag:context(_pipe@3, <<"failed to run command"/utf8>>).
+
+-spec default_pretty_help() -> pretty_help().
+default_pretty_help() ->
+ _assert_subject = gleam_community@colour:from_rgb255(182, 255, 234),
+ {ok, Usage_colour} = 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 => <<"glint"/utf8>>,
+ function => <<"default_pretty_help"/utf8>>,
+ line => 404})
+ end,
+ _assert_subject@1 = gleam_community@colour:from_rgb255(255, 175, 243),
+ {ok, Flags_colour} = 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 => <<"glint"/utf8>>,
+ function => <<"default_pretty_help"/utf8>>,
+ line => 405})
+ end,
+ _assert_subject@2 = gleam_community@colour:from_rgb255(252, 226, 174),
+ {ok, Subcommands_colour} = 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 => <<"glint"/utf8>>,
+ function => <<"default_pretty_help"/utf8>>,
+ line => 406})
+ end,
+ {pretty_help, Usage_colour, Flags_colour, Subcommands_colour}.
+
+-spec is_not_empty(binary()) -> boolean().
+is_not_empty(S) ->
+ S /= <<""/utf8>>.
+
+-spec sanitize_path(list(binary())) -> list(binary()).
+sanitize_path(Path) ->
+ _pipe = Path,
+ _pipe@1 = gleam@list:map(_pipe, fun gleam@string:trim/1),
+ gleam@list:filter(_pipe@1, fun is_not_empty/1).
+
+-spec add(glint(GIO), list(binary()), command(GIO)) -> glint(GIO).
+add(Glint, Path, Contents) ->
+ erlang:setelement(
+ 3,
+ Glint,
+ begin
+ _pipe = Path,
+ _pipe@1 = sanitize_path(_pipe),
+ do_add(erlang:element(3, Glint), _pipe@1, Contents)
+ end
+ ).
+
+-spec help_flag() -> binary().
+help_flag() ->
+ <<(<<"--"/utf8>>)/binary, "help"/utf8>>.
+
+-spec wrap_with_space(binary()) -> binary().
+wrap_with_space(S) ->
+ case S of
+ <<""/utf8>> ->
+ <<" "/utf8>>;
+
+ _ ->
+ <<<<" "/utf8, S/binary>>/binary, " "/utf8>>
+ end.
+
+-spec subcommand_help(binary(), command_node(any())) -> binary().
+subcommand_help(Name, Cmd) ->
+ case erlang:element(2, Cmd) of
+ none ->
+ Name;
+
+ {some, Contents} ->
+ <<<<Name/binary, "\t\t"/utf8>>/binary,
+ (erlang:element(4, Contents))/binary>>
+ end.
+
+-spec subcommands_help(gleam@map:map_(binary(), command_node(any()))) -> binary().
+subcommands_help(Cmds) ->
+ _pipe = Cmds,
+ _pipe@1 = gleam@map:map_values(_pipe, fun subcommand_help/2),
+ _pipe@2 = gleam@map:values(_pipe@1),
+ _pipe@3 = gleam@list:sort(_pipe@2, fun gleam@string:compare/2),
+ gleam@string:join(_pipe@3, <<"\n\t"/utf8>>).
+
+-spec heading_style(binary(), gleam_community@colour:colour()) -> binary().
+heading_style(Heading, Colour) ->
+ _pipe = Heading,
+ _pipe@1 = gleam_community@ansi:bold(_pipe),
+ _pipe@2 = gleam_community@ansi:underline(_pipe@1),
+ _pipe@3 = gleam_community@ansi:italic(_pipe@2),
+ _pipe@4 = gleam_community@ansi:hex(
+ _pipe@3,
+ gleam_community@colour:to_rgb_hex(Colour)
+ ),
+ gleam_community@ansi:reset(_pipe@4).
+
+-spec usage_help(
+ binary(),
+ gleam@map:map_(binary(), glint@flag:flag()),
+ config()
+) -> binary().
+usage_help(Cmd_name, Flags, Config) ->
+ App_name = gleam@option:unwrap(
+ erlang:element(3, Config),
+ <<"gleam run"/utf8>>
+ ),
+ Flags@1 = begin
+ _pipe = Flags,
+ _pipe@1 = gleam@map:to_list(_pipe),
+ _pipe@2 = gleam@list:map(_pipe@1, fun glint@flag:flag_type_help/1),
+ gleam@list:sort(_pipe@2, fun gleam@string:compare/2)
+ end,
+ Flag_sb = case Flags@1 of
+ [] ->
+ gleam@string_builder:new();
+
+ _ ->
+ _pipe@3 = Flags@1,
+ _pipe@4 = gleam@list:intersperse(_pipe@3, <<" "/utf8>>),
+ _pipe@5 = gleam@string_builder:from_strings(_pipe@4),
+ _pipe@6 = gleam@string_builder:prepend(_pipe@5, <<" [ "/utf8>>),
+ gleam@string_builder:append(_pipe@6, <<" ]"/utf8>>)
+ end,
+ _pipe@7 = [App_name, wrap_with_space(Cmd_name), <<"[ ARGS ]"/utf8>>],
+ _pipe@8 = gleam@string_builder:from_strings(_pipe@7),
+ _pipe@9 = gleam@string_builder:append_builder(_pipe@8, Flag_sb),
+ _pipe@12 = gleam@string_builder:prepend(
+ _pipe@9,
+ <<(begin
+ _pipe@10 = erlang:element(2, Config),
+ _pipe@11 = gleam@option:map(
+ _pipe@10,
+ fun(Styling) ->
+ heading_style(
+ <<"USAGE:"/utf8>>,
+ erlang:element(2, Styling)
+ )
+ end
+ ),
+ gleam@option:unwrap(_pipe@11, <<"USAGE:"/utf8>>)
+ end)/binary,
+ "\n\t"/utf8>>
+ ),
+ gleam@string_builder:to_string(_pipe@12).
+
+-spec cmd_help(
+ list(binary()),
+ command_node(any()),
+ config(),
+ gleam@map:map_(binary(), glint@flag:flag())
+) -> binary().
+cmd_help(Path, Cmd, Config, Global_flags) ->
+ Name = begin
+ _pipe = Path,
+ _pipe@1 = gleam@list:reverse(_pipe),
+ gleam@string:join(_pipe@1, <<" "/utf8>>)
+ end,
+ Flags = begin
+ _pipe@2 = gleam@option:map(
+ erlang:element(2, Cmd),
+ fun(Contents) -> erlang:element(3, Contents) end
+ ),
+ _pipe@3 = gleam@option:lazy_unwrap(_pipe@2, fun gleam@map:new/0),
+ gleam@map:merge(Global_flags, _pipe@3)
+ end,
+ Flags_help_body = <<<<(begin
+ _pipe@4 = erlang:element(2, Config),
+ _pipe@5 = gleam@option:map(
+ _pipe@4,
+ fun(P) ->
+ heading_style(<<"FLAGS:"/utf8>>, erlang:element(3, P))
+ end
+ ),
+ gleam@option:unwrap(_pipe@5, <<"FLAGS:"/utf8>>)
+ end)/binary,
+ "\n\t"/utf8>>/binary,
+ (gleam@string:join(
+ gleam@list:sort(
+ [<<"--help\t\t\tPrint help information"/utf8>> |
+ glint@flag:flags_help(Flags)],
+ fun gleam@string:compare/2
+ ),
+ <<"\n\t"/utf8>>
+ ))/binary>>,
+ Usage = usage_help(Name, Flags, Config),
+ Description = begin
+ _pipe@6 = erlang:element(2, Cmd),
+ _pipe@7 = gleam@option:map(
+ _pipe@6,
+ fun(Contents@1) -> erlang:element(4, Contents@1) end
+ ),
+ gleam@option:unwrap(_pipe@7, <<""/utf8>>)
+ end,
+ Header_items = begin
+ _pipe@8 = [Name, Description],
+ _pipe@9 = gleam@list:filter(_pipe@8, fun is_not_empty/1),
+ gleam@string:join(_pipe@9, <<"\n"/utf8>>)
+ end,
+ Subcommands = case subcommands_help(erlang:element(3, Cmd)) of
+ <<""/utf8>> ->
+ <<""/utf8>>;
+
+ Subcommands_help_body ->
+ <<<<(begin
+ _pipe@10 = erlang:element(2, Config),
+ _pipe@11 = gleam@option:map(
+ _pipe@10,
+ fun(P@1) ->
+ heading_style(
+ <<"SUBCOMMANDS:"/utf8>>,
+ erlang:element(4, P@1)
+ )
+ end
+ ),
+ gleam@option:unwrap(_pipe@11, <<"SUBCOMMANDS:"/utf8>>)
+ end)/binary,
+ "\n\t"/utf8>>/binary,
+ Subcommands_help_body/binary>>
+ end,
+ _pipe@12 = [Header_items, Usage, Flags_help_body, Subcommands],
+ _pipe@13 = gleam@list:filter(_pipe@12, fun is_not_empty/1),
+ gleam@string:join(_pipe@13, <<"\n\n"/utf8>>).
+
+-spec do_execute(
+ command_node(GKO),
+ config(),
+ gleam@map:map_(binary(), glint@flag:flag()),
+ list(binary()),
+ list(binary()),
+ boolean(),
+ list(binary())
+) -> {ok, out(GKO)} | {error, snag:snag()}.
+do_execute(Cmd, Config, Global_flags, Args, Flags, Help, Command_path) ->
+ case Args of
+ [] when Help ->
+ _pipe = Command_path,
+ _pipe@1 = cmd_help(_pipe, Cmd, Config, Global_flags),
+ _pipe@2 = {help, _pipe@1},
+ {ok, _pipe@2};
+
+ [] ->
+ execute_root(Cmd, Global_flags, [], Flags);
+
+ [Arg | Rest] ->
+ case gleam@map:get(erlang:element(3, Cmd), Arg) of
+ {ok, Cmd@1} ->
+ do_execute(
+ Cmd@1,
+ Config,
+ Global_flags,
+ Rest,
+ Flags,
+ Help,
+ [Arg | Command_path]
+ );
+
+ _ when Help ->
+ _pipe@3 = Command_path,
+ _pipe@4 = cmd_help(_pipe@3, Cmd, Config, Global_flags),
+ _pipe@5 = {help, _pipe@4},
+ {ok, _pipe@5};
+
+ _ ->
+ execute_root(Cmd, Global_flags, Args, Flags)
+ end
+ end.
+
+-spec execute(glint(GKK), list(binary())) -> {ok, out(GKK)} |
+ {error, snag:snag()}.
+execute(Glint, Args) ->
+ Help_flag = help_flag(),
+ {Help, Args@2} = case gleam@list:pop(Args, fun(S) -> S =:= Help_flag end) of
+ {ok, {_, Args@1}} ->
+ {true, Args@1};
+
+ _ ->
+ {false, Args}
+ end,
+ {Flags, Args@3} = gleam@list:partition(
+ Args@2,
+ fun(_capture) -> gleam@string:starts_with(_capture, <<"--"/utf8>>) end
+ ),
+ do_execute(
+ erlang:element(3, Glint),
+ erlang:element(2, Glint),
+ erlang:element(4, Glint),
+ Args@3,
+ Flags,
+ Help,
+ []
+ ).
+
+-spec run_and_handle(glint(GLC), list(binary()), fun((GLC) -> any())) -> nil.
+run_and_handle(Glint, Args, Handle) ->
+ case execute(Glint, Args) of
+ {error, Err} ->
+ _pipe = Err,
+ _pipe@1 = snag:pretty_print(_pipe),
+ gleam@io:println(_pipe@1);
+
+ {ok, {help, Help}} ->
+ gleam@io:println(Help);
+
+ {ok, {out, Out}} ->
+ Handle(Out),
+ nil
+ end.
+
+-spec run(glint(any()), list(binary())) -> nil.
+run(Glint, Args) ->
+ run_and_handle(Glint, Args, gleam@function:constant(nil)).
+
+-spec add_command_from_stub(glint(GLP), stub(GLP)) -> glint(GLP).
+add_command_from_stub(Glint, Stub) ->
+ add(
+ Glint,
+ erlang:element(2, Stub),
+ begin
+ _pipe = command(erlang:element(3, Stub)),
+ _pipe@1 = flags(_pipe, erlang:element(4, Stub)),
+ description(_pipe@1, erlang:element(5, Stub))
+ end
+ ).
diff --git a/aoc2023/build/packages/glint/src/glint.gleam b/aoc2023/build/packages/glint/src/glint.gleam
new file mode 100644
index 0000000..b159016
--- /dev/null
+++ b/aoc2023/build/packages/glint/src/glint.gleam
@@ -0,0 +1,588 @@
+import gleam/map.{type Map}
+import gleam/option.{type Option, None, Some}
+import gleam/list
+import gleam/io
+import gleam/string
+import snag.{type Result}
+import glint/flag.{type Flag, type Map as FlagMap}
+import gleam/string_builder as sb
+import gleam_community/ansi
+import gleam_community/colour.{type Colour}
+import gleam/result
+import gleam/function
+
+// --- CONFIGURATION ---
+
+// -- CONFIGURATION: TYPES --
+
+/// Config for glint
+///
+pub type Config {
+ Config(pretty_help: Option(PrettyHelp), name: Option(String))
+}
+
+/// PrettyHelp defines the header colours to be used when styling help text
+///
+pub type PrettyHelp {
+ PrettyHelp(usage: Colour, flags: Colour, subcommands: Colour)
+}
+
+// -- CONFIGURATION: CONSTANTS --
+
+/// Default config
+///
+pub const default_config = Config(pretty_help: None, name: None)
+
+// -- CONFIGURATION: FUNCTIONS --
+
+/// Add the provided config to the existing command tree
+///
+pub fn with_config(glint: Glint(a), config: Config) -> Glint(a) {
+ Glint(..glint, config: config)
+}
+
+/// Enable custom colours for help text headers
+/// For a pre-made colouring use `default_pretty_help()`
+///
+pub fn with_pretty_help(glint: Glint(a), pretty: PrettyHelp) -> Glint(a) {
+ Config(..glint.config, pretty_help: Some(pretty))
+ |> with_config(glint, _)
+}
+
+/// Disable custom colours for help text headers
+///
+pub fn without_pretty_help(glint: Glint(a)) -> Glint(a) {
+ Config(..glint.config, pretty_help: None)
+ |> with_config(glint, _)
+}
+
+pub fn with_name(glint: Glint(a), name: String) -> Glint(a) {
+ Config(..glint.config, name: Some(name))
+ |> with_config(glint, _)
+}
+
+// --- CORE ---
+
+// -- CORE: TYPES --
+
+/// Glint container type for config and commands
+///
+pub opaque type Glint(a) {
+ Glint(config: Config, cmd: CommandNode(a), global_flags: FlagMap)
+}
+
+/// CommandNode contents
+///
+pub opaque type Command(a) {
+ Command(do: Runner(a), flags: FlagMap, description: String)
+}
+
+/// Input type for `Runner`.
+///
+pub type CommandInput {
+ CommandInput(args: List(String), flags: FlagMap)
+}
+
+/// Function type to be run by `glint`.
+///
+pub type Runner(a) =
+ fn(CommandInput) -> a
+
+/// CommandNode tree representation.
+///
+type CommandNode(a) {
+ CommandNode(
+ contents: Option(Command(a)),
+ subcommands: Map(String, CommandNode(a)),
+ )
+}
+
+/// Ok type for command execution
+///
+pub type Out(a) {
+ /// Container for the command return value
+ Out(a)
+ /// Container for the generated help string
+ Help(String)
+}
+
+/// Result type for command execution
+///
+pub type CmdResult(a) =
+ Result(Out(a))
+
+// -- CORE: BUILDER FUNCTIONS --
+
+/// Creates a new command tree.
+///
+pub fn new() -> Glint(a) {
+ Glint(config: default_config, cmd: empty_command(), global_flags: map.new())
+}
+
+/// Adds a new command to be run at the specified path.
+///
+/// If the path is `[]`, the root command is set with the provided function and
+/// flags.
+///
+/// Note: all command paths are sanitized by stripping whitespace and removing any empty string elements.
+///
+pub fn add(
+ to glint: Glint(a),
+ at path: List(String),
+ do contents: Command(a),
+) -> Glint(a) {
+ Glint(
+ ..glint,
+ cmd: path
+ |> sanitize_path
+ |> do_add(to: glint.cmd, put: contents),
+ )
+}
+
+/// Recursive traversal of the command tree to find where to puth the provided command
+///
+fn do_add(
+ to root: CommandNode(a),
+ at path: List(String),
+ put contents: Command(a),
+) -> CommandNode(a) {
+ case path {
+ // update current command with provided contents
+ [] -> CommandNode(..root, contents: Some(contents))
+ // continue down the path, creating empty command nodes along the way
+ [x, ..xs] ->
+ CommandNode(
+ ..root,
+ subcommands: {
+ use node <- map.update(root.subcommands, x)
+ node
+ |> option.lazy_unwrap(empty_command)
+ |> do_add(xs, contents)
+ },
+ )
+ }
+}
+
+/// Helper for initializing empty commands
+///
+fn empty_command() -> CommandNode(a) {
+ CommandNode(contents: None, subcommands: map.new())
+}
+
+/// Trim each path element and remove any resulting empty strings.
+///
+fn sanitize_path(path: List(String)) -> List(String) {
+ path
+ |> list.map(string.trim)
+ |> list.filter(is_not_empty)
+}
+
+/// Create a Command(a) from a Runner(a)
+///
+pub fn command(do runner: Runner(a)) -> Command(a) {
+ Command(do: runner, flags: map.new(), description: "")
+}
+
+/// Attach a description to a Command(a)
+///
+pub fn description(cmd: Command(a), description: String) -> Command(a) {
+ Command(..cmd, description: description)
+}
+
+/// add a `flag.Flag` to a `Command`
+///
+pub fn flag(
+ cmd: Command(a),
+ at key: String,
+ of flag: flag.FlagBuilder(_),
+) -> Command(a) {
+ Command(..cmd, flags: map.insert(cmd.flags, key, flag.build(flag)))
+}
+
+/// Add a `flag.Flag to a `Command` when the flag name and builder are bundled as a #(String, flag.FlagBuilder(a)).
+///
+/// This is merely a convenience function and calls `glint.flag` under the hood.
+///
+pub fn flag_tuple(
+ cmd: Command(a),
+ with tup: #(String, flag.FlagBuilder(_)),
+) -> Command(a) {
+ flag(cmd, tup.0, tup.1)
+}
+
+/// Add multiple `Flag`s to a `Command`, note that this function uses `Flag` and not `FlagBuilder(_)`, so the user will need to call `flag.build` before providing the flags here.
+///
+/// It is recommended to call `glint.flag` instead.
+///
+pub fn flags(cmd: Command(a), with flags: List(#(String, Flag))) -> Command(a) {
+ use cmd, #(key, flag) <- list.fold(flags, cmd)
+ Command(..cmd, flags: map.insert(cmd.flags, key, flag))
+}
+
+/// Add global flags to the existing command tree
+///
+pub fn global_flag(
+ glint: Glint(a),
+ at key: String,
+ of flag: flag.FlagBuilder(_),
+) -> Glint(a) {
+ Glint(
+ ..glint,
+ global_flags: map.insert(glint.global_flags, key, flag.build(flag)),
+ )
+}
+
+/// Add global flags to the existing command tree.
+///
+pub fn global_flag_tuple(
+ glint: Glint(a),
+ with tup: #(String, flag.FlagBuilder(_)),
+) -> Glint(a) {
+ global_flag(glint, tup.0, tup.1)
+}
+
+/// Add global flags to the existing command tree.
+///
+/// Like `glint.flags`, this function requires `Flag`s insead of `FlagBuilder(_)`.
+///
+/// It is recommended to use `glint.global_flag` instead.
+///
+pub fn global_flags(glint: Glint(a), flags: List(#(String, Flag))) -> Glint(a) {
+ Glint(
+ ..glint,
+ global_flags: {
+ list.fold(
+ flags,
+ glint.global_flags,
+ fn(acc, tup) { map.insert(acc, tup.0, tup.1) },
+ )
+ },
+ )
+}
+
+// -- CORE: EXECUTION FUNCTIONS --
+
+/// Determines which command to run and executes it.
+///
+/// Sets any provided flags if necessary.
+///
+/// Each value prefixed with `--` is parsed as a flag.
+///
+/// This function does not print its output and is mainly intended for use within `glint` itself.
+/// If you would like to print or handle the output of a command please see the `run_and_handle` function.
+///
+pub fn execute(glint: Glint(a), args: List(String)) -> CmdResult(a) {
+ // create help flag to check for
+ let help_flag = help_flag()
+
+ // check if help flag is present
+ let #(help, args) = case list.pop(args, fn(s) { s == help_flag }) {
+ Ok(#(_, args)) -> #(True, args)
+ _ -> #(False, args)
+ }
+
+ // split flags out from the args list
+ let #(flags, args) = list.partition(args, string.starts_with(_, flag.prefix))
+
+ // search for command and execute
+ do_execute(glint.cmd, glint.config, glint.global_flags, args, flags, help, [])
+}
+
+/// Find which command to execute and run it with computed flags and args
+///
+fn do_execute(
+ cmd: CommandNode(a),
+ config: Config,
+ global_flags: FlagMap,
+ args: List(String),
+ flags: List(String),
+ help: Bool,
+ command_path: List(String),
+) -> CmdResult(a) {
+ case args {
+ // when there are no more available arguments
+ // and help flag has been passed, generate help message
+ [] if help ->
+ command_path
+ |> cmd_help(cmd, config, global_flags)
+ |> Help
+ |> Ok
+
+ // when there are no more available arguments
+ // run the current command
+ [] -> execute_root(cmd, global_flags, [], flags)
+
+ // when there are arguments remaining
+ // check if the next one is a subcommand of the current command
+ [arg, ..rest] ->
+ case map.get(cmd.subcommands, arg) {
+ // subcommand found, continue
+ Ok(cmd) ->
+ do_execute(
+ cmd,
+ config,
+ global_flags,
+ rest,
+ flags,
+ help,
+ [arg, ..command_path],
+ )
+ // subcommand not found, but help flag has been passed
+ // generate and return help message
+ _ if help ->
+ command_path
+ |> cmd_help(cmd, config, global_flags)
+ |> Help
+ |> Ok
+ // subcommand not found, but help flag has not been passed
+ // execute the current command
+ _ -> execute_root(cmd, global_flags, args, flags)
+ }
+ }
+}
+
+/// Executes the current root command.
+///
+fn execute_root(
+ cmd: CommandNode(a),
+ global_flags: FlagMap,
+ args: List(String),
+ flag_inputs: List(String),
+) -> CmdResult(a) {
+ case cmd.contents {
+ Some(contents) -> {
+ use new_flags <- result.try(list.try_fold(
+ over: flag_inputs,
+ from: map.merge(global_flags, contents.flags),
+ with: flag.update_flags,
+ ))
+ CommandInput(args, new_flags)
+ |> contents.do
+ |> Out
+ |> Ok
+ }
+ None -> snag.error("command not found")
+ }
+ |> snag.context("failed to run command")
+}
+
+/// A wrapper for `execute` that prints any errors enountered or the help text if requested.
+/// This function ignores any value returned by the command that was run.
+/// If you would like to do something with the command output please see the run_and_handle function.
+///
+pub fn run(from glint: Glint(a), for args: List(String)) -> Nil {
+ run_and_handle(from: glint, for: args, with: function.constant(Nil))
+}
+
+/// A wrapper for `execute` that prints any errors enountered or the help text if requested.
+/// This function calls the provided handler with the value returned by the command that was run.
+///
+pub fn run_and_handle(
+ from glint: Glint(a),
+ for args: List(String),
+ with handle: fn(a) -> _,
+) -> Nil {
+ case execute(glint, args) {
+ Error(err) ->
+ err
+ |> snag.pretty_print
+ |> io.println
+ Ok(Help(help)) -> io.println(help)
+ Ok(Out(out)) -> {
+ handle(out)
+ Nil
+ }
+ }
+}
+
+/// Default pretty help heading colouring
+/// mint (r: 182, g: 255, b: 234) colour for usage
+/// pink (r: 255, g: 175, b: 243) colour for flags
+/// buttercup (r: 252, g: 226, b: 174) colour for subcommands
+///
+pub fn default_pretty_help() -> PrettyHelp {
+ let assert Ok(usage_colour) = colour.from_rgb255(182, 255, 234)
+ let assert Ok(flags_colour) = colour.from_rgb255(255, 175, 243)
+ let assert Ok(subcommands_colour) = colour.from_rgb255(252, 226, 174)
+
+ PrettyHelp(
+ usage: usage_colour,
+ flags: flags_colour,
+ subcommands: subcommands_colour,
+ )
+}
+
+// constants for setting up sections of the help message
+const flags_heading = "FLAGS:"
+
+const subcommands_heading = "SUBCOMMANDS:"
+
+const usage_heading = "USAGE:"
+
+/// Helper for filtering out empty strings
+///
+fn is_not_empty(s: String) -> Bool {
+ s != ""
+}
+
+const help_flag_name = "help"
+
+const help_flag_message = "--help\t\t\tPrint help information"
+
+/// Function to create the help flag string
+/// Exported for testing purposes only
+///
+pub fn help_flag() -> String {
+ flag.prefix <> help_flag_name
+}
+
+// -- HELP: FUNCTIONS --
+
+fn wrap_with_space(s: String) -> String {
+ case s {
+ "" -> " "
+ _ -> " " <> s <> " "
+ }
+}
+
+/// generate the usage help string for a command
+fn usage_help(cmd_name: String, flags: FlagMap, config: Config) -> String {
+ let app_name = option.unwrap(config.name, "gleam run")
+ let flags =
+ flags
+ |> map.to_list
+ |> list.map(flag.flag_type_help)
+ |> list.sort(string.compare)
+
+ let flag_sb = case flags {
+ [] -> sb.new()
+ _ ->
+ flags
+ |> list.intersperse(" ")
+ |> sb.from_strings()
+ |> sb.prepend(prefix: " [ ")
+ |> sb.append(suffix: " ]")
+ }
+
+ [app_name, wrap_with_space(cmd_name), "[ ARGS ]"]
+ |> sb.from_strings
+ |> sb.append_builder(flag_sb)
+ |> sb.prepend(
+ config.pretty_help
+ |> option.map(fn(styling) { heading_style(usage_heading, styling.usage) })
+ |> option.unwrap(usage_heading) <> "\n\t",
+ )
+ |> sb.to_string
+}
+
+/// generate the help text for a command
+fn cmd_help(
+ path: List(String),
+ cmd: CommandNode(a),
+ config: Config,
+ global_flags: FlagMap,
+) -> String {
+ // recreate the path of the current command
+ // reverse the path because it is created by prepending each section as do_execute walks down the tree
+ let name =
+ path
+ |> list.reverse
+ |> string.join(" ")
+
+ let flags =
+ option.map(cmd.contents, fn(contents) { contents.flags })
+ |> option.lazy_unwrap(map.new)
+ |> map.merge(global_flags, _)
+
+ let flags_help_body =
+ config.pretty_help
+ |> option.map(fn(p) { heading_style(flags_heading, p.flags) })
+ |> option.unwrap(flags_heading) <> "\n\t" <> string.join(
+ list.sort([help_flag_message, ..flag.flags_help(flags)], string.compare),
+ "\n\t",
+ )
+
+ let usage = usage_help(name, flags, config)
+
+ let description =
+ cmd.contents
+ |> option.map(fn(contents) { contents.description })
+ |> option.unwrap("")
+
+ // create the header block from the name and description
+ let header_items =
+ [name, description]
+ |> list.filter(is_not_empty)
+ |> string.join("\n")
+
+ // create the subcommands help block
+ let subcommands = case subcommands_help(cmd.subcommands) {
+ "" -> ""
+ subcommands_help_body ->
+ config.pretty_help
+ |> option.map(fn(p) { heading_style(subcommands_heading, p.subcommands) })
+ |> option.unwrap(subcommands_heading) <> "\n\t" <> subcommands_help_body
+ }
+
+ // join the resulting help blocks into the final help message
+ [header_items, usage, flags_help_body, subcommands]
+ |> list.filter(is_not_empty)
+ |> string.join("\n\n")
+}
+
+// create the help text for subcommands
+fn subcommands_help(cmds: Map(String, CommandNode(a))) -> String {
+ cmds
+ |> map.map_values(subcommand_help)
+ |> map.values
+ |> list.sort(string.compare)
+ |> string.join("\n\t")
+}
+
+// generate the help text for a subcommand
+fn subcommand_help(name: String, cmd: CommandNode(_)) -> String {
+ case cmd.contents {
+ None -> name
+ Some(contents) -> name <> "\t\t" <> contents.description
+ }
+}
+
+/// Style heading text with the provided rgb colouring
+/// this is only intended for use within glint itself.
+///
+fn heading_style(heading: String, colour: Colour) -> String {
+ heading
+ |> ansi.bold
+ |> ansi.underline
+ |> ansi.italic
+ |> ansi.hex(colour.to_rgb_hex(colour))
+ |> ansi.reset
+}
+
+// -- DEPRECATED: STUBS --
+
+/// DEPRECATED: use `glint.cmd` and related new functions instead to create a Command
+///
+/// Create command stubs to be used in `add_command_from_stub`
+///
+pub type Stub(a) {
+ Stub(
+ path: List(String),
+ run: Runner(a),
+ flags: List(#(String, Flag)),
+ description: String,
+ )
+}
+
+/// Add a command to the root given a stub
+///
+@deprecated("use `glint.cmd` and related new functions instead to create a Command")
+pub fn add_command_from_stub(to glint: Glint(a), with stub: Stub(a)) -> Glint(a) {
+ add(
+ to: glint,
+ at: stub.path,
+ do: command(stub.run)
+ |> flags(stub.flags)
+ |> description(stub.description),
+ )
+}
diff --git a/aoc2023/build/packages/glint/src/glint/flag.gleam b/aoc2023/build/packages/glint/src/glint/flag.gleam
new file mode 100644
index 0000000..0a6cae1
--- /dev/null
+++ b/aoc2023/build/packages/glint/src/glint/flag.gleam
@@ -0,0 +1,478 @@
+import gleam/map
+import gleam/string
+import gleam/result
+import gleam/int
+import gleam/list
+import gleam/float
+import snag.{type Result, type Snag}
+import gleam/option.{type Option, None, Some}
+import glint/flag/constraint.{type Constraint}
+import gleam
+
+/// Flag inputs must start with this prefix
+///
+pub const prefix = "--"
+
+/// The separation character for flag names and their values
+const delimiter = "="
+
+/// Supported flag types.
+///
+pub type Value {
+ /// Boolean flags, to be passed in as `--flag=true` or `--flag=false`.
+ /// Can be toggled by omitting the desired value like `--flag`.
+ /// Toggling will negate the existing value.
+ ///
+ B(Internal(Bool))
+
+ /// Int flags, to be passed in as `--flag=1`
+ ///
+ I(Internal(Int))
+
+ /// List(Int) flags, to be passed in as `--flag=1,2,3`
+ ///
+ LI(Internal(List(Int)))
+
+ /// Float flags, to be passed in as `--flag=1.0`
+ ///
+ F(Internal(Float))
+
+ /// List(Float) flags, to be passed in as `--flag=1.0,2.0`
+ ///
+ LF(Internal(List(Float)))
+
+ /// String flags, to be passed in as `--flag=hello`
+ ///
+ S(Internal(String))
+
+ /// List(String) flags, to be passed in as `--flag=hello,world`
+ ///
+ LS(Internal(List(String)))
+}
+
+/// A type that facilitates the creation of `Flag`s
+///
+pub opaque type FlagBuilder(a) {
+ FlagBuilder(
+ desc: Description,
+ parser: Parser(a, Snag),
+ value: fn(Internal(a)) -> Value,
+ default: Option(a),
+ )
+}
+
+/// An internal representation of flag contents
+///
+pub opaque type Internal(a) {
+ Internal(value: Option(a), parser: Parser(a, Snag))
+}
+
+// Builder initializers
+
+type Parser(a, b) =
+ fn(String) -> gleam.Result(a, b)
+
+/// initialise an int flag builder
+///
+pub fn int() -> FlagBuilder(Int) {
+ use input <- new(I)
+ input
+ |> int.parse
+ |> result.replace_error(cannot_parse(input, "int"))
+}
+
+/// initialise an int list flag builder
+///
+pub fn int_list() -> FlagBuilder(List(Int)) {
+ use input <- new(LI)
+ input
+ |> string.split(",")
+ |> list.try_map(int.parse)
+ |> result.replace_error(cannot_parse(input, "int list"))
+}
+
+/// initialise a float flag builder
+///
+pub fn float() -> FlagBuilder(Float) {
+ use input <- new(F)
+ input
+ |> float.parse
+ |> result.replace_error(cannot_parse(input, "float"))
+}
+
+/// initialise a float list flag builder
+///
+pub fn float_list() -> FlagBuilder(List(Float)) {
+ use input <- new(LF)
+ input
+ |> string.split(",")
+ |> list.try_map(float.parse)
+ |> result.replace_error(cannot_parse(input, "float list"))
+}
+
+/// initialise a string flag builder
+///
+pub fn string() -> FlagBuilder(String) {
+ new(S, fn(s) { Ok(s) })
+}
+
+/// intitialise a string list flag builder
+///
+pub fn string_list() -> FlagBuilder(List(String)) {
+ use input <- new(LS)
+ input
+ |> string.split(",")
+ |> Ok
+}
+
+/// initialise a bool flag builder
+///
+pub fn bool() -> FlagBuilder(Bool) {
+ use input <- new(B)
+ case string.lowercase(input) {
+ "true" | "t" -> Ok(True)
+ "false" | "f" -> Ok(False)
+ _ -> Error(cannot_parse(input, "bool"))
+ }
+}
+
+/// initialize custom builders using a Value constructor and a parsing function
+///
+fn new(valuer: fn(Internal(a)) -> Value, p: Parser(a, Snag)) -> FlagBuilder(a) {
+ FlagBuilder(desc: "", parser: p, value: valuer, default: None)
+}
+
+/// convert a FlagBuilder(a) into its corresponding Flag representation
+///
+pub fn build(fb: FlagBuilder(a)) -> Flag {
+ Flag(
+ value: fb.value(Internal(value: fb.default, parser: fb.parser)),
+ description: fb.desc,
+ )
+}
+
+/// attach a constraint to a `Flag`
+///
+pub fn constraint(
+ builder: FlagBuilder(a),
+ constraint: Constraint(a),
+) -> FlagBuilder(a) {
+ FlagBuilder(
+ ..builder,
+ parser: wrap_with_constraint(builder.parser, constraint),
+ )
+}
+
+/// attach a Constraint(a) to a Parser(a,Snag)
+/// this function should not be used directly unless
+fn wrap_with_constraint(
+ p: Parser(a, Snag),
+ constraint: Constraint(a),
+) -> Parser(a, Snag) {
+ fn(input: String) -> Result(a) { attempt(p(input), constraint) }
+}
+
+fn attempt(
+ val: gleam.Result(a, e),
+ f: fn(a) -> gleam.Result(_, e),
+) -> gleam.Result(a, e) {
+ use a <- result.try(val)
+ result.replace(f(a), a)
+}
+
+/// Flag descriptions
+///
+pub type Description =
+ String
+
+/// Flag data and descriptions
+///
+pub type Flag {
+ Flag(value: Value, description: Description)
+}
+
+/// attach a description to a `Flag`
+///
+pub fn description(
+ for builder: FlagBuilder(a),
+ of description: Description,
+) -> FlagBuilder(a) {
+ FlagBuilder(..builder, desc: description)
+}
+
+/// Set the default value for a flag `Value`
+///
+pub fn default(for builder: FlagBuilder(a), of default: a) -> FlagBuilder(a) {
+ FlagBuilder(..builder, default: Some(default))
+}
+
+/// Associate flag names to their current values.
+///
+pub type Map =
+ map.Map(String, Flag)
+
+/// Convert a list of flags to a Map.
+///
+pub fn build_map(flags: List(#(String, Flag))) -> Map {
+ map.from_list(flags)
+}
+
+/// Updates a flag value, ensuring that the new value can satisfy the required type.
+/// Assumes that all flag inputs passed in start with --
+/// This function is only intended to be used from glint.execute_root
+///
+pub fn update_flags(in flags: Map, with flag_input: String) -> Result(Map) {
+ let flag_input = string.drop_left(flag_input, string.length(prefix))
+
+ case string.split_once(flag_input, delimiter) {
+ Ok(data) -> update_flag_value(flags, data)
+ Error(_) -> attempt_toggle_flag(flags, flag_input)
+ }
+}
+
+fn update_flag_value(in flags: Map, with data: #(String, String)) -> Result(Map) {
+ let #(key, input) = data
+ use contents <- result.try(access(flags, key))
+ use value <- result.map(
+ compute_flag(with: input, given: contents.value)
+ |> result.map_error(layer_invalid_flag(_, key)),
+ )
+ map.insert(flags, key, Flag(..contents, value: value))
+}
+
+fn attempt_toggle_flag(in flags: Map, at key: String) -> Result(Map) {
+ use contents <- result.try(access(flags, key))
+ case contents.value {
+ B(Internal(None, ..) as internal) ->
+ Internal(..internal, value: Some(True))
+ |> B
+ |> fn(val) { Flag(..contents, value: val) }
+ |> map.insert(into: flags, for: key)
+ |> Ok()
+ B(Internal(Some(val), ..) as internal) ->
+ Internal(..internal, value: Some(!val))
+ |> B
+ |> fn(val) { Flag(..contents, value: val) }
+ |> map.insert(into: flags, for: key)
+ |> Ok()
+ _ -> Error(no_value_flag_err(key))
+ }
+}
+
+fn access_type_error(flag_type) {
+ snag.error("cannot access flag as " <> flag_type)
+}
+
+fn flag_not_provided_error() {
+ snag.error("no value provided")
+}
+
+fn construct_value(
+ input: String,
+ internal: Internal(a),
+ constructor: fn(Internal(a)) -> Value,
+) -> Result(Value) {
+ use val <- result.map(internal.parser(input))
+ constructor(Internal(..internal, value: Some(val)))
+}
+
+/// Computes the new flag value given the input and the expected flag type
+///
+fn compute_flag(with input: String, given current: Value) -> Result(Value) {
+ input
+ |> case current {
+ I(internal) -> construct_value(_, internal, I)
+ LI(internal) -> construct_value(_, internal, LI)
+ F(internal) -> construct_value(_, internal, F)
+ LF(internal) -> construct_value(_, internal, LF)
+ S(internal) -> construct_value(_, internal, S)
+ LS(internal) -> construct_value(_, internal, LS)
+ B(internal) -> construct_value(_, internal, B)
+ }
+ |> snag.context("failed to compute value for flag")
+}
+
+// Error creation and manipulation functions
+fn layer_invalid_flag(err: Snag, flag: String) -> Snag {
+ snag.layer(err, "invalid flag '" <> flag <> "'")
+}
+
+fn no_value_flag_err(flag_input: String) -> Snag {
+ { "flag '" <> flag_input <> "' has no assigned value" }
+ |> snag.new()
+ |> layer_invalid_flag(flag_input)
+}
+
+fn undefined_flag_err(key: String) -> Snag {
+ "flag provided but not defined"
+ |> snag.new()
+ |> layer_invalid_flag(key)
+}
+
+fn cannot_parse(with value: String, is kind: String) -> Snag {
+ { "cannot parse value '" <> value <> "' as " <> kind }
+ |> snag.new()
+}
+
+// Help Message Functions
+/// Generate the help message contents for a single flag
+///
+pub fn flag_type_help(flag: #(String, Flag)) {
+ let #(name, contents) = flag
+ let kind = case contents.value {
+ I(_) -> "INT"
+ B(_) -> "BOOL"
+ F(_) -> "FLOAT"
+ LF(_) -> "FLOAT_LIST"
+ LI(_) -> "INT_LIST"
+ LS(_) -> "STRING_LIST"
+ S(_) -> "STRING"
+ }
+
+ prefix <> name <> delimiter <> "<" <> kind <> ">"
+}
+
+/// Generate help message line for a single flag
+///
+fn flag_help(flag: #(String, Flag)) -> String {
+ flag_type_help(flag) <> "\t\t" <> { flag.1 }.description
+}
+
+/// Generate help messages for all flags
+///
+pub fn flags_help(flags: Map) -> List(String) {
+ flags
+ |> map.to_list
+ |> list.map(flag_help)
+}
+
+// -- FLAG ACCESS FUNCTIONS --
+
+/// Access the contents for the associated flag
+///
+fn access(flags: Map, name: String) -> Result(Flag) {
+ map.get(flags, name)
+ |> result.replace_error(undefined_flag_err(name))
+}
+
+fn get_value(
+ from flags: Map,
+ at key: String,
+ expecting kind: fn(Flag) -> Result(a),
+) -> Result(a) {
+ access(flags, key)
+ |> result.try(kind)
+ |> snag.context("failed to retrieve value for flag '" <> key <> "'")
+}
+
+/// Gets the current value for the provided int flag
+///
+pub fn get_int_value(from flag: Flag) -> Result(Int) {
+ case flag.value {
+ I(Internal(value: Some(val), ..)) -> Ok(val)
+ I(Internal(value: None, ..)) -> flag_not_provided_error()
+ _ -> access_type_error("int")
+ }
+}
+
+/// Gets the current value for the associated int flag
+///
+pub fn get_int(from flags: Map, for name: String) -> Result(Int) {
+ get_value(flags, name, get_int_value)
+}
+
+/// Gets the current value for the provided ints flag
+///
+pub fn get_ints_value(from flag: Flag) -> Result(List(Int)) {
+ case flag.value {
+ LI(Internal(value: Some(val), ..)) -> Ok(val)
+ LI(Internal(value: None, ..)) -> flag_not_provided_error()
+ _ -> access_type_error("int list")
+ }
+}
+
+/// Gets the current value for the associated ints flag
+///
+pub fn get_ints(from flags: Map, for name: String) -> Result(List(Int)) {
+ get_value(flags, name, get_ints_value)
+}
+
+/// Gets the current value for the provided bool flag
+///
+pub fn get_bool_value(from flag: Flag) -> Result(Bool) {
+ case flag.value {
+ B(Internal(Some(val), ..)) -> Ok(val)
+ B(Internal(None, ..)) -> flag_not_provided_error()
+ _ -> access_type_error("bool")
+ }
+}
+
+/// Gets the current value for the associated bool flag
+///
+pub fn get_bool(from flags: Map, for name: String) -> Result(Bool) {
+ get_value(flags, name, get_bool_value)
+}
+
+/// Gets the current value for the provided string flag
+///
+pub fn get_string_value(from flag: Flag) -> Result(String) {
+ case flag.value {
+ S(Internal(value: Some(val), ..)) -> Ok(val)
+ S(Internal(value: None, ..)) -> flag_not_provided_error()
+ _ -> access_type_error("string")
+ }
+}
+
+/// Gets the current value for the associated string flag
+///
+pub fn get_string(from flags: Map, for name: String) -> Result(String) {
+ get_value(flags, name, get_string_value)
+}
+
+/// Gets the current value for the provided strings flag
+///
+pub fn get_strings_value(from flag: Flag) -> Result(List(String)) {
+ case flag.value {
+ LS(Internal(value: Some(val), ..)) -> Ok(val)
+ LS(Internal(value: None, ..)) -> flag_not_provided_error()
+ _ -> access_type_error("string list")
+ }
+}
+
+/// Gets the current value for the associated strings flag
+///
+pub fn get_strings(from flags: Map, for name: String) -> Result(List(String)) {
+ get_value(flags, name, get_strings_value)
+}
+
+/// Gets the current value for the provided float flag
+///
+pub fn get_float_value(from flag: Flag) -> Result(Float) {
+ case flag.value {
+ F(Internal(value: Some(val), ..)) -> Ok(val)
+ F(Internal(value: None, ..)) -> flag_not_provided_error()
+ _ -> access_type_error("float")
+ }
+}
+
+/// Gets the current value for the associated float flag
+///
+pub fn get_float(from flags: Map, for name: String) -> Result(Float) {
+ get_value(flags, name, get_float_value)
+}
+
+/// Gets the current value for the provided floats flag
+///
+pub fn get_floats_value(from flag: Flag) -> Result(List(Float)) {
+ case flag.value {
+ LF(Internal(value: Some(val), ..)) -> Ok(val)
+ LF(Internal(value: None, ..)) -> flag_not_provided_error()
+ _ -> access_type_error("float list")
+ }
+}
+
+/// Gets the current value for the associated floats flag
+///
+pub fn get_floats(from flags: Map, for name: String) -> Result(List(Float)) {
+ get_value(flags, name, get_floats_value)
+}
diff --git a/aoc2023/build/packages/glint/src/glint/flag/constraint.gleam b/aoc2023/build/packages/glint/src/glint/flag/constraint.gleam
new file mode 100644
index 0000000..e474bc2
--- /dev/null
+++ b/aoc2023/build/packages/glint/src/glint/flag/constraint.gleam
@@ -0,0 +1,66 @@
+import gleam/list
+import gleam/result
+import gleam/string
+import gleam/set
+import snag.{type Result}
+
+/// Constraint type for verifying flag values
+///
+pub type Constraint(a) =
+ fn(a) -> Result(Nil)
+
+/// one_of returns a Constraint that ensures the parsed flag value is
+/// one of the allowed values.
+///
+pub fn one_of(allowed: List(a)) -> Constraint(a) {
+ let allowed_set = set.from_list(allowed)
+ fn(val: a) -> Result(Nil) {
+ case set.contains(allowed_set, val) {
+ True -> Ok(Nil)
+ False ->
+ snag.error(
+ "invalid value '" <> string.inspect(val) <> "', must be one of: [" <> {
+ allowed
+ |> list.map(fn(a) { "'" <> string.inspect(a) <> "'" })
+ |> string.join(", ") <> "]"
+ },
+ )
+ }
+ }
+}
+
+/// none_of returns a Constraint that ensures the parsed flag value is not one of the disallowed values.
+///
+pub fn none_of(disallowed: List(a)) -> Constraint(a) {
+ let disallowed_set = set.from_list(disallowed)
+ fn(val: a) -> Result(Nil) {
+ case set.contains(disallowed_set, val) {
+ False -> Ok(Nil)
+ True ->
+ snag.error(
+ "invalid value '" <> string.inspect(val) <> "', must not be one of: [" <> {
+ {
+ disallowed
+ |> list.map(fn(a) { "'" <> string.inspect(a) <> "'" })
+ |> string.join(", ") <> "]"
+ }
+ },
+ )
+ }
+ }
+}
+
+/// each is a convenience function for applying a Constraint(a) to a List(a).
+/// This is useful because the default behaviour for constraints on lists is that they will apply to the list as a whole.
+///
+/// For example, to apply one_of to all items in a `List(Int)`:
+/// ```gleam
+/// [1, 2, 3, 4] |> one_of |> each
+/// ```
+pub fn each(constraint: Constraint(a)) -> Constraint(List(a)) {
+ fn(l: List(a)) -> Result(Nil) {
+ l
+ |> list.try_map(constraint)
+ |> result.replace(Nil)
+ }
+}
diff --git a/aoc2023/build/packages/glint/src/glint@flag.erl b/aoc2023/build/packages/glint/src/glint@flag.erl
new file mode 100644
index 0000000..bcce6db
--- /dev/null
+++ b/aoc2023/build/packages/glint/src/glint@flag.erl
@@ -0,0 +1,523 @@
+-module(glint@flag).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([string/0, string_list/0, build/1, constraint/2, description/2, default/2, build_map/1, int/0, int_list/0, float/0, float_list/0, bool/0, flag_type_help/1, flags_help/1, update_flags/2, get_int_value/1, get_int/2, get_ints_value/1, get_ints/2, get_bool_value/1, get_bool/2, get_string_value/1, get_string/2, get_strings_value/1, get_strings/2, get_float_value/1, get_float/2, get_floats_value/1, get_floats/2]).
+-export_type([value/0, flag_builder/1, internal/1, flag/0]).
+
+-type value() :: {b, internal(boolean())} |
+ {i, internal(integer())} |
+ {li, internal(list(integer()))} |
+ {f, internal(float())} |
+ {lf, internal(list(float()))} |
+ {s, internal(binary())} |
+ {ls, internal(list(binary()))}.
+
+-opaque flag_builder(FTZ) :: {flag_builder,
+ binary(),
+ fun((binary()) -> {ok, FTZ} | {error, snag:snag()}),
+ fun((internal(FTZ)) -> value()),
+ gleam@option:option(FTZ)}.
+
+-opaque internal(FUA) :: {internal,
+ gleam@option:option(FUA),
+ fun((binary()) -> {ok, FUA} | {error, snag:snag()})}.
+
+-type flag() :: {flag, value(), binary()}.
+
+-spec new(
+ fun((internal(FUR)) -> value()),
+ fun((binary()) -> {ok, FUR} | {error, snag:snag()})
+) -> flag_builder(FUR).
+new(Valuer, P) ->
+ {flag_builder, <<""/utf8>>, P, Valuer, none}.
+
+-spec string() -> flag_builder(binary()).
+string() ->
+ new(fun(Field@0) -> {s, Field@0} end, fun(S) -> {ok, S} end).
+
+-spec string_list() -> flag_builder(list(binary())).
+string_list() ->
+ new(fun(Field@0) -> {ls, Field@0} end, fun(Input) -> _pipe = Input,
+ _pipe@1 = gleam@string:split(_pipe, <<","/utf8>>),
+ {ok, _pipe@1} end).
+
+-spec build(flag_builder(any())) -> flag().
+build(Fb) ->
+ {flag,
+ (erlang:element(4, Fb))(
+ {internal, erlang:element(5, Fb), erlang:element(3, Fb)}
+ ),
+ erlang:element(2, Fb)}.
+
+-spec attempt(
+ {ok, FVI} | {error, FVJ},
+ fun((FVI) -> {ok, any()} | {error, FVJ})
+) -> {ok, FVI} | {error, FVJ}.
+attempt(Val, F) ->
+ gleam@result:'try'(Val, fun(A) -> gleam@result:replace(F(A), A) end).
+
+-spec wrap_with_constraint(
+ fun((binary()) -> {ok, FVC} | {error, snag:snag()}),
+ fun((FVC) -> {ok, nil} | {error, snag:snag()})
+) -> fun((binary()) -> {ok, FVC} | {error, snag:snag()}).
+wrap_with_constraint(P, Constraint) ->
+ fun(Input) -> attempt(P(Input), Constraint) end.
+
+-spec constraint(
+ flag_builder(FUY),
+ fun((FUY) -> {ok, nil} | {error, snag:snag()})
+) -> flag_builder(FUY).
+constraint(Builder, Constraint) ->
+ erlang:setelement(
+ 3,
+ Builder,
+ wrap_with_constraint(erlang:element(3, Builder), Constraint)
+ ).
+
+-spec description(flag_builder(FVR), binary()) -> flag_builder(FVR).
+description(Builder, Description) ->
+ erlang:setelement(2, Builder, Description).
+
+-spec default(flag_builder(FVU), FVU) -> flag_builder(FVU).
+default(Builder, Default) ->
+ erlang:setelement(5, Builder, {some, Default}).
+
+-spec build_map(list({binary(), flag()})) -> gleam@map:map_(binary(), flag()).
+build_map(Flags) ->
+ gleam@map:from_list(Flags).
+
+-spec access_type_error(binary()) -> {ok, any()} | {error, snag:snag()}.
+access_type_error(Flag_type) ->
+ snag:error(<<"cannot access flag as "/utf8, Flag_type/binary>>).
+
+-spec flag_not_provided_error() -> {ok, any()} | {error, snag:snag()}.
+flag_not_provided_error() ->
+ snag:error(<<"no value provided"/utf8>>).
+
+-spec construct_value(binary(), internal(FWE), fun((internal(FWE)) -> value())) -> {ok,
+ value()} |
+ {error, snag:snag()}.
+construct_value(Input, Internal, Constructor) ->
+ gleam@result:map(
+ (erlang:element(3, Internal))(Input),
+ fun(Val) -> Constructor(erlang:setelement(2, Internal, {some, Val})) end
+ ).
+
+-spec compute_flag(binary(), value()) -> {ok, value()} | {error, snag:snag()}.
+compute_flag(Input, Current) ->
+ _pipe = Input,
+ _pipe@1 = case Current of
+ {i, Internal} ->
+ fun(_capture) ->
+ construct_value(
+ _capture,
+ Internal,
+ fun(Field@0) -> {i, Field@0} end
+ )
+ end;
+
+ {li, Internal@1} ->
+ fun(_capture@1) ->
+ construct_value(
+ _capture@1,
+ Internal@1,
+ fun(Field@0) -> {li, Field@0} end
+ )
+ end;
+
+ {f, Internal@2} ->
+ fun(_capture@2) ->
+ construct_value(
+ _capture@2,
+ Internal@2,
+ fun(Field@0) -> {f, Field@0} end
+ )
+ end;
+
+ {lf, Internal@3} ->
+ fun(_capture@3) ->
+ construct_value(
+ _capture@3,
+ Internal@3,
+ fun(Field@0) -> {lf, Field@0} end
+ )
+ end;
+
+ {s, Internal@4} ->
+ fun(_capture@4) ->
+ construct_value(
+ _capture@4,
+ Internal@4,
+ fun(Field@0) -> {s, Field@0} end
+ )
+ end;
+
+ {ls, Internal@5} ->
+ fun(_capture@5) ->
+ construct_value(
+ _capture@5,
+ Internal@5,
+ fun(Field@0) -> {ls, Field@0} end
+ )
+ end;
+
+ {b, Internal@6} ->
+ fun(_capture@6) ->
+ construct_value(
+ _capture@6,
+ Internal@6,
+ fun(Field@0) -> {b, Field@0} end
+ )
+ end
+ end(_pipe),
+ snag:context(_pipe@1, <<"failed to compute value for flag"/utf8>>).
+
+-spec layer_invalid_flag(snag:snag(), binary()) -> snag:snag().
+layer_invalid_flag(Err, Flag) ->
+ snag:layer(Err, <<<<"invalid flag '"/utf8, Flag/binary>>/binary, "'"/utf8>>).
+
+-spec no_value_flag_err(binary()) -> snag:snag().
+no_value_flag_err(Flag_input) ->
+ _pipe = (<<<<"flag '"/utf8, Flag_input/binary>>/binary,
+ "' has no assigned value"/utf8>>),
+ _pipe@1 = snag:new(_pipe),
+ layer_invalid_flag(_pipe@1, Flag_input).
+
+-spec undefined_flag_err(binary()) -> snag:snag().
+undefined_flag_err(Key) ->
+ _pipe = <<"flag provided but not defined"/utf8>>,
+ _pipe@1 = snag:new(_pipe),
+ layer_invalid_flag(_pipe@1, Key).
+
+-spec cannot_parse(binary(), binary()) -> snag:snag().
+cannot_parse(Value, Kind) ->
+ _pipe = (<<<<<<"cannot parse value '"/utf8, Value/binary>>/binary,
+ "' as "/utf8>>/binary,
+ Kind/binary>>),
+ snag:new(_pipe).
+
+-spec int() -> flag_builder(integer()).
+int() ->
+ new(fun(Field@0) -> {i, Field@0} end, fun(Input) -> _pipe = Input,
+ _pipe@1 = gleam@int:parse(_pipe),
+ gleam@result:replace_error(
+ _pipe@1,
+ cannot_parse(Input, <<"int"/utf8>>)
+ ) end).
+
+-spec int_list() -> flag_builder(list(integer())).
+int_list() ->
+ new(fun(Field@0) -> {li, Field@0} end, fun(Input) -> _pipe = Input,
+ _pipe@1 = gleam@string:split(_pipe, <<","/utf8>>),
+ _pipe@2 = gleam@list:try_map(_pipe@1, fun gleam@int:parse/1),
+ gleam@result:replace_error(
+ _pipe@2,
+ cannot_parse(Input, <<"int list"/utf8>>)
+ ) end).
+
+-spec float() -> flag_builder(float()).
+float() ->
+ new(fun(Field@0) -> {f, Field@0} end, fun(Input) -> _pipe = Input,
+ _pipe@1 = gleam@float:parse(_pipe),
+ gleam@result:replace_error(
+ _pipe@1,
+ cannot_parse(Input, <<"float"/utf8>>)
+ ) end).
+
+-spec float_list() -> flag_builder(list(float())).
+float_list() ->
+ new(fun(Field@0) -> {lf, Field@0} end, fun(Input) -> _pipe = Input,
+ _pipe@1 = gleam@string:split(_pipe, <<","/utf8>>),
+ _pipe@2 = gleam@list:try_map(_pipe@1, fun gleam@float:parse/1),
+ gleam@result:replace_error(
+ _pipe@2,
+ cannot_parse(Input, <<"float list"/utf8>>)
+ ) end).
+
+-spec bool() -> flag_builder(boolean()).
+bool() ->
+ new(
+ fun(Field@0) -> {b, Field@0} end,
+ fun(Input) -> case gleam@string:lowercase(Input) of
+ <<"true"/utf8>> ->
+ {ok, true};
+
+ <<"t"/utf8>> ->
+ {ok, true};
+
+ <<"false"/utf8>> ->
+ {ok, false};
+
+ <<"f"/utf8>> ->
+ {ok, false};
+
+ _ ->
+ {error, cannot_parse(Input, <<"bool"/utf8>>)}
+ end end
+ ).
+
+-spec flag_type_help({binary(), flag()}) -> binary().
+flag_type_help(Flag) ->
+ {Name, Contents} = Flag,
+ Kind = case erlang:element(2, Contents) of
+ {i, _} ->
+ <<"INT"/utf8>>;
+
+ {b, _} ->
+ <<"BOOL"/utf8>>;
+
+ {f, _} ->
+ <<"FLOAT"/utf8>>;
+
+ {lf, _} ->
+ <<"FLOAT_LIST"/utf8>>;
+
+ {li, _} ->
+ <<"INT_LIST"/utf8>>;
+
+ {ls, _} ->
+ <<"STRING_LIST"/utf8>>;
+
+ {s, _} ->
+ <<"STRING"/utf8>>
+ end,
+ <<<<<<<<<<"--"/utf8, Name/binary>>/binary, "="/utf8>>/binary, "<"/utf8>>/binary,
+ Kind/binary>>/binary,
+ ">"/utf8>>.
+
+-spec flag_help({binary(), flag()}) -> binary().
+flag_help(Flag) ->
+ <<<<(flag_type_help(Flag))/binary, "\t\t"/utf8>>/binary,
+ (erlang:element(3, (erlang:element(2, Flag))))/binary>>.
+
+-spec flags_help(gleam@map:map_(binary(), flag())) -> list(binary()).
+flags_help(Flags) ->
+ _pipe = Flags,
+ _pipe@1 = gleam@map:to_list(_pipe),
+ gleam@list:map(_pipe@1, fun flag_help/1).
+
+-spec access(gleam@map:map_(binary(), flag()), binary()) -> {ok, flag()} |
+ {error, snag:snag()}.
+access(Flags, Name) ->
+ _pipe = gleam@map:get(Flags, Name),
+ gleam@result:replace_error(_pipe, undefined_flag_err(Name)).
+
+-spec update_flag_value(gleam@map:map_(binary(), flag()), {binary(), binary()}) -> {ok,
+ gleam@map:map_(binary(), flag())} |
+ {error, snag:snag()}.
+update_flag_value(Flags, Data) ->
+ {Key, Input} = Data,
+ gleam@result:'try'(
+ access(Flags, Key),
+ fun(Contents) ->
+ gleam@result:map(
+ begin
+ _pipe = compute_flag(Input, erlang:element(2, Contents)),
+ gleam@result:map_error(
+ _pipe,
+ fun(_capture) -> layer_invalid_flag(_capture, Key) end
+ )
+ end,
+ fun(Value) ->
+ gleam@map:insert(
+ Flags,
+ Key,
+ erlang:setelement(2, Contents, Value)
+ )
+ end
+ )
+ end
+ ).
+
+-spec attempt_toggle_flag(gleam@map:map_(binary(), flag()), binary()) -> {ok,
+ gleam@map:map_(binary(), flag())} |
+ {error, snag:snag()}.
+attempt_toggle_flag(Flags, Key) ->
+ gleam@result:'try'(
+ access(Flags, Key),
+ fun(Contents) -> case erlang:element(2, Contents) of
+ {b, {internal, none, _} = Internal} ->
+ _pipe = erlang:setelement(2, Internal, {some, true}),
+ _pipe@1 = {b, _pipe},
+ _pipe@2 = (fun(Val) ->
+ erlang:setelement(2, Contents, Val)
+ end)(_pipe@1),
+ _pipe@3 = gleam@map:insert(Flags, Key, _pipe@2),
+ {ok, _pipe@3};
+
+ {b, {internal, {some, Val@1}, _} = Internal@1} ->
+ _pipe@4 = erlang:setelement(
+ 2,
+ Internal@1,
+ {some, not Val@1}
+ ),
+ _pipe@5 = {b, _pipe@4},
+ _pipe@6 = (fun(Val@2) ->
+ erlang:setelement(2, Contents, Val@2)
+ end)(_pipe@5),
+ _pipe@7 = gleam@map:insert(Flags, Key, _pipe@6),
+ {ok, _pipe@7};
+
+ _ ->
+ {error, no_value_flag_err(Key)}
+ end end
+ ).
+
+-spec update_flags(gleam@map:map_(binary(), flag()), binary()) -> {ok,
+ gleam@map:map_(binary(), flag())} |
+ {error, snag:snag()}.
+update_flags(Flags, Flag_input) ->
+ Flag_input@1 = gleam@string:drop_left(
+ Flag_input,
+ gleam@string:length(<<"--"/utf8>>)
+ ),
+ case gleam@string:split_once(Flag_input@1, <<"="/utf8>>) of
+ {ok, Data} ->
+ update_flag_value(Flags, Data);
+
+ {error, _} ->
+ attempt_toggle_flag(Flags, Flag_input@1)
+ end.
+
+-spec get_value(
+ gleam@map:map_(binary(), flag()),
+ binary(),
+ fun((flag()) -> {ok, FWM} | {error, snag:snag()})
+) -> {ok, FWM} | {error, snag:snag()}.
+get_value(Flags, Key, Kind) ->
+ _pipe = access(Flags, Key),
+ _pipe@1 = gleam@result:'try'(_pipe, Kind),
+ snag:context(
+ _pipe@1,
+ <<<<"failed to retrieve value for flag '"/utf8, Key/binary>>/binary,
+ "'"/utf8>>
+ ).
+
+-spec get_int_value(flag()) -> {ok, integer()} | {error, snag:snag()}.
+get_int_value(Flag) ->
+ case erlang:element(2, Flag) of
+ {i, {internal, {some, Val}, _}} ->
+ {ok, Val};
+
+ {i, {internal, none, _}} ->
+ flag_not_provided_error();
+
+ _ ->
+ access_type_error(<<"int"/utf8>>)
+ end.
+
+-spec get_int(gleam@map:map_(binary(), flag()), binary()) -> {ok, integer()} |
+ {error, snag:snag()}.
+get_int(Flags, Name) ->
+ get_value(Flags, Name, fun get_int_value/1).
+
+-spec get_ints_value(flag()) -> {ok, list(integer())} | {error, snag:snag()}.
+get_ints_value(Flag) ->
+ case erlang:element(2, Flag) of
+ {li, {internal, {some, Val}, _}} ->
+ {ok, Val};
+
+ {li, {internal, none, _}} ->
+ flag_not_provided_error();
+
+ _ ->
+ access_type_error(<<"int list"/utf8>>)
+ end.
+
+-spec get_ints(gleam@map:map_(binary(), flag()), binary()) -> {ok,
+ list(integer())} |
+ {error, snag:snag()}.
+get_ints(Flags, Name) ->
+ get_value(Flags, Name, fun get_ints_value/1).
+
+-spec get_bool_value(flag()) -> {ok, boolean()} | {error, snag:snag()}.
+get_bool_value(Flag) ->
+ case erlang:element(2, Flag) of
+ {b, {internal, {some, Val}, _}} ->
+ {ok, Val};
+
+ {b, {internal, none, _}} ->
+ flag_not_provided_error();
+
+ _ ->
+ access_type_error(<<"bool"/utf8>>)
+ end.
+
+-spec get_bool(gleam@map:map_(binary(), flag()), binary()) -> {ok, boolean()} |
+ {error, snag:snag()}.
+get_bool(Flags, Name) ->
+ get_value(Flags, Name, fun get_bool_value/1).
+
+-spec get_string_value(flag()) -> {ok, binary()} | {error, snag:snag()}.
+get_string_value(Flag) ->
+ case erlang:element(2, Flag) of
+ {s, {internal, {some, Val}, _}} ->
+ {ok, Val};
+
+ {s, {internal, none, _}} ->
+ flag_not_provided_error();
+
+ _ ->
+ access_type_error(<<"string"/utf8>>)
+ end.
+
+-spec get_string(gleam@map:map_(binary(), flag()), binary()) -> {ok, binary()} |
+ {error, snag:snag()}.
+get_string(Flags, Name) ->
+ get_value(Flags, Name, fun get_string_value/1).
+
+-spec get_strings_value(flag()) -> {ok, list(binary())} | {error, snag:snag()}.
+get_strings_value(Flag) ->
+ case erlang:element(2, Flag) of
+ {ls, {internal, {some, Val}, _}} ->
+ {ok, Val};
+
+ {ls, {internal, none, _}} ->
+ flag_not_provided_error();
+
+ _ ->
+ access_type_error(<<"string list"/utf8>>)
+ end.
+
+-spec get_strings(gleam@map:map_(binary(), flag()), binary()) -> {ok,
+ list(binary())} |
+ {error, snag:snag()}.
+get_strings(Flags, Name) ->
+ get_value(Flags, Name, fun get_strings_value/1).
+
+-spec get_float_value(flag()) -> {ok, float()} | {error, snag:snag()}.
+get_float_value(Flag) ->
+ case erlang:element(2, Flag) of
+ {f, {internal, {some, Val}, _}} ->
+ {ok, Val};
+
+ {f, {internal, none, _}} ->
+ flag_not_provided_error();
+
+ _ ->
+ access_type_error(<<"float"/utf8>>)
+ end.
+
+-spec get_float(gleam@map:map_(binary(), flag()), binary()) -> {ok, float()} |
+ {error, snag:snag()}.
+get_float(Flags, Name) ->
+ get_value(Flags, Name, fun get_float_value/1).
+
+-spec get_floats_value(flag()) -> {ok, list(float())} | {error, snag:snag()}.
+get_floats_value(Flag) ->
+ case erlang:element(2, Flag) of
+ {lf, {internal, {some, Val}, _}} ->
+ {ok, Val};
+
+ {lf, {internal, none, _}} ->
+ flag_not_provided_error();
+
+ _ ->
+ access_type_error(<<"float list"/utf8>>)
+ end.
+
+-spec get_floats(gleam@map:map_(binary(), flag()), binary()) -> {ok,
+ list(float())} |
+ {error, snag:snag()}.
+get_floats(Flags, Name) ->
+ get_value(Flags, Name, fun get_floats_value/1).
diff --git a/aoc2023/build/packages/glint/src/glint@flag@constraint.erl b/aoc2023/build/packages/glint/src/glint@flag@constraint.erl
new file mode 100644
index 0000000..2978be0
--- /dev/null
+++ b/aoc2023/build/packages/glint/src/glint@flag@constraint.erl
@@ -0,0 +1,68 @@
+-module(glint@flag@constraint).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([one_of/1, none_of/1, each/1]).
+
+-spec one_of(list(FSI)) -> fun((FSI) -> {ok, nil} | {error, snag:snag()}).
+one_of(Allowed) ->
+ Allowed_set = gleam@set:from_list(Allowed),
+ fun(Val) -> case gleam@set:contains(Allowed_set, Val) of
+ true ->
+ {ok, nil};
+
+ false ->
+ snag:error(
+ <<<<<<"invalid value '"/utf8,
+ (gleam@string:inspect(Val))/binary>>/binary,
+ "', must be one of: ["/utf8>>/binary,
+ ((<<(begin
+ _pipe = Allowed,
+ _pipe@1 = gleam@list:map(
+ _pipe,
+ fun(A) ->
+ <<<<"'"/utf8,
+ (gleam@string:inspect(A))/binary>>/binary,
+ "'"/utf8>>
+ end
+ ),
+ gleam@string:join(_pipe@1, <<", "/utf8>>)
+ end)/binary,
+ "]"/utf8>>))/binary>>
+ )
+ end end.
+
+-spec none_of(list(FSL)) -> fun((FSL) -> {ok, nil} | {error, snag:snag()}).
+none_of(Disallowed) ->
+ Disallowed_set = gleam@set:from_list(Disallowed),
+ fun(Val) -> case gleam@set:contains(Disallowed_set, Val) of
+ false ->
+ {ok, nil};
+
+ true ->
+ snag:error(
+ <<<<<<"invalid value '"/utf8,
+ (gleam@string:inspect(Val))/binary>>/binary,
+ "', must not be one of: ["/utf8>>/binary,
+ (((<<(begin
+ _pipe = Disallowed,
+ _pipe@1 = gleam@list:map(
+ _pipe,
+ fun(A) ->
+ <<<<"'"/utf8,
+ (gleam@string:inspect(A))/binary>>/binary,
+ "'"/utf8>>
+ end
+ ),
+ gleam@string:join(_pipe@1, <<", "/utf8>>)
+ end)/binary,
+ "]"/utf8>>)))/binary>>
+ )
+ end end.
+
+-spec each(fun((FSO) -> {ok, nil} | {error, snag:snag()})) -> fun((list(FSO)) -> {ok,
+ nil} |
+ {error, snag:snag()}).
+each(Constraint) ->
+ fun(L) -> _pipe = L,
+ _pipe@1 = gleam@list:try_map(_pipe, Constraint),
+ gleam@result:replace(_pipe@1, nil) end.