Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased](https://github.com/TanklesXL/glint/compare/v1.1.0...HEAD)

- captured each flag's configured default value in the help data
(`help.Flag.default`) and added the `glint.show_flag_defaults` builder
to opt in to rendering `(default: <value>)` in `--help` output

# v1

## [1.3.0](https://github.com/TanklesXL/glint/compare/v1.2.1...v1.3.0)
Expand Down
72 changes: 57 additions & 15 deletions src/glint.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import gleam/result
import gleam/string
import gleam_community/colour.{type Colour}
import glint/constraint
import glint/help as pub_help
import glint/internal/help
import snag.{type Snag}

Expand All @@ -29,6 +30,7 @@ type Config {
max_output_width: Int,
min_first_column_width: Int,
column_gap: Int,
show_flag_defaults: Bool,
)
}

Expand All @@ -52,6 +54,7 @@ const default_config = Config(
max_output_width: 80,
min_first_column_width: 20,
column_gap: 2,
show_flag_defaults: False,
)

// -- CONFIGURATION: FUNCTIONS --
Expand Down Expand Up @@ -127,6 +130,15 @@ pub fn with_column_gap(glint: Glint(a), column_gap: Int) -> Glint(a) {
Glint(..glint, config: Config(..glint.config, column_gap:))
}

/// Enable rendering of flag default values in `--help` output. When enabled,
/// each flag with a configured default has `(default: <value>)` appended to
/// its description.
///
/// Disabled by default to preserve existing help text formatting.
pub fn show_flag_defaults(glint: Glint(a), enabled: Bool) -> Glint(a) {
Glint(..glint, config: Config(..glint.config, show_flag_defaults: enabled))
}

// --- CORE ---

// -- CORE: TYPES --
Expand Down Expand Up @@ -682,6 +694,7 @@ fn build_help_config(config: Config) -> help.Config {
column_gap: config.column_gap,
flag_prefix: flag_prefix,
flag_delimiter: flag_delimiter,
show_flag_defaults: config.show_flag_defaults,
)
}

Expand All @@ -693,28 +706,32 @@ fn build_command_help(name: String, node: CommandNode(_)) -> help.Command {
|> option.map(fn(cmd) {
#(
node.description,
build_flags_help(merge(node.group_flags, cmd.flags)),
build_flags(merge(node.group_flags, cmd.flags)),
cmd.unnamed_args,
cmd.named_args,
)
})
|> option.unwrap(#(node.description, [], None, []))

help.Command(
meta: help.Metadata(name: name, description: description),
meta: pub_help.Metadata(name: name, description: description),
flags: flags,
subcommands: build_subcommands_help(node.subcommands),
unnamed_args: {
use args <- option.map(unnamed_args)
case args {
EqArgs(n) -> help.EqArgs(n)
MinArgs(n) -> help.MinArgs(n)
}
},
unnamed_args: to_help_args(unnamed_args),
named_args: named_args,
)
}

/// remap an internal `ArgsCount` to the public `help.ArgsCount`.
///
fn to_help_args(args: Option(ArgsCount)) -> Option(pub_help.ArgsCount) {
use args <- option.map(args)
case args {
EqArgs(n) -> pub_help.EqArgs(n)
MinArgs(n) -> pub_help.MinArgs(n)
}
}

/// generate the string representation for the type of a flag
///
fn flag_type_info(flag: FlagEntry) {
Expand All @@ -729,14 +746,39 @@ fn flag_type_info(flag: FlagEntry) {
}
}

/// build the help representation for a list of flags
fn flag_default_info(flag: FlagEntry) -> Option(String) {
case flag.value {
I(FlagInternals(value: Some(v), ..)) -> Some(int.to_string(v))
F(FlagInternals(value: Some(v), ..)) -> Some(float.to_string(v))
S(FlagInternals(value: Some(v), ..)) -> Some(v)
B(FlagInternals(value: Some(v), ..)) ->
Some(case v {
True -> "true"
False -> "false"
})
LI(FlagInternals(value: Some(v), ..)) -> Some(join_csv(v, int.to_string))
LF(FlagInternals(value: Some(v), ..)) -> Some(join_csv(v, float.to_string))
LS(FlagInternals(value: Some(v), ..)) -> Some(string.join(v, ","))
_ -> None
}
}

/// stringify each item and join with commas, for list-flag defaults.
///
fn join_csv(items: List(a), to_string: fn(a) -> String) -> String {
items |> list.map(to_string) |> string.join(",")
}

/// build the public flag representation for a list of flags.
/// Shared by both `--help` rendering and the `document` doc tree.
///
fn build_flags_help(flags: Flags) -> List(help.Flag) {
fn build_flags(flags: Flags) -> List(pub_help.Flag) {
use acc, name, flag <- fold(flags, [])
[
help.Flag(
meta: help.Metadata(name: name, description: flag.description),
pub_help.Flag(
meta: pub_help.Metadata(name: name, description: flag.description),
type_: flag_type_info(flag),
default: flag_default_info(flag),
),
..acc
]
Expand All @@ -746,9 +788,9 @@ fn build_flags_help(flags: Flags) -> List(help.Flag) {
///
fn build_subcommands_help(
subcommands: dict.Dict(String, CommandNode(_)),
) -> List(help.Metadata) {
) -> List(pub_help.Metadata) {
use acc, name, node <- dict.fold(subcommands, [])
[help.Metadata(name: name, description: node.description), ..acc]
[pub_help.Metadata(name: name, description: node.description), ..acc]
}

// ----- FLAGS -----
Expand Down
30 changes: 30 additions & 0 deletions src/glint/help.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//// Stable, public introspection API for glint command trees.
////
//// This module exposes the shared public help types (`Metadata`, `Flag`,
//// and `ArgsCount`) used when rendering help text and, in future, when
//// auto-generating reference documentation from a command tree.

import gleam/option.{type Option}

/// Metadata shared by commands and flags: the `name` used in usage text and
/// headings, plus a human-readable `description`.
///
/// Re-declared as a fresh public type (rather than aliasing
/// `glint/internal/help.Metadata`) so downstream tools can both read and
/// construct `Metadata` values without importing `glint/internal/help`.
pub type Metadata {
Metadata(name: String, description: String)
}

/// Number of unnamed positional arguments accepted by a command.
///
/// Re-declared (rather than aliased) so that the `EqArgs` and `MinArgs`
/// constructors are accessible without importing `glint/internal/help`.
pub type ArgsCount {
EqArgs(Int)
MinArgs(Int)
}

pub type Flag {
Flag(meta: Metadata, type_: String, default: Option(String))
}
33 changes: 14 additions & 19 deletions src/glint/internal/help.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import gleam/option.{type Option, None, Some}
import gleam/string
import gleam_community/ansi
import gleam_community/colour.{type Colour}
import glint/help.{
type ArgsCount, type Flag, type Metadata, EqArgs, Flag, Metadata, MinArgs,
}
import glint/internal/utils

/// Style heading text with the provided rgb colouring
Expand All @@ -20,7 +23,7 @@ fn heading_style(heading: String, colour: Colour) -> String {

// --- HELP: CONSTANTS ---
//
pub const help_flag = Flag(Metadata("help", "Print help information"), "")
pub const help_flag = Flag(Metadata("help", "Print help information"), "", None)

const flags_heading = "FLAGS:"

Expand All @@ -30,11 +33,6 @@ const usage_heading = "USAGE:"

// --- HELP: TYPES ---

pub type ArgsCount {
MinArgs(Int)
EqArgs(Int)
}

pub type Config {
Config(
name: Option(String),
Expand All @@ -49,21 +47,10 @@ pub type Config {
column_gap: Int,
flag_prefix: String,
flag_delimiter: String,
show_flag_defaults: Bool,
)
}

/// Common metadata for commands and flags
///
pub type Metadata {
Metadata(name: String, description: String)
}

/// Help type for flag metadata
///
pub type Flag {
Flag(meta: Metadata, type_: String)
}

/// Help type for command metadata
pub type Command {
Command(
Expand Down Expand Up @@ -208,7 +195,15 @@ fn flags_help_to_string(help: List(Flag), config: Config) -> String {
let content =
to_spaced_indented_string(
[help_flag, ..help],
fn(help) { #(flag_help_to_string(help, config), help.meta.description) },
fn(help) {
let description = case config.show_flag_defaults, help.default {
True, Some(default) ->
help.meta.description <> " (default: " <> default <> ")"
_, _ -> help.meta.description
}

#(flag_help_to_string(help, config), description)
},
longest_flag_length,
config,
)
Expand Down
36 changes: 36 additions & 0 deletions test/glint/show_flag_defaults_test.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import gleam/string
import gleeunit/should
import glint.{Help}

fn cli(show_defaults: Bool) -> glint.Glint(Nil) {
let count =
glint.int_flag("count")
|> glint.flag_default(3)
|> glint.flag_help("How many times")

glint.new()
|> glint.show_flag_defaults(show_defaults)
|> glint.add(at: [], do: {
use _count <- glint.flag(count)
glint.command(fn(_, _, _) { Nil })
})
}

fn help_text(g: glint.Glint(Nil)) -> String {
let assert Ok(Help(help)) = glint.execute(g, ["--help"])
help
}

pub fn enabled_renders_default_test() {
cli(True)
|> help_text
|> string.contains("(default: 3)")
|> should.be_true
}

pub fn disabled_omits_default_test() {
cli(False)
|> help_text
|> string.contains("(default:")
|> should.be_false
}