Coding 2021-07-26

By Max Woerner Chase

Okay, let's try to speedrun this post and go back to playing Understand for a bit. No touching the code; that's a sucker's game on this network, this late at night.

Earlier today, I got my cmd helper to work, through... somewhat blunt methods.

In any case, the next layer to bring in is the virtual environments, and I sense I should try a little harder to plan this stuff out, because I've got use cases that I intend for the future that I won't be battle-testing as I work on the runner itself.

One question I need to answer is whether I want to use the virtualenv package exclusively in the long term, or if I want to provide interfaces to venv as well. Since I definitely want virtualenv to be usable, I'm not sure if venv offers anything besides "you don't have to install another package". I mean, this project already has a few transitive dependencies.

I think the way forward is to add the dependency, and adapt my Venv class from the config prototype with an Optional[str] python attribute. Then start cribbing code from Nox or tox to convert that into a usable path in a cross-platform fashion.

Now, whether a Session needs a python argument (or multiple python arguments) is a function of its semantics, so that shouldn't be a part of the Protocol.

Let's sketch out a Flake8HTML class.

@attr.dataclass(frozen=True)
class Flake8HTML:
    additional_args: typing.Tuple[str, ...]

    # I'm using a bad default argument because I don't want to bring in the machinery needed for an immutable map.
    def __call__(self, projects: typing.Dict[str, Project] = {"": ROOT}) -> ChangeStream:
        report_dir = REPORTS / "flake8"

        # This is a higher-level wrapper that is specific to my current preferred project structure.
        # The first argument is the name to use for the various Targets and Actions
        # The second argument is the name of the command to export from the virtualenv
        # The third argument is the name of the requirements file to install
        # I expect this to change by the time all of this code is ready to use, since the concept of
        # "module command plus module runner" better fits a variable number of requirements files
        flake8 = yield from venv_wrapper("check", "flake8", "check")

        for project in projects.values():
            project_report_dir = project.suffix(report_dir)  # suffix() probably returns an Input

            # This is a common pattern that I may want a helper function for.
            project_report_file = project_report_dir.path / "index.html"

            # This might not be the right way to handle this
            # (I'm really not sure)
            # But it's close enough for now
            action_name = project.prefix("check")

            yield from mkdir(project_report_dir)
            yield from cmd(
                [
                    flake8,
                    "--format=html",
                    "--htmldir",
                    project_report_dir,
                    *self.additional_args,
                    project.prefix("src"),
                    project.prefix("tests"),
                ],
                action_name,
                allowed_codes={1},
            )
            yield from add_target(project_report_file, action_name, "check")

This all basically provides a basic example of how the interfaces involved should probably work, because this is about the least complicated thing you can do while still leveraging the Project interface. So, whatever I end up with, it should support writing something like this without additional complication.

Handling stuff like different python versions or commands that need the project installed would add those as arguments to the venv_wrapper call and move it into a for loop.

Maybe it would help things to have some kind of an "environment builder" interface that could be passed into the session to handle the environment creation. That way, people, such as my future self, wouldn't need to lay out all of their projects in the exact same way.

So, if I can nail down that interface, then I'll be getting somewhere.

Anyway, this as supposed to be quick and it... wasn't. If I don't call this now, I'm pretty much guaranteed to suffer in the morning. So...

Good night.