aboutsummaryrefslogtreecommitdiff
path: root/aoc2023/build/packages/glint/src/glint.gleam
diff options
context:
space:
mode:
Diffstat (limited to 'aoc2023/build/packages/glint/src/glint.gleam')
-rw-r--r--aoc2023/build/packages/glint/src/glint.gleam588
1 files changed, 0 insertions, 588 deletions
diff --git a/aoc2023/build/packages/glint/src/glint.gleam b/aoc2023/build/packages/glint/src/glint.gleam
deleted file mode 100644
index b159016..0000000
--- a/aoc2023/build/packages/glint/src/glint.gleam
+++ /dev/null
@@ -1,588 +0,0 @@
-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),
- )
-}