Skip to content
Closed
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
38 changes: 34 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ https://github.com/Textualize/trogon/assets/554369/c9e5dabb-5624-45cb-8612-f6ecf
</details>


Trogon works with the popular [Click](https://click.palletsprojects.com/) library for Python, but will support other libraries and languages in the future.
Trogon works with the popular Python libraries [Argparse](https://docs.python.org/3/library/argparse.html), [Click](https://click.palletsprojects.com/), [Typer](https://github.com/tiangolo/typer), [Yapx](https://www.f2dv.com/code/r/yapx/i/), and [myke](https://www.f2dv.com/code/r/myke/i/), and will support other libraries and languages in the future. You can also manually build your own TUI schema and use it however you like, even in conjunction with `sys.argv`. See the `examples/` directory for examples of each.

## How it works

Expand Down Expand Up @@ -90,7 +90,9 @@ pip install trogon

## Quickstart

1. Import `from trogon import tui`
### Click

1. Import `from trogon.click import tui`
2. Add the `@tui` decorator above your click app. e.g.
```python
@tui()
Expand All @@ -100,13 +102,30 @@ pip install trogon
```
3. Your click app will have a new `tui` command available.

See also the `examples` folder for two example apps.
### Argparse

1. Import `from trogon.argparse import add_tui_argument`
or, `from trogon.argparse import add_tui_command`
2. Add the TUI argument/command to your argparse parser. e.g.
```python
parser = argparse.ArgumentParser()

# add tui argument (my-cli --tui)
add_tui_argument(parser)
# and/or, add tui command (my-cli tui)
add_tui_command(parser)
```
3. Your argparse parser will have a new parameter `--tui` and/or a new command `tui`.

See also the `examples` folder for example apps.

## Custom command name and custom help

By default the command added will be called `tui` and the help text for it will be `Open Textual TUI.`

You can customize one or both of these using the `help=` and `command=` parameters:
You can customize one or both of these using the `help=` and `command=` parameters.

### Click

```python
@tui(command="ui", help="Open terminal UI")
Expand All @@ -115,6 +134,17 @@ def cli():
...
```

### Argparse

```python
parser = argparse.ArgumentParser()

# add tui argument (my-cli --tui)
add_tui_argument(parser, option_strings=["--ui"], help="Open terminal UI")
# and/or, add tui command (my-cli tui)
add_tui_command(parser, command="ui", help="Open terminal UI")
```

## Follow this project

If this app interests you, you may want to join the Textual [Discord server](https://discord.gg/Enf6Z3qhVr) where you can talk to Textual developers / community.
35 changes: 35 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Trogon Example TUI Apps

To run the example TUI apps, first clone this repo and install the requirements:

```sh
git clone https://github.com/Textualize/trogon.git

cd trogon/examples

pip install -r requirements.txt
```

```sh
./demo_argv.py tui
```

```sh
./demo_click.py tui
```

```sh
./demo_click_nogroup.py tui
```

```sh
./demo_typer.py tui
```

```sh
./demo_yapx.py --tui
```

```sh
myke --tui --myke-file ./demo_myke.py
```
123 changes: 123 additions & 0 deletions examples/demo_argparse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#!/usr/bin/env python3

import sys
import argparse

from trogon.argparse import add_tui_argument, add_tui_command

from getpass import getpass
from pprint import pprint


def _print_args(command: str, **kwargs):
print("---")
print("*** Command:", command)
print("*** kwargs:")
pprint(kwargs)


def root(**kwargs):
_print_args("root", **kwargs)


def add(**kwargs):
_print_args("add", **kwargs)


def remove(**kwargs):
_print_args("remove", **kwargs)


def auth(password: str, **kwargs):
if not password:
password = getpass("Password: ")
_print_args("auth", password=password, **kwargs)


def list_tasks(**kwargs):
_print_args("list_tasks", **kwargs)


def cant_see_me(**kwargs):
_print_args("cant_see_me", **kwargs)


if __name__ == "__main__":
parser = argparse.ArgumentParser()

parser.add_argument(
"--verbose", "-v", action="count", default=0, help="Increase verbosity level."
)
parser.add_argument("--hidden-arg", help=argparse.SUPPRESS)
parser.set_defaults(_func=root)

subparsers = parser.add_subparsers()

sp_add = subparsers.add_parser("add")
sp_add.set_defaults(_func=add)
sp_add.add_argument("task")
sp_add.add_argument(
"--priority", "-p", default=1, help="Set task priority (default: 1)"
)
sp_add.add_argument(
"--tags", "-t", action="append", help="Add tags to the task (repeatable)"
)
sp_add.add_argument(
"--extra",
"-e",
nargs=2,
type=str,
default=[("one", "1"), ("two", "2")],
action="append",
)
sp_add.add_argument(
"--category", "-c", default="home", choices=["work", "home", "leisure"]
)
sp_add.add_argument(
"--labels",
"-l",
action="append",
choices=["important", "urgent", "later"],
default=["urgent"],
help="Add labels to the task (repeatable)",
)

sp_remove = subparsers.add_parser("remove")
sp_remove.set_defaults(_func=remove)
sp_remove.add_argument("task_id", type=int)

sp_auth = subparsers.add_parser("auth")
sp_auth.set_defaults(_func=auth)
sp_auth.add_argument("--user", help="User Name")
sp_auth.add_argument("--password", help="User Password. <secret, prompt>")
sp_auth.add_argument("--tokens", action="append", help="Sensitive input. <secret>")

sp_list_tasks = subparsers.add_parser("list-tasks")
sp_list_tasks.set_defaults(_func=list_tasks)
if sys.version_info >= (3, 9):
sp_list_tasks.add_argument("--all", default=True, action=argparse.BooleanOptionalAction)
sp_list_tasks.add_argument("--completed", "-c", action="store_true")

sp_cant_see_me = subparsers.add_parser("cant-see-me", description=argparse.SUPPRESS)
sp_cant_see_me.set_defaults(_func=cant_see_me)
sp_cant_see_me.add_argument("--user")

# add tui argument (my-cli --tui)
add_tui_argument(parser)
# and/or, add tui command (my-cli tui)
add_tui_command(parser)

args = sys.argv[1:]

if not args:
# if no args given, print help and exit.
parser.print_help()
parser.exit()

# parse args
parsed_args = parser.parse_args(args)

# call matching function with parsed args
parsed_args._func(
**{k: v for k, v in vars(parsed_args).items() if not k.startswith("_")}
)
58 changes: 58 additions & 0 deletions examples/demo_argv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env python3

from __future__ import annotations

import sys

from trogon import Trogon
from trogon.schemas import ArgumentSchema, CommandName, CommandSchema, OptionSchema
from trogon.constants import DEFAULT_COMMAND_NAME

root_schema: CommandSchema = CommandSchema(
name=CommandName("hello"),
docstring="just sayin'",
options=[
OptionSchema(
name=["--name"],
default="world",
),
OptionSchema(
name=["-u", "--to-upper"],
type=bool,
is_flag=True,
),
OptionSchema(name=["-t", "--test"], type=int, choices=[1, 2, 3]),
OptionSchema(
name=["-s", "--subjects"], type=str, multiple=True, multi_value=True
),
],
arguments=[
ArgumentSchema(name=["subjects"], type=str, multiple=True, multi_value=True),
],
)

subcmd_1: CommandSchema = CommandSchema(
name=CommandName("wat"),
arguments=[
ArgumentSchema(name="anything", help="wat!"),
],
options=[
OptionSchema(
name=["--name"],
default="world",
),
OptionSchema(
name=["--to-upper"],
type=bool,
is_flag=True,
),
],
)

tui: Trogon = Trogon.from_schemas(root_schema, subcmd_1, app_name=None)

if __name__ == "__main__":
if DEFAULT_COMMAND_NAME in sys.argv:
tui.run()
else:
print(sys.argv)
39 changes: 31 additions & 8 deletions examples/demo.py → examples/demo_click.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import click
#!/usr/bin/env python3

from trogon import tui
import click
from trogon.click import tui


@tui()
@click.group()
@click.option(
"--verbose", "-v", count=True, default=1, help="Increase verbosity level."
)
@click.option(
"--hidden-arg", type=int, default=1, hidden=True, help="Set task priority (default: 1)"
)
@click.pass_context
def cli(ctx, verbose):
def cli(ctx, verbose, hidden_arg):
ctx.ensure_object(dict)
ctx.obj["verbose"] = verbose

Expand All @@ -24,9 +28,9 @@ def cli(ctx, verbose):
"--extra",
"-e",
nargs=2,
type=(str, int),
type=(str, click.Choice(["1", "2", "3"])),
multiple=True,
default=[("one", 1), ("two", 2)],
default=[("one", "1"), ("two", "2")],
help="Add extra data as key-value pairs (repeatable)",
)
@click.option(
Expand All @@ -45,7 +49,7 @@ def cli(ctx, verbose):
help="Add labels to the task (repeatable)",
)
@click.pass_context
def add(ctx, task, priority, tags, extra):
def add(ctx, task, extra, priority, category, tags, labels):
"""Add a new task to the to-do list.
Note:
Control the output of this using the verbosity option.
Expand All @@ -54,7 +58,6 @@ def add(ctx, task, priority, tags, extra):
click.echo(f"Adding task: {task}")
click.echo(f"Priority: {priority}")
click.echo(f'Tags: {", ".join(tags)}')
click.echo(f"Extra data: {extra}")
elif ctx.obj["verbose"] >= 1:
click.echo(f"Adding task: {task}")
else:
Expand All @@ -81,9 +84,29 @@ def remove(ctx, task_id):
def list_tasks(ctx, all, completed):
"""List tasks from the to-do list."""
if ctx.obj["verbose"] >= 1:
click.echo(f"Listing tasks:")
click.echo("Listing tasks:")
# Implement the task listing functionality here

@cli.command()
@click.option("--user", help="User Name")
@click.option("--password", prompt=True, prompt_required=True, hide_input=True, help="Required prompt.")
@click.option("--tokens", multiple=True, hide_input=True, help="Sensitive input.")
@click.pass_context
def auth(
ctx,
user,
password,
tokens,
):
print('---')
print('User:', user)
print('Password:', password)
print('Tokens:', list(tokens))

@cli.command(hidden=True)
@click.option("--user", help="User Name")
def cant_see_me():
pass

if __name__ == "__main__":
cli(obj={})
8 changes: 5 additions & 3 deletions examples/nogroup_demo.py → examples/demo_click_nogroup.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import click
#!/usr/bin/env python3

from trogon import tui
import click
from trogon.click import tui


@tui()
Expand All @@ -13,8 +14,9 @@
"--extra",
"-e",
nargs=2,
type=(str, int),
type=(str, click.Choice(["1", "2", "3"])),
multiple=True,
default=[("one", "1"), ("two", "2")],
help="Add extra data as key-value pairs (repeatable)",
)
@click.option(
Expand Down
Loading