Skip to content

Commit

Permalink
Switch to using CLI context (#43)
Browse files Browse the repository at this point in the history
* refactor to use context

* refactor to use context

* readme updated

* refactor command parsing

* refactor command parsing

* update changelog

* fix tests
  • Loading branch information
thatstoasty authored Oct 7, 2024
1 parent d178090 commit f7289f2
Show file tree
Hide file tree
Showing 22 changed files with 2,123 additions and 412 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased] - yyyy-mm-dd

## [0.1.6] - 2024-10-06

- Switch to using a cli context as the command argument.
- Refactor code to reduce copies of commands instead using `Arc[Command]` generally.
- Add example projects.

## [0.1.4] - 2024-10-05

- Refactor code to split out functionality into separate files and classes.
Expand Down
92 changes: 47 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,43 +21,47 @@ Here's an example of a basic command and subcommand!

```mojo
from memory import Arc
from prism import Command
from prism import Command, Context
fn test(inout command: Arc[Command], args: List[String]) -> None:
fn test(context: Context) -> None:
print("Pass chromeria as a subcommand!")
fn hello(inout command: Arc[Command], args: List[String]) -> None:
fn hello(context: Context) -> None:
print("Hello from Chromeria!")
fn main() -> None:
var root = Arc(
Command(
name="hello",
description="This is a dummy command!",
run=test,
)
var root = Command(
name="hello",
description="This is a dummy command!",
run=test,
)
var hello_command = Arc(Command(name="chromeria", description="This is a dummy command!", run=hello))
root[].add_subcommand(hello_command)
root[].execute()
root.add_subcommand(hello_command)
root.execute()
```

![Chromeria](https://github.com/thatstoasty/prism/blob/main/doc/tapes/hello-chromeria.gif)

### Why are subcommands wrapped with `Arc`?

Due to the nature of self-referential structs, we need to use a smart pointer to reference the subcommand. The child command is owned by the `Arc` pointer, and that pointer is then shared across the program execution.

This will be changed to `Box` in the upcoming release.

### Command Flags

Commands can have typed flags added to them to enable different behaviors.

```mojo
var root = Arc(Command(
var root = Command(
name="logger", description="Base command.", run=handler
))
root[].flags.add_string_flag(name="type", shorthand="t", usage="Formatting type: [json, custom]")
)
root.flags.add_string_flag(name="type", shorthand="t", usage="Formatting type: [json, custom]")
```

![Logging](https://github.com/thatstoasty/prism/blob/main/doc/tapes/logging.gif)
Expand All @@ -79,24 +83,24 @@ var print_tool = Arc(Command(
Commands can be configured to run pre-hook and post-hook functions before and after the command's main run function.

```mojo
fn pre_hook(inout command: Arc[Command], args: List[String]) -> None:
fn pre_hook(context: Context) -> None:
print("Pre-hook executed!")
return None
fn post_hook(inout command: Arc[Command], args: List[String]) -> None:
fn post_hook(context: Context) -> None:
print("Post-hook executed!")
return None
fn init() -> None:
var root = Arc(Command(
fn main() -> None:
var root = Command(
name="printer",
description="Base command.",
run=printer,
pre_run=pre_hook,
post_run=post_hook,
))
)
```

![Printer](https://github.com/thatstoasty/prism/blob/main/doc/tapes/printer.gif)
Expand All @@ -106,8 +110,8 @@ fn init() -> None:
Flags and hooks can also be inherited by children commands! This can be useful for setting global flags or hooks that should be applied to all child commands.

```mojo
fn init() -> None:
var root = Arc(Command(name="nested", description="Base command.", run=base))
fn main() -> None:
var root = Command(name="nested", description="Base command.", run=base)
var get_command = Arc(Command(
name="get",
Expand Down Expand Up @@ -138,13 +142,13 @@ var print_tool = Arc(Command(
Same for persistent flags:

```mojo
var root = Arc(Command(
var root = Command(
name="my",
description="This is a dummy command!",
run=test,
))
root[].persistent_flags.add_bool_flag(name="free", shorthand="f", usage="Always required.")
root[].mark_persistent_flag_required("free")
)
root.persistent_flags.add_bool_flag(name="free", shorthand="f", usage="Always required.")
root.mark_persistent_flag_required("free")
```

### Flag Groups
Expand Down Expand Up @@ -185,10 +189,10 @@ If you want to require at least one flag from a group to be present, you can use

In these cases:

- both local and persistent flags can be used
- NOTE: the group is only enforced on commands where every flag is defined
- a flag may appear in multiple groups
- a group may contain any number of flags
- Both local and persistent flags can be used.
- NOTE: the group is only enforced on commands where every flag is defined.
- A flag may appear in multiple groups.
- A group may contain any number of flags.

![Flag Groups](https://github.com/thatstoasty/prism/blob/main/doc/tapes/flag_groups.gif)

Expand All @@ -197,17 +201,17 @@ In these cases:
See `examples/flag_groups/child.mojo` for an example.

```mojo
fn init() -> None:
var root = Arc(Command(
fn main() -> None:
var root = Command(
name="my",
description="This is a dummy command!",
run=test,
))
)
# Persistent flags are defined on the parent command.
root[].persistent_flags.add_bool_flag(name="required", shorthand="r", usage="Always required.")
root[].persistent_flags.add_string_flag(name="host", shorthand="h", usage="Host")
root[].persistent_flags.add_string_flag(name="port", shorthand="p", usage="Port")
root[].mark_persistent_flag_required("required")
root.persistent_flags.add_bool_flag(name="required", shorthand="r", usage="Always required.")
root.persistent_flags.add_string_flag(name="host", shorthand="h", usage="Host")
root.persistent_flags.add_string_flag(name="port", shorthand="p", usage="Port")
root.mark_persistent_flag_required("required")
var print_tool = Arc(Command(
name="tool", description="This is a dummy command!", run=tool_func
Expand All @@ -216,7 +220,7 @@ fn init() -> None:
print_tool[].flags.add_string_flag(name="uri", shorthand="u", usage="URI")
# Child commands are added to the parent command.
root[].add_subcommand(print_tool)
root.add_subcommand(print_tool)
# Rules are set on the child command, which can include persistent flags inherited from the parent command.
# When executing `mark_flags_required_together()` or `mark_flags_mutually_exclusive()`,
Expand All @@ -225,7 +229,7 @@ fn init() -> None:
print_tool[].mark_flags_required_together("host", "port")
print_tool[].mark_flags_mutually_exclusive("host", "uri")
root[].execute()
root.execute()
```

![Flag Groups 2](https://github.com/thatstoasty/prism/blob/main/doc/tapes/flag_groups-2.gif)
Expand Down Expand Up @@ -255,7 +259,7 @@ fn test_match_all():
var result = match_all[
List[ArgValidator](
range_args[0, 1](),
valid_args[List[String]("Pineapple")]()
valid_args()
)
]()(List[String]("abc", "123"))
testing.assert_equal(result.value()[], "Command accepts between 0 to 1 argument(s). Received: 2.")
Expand All @@ -271,12 +275,12 @@ Commands are configured to accept a `--help` flag by default. This will print th
fn help_func(inout command: Arc[Command]) -> String:
return ""
fn init() -> None:
var root = Arc(Command(
fn main() -> None:
var root = Command(
name="hello",
description="This is a dummy command!",
run=test,
))
)
var hello_command = Arc(Command(name="chromeria", description="This is a dummy command!", run=hello, help=help_func))
```
Expand All @@ -301,9 +305,7 @@ fn init() -> None:
### Improvements

- Tree traversal improvements.
- Once we have `Result[T]`, I will refactor raising functions to return results instead.
- Arc[Command] being passed to validators and command functions is marked as inout because the compiler complains about forming a reference to a borrowed register value. This is a temporary fix, I will try to get it back to a borrowed reference.
- For now, help functions and arg validators will need to be set after the command is constructed. This is to help reduce cyclical dependencies, but I will work on a way to set these values in the constructor as the type system matures.

### Bugs

- `Command` has 2 almost indentical init functions because setting a default `arg_validator` value, breaks the compiler as of 24.2.
20 changes: 9 additions & 11 deletions examples/aliases.mojo
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
from memory import Arc
from prism import Command, CommandArc
from prism import Context, Command


fn test(inout command: Arc[Command], args: List[String]) -> None:
fn test(context: Context) -> None:
print("Pass tool, object, or thing as a subcommand!")


fn tool_func(inout command: Arc[Command], args: List[String]) -> None:
fn tool_func(context: Context) -> None:
print("My tool!")


fn main() -> None:
var root = Arc(
Command(
name="my",
description="This is a dummy command!",
run=test,
)
var root = Command(
name="my",
description="This is a dummy command!",
run=test,
)

var print_tool = Arc(
Expand All @@ -25,5 +23,5 @@ fn main() -> None:
)
)

root[].add_subcommand(print_tool)
root[].execute()
root.add_subcommand(print_tool)
root.execute()
84 changes: 44 additions & 40 deletions examples/arg_validators.mojo
Original file line number Diff line number Diff line change
@@ -1,57 +1,61 @@
from memory import Arc
from prism import Command, CommandArc, no_args, valid_args, minimum_n_args, maximum_n_args, exact_args, range_args


fn test(inout command: Arc[Command], args: List[String]) -> None:
for arg in args:
from prism import (
Command,
Context,
CommandArc,
no_args,
valid_args,
minimum_n_args,
maximum_n_args,
exact_args,
range_args,
)


fn test(context: Context) -> None:
for arg in context.args:
print("Received", arg[])


fn hello(inout command: Arc[Command], args: List[String]) -> None:
print(command[].name, "Hello from Chromeria!")
fn hello(context: Context) -> None:
print(context.command[].name, "Hello from Chromeria!")


fn main() -> None:
var root = Arc(
Command(
name="hello",
description="This is a dummy command!",
run=test,
)
var root = Command(
name="hello",
description="This is a dummy command!",
run=test,
)

var no_args_command = Arc(
Command(name="no_args", description="This is a dummy command!", run=hello, arg_validator=no_args)
)
var no_args_command = Arc(Command(name="no_args", description="This is a dummy command!", run=hello))
no_args_command[].arg_validator = no_args

var valid_args_command = Arc(
Command(
name="valid_args",
description="This is a dummy command!",
run=hello,
arg_validator=valid_args(),
)
)
var minimum_n_args_command = Arc(
Command(
name="minimum_n_args", description="This is a dummy command!", run=hello, arg_validator=minimum_n_args[4]()
)
)
var maximum_n_args_command = Arc(
Command(
name="maximum_n_args", description="This is a dummy command!", run=hello, arg_validator=maximum_n_args[1]()
)
)
var exact_args_command = Arc(
Command(name="exact_args", description="This is a dummy command!", run=hello, arg_validator=exact_args[1]())
)
var range_args_command = Arc(
Command(name="range_args", description="This is a dummy command!", run=hello, arg_validator=range_args[0, 1]())
)
valid_args_command[].arg_validator = valid_args

var minimum_n_args_command = Arc(Command(name="minimum_n_args", description="This is a dummy command!", run=hello))
minimum_n_args_command[].arg_validator = minimum_n_args[4]()

var maximum_n_args_command = Arc(Command(name="maximum_n_args", description="This is a dummy command!", run=hello))
maximum_n_args_command[].arg_validator = maximum_n_args[1]()

var exact_args_command = Arc(Command(name="exact_args", description="This is a dummy command!", run=hello))
exact_args_command[].arg_validator = exact_args[1]()

var range_args_command = Arc(Command(name="range_args", description="This is a dummy command!", run=hello))
range_args_command[].arg_validator = range_args[0, 1]()

root[].add_subcommand(no_args_command)
root[].add_subcommand(valid_args_command)
root[].add_subcommand(minimum_n_args_command)
root[].add_subcommand(maximum_n_args_command)
root[].add_subcommand(exact_args_command)
root[].add_subcommand(range_args_command)
root[].execute()
root.add_subcommand(no_args_command)
root.add_subcommand(valid_args_command)
root.add_subcommand(minimum_n_args_command)
root.add_subcommand(maximum_n_args_command)
root.add_subcommand(exact_args_command)
root.add_subcommand(range_args_command)
root.execute()
Loading

0 comments on commit f7289f2

Please sign in to comment.