Skip to main content

Vulcano

Project description

PyPI version Code style: black Build Status codecov readthedocs Downloads

Vulcano is a Python framework for building interactive command-line applications with minimal boilerplate.

Support the project on Patreon

What is Vulcano?

Built on top of prompt_toolkit, Vulcano turns plain Python functions into fully featured CLI commands โ€” complete with autocompletion, inline help, syntax highlighting, and command history โ€” with no extra configuration required.

Its simplicity makes it suitable for a wide range of scenarios where you need to expose existing functions through a REPL or a one-shot argument interface.

$ python your_app.py help

                           ๐Ÿ“–  Available Commands
โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ Command           โ”‚ Description                                          โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ reverse_word      โ”‚ Return the word reversed.                            โ”‚
โ”‚ random_upper_word โ”‚ Return the word with randomly capitalised letters.   โ”‚
โ”‚ multiply          โ”‚ Multiply two numbers.                                โ”‚
โ”‚ bye               โ”‚ Say goodbye to someone.                              โ”‚
โ”‚ help              โ”‚ Print global help or details for a specific command. โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

Key Features

  • Autocomplete โ€” Vulcano inspects every registered function and automatically builds a completion list that includes command names and their arguments.

  • Inline help โ€” Help text is derived from docstrings or from the description provided at registration time.

  • History โ€” Use the up and down arrow keys to navigate through previous commands.

  • Module registration โ€” Register all public functions from an existing module without modifying its source.

  • Syntax highlighting โ€” Powered by Pygments and prompt_toolkit for a polished REPL experience.

  • Argument value options โ€” Attach a list of predefined choices to any argument with arg_opts; the autocompleter offers them and quotes values that contain spaces automatically:

    @app.command("greet", "Greet by role", arg_opts={"role": ["admin", "user", "guest"]})
    def greet(name, role="user"):
        return "Hello, {} {}!".format(role.capitalize(), name)
  • Concatenated commands โ€” Chain multiple commands with and, both in argument mode and in the interactive REPL:

    python your_script.py my_func arg="something" and my_func_2 arg="another thing"
  • Context a plain dictionary available to all registered functions.

  • Command templating โ€” Use any value stored in the context to parameterise commands at runtime.

  • Autosuggestion โ€” When an unknown command is entered, Vulcano suggests the closest match:

    ๐ŸŒ‹   niu
    ๐Ÿค”  Command 'niu' not found
    ๐Ÿ’ก  Did you mean: "new"?
    ๐ŸŒ‹
  • Command groups โ€” Organise commands into named sub-contexts with app.group(). Typing the group name in the REPL enters an isolated sub-session; exit returns to the parent. Commands in any group can also be run directly with dot-path syntax โ€” both in REPL and argument modes โ€” without entering the sub-session at all:

    ๐ŸŒ‹   text.hi name=Alice
    Hi! Mr. Alice :) Glad to see you.
    ๐ŸŒ‹

    Groups can be nested to any depth. The prompt chains all ancestor names so the current nesting level is always visible:

    ๐ŸŒ‹   text
    ๐ŸŒ‹  text > formal
    ๐ŸŒ‹  text > formal > dear name=Alice
    Dear Dr. Alice, I trust this finds you well.
    ๐ŸŒ‹  text > formal > exit
    ๐ŸŒ‹  text > exit
    ๐ŸŒ‹
  • Source inspection โ€” Append ? to any command name to view its source code with syntax highlighting. Dot-path commands are supported too:

    ๐ŸŒ‹   bye?
    @app.command
    def bye(name="User"):
        """ Say goodbye to someone """
        return "Bye {}!".format(name)
    ๐ŸŒ‹

Installation

Install the latest release from PyPI:

pip install vulcano

To install a development version directly from the repository:

git clone https://github.com/dgarana/vulcano.git
cd vulcano
pip install -e .

Getting Started

The repository includes a complete example in examples/simple_example.py. The snippet below covers the most common features:

import random
from vulcano.app import VulcanoApp
from vulcano.themes import MonokaiTheme


app = VulcanoApp()


@app.command("hi", "Greet someone by name")
def salute_method_here(name, title="Mr."):
    """Greet a person.

    Args:
        name (str): Name of the person to greet.
        title (str): Honorific title.
    """
    print("Hi! {} {} โ€” glad to see you.".format(title, name))


def has_context_name():
    """Return True only when a name has been set in the context."""
    return "name" in app.context


@app.command
def i_am(name):
    """Store your name in the context.

    Args:
        name (str): Your name.
    """
    app.context["name"] = name


@app.command(show_if=has_context_name)
def whoami():
    """Return your name from the context.

    Only shown after ``i_am`` has been called.
    """
    return app.context["name"]


@app.command
def bye(name="User"):
    """Say goodbye to someone."""
    return "Bye {}!".format(name)


@app.command
def sum_numbers(*args):
    """Return the sum of all provided numbers."""
    return sum(args)


@app.command
def multiply(number1, number2):
    """Multiply two numbers."""
    return number1 * number2


@app.command
def reverse_word(word):
    """Return the word reversed."""
    return word[::-1]


@app.command
def random_upper_word(word):
    """Return the word with randomly capitalised letters."""
    return "".join(random.choice([letter.upper(), letter]) for letter in word)


@app.command("greet", "Greet someone by role", arg_opts={"role": ["admin", "user", "guest"]})
def greet_by_role(name, role="user"):
    """Greet someone and mention their role.

    Args:
        name (str): Name of the person to greet.
        role (str): Role of the person.
    """
    return "Hello, {} {}!".format(role.capitalize(), name)


if __name__ == "__main__":
    app.run(theme=MonokaiTheme)

Command Groups

Groups let you organise commands into named sub-contexts, each with its own isolated sub-REPL. Create a group with app.group() and register commands on it the same way you would on the main app:

from vulcano.app import VulcanoApp

app = VulcanoApp()

# Create a group โ€” its name is what the user types to enter it.
text = app.group("text", "Text-related commands")


@text.command("hi", "Greet someone")
def say_hi(name, title="Mr."):
    print("Hi! {} {}!".format(title, name))


@text.command("greet", "Greet by role", arg_opts={"role": ["admin", "user"]})
def greet_by_role(name, role="user"):
    return "Hello, {} {}!".format(role.capitalize(), name)


if __name__ == "__main__":
    app.run()

Typing the group name in the REPL enters the sub-session, where only that groupโ€™s commands โ€” plus a local help and exit โ€” are available. The prompt reflects the current depth:

๐ŸŒ‹   text
๐ŸŒ‹  text > hi name=Alice
Hi! Mr. Alice!
๐ŸŒ‹  text > exit
๐ŸŒ‹

Nested groups โ€” Groups can contain other groups to any depth:

formal = text.group("formal", "Formal greetings")


@formal.command("dear", "Send a formal greeting")
def formal_dear(name, title="Dr."):
    return "Dear {} {}, I trust this finds you well.".format(title, name)

The prompt chains all ancestor names:

๐ŸŒ‹   text
๐ŸŒ‹  text > formal
๐ŸŒ‹  text > formal > dear name=Alice title=Prof.
Dear Prof. Alice, I trust this finds you well.
๐ŸŒ‹  text > formal > exit
๐ŸŒ‹  text > exit
๐ŸŒ‹

Dot-path syntax โ€” Run any group command directly without entering the sub-session, using group.command notation. This works in both REPL and argument modes and can cross multiple nesting levels:

๐ŸŒ‹   text.hi name=Alice
Hi! Mr. Alice!
๐ŸŒ‹   text.formal.dear name=Alice title="Prof."
Dear Prof. Alice, I trust this finds you well.
$ python your_app.py text.hi name=Alice
Hi! Mr. Alice!
$ python your_app.py text.formal.dear name=Alice and bye
Dear Dr. Alice, I trust this finds you well.
Bye User!

Autocomplete is dot-path aware: typing text. offers text.hi, text.greet, text.formal; typing text.formal. narrows to text.formal.dear. Argument and arg_opts completions work the same as for top-level commands once the full path is typed.

Themes

Vulcano ships five built-in themes, all importable from vulcano.themes:

Theme

Description

MonokaiTheme

Classic Monokai (default).

DraculaTheme

Dracula โ€” pink keywords, purple numbers, yellow strings.

NordTheme

Nord โ€” muted blues and greens on a dark background.

SolarizedDarkTheme | Solarized Dark โ€” blue keywords, cyan
strings, magenta numbers.

OneDarkTheme

Atom One Dark โ€” purple keywords, green strings, orange numbers.

Pass any theme to app.run():

from vulcano.app import VulcanoApp
from vulcano.themes import DraculaTheme

app.run(theme=DraculaTheme)

To create a custom theme, subclass VulcanoStyle and define a styles dict using standard Pygments token types:

from pygments.token import Keyword, Name, Number, String, Text
from vulcano.themes import VulcanoStyle

class MyTheme(VulcanoStyle):
    styles = {
        Text:    "#ffffff",
        Keyword: "bold #ff0000",
        Name:    "#00ff00",
        Number:  "#0000ff",
        String:  "#ffff00",
    }

app.run(theme=MyTheme)

The snippet above registers the following commands: i_am, whoami, bye, sum_numbers, multiply, reverse_word, random_upper_word, and greet. See Command Groups below to learn how to organise commands into named sub-contexts.

Commands that return a value have their result printed automatically and stored in context["last_result"], making it available for subsequent commands via templating.

REPL mode โ€” launch with no arguments to start the interactive shell:

$ python simple_example.py
๐ŸŒ‹   i_am name=Alice
๐ŸŒ‹   whoami
Alice
๐ŸŒ‹   reverse_word word=vulcano
onacluv
๐ŸŒ‹   multiply number1=6 number2=7
42
๐ŸŒ‹   bye
Bye User!
๐ŸŒ‹   help command=reverse_word
โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โš™๏ธ  reverse_word โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ Return the word reversed.                                            โ”‚
โ”‚                                                                      โ”‚
โ”‚   Argument   Type   Default   Description                            โ”‚
โ”‚  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€  โ”‚
โ”‚   โšก word    str              The word to reverse.                   โ”‚
โ”‚                                                                      โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ
๐ŸŒ‹   greet name=Alice role=admin
Hello, Admin Alice!
๐ŸŒ‹   multiply number1=6 number2=7 and reverse_word word=vulcano
42
onacluv
๐ŸŒ‹   exit
๐Ÿ‘‹  See you soon!

Argument mode โ€” pass commands directly; chain with and:

$ python simple_example.py multiply number1=6 number2=7 and reverse_word word=vulcano
42
onacluv
$ python simple_example.py reverse_word "Hello Baby! This is awesome" and random_upper_word "{last_result}"
emosewa si sihT !ybaB olleH
eMOsEwa SI SIHT !YbaB OLlEH

Development

The project ships a Makefile that wraps all common development tasks. Run make help (or just make) to see the full list of targets:

make fmt        # Format code with black and isort
make black      # Format with black only
make isort      # Sort imports with isort only
make lint       # Check style with flake8
make flake8     # Run flake8 only
make security   # Scan with bandit
make bandit     # Run bandit only
make test       # Run the test suite
make check      # All checks without modifying files (CI-friendly)
make all        # Format, check, and test in one step

Contributing

Contributions of all kinds are welcome โ€” bug reports, feature requests, documentation improvements, and pull requests.

Before submitting a pull request, please ensure that:

  1. All existing tests pass (make test).

  2. New functionality is covered by tests.

  3. Code is formatted and all checks pass (make check).

Open an issue first if you are planning a significant change, so the approach can be discussed before implementation.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vulcano-2.1.0.tar.gz (39.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vulcano-2.1.0-py3-none-any.whl (40.3 kB view details)

Uploaded Python 3

File details

Details for the file vulcano-2.1.0.tar.gz.

File metadata

  • Download URL: vulcano-2.1.0.tar.gz
  • Upload date:
  • Size: 39.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for vulcano-2.1.0.tar.gz
Algorithm Hash digest
SHA256 65446dc84a171f9eccf720947cf5ff90d1fa5b5f6928944d4995a87d9f65f134
MD5 e054b03dbf4f4c7beb48d8e7d6e1ce8c
BLAKE2b-256 a2d4782ea87673001e3ca0786a2e574b43d494a0b52220fa045479aaf28ca79a

See more details on using hashes here.

Provenance

The following attestation bundles were made for vulcano-2.1.0.tar.gz:

Publisher: publish.yml on dgarana/vulcano

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file vulcano-2.1.0-py3-none-any.whl.

File metadata

  • Download URL: vulcano-2.1.0-py3-none-any.whl
  • Upload date:
  • Size: 40.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for vulcano-2.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2c67c877eac037771369a81345f92004cae2fc6bbc7203d8f52d590ddaa37c26
MD5 af1d85607e49d8cd97909da56c47add3
BLAKE2b-256 62c1476eac7cab78992eee8d940c08bb9d54bc425f4bef8bb62eb690d62d7976

See more details on using hashes here.

Provenance

The following attestation bundles were made for vulcano-2.1.0-py3-none-any.whl:

Publisher: publish.yml on dgarana/vulcano

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page