diff options
Diffstat (limited to 'aoc2023/build/packages/glint/src/glint.gleam')
-rw-r--r-- | aoc2023/build/packages/glint/src/glint.gleam | 588 |
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), - ) -} |