Skip to content

Commit

Permalink
lots of new docs for the readme
Browse files Browse the repository at this point in the history
  • Loading branch information
TanklesXL committed May 9, 2024
1 parent 14782f4 commit 3e43fc6
Showing 1 changed file with 122 additions and 21 deletions.
143 changes: 122 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# glint

> [!WARNING]
> This README is being updated in preperation for glint `v1.0.0`. For documentation on the latest released glint versions please use the hexdocs link found below.
> This README is being updated in preperation for glint `v1.0.0`. For documentation on the latest released glint versions please see [hexdocs](https://hexdocs.pm/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)
Expand All @@ -20,26 +20,7 @@ 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.command`
1. assign that command any flags required
1. assign the command a custom description
1. run your cli with `glint.run`, run with a function to handle command output with `glint.run_and_handle`

## ✨ Complementary packages

Glint works amazingly with these other packages:

- [argv](https://github.com/lpil/argv), use this for cross-platform argument fetching
- [gleescript](https://github.com/lpil/gleescript), use this to generate erlang escripts for your applications

## Mini Example
### 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/test/examples/hello.gleam)

Expand All @@ -58,18 +39,30 @@ import glint
// this function returns the builder for the caps flag
fn caps_flag() -> glint.Flag(Bool) {
// create a new boolean flag with key "caps"
// this flag will be called as --caps=true (or simply --caps as glint handles boolean flags in a bit of a special manner) from the command line
glint.bool_flag("caps")
// set the flag default value to False
|> glint.flag_default(False)
// set the flag help text
|> glint.flag_help("Capitalize the hello message")
}
/// the glint command that will be executed
///
fn hello() -> glint.Command(Nil) {
// set the help text for the hello command
use <- glint.command_help("Prints Hello, <NAME>!")
// register the caps flag with the command
// the `caps` variable there is a type-safe getter for the flag value
use caps <- glint.flag(caps_flag())
// start the body of the command
// this is what will be executed when the command is called
use _, args, flags <- glint.command()
// we can assert here because the caps flag has a default
// and will therefore always have a value assigned to it
let assert Ok(caps) = caps(flags)
// this is where the business logic of our command starts
let name = case args {
[] -> "Joe"
[name,..] -> name
Expand All @@ -95,3 +88,111 @@ pub fn main() {
|> glint.run(argv.load().arguments)
}
```

What follows is glint at-a-glance,

### Glint core: `glint.Glint(a)`

`glint` is conceptually quite small, your general flow will be:

- create a new glint instance with `glint.new`.
- configure glint with functions like `glint.with_pretty_help`.
- add commands with `glint.add`.
- run your cli with `glint.run`, run with a function to handle command output with `glint.run_and_handle`.

### Glint commands: `glint.Command(a)`

_Note_: Glint commands are most easily set up by chaining functions with `use`. (See the above example)

- Create a new command with `glint.command`.
- Set the command description with `glint.command_help`.
- Add a flag to a command with `glint.flag`.
- Create a named argumend with `glint.named_arg`.
- Set the expectation for unnamed args with `glint.unnamed_args`.

### Glint flags: `glint.Flag(a)`

Glint flags are a type-safe way to provide options to your commands.

- Create a new flag with a typed flag constructor function:

- `glint.int_flag`: `glint.Flag(Int)`
- `glint.ints_flag`: `glint.Flag(List(Int))`
- `glint.float_flag`: `glint.Flag(Float)`
- `glint.floats_flag`: `glint.Flag(List(Floats))`
- `glint.string_flag`: `glint.Flag(String)`
- `glint.strings_flag`: `glint.Flag(List(String))`
- `glint.bool_flag`: `glint.Flag(Bool)`

- Set the flag description with `glint.flag_help`
- Set the flag default value with `glint.flag_default`, **note**: it is safe to use `let assert` when fetching values for flags with default values.
- Add a flag to a command with `glint.flag`.
- Add a `constraint.Constraint(a)` to a `glint.Flag(a)` with `glint.flag_constraint`

#### Glint flag constraints: `constraint.Constraint(a)`

Constraints are functions of shape `fn(a) -> Result(a, snag.Snag)` that are executed after a flag value has been successfully parsed, all constraints applied to a flag must succeed for that flag to be successfully processed.

Constraints can be any function so long as it satisfies the required type signature, and are useful for ensuring that data is correctly shaped **before** your glint commands are executed. This reduces unnecessary checks polluting the business logic of your commands.

Here is an example of a constraint that guarantees a processed integer flag will be a positive number.

_Note_ that constraints can both nicely be set up via pipes (`|>`) or with `use`.

```gleam
import glint
import snag
// ...
// with pipes
glint.int_flag("my_int")
|> glint.flag_default(0)
|> glint.constraint(fn(i){
case i < 0 {
True -> snag.error("cannot be negative")
False -> Ok(i)
}
})
// or
// with use
use i <- glint.flag_constraint(
glint.int_flag("my_int")
|> glint.flag_default(0))
case i < 0 {
True -> snag.error("cannot be negative")
False -> Ok(i)
}
```

The `glint/constraint` module provides a few helpful utilities for applying constraints, namely

- `constraint.one_of`: ensures that a value is one of some list of allowed values.
- `constraint.none_of`: ensures that a value is not one of some list of disallowed values.
- `constraint.each`: applies a constraint on individual items in a list of values (useful for applying constraints like `one_of` and `none_of` to lists.

The following example demonstrates how to constrain a `glint.Flag(List(Int))` to only allow the values 1, 2, 3 or 4 by combining `constraint.each` with `constraint.one_of`

```gleam
import glint
import glint/constraint
import snag
// ...
glint.ints_flag("my_ints")
|> glint.flag_default([])
|> glint.flag_constraint(
[1, 2, 3, 4]
|> constraint.one_of
|> constraint.each
)
```

## ✨ Complementary packages

Glint works amazingly with these other packages:

- [argv](https://github.com/lpil/argv), use this for cross-platform argument fetching
- [gleescript](https://github.com/lpil/gleescript), use this to generate erlang escripts for your applications

```
```

0 comments on commit 3e43fc6

Please sign in to comment.