clk

A very opinionated framework to ease the creation of command line interfaces

View on GitHub

There are several stuff that you will always need to have at hand when developing command line commands.

Getting files

Often, files are available as zip of tar archives in web servers. A function that, given a url, would download and extract its content would be very useful.

from pathlib import Path

from clk.lib import extract

f = Path("readme")

assert not f.exists()
extract("https://github.com/clk-project/clk/raw/main/tests/zipfile.zip")
assert f.exists()
assert f.read_text() == "hello from some zip file\n"

If you wish to simply download the file, just call download. See fetching and displaying JSON data for a complete example of using download.

When you know what the file should be, say so with sha256.

from clk.lib import download

archive = download(
    "https://github.com/clk-project/clk/raw/main/tests/zipfile.zip",
    sha256="702bb46372dfad9632c8dc3d8b5bbe945f9efd2f5575723bf66a0128486b7fb5",
)
assert archive.exists()

Should the server hand you anything else, nothing is written at all.

import click
import pytest

archive.unlink()
with pytest.raises(click.ClickException):
    download(
        "https://github.com/clk-project/clk/raw/main/tests/zipfile.zip",
        sha256="0" * 64,
    )
assert not archive.exists()

When the server says how big the file is and you are looking at a terminal, download draws a progress bar as it goes. Here we hand it a terminal that keeps what was drawn on it.

import sys
from unittest import mock

from clk.lib import download

class Terminal:
    "A stderr that says it is a terminal and remembers what is drawn on it"

    def __init__(self):
        self.written = ""

    def isatty(self):
        return True

    def write(self, text):
        self.written += text

    def flush(self):
        pass

terminal = Terminal()
with mock.patch.object(sys, "stderr", terminal):
    download("https://github.com/clk-project/clk/raw/main/tests/zipfile.zip")

assert "100%" in terminal.written

Running programs

check_output hands you what the program wrote. When it fails, it tells what the program said on its error output before raising, so you don’t have to.

import subprocess

import pytest

from clk.lib import check_output

with pytest.raises(subprocess.CalledProcessError) as failure:
    check_output(["bash", "-c", "echo 'model.cpp:42: chaos is not defined' >&2 ; exit 2"])

assert failure.value.returncode == 2
assert failure.value.stderr == "model.cpp:42: chaos is not defined\n"

A program that wins while complaining keeps its complaint, which goes to your error output, and you get its answer.

from clk.lib import check_output

assert check_output(
    ["bash", "-c", "echo 'linking takes a while' >&2 ; echo ./build/simulator"]
) == "./build/simulator\n"