cliffy


Namecliffy JSON
Version 0.5.0 PyPI version JSON
download
home_pageNone
Summary$ cli load from.yaml
upload_time2025-01-14 05:39:08
maintainerNone
docs_urlNone
authorJay
requires_python<4.0,>=3.9
licenseNone
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            ![Cliffy logo](docs/images/logo.svg)

[![GitHub Workflow Status (with branch)](https://img.shields.io/github/actions/workflow/status/jaykv/cliffy/python-app.yaml?branch=main)](https://github.com/jaykv/cliffy/actions)
[![PyPI](https://img.shields.io/pypi/v/cliffy)](https://pypi.org/project/cliffy/)
![GitHub](https://img.shields.io/github/license/jaykv/cliffy)
[![docs](https://img.shields.io/badge/docs-8A2BE2)](https://jaykv.github.io/cliffy)

**cliffy** simplifies the creation, management, and deployment of CLIs. Define your CLI's structure and behavior in a YAML manifest, and let cliffy handle the rest.

## Features
* Write CLIs with YAML manifests
* Manage CLIs- load, test, update, list, and remove
* Built-in shell and Python scripting support
* Supports Jinja2 templating
* Hot-reload CLIs on manifest changes for easier development
* Build CLIs into self-contained, single-file portable zipapps for sharing

### Load

1. Define a manifest
```yaml
# hello.yaml
name: hello
version: 0.1.0

commands:
  shell: 
    help: Print hello in shell
    run: $echo "hello from shell"
  python: print("hello from python")
```

2. Load CLI
```
$ cli load hello.yaml
```
Parses `hello.yaml` to generate a Typer CLI and load it into the running Python environment.

3. Run CLI directly

`hello -h`

![hello-demo](docs/images/hello.png)

For more examples, check [examples](examples/) directory.

### Build

1. Define a manifest
```yaml
# requires.yaml
name: requires
version: 0.1.0

requires:
  - requests >= 2.30.0
  - six

imports:
  - import six

commands:
  shell: $echo "hello from shell"
  python: print("hello from python")
  py: |
    if six.PY2:
        print("python 2")
    if six.PY3:
        print("python 3")
```

2. Build CLI
```
$ cli build requires.yaml -o dist
```

Builds a portable zipapp containing the CLI and its package requirements.

3. Run CLI
```
./dist/requires -h
```

## Usage
`cli <command>`


| Command | Description |
|---|---|
| init \<cli name> | Generate a template CLI manifest for a new CLI |
| load \<manifest> | Add a new CLI based on the manifest |
| render \<manifest> | View generated CLI script for a manifest |
| list, ls | Output a list of loaded CLIs |
| update \<cli name> | Reload a loaded CLI |
| remove \<cli name>, rm \<cli name> | Remove a loaded CLI |
| run \<manifest> -- \<args> | Runs a CLI manifest command in isolation|
| build \<cli name or manifest> | Build a CLI manifest or a loaded CLI into a self-contained zipapp |
| info \<cli name> | Display CLI metadata |
| dev \<manifest> | Start hot-reloader for a manifest for active development |
| test \<manifest> | Run tests defined in a manifest |
| validate \<manifest> | Validate the syntax and structure of a CLI manifest |

## How it works
1. Define CLI manifests in YAML files
2. Run `cli` commands to load, build, and manage CLIs
3. When loaded, cliffy parses the manifest and generates a [Typer](https://github.com/tiangolo/typer) CLI that is deployed directly as a script
4. Any code starting with `$` will translate to subprocess calls via [PyBash](https://github.com/cliffy-sh/pybash)
5. Run loaded CLIs straight from the terminal
6. When ready to share, run `build` to generate portable zipapps built with [Shiv](https://github.com/linkedin/shiv)

## Get started

Cliffy can be installed using either pip or uv package managers.

### With pip
* `pip install "cliffy[rich]"` to include [rich-click](https://github.com/ewels/rich-click) for colorful CLI help output formatted with [rich](https://github.com/Textualize/rich).

or 

* `pip install cliffy` to use the default help output.

### With uv
* `uvx cliffy --help`
* Load: `uvx cliffy load examples/hello.yaml`
* Run: `uvx --from cliffy hello`

## Manifest template
Generated by `cli init`. For a barebones template, run `cli init --raw`

```yaml
manifestVersion: v3

# The name of the CLI, used when invoking from command line.
name: cliffy

# CLI version
version: 0.1.0

# Brief description of the CLI
help: A brief description of your CLI

# List of Python package dependencies for the CLI.Supports requirements specifier syntax.
requires: []
  # - requests>=2.25.1
  # - pyyaml~=5.4

# List of external CLI manifests to include.Performs a deep merge of manifests sequentially in the order given to assemble a merged manifest
# and finally, deep merges the merged manifest with this manifest.
includes: []
  # - path/to/other/manifest.yaml

# Mapping defining manifest variables that can be referenced in any other blocks
# Environments variables can be used in this section with ${some_env_var} for dynamic parsing
# Supports jinja2 formatted expressions as values
# Interpolate defined vars in other blocks jinja2-styled {{ var_name }}.
vars:
  data_file: "data.json"
  debug_mode: "{{ env['DEBUG'] or 'False' }}"

# String block or list of strings containing any module imports
# These can be used to import any python modules that the CLI depends on.
imports: |
  import json
  import os
  from pathlib import Path

# List of helper function definitions
# These functions should be defined as strings that can be executed by the Python interpreter.
functions:
  - |
    def load_data() -> dict:
        data_path = Path("{{ data_file }}")
        if data_path.exists():
            with data_path.open() as f:
                return json.load(f)
        return {}
  - |
      def save_data(data):
          with open("{{data_file}}", "w") as f:
              json.dump(data, f, indent=2)
# A mapping containing any shared type definitions
# These types can be referenced by name in the args section to provide type annotations for params and options defined in the args section.
types:
  Filename: str = typer.Argument(..., help="Name of the file to process")
  Verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose output")

# Arguments applied to all commands
global_params:
  - verbose: Verbose

# Reusable command templates
command_templates:
  with_confirmation:
    params:
      - "yes": bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt")
    pre_run: |
      if not yes:
        typer.confirm("Are you sure you want to proceed?", abort=True)

# A mapping containing the command definitions for the CLI
# Each command should have a unique key- which can be either a group command or nested subcommands
# Nested subcommands are joined by '.' in between each level
# Aliases for commands can be separated in the key by '|'
# A special '(*)' wildcard can be used to spread the subcommand to all group-level commands
commands:
  hello:
    help: Greet the user
    params:
      - name: str = typer.Option("World", "--name", "-n", help="Name to greet")
    run: |
      print(f"Hello, {name}!")
      $ echo "i can also mix-and-match this command script to run shell commands"

  file.process:
    help: Process a file
    params:
      - filename: Filename
    run: |
      data = load_data()
      print(f"Processing {filename}")
      if verbose:
        print("Verbose output enabled")
      data["processed"] = [filename]
      # Process the file here
      save_data(data)

  delete|rm:
    help: Delete a file
    template: with_confirmation
    params: [filename: Filename]
    run: |
      if verbose:
        print(f"Deleting {filename}")
      os.remove(filename)
      print("File deleted successfully")

# Additional CLI configuration options
cli_options:
  rich_help_panel: True

# Test cases for commands
tests:
  - hello --name Alice: assert 'Hello, Alice!' in result.output
  - file process test.txt: assert 'Processing test.txt' in result.output
```


            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "cliffy",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4.0,>=3.9",
    "maintainer_email": null,
    "keywords": null,
    "author": "Jay",
    "author_email": "jay.github0@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/c5/40/1f0df7ebcb98aa05a34aabb0f93695fd8c3ab79e0b1ddd5febed466ae5c5/cliffy-0.5.0.tar.gz",
    "platform": null,
    "description": "![Cliffy logo](docs/images/logo.svg)\n\n[![GitHub Workflow Status (with branch)](https://img.shields.io/github/actions/workflow/status/jaykv/cliffy/python-app.yaml?branch=main)](https://github.com/jaykv/cliffy/actions)\n[![PyPI](https://img.shields.io/pypi/v/cliffy)](https://pypi.org/project/cliffy/)\n![GitHub](https://img.shields.io/github/license/jaykv/cliffy)\n[![docs](https://img.shields.io/badge/docs-8A2BE2)](https://jaykv.github.io/cliffy)\n\n**cliffy** simplifies the creation, management, and deployment of CLIs. Define your CLI's structure and behavior in a YAML manifest, and let cliffy handle the rest.\n\n## Features\n* Write CLIs with YAML manifests\n* Manage CLIs- load, test, update, list, and remove\n* Built-in shell and Python scripting support\n* Supports Jinja2 templating\n* Hot-reload CLIs on manifest changes for easier development\n* Build CLIs into self-contained, single-file portable zipapps for sharing\n\n### Load\n\n1. Define a manifest\n```yaml\n# hello.yaml\nname: hello\nversion: 0.1.0\n\ncommands:\n  shell: \n    help: Print hello in shell\n    run: $echo \"hello from shell\"\n  python: print(\"hello from python\")\n```\n\n2. Load CLI\n```\n$ cli load hello.yaml\n```\nParses `hello.yaml` to generate a Typer CLI and load it into the running Python environment.\n\n3. Run CLI directly\n\n`hello -h`\n\n![hello-demo](docs/images/hello.png)\n\nFor more examples, check [examples](examples/) directory.\n\n### Build\n\n1. Define a manifest\n```yaml\n# requires.yaml\nname: requires\nversion: 0.1.0\n\nrequires:\n  - requests >= 2.30.0\n  - six\n\nimports:\n  - import six\n\ncommands:\n  shell: $echo \"hello from shell\"\n  python: print(\"hello from python\")\n  py: |\n    if six.PY2:\n        print(\"python 2\")\n    if six.PY3:\n        print(\"python 3\")\n```\n\n2. Build CLI\n```\n$ cli build requires.yaml -o dist\n```\n\nBuilds a portable zipapp containing the CLI and its package requirements.\n\n3. Run CLI\n```\n./dist/requires -h\n```\n\n## Usage\n`cli <command>`\n\n\n| Command | Description |\n|---|---|\n| init \\<cli name> | Generate a template CLI manifest for a new CLI |\n| load \\<manifest> | Add a new CLI based on the manifest |\n| render \\<manifest> | View generated CLI script for a manifest |\n| list, ls | Output a list of loaded CLIs |\n| update \\<cli name> | Reload a loaded CLI |\n| remove \\<cli name>, rm \\<cli name> | Remove a loaded CLI |\n| run \\<manifest> -- \\<args> | Runs a CLI manifest command in isolation|\n| build \\<cli name or manifest> | Build a CLI manifest or a loaded CLI into a self-contained zipapp |\n| info \\<cli name> | Display CLI metadata |\n| dev \\<manifest> | Start hot-reloader for a manifest for active development |\n| test \\<manifest> | Run tests defined in a manifest |\n| validate \\<manifest> | Validate the syntax and structure of a CLI manifest |\n\n## How it works\n1. Define CLI manifests in YAML files\n2. Run `cli` commands to load, build, and manage CLIs\n3. When loaded, cliffy parses the manifest and generates a [Typer](https://github.com/tiangolo/typer) CLI that is deployed directly as a script\n4. Any code starting with `$` will translate to subprocess calls via [PyBash](https://github.com/cliffy-sh/pybash)\n5. Run loaded CLIs straight from the terminal\n6. When ready to share, run `build` to generate portable zipapps built with [Shiv](https://github.com/linkedin/shiv)\n\n## Get started\n\nCliffy can be installed using either pip or uv package managers.\n\n### With pip\n* `pip install \"cliffy[rich]\"` to include [rich-click](https://github.com/ewels/rich-click) for colorful CLI help output formatted with [rich](https://github.com/Textualize/rich).\n\nor \n\n* `pip install cliffy` to use the default help output.\n\n### With uv\n* `uvx cliffy --help`\n* Load: `uvx cliffy load examples/hello.yaml`\n* Run: `uvx --from cliffy hello`\n\n## Manifest template\nGenerated by `cli init`. For a barebones template, run `cli init --raw`\n\n```yaml\nmanifestVersion: v3\n\n# The name of the CLI, used when invoking from command line.\nname: cliffy\n\n# CLI version\nversion: 0.1.0\n\n# Brief description of the CLI\nhelp: A brief description of your CLI\n\n# List of Python package dependencies for the CLI.Supports requirements specifier syntax.\nrequires: []\n  # - requests>=2.25.1\n  # - pyyaml~=5.4\n\n# List of external CLI manifests to include.Performs a deep merge of manifests sequentially in the order given to assemble a merged manifest\n# and finally, deep merges the merged manifest with this manifest.\nincludes: []\n  # - path/to/other/manifest.yaml\n\n# Mapping defining manifest variables that can be referenced in any other blocks\n# Environments variables can be used in this section with ${some_env_var} for dynamic parsing\n# Supports jinja2 formatted expressions as values\n# Interpolate defined vars in other blocks jinja2-styled {{ var_name }}.\nvars:\n  data_file: \"data.json\"\n  debug_mode: \"{{ env['DEBUG'] or 'False' }}\"\n\n# String block or list of strings containing any module imports\n# These can be used to import any python modules that the CLI depends on.\nimports: |\n  import json\n  import os\n  from pathlib import Path\n\n# List of helper function definitions\n# These functions should be defined as strings that can be executed by the Python interpreter.\nfunctions:\n  - |\n    def load_data() -> dict:\n        data_path = Path(\"{{ data_file }}\")\n        if data_path.exists():\n            with data_path.open() as f:\n                return json.load(f)\n        return {}\n  - |\n      def save_data(data):\n          with open(\"{{data_file}}\", \"w\") as f:\n              json.dump(data, f, indent=2)\n# A mapping containing any shared type definitions\n# These types can be referenced by name in the args section to provide type annotations for params and options defined in the args section.\ntypes:\n  Filename: str = typer.Argument(..., help=\"Name of the file to process\")\n  Verbose: bool = typer.Option(False, \"--verbose\", \"-v\", help=\"Enable verbose output\")\n\n# Arguments applied to all commands\nglobal_params:\n  - verbose: Verbose\n\n# Reusable command templates\ncommand_templates:\n  with_confirmation:\n    params:\n      - \"yes\": bool = typer.Option(False, \"--yes\", \"-y\", help=\"Skip confirmation prompt\")\n    pre_run: |\n      if not yes:\n        typer.confirm(\"Are you sure you want to proceed?\", abort=True)\n\n# A mapping containing the command definitions for the CLI\n# Each command should have a unique key- which can be either a group command or nested subcommands\n# Nested subcommands are joined by '.' in between each level\n# Aliases for commands can be separated in the key by '|'\n# A special '(*)' wildcard can be used to spread the subcommand to all group-level commands\ncommands:\n  hello:\n    help: Greet the user\n    params:\n      - name: str = typer.Option(\"World\", \"--name\", \"-n\", help=\"Name to greet\")\n    run: |\n      print(f\"Hello, {name}!\")\n      $ echo \"i can also mix-and-match this command script to run shell commands\"\n\n  file.process:\n    help: Process a file\n    params:\n      - filename: Filename\n    run: |\n      data = load_data()\n      print(f\"Processing {filename}\")\n      if verbose:\n        print(\"Verbose output enabled\")\n      data[\"processed\"] = [filename]\n      # Process the file here\n      save_data(data)\n\n  delete|rm:\n    help: Delete a file\n    template: with_confirmation\n    params: [filename: Filename]\n    run: |\n      if verbose:\n        print(f\"Deleting {filename}\")\n      os.remove(filename)\n      print(\"File deleted successfully\")\n\n# Additional CLI configuration options\ncli_options:\n  rich_help_panel: True\n\n# Test cases for commands\ntests:\n  - hello --name Alice: assert 'Hello, Alice!' in result.output\n  - file process test.txt: assert 'Processing test.txt' in result.output\n```\n\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "$ cli load from.yaml",
    "version": "0.5.0",
    "project_urls": {
        "Repository": "https://github.com/jaykv/cliffy"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "91db515ac7451b0bea39d817fb05b8a623aaca45ee57011719233a4c6d1aa2ec",
                "md5": "e459f2697b6b730b37dacdbba05cf11e",
                "sha256": "91d65e02f6df32b6107b4daa5cf7fc3fe3d54a3b261d2460f46e18f31ab715e8"
            },
            "downloads": -1,
            "filename": "cliffy-0.5.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "e459f2697b6b730b37dacdbba05cf11e",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.9",
            "size": 32470,
            "upload_time": "2025-01-14T05:39:05",
            "upload_time_iso_8601": "2025-01-14T05:39:05.856552Z",
            "url": "https://files.pythonhosted.org/packages/91/db/515ac7451b0bea39d817fb05b8a623aaca45ee57011719233a4c6d1aa2ec/cliffy-0.5.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "c5401f0df7ebcb98aa05a34aabb0f93695fd8c3ab79e0b1ddd5febed466ae5c5",
                "md5": "61544eb4b0a19c1fe548c14cc4847cd7",
                "sha256": "bc7d2b15959dbe6399dedb5c7465c1f4e6e180ba61293c2bf74ea18dbefb3b2e"
            },
            "downloads": -1,
            "filename": "cliffy-0.5.0.tar.gz",
            "has_sig": false,
            "md5_digest": "61544eb4b0a19c1fe548c14cc4847cd7",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0,>=3.9",
            "size": 28905,
            "upload_time": "2025-01-14T05:39:08",
            "upload_time_iso_8601": "2025-01-14T05:39:08.276064Z",
            "url": "https://files.pythonhosted.org/packages/c5/40/1f0df7ebcb98aa05a34aabb0f93695fd8c3ab79e0b1ddd5febed466ae5c5/cliffy-0.5.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-01-14 05:39:08",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "jaykv",
    "github_project": "cliffy",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "cliffy"
}
        
Jay
Elapsed time: 0.39727s