Skip to content

Instantly share code, notes, and snippets.

@smores56
Created January 8, 2025 08:56
Show Gist options
  • Save smores56/dc7b37f73114df11d28cd6a148987dea to your computer and use it in GitHub Desktop.
Save smores56/dc7b37f73114df11d28cd6a148987dea to your computer and use it in GitHub Desktop.
Weaver experimental API using terse builders
module [ArgTypeSelector.*, custom, str, u64]
# Making builders pass through this type sets up the type to parse each Arg into.
ArgTypeSelector := [ArgTypeSelector ()]
custom : ArgTypeSelector, Str, ValueParser a -> ArgValueParser a
custom = |ArgTypeSelector.(), type_name, parser|
ArgValueParser.({ type_name, parser })
str : ArgTypeSelector -> ArgValueParser Str
str = |ArgTypeSelector.()|
ArgValueParser.({ type_name: "str", parser: Arg.as_str })
u64 : ArgTypeSelector -> ArgValueParser U64
u64 = |ArgTypeSelector.()|
ArgValueParser.({ type_name: "u64", parser: .as_str().try(.to_u64()) })
module [ArgValueParser.*, short, long]
# Making builders pass through this type ensures that options provide at least `short` or `long`.
ArgValueParser a := [ArgValueParser { type_name: Str, parser: (ValueParser a) }]
short : ArgValueParser a, Str -> OptionConfigParams a
short = |ArgValueParser.({ type_name, parser }), short|
OptionConfigParams.({
short,
long: "",
help: "",
type: type_name,
parser,
default: NoDefault,
})
long : ArgValueParser a, Str -> OptionConfigParams a
long = |ArgValueParser.({ type_name, parser }), short|
OptionConfigParams.({
short: "",
long,
help: "",
type: type_name,
parser,
default: NoDefault,
})
module [InvalidValue, DefaultValue, ValueParser]
InvalidValue : [InvalidNumStr, InvalidValue Str, InvalidUtf8]
DefaultValue a : [NoDefault, Value a, Generate (() -> a)]
## A parser that extracts an argument value from a string.
ValueParser a : Arg -> Result a InvalidValue
module [single, maybe]
builder_with_option_parser : OptionConfig, (List ArgValue -> Result data ArgExtractErr) -> CliBuilder data from_action to_action
builder_with_option_parser = \option, value_parser ->
arg_parser = |args|
{ values, remaining_args } = extract_option_values({ args, option })?
data = value_parser(values)?
Ok({ data, remaining_args })
Builder.from_arg_parser(arg_parser).add_option(option)
get_default_for_option = |option, default|
when default is
Value(default_value) -> Ok(default_value)
Generate(default_value_fn) -> Ok(default_value_fn())
NoDefault -> Err(MissingOption(option))
single : (ArgTypeSelector -> OptionConfigParams a) -> CliBuilder a GetOptionsAction GetOptionsAction
single = |params_builder|
OptionConfigParams.(params) = params_builder(ArgTypeSelector.())
option = {
expected_value: ExpectsValue(params.type),
plurality: One,
short: params.short,
long: params.long,
help: params.help,
}
value_parser = |values|
value = get_maybe_value(values, option)?
when value is
Ok(Ok(val)) ->
parser(val).map_err(|err| InvalidOptionValue(err, option))
Ok(Err(NoValue)) -> Err(NoValueProvidedForOption(option))
Err(NoValue) -> get_default_for_option(option, default)
builder_with_option_parser(option, value_parser)
# Default values can't be used with `maybe`, we could prevent them from being passed with
# a phantom type variable on `OptionConfigParams`.
maybe : (ArgTypeSelector -> OptionConfigParams a) -> CliBuilder (Result data [NoValue]) GetOptionsAction GetOptionsAction
maybe = |params_builder|
OptionConfigParams.(params) = params_builder(ArgTypeSelector.())
option = {
expected_value: ExpectsValue(params.type),
plurality: Optional,
short: params.short,
long: params.long,
help: params.help,
}
value_parser = |values|
value = get_maybe_value(values, option)?
when value is
Ok(Ok(val)) ->
parser(val).map(Ok).map_err(|err| InvalidOptionValue(err, option))
Ok(Err(NoValue)) -> Err(NoValueProvidedForOption(option))
Err(NoValue) -> Ok(Err(NoValue))
builder_with_option_parser(option, value_parser)
module [
OptionConfigParams.*,
short,
long,
help,
no_default,
default,
default_fn,
]
OptionConfigParams a := [OptionConfigParams {
short : Str,
long : Str,
help : Str,
type : Str,
parser : ValueParser a,
default : DefaultValue a,
}]
short : OptionConfigParams a, Str -> OptionConfigParams a
short = |OptionConfigParams.(params), short|
OptionConfigParams.({ short, ..params })
long : OptionConfigParams a, Str -> OptionConfigParams a
long = |OptionConfigParams.(params), long|
OptionConfigParams.({ long, ..params })
help : OptionConfigParams a, Str -> OptionConfigParams a
help = |OptionConfigParams.(params), help|
OptionConfigParams.({ help, ..params })
no_default : OptionConfigParams a -> OptionConfigParams a
no_default = |OptionConfigParams.(params)|
OptionConfigParams.({ default: NoDefault, ..params })
default : OptionConfigParams a, a -> OptionConfigParams a
default = |OptionConfigParams.(params), default|
OptionConfigParams.({ default: Value(default), ..params })
default_fn : OptionConfigParams a, (() -> a) -> OptionConfigParams a
default_fn = |OptionConfigParams.(params), default_fn|
OptionConfigParams.({ default: Calculate(default_fn), ..params })
app [main!] { cli: platform "<basic-cli>" }
import weaver.Opt
import weaver.Cli
import cli.Stdout
main! = |args|
data =
cli_parser.parse_or_display_message(args)
.on_err!(|message|
Stdout.line!(message)?
Err(Exit(1, "")))
)?
Stdout.line!(
"""
Successfully parsed! Here's what I got:
${data.to_str()}
"""
)?
Ok({})
cli_parser =
{ Cli.weave <-
force: Opt.flag(.short("f").help("Force the task to complete.")),
alpha: Opt.single(
.u64()
.short("a")
.help("Set the alpha level.")
.default_fn(|| 1.left_shift_by(7)),
),
files: Param.list(.str().name("files").help("The rest of the files.")),
}
.finish({
name: "basic",
version: "v0.0.1",
authors: ["Some One <[email protected]>"],
description: "This is a basic example of what you can build with Weaver",
})
.assert_valid()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment