Skip to main content

Python3 module for reading/writing commment-delimited blocks in files

Project description

blockgen

blockgen is a Python3 module that allows reading and writing comment-delimited blocks inside files, which enables generating code next to traditionally handmade code efficiently.

blockgen can be seen as a preprocessor for most file formats accepting comments (e.g. .cpp, .html, .md, .py, ...).

Installation

$ pip install blockgen

Reading blocks

Blocks are sections of a file delimited by the <<[ block_name ]>> and <<[ end ]>> markers :

// main.cpp

// <<[ block1 ]>>
#include <iostream>
#include <vector>
// <<[ end ]>>

int main() {
    std::cout << /*<<[ block2 ]>>*/ "Hello world !" /*<<[ end ]>>*/ << std::endl;
    return 0;
}

...whose content can be easily retrieved with the get_blocks function :

import blockgen

blocks: dict[str, str] = blockgen.file.get_blocks("/path/to/main.cpp")
assert blocks["block1"] == '#include <iostream>\n#include <vector>'
assert blocks["block2"] == '"Hello world !"'

Writing blocks

The content of the blocks can also be modified with the set_blocks function :

import blockgen

new_blocks = {
    "block1": '#include <iostream>\n#include <string>',
    "block2": '"Hello blockgen !"',
}
blockgen.file.set_blocks("/path/to/main.cpp", new_blocks)

...resulting in the following file :

// main.cpp

// <<[ block1 ]>>
#include <iostream>
#include <string>
// <<[ end ]>>

int main() {
    std::cout << /*<<[ block2 ]>>*/ "Hello blockgen !" /*<<[ end ]>>*/ << std::endl;
    return 0;
}

Reinjecting blocks

Blocks can be used to inject generated sections inside handwritten files, but the other way around is also possible: you can maintain handwritten sections inside generated files using the write_and_reinject_blocks function :

import blockgen

file_content = """
void function_1() {
    // <<[ function_1_impl ]>>
    static_assert(false, "function_1 not implemented"); // TODO: implement me
    // <<[ end ]>>
}
"""

blockgen.file.write_and_reinject_blocks("/path/to/utils.cpp", file_content)

...which generates the following file :

// utils.cpp

void function_1() {
    // <<[ function_1_impl ]>>
    static_assert(false, "function_1 not implemented"); // TODO: implement me
    // <<[ end ]>>
}

The file can then be modified by hand, and these handwritten sections will be preserved across future generations.

This means we can modify how the file is generated and execute write_and_reinject_blocks a second time :

import blockgen

file_content = """
void function_1() {
    // <<[ function_1_impl ]>>
    static_assert(false, "function_1 not implemented"); // TODO: implement me
    // <<[ end ]>>
}
void function_2() {
    // <<[ function_2_impl ]>>
    static_assert(false, "function_2 not implemented"); // TODO: implement me
    // <<[ end ]>>
}
"""

blockgen.file.write_and_reinject_blocks("/path/to/utils.cpp", file_content)

...while still preserving the sections written by hand so far :

// utils.cpp

void function_1() {
    // <<[ function_1_impl ]>>
    std::cout << "function_1" << std::endl; // Written by hand
    // <<[ end ]>>
}
void function_2() {
    // <<[ function_2_impl ]>>
    static_assert(false, "function_2 not implemented"); // TODO: implement me
    // <<[ end ]>>
}

This technique allows to seamlessly maintain handwritten code and generated code together in the same file, compared to traditionally having the generated code and handwritten code in separate files.

Under the hood, write_and_reinject_blocks is equivalent to :

import blockgen

old_blocks = blockgen.file.get_blocks("/path/to/utils.cpp")
with open("/path/to/utils.cpp", "w") as f:
    f.write(file_content)
blockgen.file.set_blocks("/path/to/utils.cpp", old_blocks)

As a safety feature, all old blocks must be reinjected into the new file, inside each section with the same name, otherwise it will be considered as code loss and an exception will be raised. Human intervention is required to deal with such scenarios, like deleting the concerned blocks in the file before executing write_and_reinject_blocks.

Integration in build pipelines

Integrating blockgen in your build pipeline is straightforward: simply call the concerned Python scripts from your build file (e.g. makefile, CMake file, Maven file, ...) before the compilation step.

All the write operations of blockgen are designed to only modify the files if the new content is different from the old one, avoiding unnecessary file modifications and thus unnecessary recompilations.

If some of your scripts require some additional dependencies, such as JSON files, you can configure your build pipeline to execute the concerned scripts only when these dependencies are modified, to save some build time.

Integration with CI pipelines

In your CI pipeline (e.g. GitHub Actions, GitLab CI, Jenkins, ...), it is recommended for your CI job to check for source divergence after executing the scripts, and abort the job if any divergence is detected. For example with Jenkins, you can easily do that at the end of your Python scripts :

import os
import subprocess

is_Jenkins_build = os.environ.get("JENKINS_URL") is not None \
                or os.environ.get("JENKINS_HOME") is not None \
                or os.environ.get("JENKINS_INSTANCE") is not None

if is_Jenkins_build:
    output = subprocess.check_output("git status --porcelain", shell=True).decode()
    if output:
        message = "Source divergence detected:\n\n"
        message += f"{output}\n"
        message += "Please build before pushing your changes."
        raise Exception(message)

This will ensure that all the generated code is part of the repository and that no one forgets to run the scripts before pushing their changes, which is a common source of friction when using code generation.

As for the question "Is it wise to push generated code ?", the philosophical answer is that without blockgen you would have to write the generated code by hand and push it anyway. If you have a lot of generated code, you can still do it the old way and try to have the handwritten code (part of the repository) and the generated code (not part of the repository) in separate files.

Links

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

blockgen-1.0.0.tar.gz (17.1 kB view details)

Uploaded Source

Built Distribution

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

blockgen-1.0.0-py3-none-any.whl (19.9 kB view details)

Uploaded Python 3

File details

Details for the file blockgen-1.0.0.tar.gz.

File metadata

  • Download URL: blockgen-1.0.0.tar.gz
  • Upload date:
  • Size: 17.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for blockgen-1.0.0.tar.gz
Algorithm Hash digest
SHA256 02f13b987f0a0b4da0d934d92e17d04ae3545c9d7917f7890a13992636e67532
MD5 aef7cf5a46553c0eeb5dcd14adb48ada
BLAKE2b-256 692447f681db105bdf1b8b9a0c6bc388b4a5099749cce6fddb2ff744c5da88a6

See more details on using hashes here.

File details

Details for the file blockgen-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: blockgen-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 19.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for blockgen-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 04f8e68a4af4ca549911cd1d12bcb1ea07ec39436f8cc8404c4d0ad23ad12d51
MD5 470204ad4a794b365ed8877ea6b0c811
BLAKE2b-256 5b3076d8c0997658c2f6a894ffc702b0e3d0e35fccdff09d3fe1cfd2ca1e1cf4

See more details on using hashes here.

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