A declarative DSL for composable CLI applications with runtime and REPL
Project description
cli-def
A declarative DSL for defining CLI structures with a unified runtime and REPL for argparse/click backends.
project page: https://cli-def.tomesoft.net
✨ Features
- Define CLI structure declaratively (TOML / Python models)
- Generate CLI implementations for:
argparse(standard library, no dependencies)click(optional dependency)
- Separation of:
- CLI definition (DSL)
- Runtime implementation (builders)
- Built-in runtime system:
- Event model (
CliEvent) - Runner
- Dispatcher
- Entrypoint resolution (
module:function) - Result propagation
- Event model (
- Interactive REPL support
- including safety eval mode
- CLI chaining (execute one CLI from another)
📦 Installation
Core (argparse only)
pip install cli-def
With click support
pip install cli-def[click]
🚀 Quick Start (Demo)
Try the interactive demo:
cli-def demo beginner
beginner profile reads in beginner.toml
Type 'help' to list commands, 'exit' to exit
demo[beginner]> echo a b c d
a b c d
-> out[0]
demo[beginner]> greet John --upper
HELLO, JOHN!
-> out[1]
Advanced demo:
cli-def demo advanced
advanced profile reads in advanced.toml
🖥 Interactive Mode (REPL)
cli-def repl --file your_cli.toml
yourcli> help
yourcli> command arg1 --option value
safety eval mode
for explanation, run builtin repl command
cli-def repl
then for example, run scan command in REPL;
cli-def> scan cli_def.script.handlers.dummy2
package_name: cli_def.script.handlers.dummy2
# defpath entrypoint desc
----------------------------------------------------------------------------
1 /cli-def/testA cli_def.script.handlers.dummy2:dummyA late binding testA
2 /cli-def/testB cli_def.script.handlers.dummy2:dummyB late binding testB
3 /cli-def/testC cli_def.script.handlers.dummy2:dummyC late binding testC
----------------------------------------------------------------------------
-> out[0]
The las message -> out[0] means the result has stored as out[0] (like Jupyter).
Then just enter > to enter safety eval mode;
cli-def> >
cli-def>eval>> len(_)
3
The prompt eval>> is stacked, so you can see in safety eval mode.
In eval mode, input string is evaluated as a formula of python syntax, _ is an alias to the last result (identical to out[0] here);
cli-def>eval>> [k for k in _]
['/cli-def/testA', '/cli-def/testB', '/cli-def/testC']
cli-def>eval>> _["/cli-def/testA"]
[{'description': 'late binding testA',
'entrypoint': 'cli_def.script.handlers2:dummyA',
'late_binding': True,
'module': 'cli_def.script.handlers2',
'name': 'dummyA',
'path': '/cli-def/testA',
'tags': []}]
cli-def>eval>>
And just return to end safety eval mode then back to REPL;
cli-def>
🔁 Run Another CLI (CLI chaining)
cli-def run example.toml -- command arg1 arg2
--separates cli-def arguments from the target CLI arguments- Remaining arguments are forwarded to the next CLI
🧩 Entrypoint
[cli.run]
entrypoint = "myapp.handlers:run"
Resolved as:
module:function
🚀 Quick Example
Define CLI (TOML)
# cli_def.toml
[cli] # is fixed root entry
key = "MyCLI"
help = "HELP of my CLI"
[cli.hello] # defines "hello" command
args = [
{key="name", mult="1", type="str"},
{key="to_upper", option="--upper", is_flat=true, default=false},
]
# one implementation option, CliRunner dispatches "hello" command to the specific entrypoint if it is specified like below.
# entrypoint = "myapp.handlers:hello"
Usecase #1 : Build CLI and Run it
The first usecase is processing entire CLI pipe line. You can easily add CLI functionaly to your apps.
from cli_def import CliDefParser
from cli_def.runtime import CliRunner, CliEvent
def main():
parser = CliDefParser()
cli_def = parser.parse_from_toml("cli_def.toml")
runner = CliRunner(cli_def, fallback_handler=my_command_handler)
result = runner.run()
return result.exit_code
# one implementation option, handling commands in fallback handler
def my_command_handler(event: CliEvent):
if event.command.defpath == "/MyCLI/hello":
name = event.params.get("name")
text = f"Hello {name}"
if event.params.get("to_upper"):
text = text.upper()
print(text)
Usecase #2 : Build CLI and go REPL
The second usecase is REPL mode.
from cli_def import CliDefParser
from cli_def.runtime import CliSession, CliEvent, cli_def_handler
def main():
parser = CliDefParser()
cli_def = parser.parse_from_toml("cli_def.toml")
session = CliSession(cli_def)
session.repl(prompt="MyCLI> ")
# you can access to the stored result of the session
# result = session.result_store.all_data()
return 0
# another implementation option, handling specific command with early binding function that is marked decorator `cli_def_handler`
@cli_def_handler("/MyCLI/hello")
def do_hello(event: CliEvent):
name = event.params.get("name")
text = f"Hello {name}"
if event.params.get("to_upper"):
text = text.upper()
print(text)
Usecase #3 : Build argparse.ArgumentParser
The third usecase is building argparse.ArgumentBuilder from CLI definition TOML instead of handwriting it. And you can easily join your existing code.
from cli_def import CliDefParser
from cli_def.backend.argparse import ArgparseBuilder
parser = CliDefParser()
cli_def = parser.parse_from_toml("cli_def.toml")
builder = ArgparseBuilder()
argparser = builder.build(cli_def)
# if you'd like to tune the argparser, you can access to specific part using defpath
# part: ArgumentParser = builder.mapping["/MyCLI/hello"]
# part.allow_abbrev = False
args = argparser.parse_args()
if args.command == "hello":
text = f"Hello {args.name}"
if args.to_upper:
text = text.upper()
print(text)
🧠 Concept
TOML / Model
↓
CliDef (AST)
↓
Builder (argparse / click)
↓
Runtime (CliEvent / Dispatcher)
↓
Executable CLI / REPL
🏗 Architecture
cli_def/
backend/
argparse/
click/
models/
ops/
parsers/
runtime/
script/
🔌 Optional Dependencies
pip install cli-def[click]
for developers;
pip install -e ".[dev]"
🧪 Testing
pytest
pytest -m "not click"
pytest -m click
📌 Roadmap
- See TODO.md
🤝 Contributing
Contributions are welcome!
📄 License
MIT License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cli_def-0.1.0.tar.gz.
File metadata
- Download URL: cli_def-0.1.0.tar.gz
- Upload date:
- Size: 37.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6469885bf2caf870e0b2bce4333ebe5b680f4aea6165a0ec90a6482927ed8363
|
|
| MD5 |
13bc6183420b34e351cc33cb91c63147
|
|
| BLAKE2b-256 |
b8065729f4617c99dd00d1264e4150c789bc4931a6e8a2e9f49bc75fdd92d1be
|
File details
Details for the file cli_def-0.1.0-py3-none-any.whl.
File metadata
- Download URL: cli_def-0.1.0-py3-none-any.whl
- Upload date:
- Size: 50.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
213d07f5ff8e0a10665ca047cdba825ee78128fa5f9e5a861b86b30b3de2da4b
|
|
| MD5 |
b4ed4fc682210c1a202a7eec8e60d459
|
|
| BLAKE2b-256 |
3cfd527d666c5cb37dac2a11a46046da32342a44f6eab06ed650578f0af24f00
|