Skip to content

tackle

pypi python codecov codeql

Tackle is a multi-paradigm configuration language for building modular code generators, schema validators, and declarative CLIs. Built as a fork of cookiecutter and on top of pydantic, it can make any config file into a CLI with both strong and weakly typed programmable flow control common to a general purpose programming language. Comparable with CUE, Jsonnet, and Dhall, with more features and cleaner syntax, you can write a fully functional Turing-complete program out of a json/yaml/toml file. It's wild.

With tackle, you can build: - Modular code generators / repo scaffolding tools that can be updated over time - Interactive glue code for infrastructure-as-code deployment strategies - Validate complex schemas composing them with strongly typed objects - Generic utilities like SSH helpers and dotfile managers - Combinations of all of the above and anything else you can think of

WARNING: Tackle is still in alpha so expect some changes in the future.

Features

  • Multi-paradigm
  • Create strong and weakly typed objects with custom validation logic
  • Objects can have methods and be extended through inheritance
  • Compose objects to create nested validation structures

  • Makes arbitrary yaml / json / toml dynamic

  • Embed loops, conditionals, and other custom flow control logic
  • Self documenting CLI to call tackle from command line
  • Ships with a collection of over 100 hooks that act like plugins within your config file
  • Prompt user for inputs
  • Generate code from templates
  • Read and write yaml / toml / json files
  • Make http calls
  • Run arbitrary system commands
  • Manipulate the context
  • Run other tackle files
  • Modular design allows creating / importing new hooks easy
  • Supports both python and declarative hooks which can be imported / called / defined in-line or within jinja templates
  • Hooks can be composed / inherited with / from other hooks allowing complex objects to be validated and operated against
  • Expressive macro system creating intuitive interfaces

Install

Note: tackle can install dependencies on its own. Check docs for advanced installation methods to isolate tackle from your system python.

python -m venv env && source env/bin/activate
pip install tackle

Quick Demo: tackle sudoblockio/tackle-hello-world

Hello world

Check out the docs for >10 hello worlds that demonstrate the various aspects of the syntax with the simplest one using the print hook, one of >100 hooks.

hello.yaml

# Any time a key ends with `->`, we are calling a hook
any key->: print Hello world!
# `print` is a hook which can also be used as a special key
print->: Hello world!

To run, call tackle hello.yaml. Can also be version controlled -> tackle sudoblockio/tackle-hello-world.

Can also use loops, conditionals, and other base methods like try / except.

hello.yaml

the:
  words:
    - Hello
    - cruel
    - world!
# Compact one liners
one liner->: print {{i}} --for i in the.words --if i != 'cruel'
# Which can also be expressed in multiple lines
multiple lines:
  ->: print
  objects: {{item}}
  for:
    - Hello
    - cruel
    - world!
  if: item != 'cruel'
# Or combinations of the above
combination:
  ->: print {{i}}
  for: i in ['Hello','world!']
# As a special key
print->: Hello {{i}} --for i in the.words
# Or through jinja rendering
with rendering->: "{{print('Hello','world!'}}"

New hooks can be made in python which under the hood is a pydantic model.

.hooks/hello.py

from tackle import BaseHook


class Greeter(BaseHook):
  hook_name: str = "greeter"
  target: str
  args: list = ['target']

  def exec(self):
    expression = f"Hello {self.target}"
    print(expression)
    return expression

Or can be defined inline within your tackle file, imported remotely, or in a hooks directory..

.hooks/hello.yaml

# Keys ending with `<-` mean we are creating a hook / method
greeter<-:
  target: str
  args: ['target']
  exec<-:
    expression->: var Hello {{target}}  # var hook renders variables
    p->: print {{expression}}
  return: expression

And both can be called the same way.

tackle.yaml

hello: world!
With a flag->: greeter --target {{hello}}
Target in argument->: greeter {{hello}}
Expanded fields:
  ->: greeter
  target: {{hello}}
Jinja template->: {{ greeter(hello) }}
# Or combinations jinja and compact / expanded hooks allowing chaining of hook calls.

Hooks are also callable from the command line:

tackle hello.yaml greeter --target world!
# Or from a github repo
tackle sudoblockio/tackle-hello-world greeter --target world!

Documentation can be embedded into the hooks.

hello.yaml

<-:
  help: This is the default hook
  target:
    type: str
    default->: input
    description: The thing to say hello to
  exec<-:
    greeting->: select Who to greet? --choices ['world',target]
    hi->: greeter --target {{greeting}}
  greeting-method<-:
    help: A method that greets
    # ... Greeting options / logic
    extends: greeter
greeter<-:
  help: A reusable greeter object
  target: str
  exec<-:
    hi->: print Hello {{target}}

Which when running tackle hello.yaml help produces its own help screen.

usage: tackle hello.yaml [--target]

This is the default hook

options:
    --target [str] The thing to say hello to
methods:
    greeting-method     A method that greets
    greeter     A reusable greeter object

Hooks can be imported within a tackle provider or through hooks, linked, and/or combined with inheritance or composition creating a web of CLIs.

Use Cases

WIP Tutorials

  • Declarative Utilities
  • Infrastructure-as-Code Management
  • Kubernetes Manifest Management
  • Toolchain Management

Topics

Known Issues

  • Windows Support
  • tackle is lacking some windows support as shown in the failed tests. If you are a windows user, it is highly recommended to use WSL. Please get in touch if you are motivated to fix these tests to make tackle fully cross-platform. It probably isn't that hard to fix them as they mostly are due to differences in how windows handles paths.
  • Whitespaces
  • tackle relies heavily on parsing based on whitespaces which if you are not careful can easily bite you. Whenever you need to have some whitespaces preserved, make sure to quote the entire expression. Future work will be put in to overhaul the regex based parser with a PEG parser or AST.

Contributing

git clone
cd tackle
make  # Creates and sources virtualenv
tackle --version  # Stable version installed from pypi for managing tackle
tackle test  # Run tests with tackle
make test  # Alternatively run with make
Contributions are welcome but please be advised of the following notes.

  • This project uses conventional commits which generates the changelog with release-please-action in the release CI workflow. If commits have been made outside of this convention they will be squashed accordingly.
  • For making changes to providers, please include test coverage using the existing fixtures and patterns from prior tests or communicate any suggestions that deviate from this style. It definitely can be improved but consistency is more important than making directed improvements. Tests should be runnable from the test's directory and via tackle test.
  • For making changes to the core parser, please create a proposal first outlining your suggestions with examples before spending time working on code.

It is very easy to create new providers / hooks with tackle. Over time, it will adopt the same import pattern of what Ansible does where all provider / hooks (modules) are stored in version controlled locations. In the meantime, please feel free to contribute to this repository for hooks that have general applicability with no dependencies in the providers directory or or create your own hooks in other repositories that are more bespoke / opinionated in nature.

Future Directions

It was always the intention for tackle to be re-written in some kind of compiled language and if the language gets some decent traction, that will be done. Goals will be:

  • Improved parsing performance - tackle should be blazing fast
  • Overhaul of string parsers - should be token based exposing more language features
  • Improved linking cabablities -
  • Ship as a single binary - Not only the tackle interpreter, but an entire tackle provider with linked dependencies
  • Compiled to WASM - Make tackle usable from any language
  • A registry - This is a no brainer when you have a language with modular charectoristics

Instead, the intention will be to integrate Mojo's language features to allow binaries to be compiled from tackle scripts and providers. This will be a major challenge but the benefits will be worth it. Theoretically, tackle could be very fast when it make this switch and useful in a variety of other interesting applications.

Code of Conduct

Everyone interacting in the tackle project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the PyPA Code of Conduct.

Credit

Special thanks to the cookiecutter community for laying the basis for this project.