Skip to main content

Create your own syntax stupidly simple!

Project description

sugaru

🍭 Your own syntax you've always been dreaming of. 🍭


Sugaru

Sugaru is a lightweight completely customizable plugin system, that gives you an opportunity to do things like:

  • Writing files followed your own syntax.
  • Translating such files to ones with any other syntax.
  • Any custom user-defined replacements (e.g. templates' replacements with environment variables).
  • Converting files from one format to another (yaml --> json, toml --> ini, etc).
  • Any other fascinating features you can imagine not listed above.

Requirements

Python 3.7+

Installation

$ pip3 install sugaru

Table of contents

Quickstart

It's better to see something once than to read documentation a thousand times. Let's follow this principle.
You are a kind of DevOps engineer. You write CI files every day. That's why you've learnt by heart some CI stages: literally, line-by-line.
One day you've fed up with copy-pasting/writing complete stages into the new project. And you have decided to reduce time and effort to write the same stage the hundredth time.
You took a close look to your stage once again:

stages:
  - tests

pytest:
  stage: "tests"
  image: "python:3.12.0"
  before_script:
    - poetry install
  script:
    - poetry run pytest

And came up with idea to simply remove it. Indeed, the presence of some python-tests stage can mean the same stage's code every time. Why not just generate such a stage then? You ended up with the syntax like:

stages: ""
python-tests: "python:3.12.0"

That's it! Looks good, doesn't it?
To translate python-tests stage to tests one let's write a couple of plugins that will do the job.
The first one will generate stages section:

from typing import Any, Dict, List


def generate_stages(section_name: str, section: Any, sections: Dict[str, Any]) -> List[str]:
    if section_name != "stages":
        return section

    known_stages: Dict[str, str] = {"python-tests": "tests"}
    try:
        return [known_stages[stage] for stage in sections if stage != "stages"]
    except KeyError as unknown_stage:
        raise ValueError(f"Unknown stage: {unknown_stage}") from None

The second plugin will generate python-tests section:

def generate_tests(section_name: str, section: Any) -> Dict[str, Any]:
    if section_name != "python-tests":
        return section

    py_image: str = section
    return {
        "stage": "tests",
        "image": py_image,
        "before_script": ["poetry install"],
        "script": ["poetry run pytest"],
    }

Let's put our plugins into the file called python_tests.py. And put our awesome yml syntax into the file called .my-gitlab-ci.yml.
To make it work simply type:

$ python3 -m sugaru .my-gitlab-ci.yml --plugin python_tests

That's it. You will see the correct .gitlab-ci.yml syntax output on your screen.

how-it-works-no-detail

This picture illustrates how sugaru works in a nutshell.

How it actually works

There are some classes under the hood that work as a pipeline:

how-it-works

There is a file path as an entry point parameter (e.g. path to `.my-gitlab-ci.yml`). Then the output of every component is the input of the next component.
  • File Loader
    • Output: file content as any JSON type
  • Section Encoder
    • Output: sections, i.e. mapping section-name: section-content.
  • Plugin Executor
    • Output: sections after every plugin execution
  • Section Decoder
    • Output: file content as any JSON type
  • File Writer
    • Output: the file with the content (including stdout)

There is also an interesting component called Object Loader. We'll discuss it later.

Preparations under the hood

There is the default implementation for every component listed above.
Instead of using default components, you can define your own ones.
If you do so, such components are loaded by Object Loader component.

components-loading

And what about the Object Loader component itself?
Actually, you can even implement a custom Object Loader component. Such the custom Object Loader will be loaded by default Object Loader first and then will replace the default one.

How objects are loaded

All custom defined objects -- including user plugins -- are loaded by interfaces.
To implement your own component you have to implement the interface dedicated to it.
For example, to implement custom Section Encoder you have to implement the following interface:

class SectionEncoder(Protocol):
    def __call__(self, content: JSON) -> Dict[SecName, Section]:
        ...

The only exception is user Plugin. A plugin interface is defined as

class Plugin(Protocol):
    def __call__(
        self,
        *,
        section: Section,
        section_name: str,
        sections: Mapping[SecName, Section],
    ) -> Section:
        ...

To implement a plugin you should define any combination of interfaces' parameters. For example the following implementation also fits:

def empty_section(section_name: str) -> Any:
    print(section_name)
    return {}

The last thing you should known about interface's implementation is that type hints are not validated by sugaru (yet). It's up to you to use type hints for your own purposes.
Take a closer look to interfaces.py file for more interfaces' detail.

Examples

You will find more examples with detail explanations inside examples folder.

Dependencies

Sugaru has some default dependencies, that are:

  • typer. Typer makes sugaru more convenient to use.
  • pyyaml. Sugaru manages to deal with yaml files by default.

You can also install loguru for more beautiful logs.

Changelog

You can see the release history here: https://github.com/Mityuha/sugaru/releases/


Sugaru is MIT licensed code.

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

sugaru-0.3.1.tar.gz (14.8 kB view details)

Uploaded Source

Built Distribution

sugaru-0.3.1-py3-none-any.whl (18.6 kB view details)

Uploaded Python 3

File details

Details for the file sugaru-0.3.1.tar.gz.

File metadata

  • Download URL: sugaru-0.3.1.tar.gz
  • Upload date:
  • Size: 14.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.5.1 CPython/3.10.12 Linux/5.15.0-1040-azure

File hashes

Hashes for sugaru-0.3.1.tar.gz
Algorithm Hash digest
SHA256 d4d4afff79730e8830672d234e6302bedbed58c88ee6f6a6caaef34f2660c162
MD5 196ee4307aa7fdb00c8a509258491df4
BLAKE2b-256 4a3a29f11eb68b19e7d6b5767f3a01398ceed24b6e70298e228064ee4ffba5b5

See more details on using hashes here.

File details

Details for the file sugaru-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: sugaru-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 18.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.5.1 CPython/3.10.12 Linux/5.15.0-1040-azure

File hashes

Hashes for sugaru-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 f7574fe481be3e182793e81fe80b0a1b22b359047974498ba2c98362443ac867
MD5 2541bab7826fbac3630a4676e00da0ff
BLAKE2b-256 ae1ab47d9b1442096ebadb8110afae9e413ff0c3fa4fa7d4de321c9d0fdaee27

See more details on using hashes here.

Supported by

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