aoctools 0.2.0
A CLI for working with Advent of Code (AOC) problems
Requires Python: >=3.7

# Automating your Advent of Code (AOC) Workflow: aoctools

## Introduction

`aoctools` is a command line interface (cli) that helps you automate
[Advent of Code (AOC)](https://adventofcode.com/) boilerplate, e.g., downloading
the day's problem input and writing python boilerplate.

## Workflow

### Step 0: Install

`pip3 install aoctools`

### Step 1: Initialize a project

```text
usage: aoctools init [-h] root_path year

positional arguments:
  root_path   the root project directory
  year
```

The `init` command will create an `aoctools` project at the path that you
specify.

### Step 2: Bootstrap the boilerplate for a new day

Make sure that you set an environment variable named `AOC_SESSION_COOKIE` to the
`session` cookie of your AOC login; in Chrome, you can obtain this cookie by
opening Developer Tools (`View > Developer > Developer Tools`), opening the
`Application` tab, and looking for the value that appears next to `session`.

As an alternative to setting this variable every time that you run the tool, `aoctools` supports `.env` files. You can create a `.env` file under your
project directory and run `aoctools` from said directory for an even more
ergonomic workflow. NB: `.env` files are loaded relative to the working
directory (where you run `aoctools` from) - *not* the project directory.

```text
usage: aoctools init_day [-h] [--root-path [ROOT_PATH]] [--year YEAR]
                         [--skip-bootstrap] [--skip-download]
                         [--session-cookie SESSION_COOKIE]
                         day

positional arguments:
  day                   aoc challenge day

optional arguments:
  -h, --help            show this help message and exit
  --root-path [ROOT_PATH]
                        the root project directory; defaults to the current
                        working directory
  --year YEAR           aoc challenge year; defaults to the project year
  --skip-bootstrap      don't generate python boilerplate
  --skip-download       don't download the challenge input data
  --session-cookie SESSION_COOKIE
                        an AOC login session cookie; defaults to the
                        environment variable AOC_SESSION_COOKIE
```

If you want to generate boilerplate for the challenge day without downloading
the day's input file, pass `--skip-download` to the `aoctools init_day`
command; you can download the day's input file later by passing
`--skip-bootstrap`; NB: the `init_day` command will not clobber existing
python files, so running it twice without passing `--skip-bootstrap` will fail.

### Step 3: Hack

Bootstrapping will generate a new module under the root `aoc` module named after
the freshly bootstrapped day. For example, running `aoctools init_day 1` will
create a file named `aoc/d01/main.py` containing:

```python
from typing import IO

def p_1(input_file: IO,
        debug=False): # pylint: disable=unused-argument
    pass


def p_2(input_file: IO,
        debug=False): # pylint: disable=unused-argument
    pass

```

Filling in `p_1` and `p_2` is where you come in; `aoctools` requires that
you return a (stringifiable) value if you want to use the built-in solution
printer and regression test framework, but there are no restrictions beyond
that.

Running your solution with `aoctools` will result in `p_1` or `p_2` being
called with a reference to the input file; semantically, it's the same as if
you had written:

```python
import aoc.d01

if __name__ == '__main__':
    with open('data/d01/input.txt', 'r', encoding='utf8') as input_file:
        aoc.d01.p_1(input_file, False) # or True
```

and run the file from the root of your project directory. The `debug` variable
is set via the `cli` and makes it easy to control print output:

```python
def p_1(input_file: IO, debug=False):
    v_1 = 1
    v_2 = 2

    if debug:
        print(f"v_1 + v_2 = {v_1} + {v_2} = {v_1 + v_2}")

    return v_1 + v_2
```

### Step 4: Test the example inputs

Create a new example by running the `init_example` command:

```text
usage: aoctools init_example [-h] [--root-path [ROOT_PATH]]
                             day {1,2} expected [example_number]

positional arguments:
  day                   aoc challenge day
  {1,2}                 the challenge part for the example
  expected              the expected example value
  example_number        aoc challenge day; optional (autoincrement)

optional arguments:
  -h, --help            show this help message and exit
  --root-path [ROOT_PATH]
                        the root project directory; defaults to the current
                        working directory
```

Examples are created in `data/day/example_n.txt`. By default, your examples will
be numbered sequentially; you can specify a number explicitly by passing an
additional `[example_number]` argument.

Example files take the form:

```text
part: 1
expected: 2

1-3 a: abcde
1-3 b: cdefg
2-9 c: ccccccccc
```

The first line must be `part: (1|2)` corresponding to the part of the AOC
problem that the example pertains to. The second line must be an expected
value: `expected: (.+)`. The third line must be blank line. The rest of the file
should contain the example text.

Run examples via the `run_examples` command:

```text
usage: aoctools run_examples [-h] [--root-path [ROOT_PATH]] [--debug]
                             day [example_number]

positional arguments:
  day                   aoc challenge day
  example_number        the example to run; defaults to all examples

optional arguments:
  -h, --help            show this help message and exit
  --root-path [ROOT_PATH]
                        the root project directory
  --debug               print debug info
```

You must specify a day. You can optionally specify a specific example to run;
by default, all examples for the given day will run.

### Step 5: Run your solution

```text
usage: aoctools run [-h] [--root-path [ROOT_PATH]] [--debug] day {1,2}

positional arguments:
  day                   aoc challenge day
  {1,2}                 the challenge part to run

optional arguments:
  -h, --help            show this help message and exit
  --root-path [ROOT_PATH]
                        the root project directory
  --debug               print debug info
```

As an example, you can run Day 1, Pt 1 from your root project directory by
invoking:

```text
aoctools run 1 1

solution to day 1, part 1: xxxx
```

### Step 6 (Optional): Regression tests & Code Quality

Once you identify a solution you can populate the automatically generated
regression tests under `tests/aoc/test_d*`:

```python
import aoc.d01

from tests.aoc.test_base import BaseTestCase


class TestAll(BaseTestCase):
    def test_part_one(self):
        self.run_aoc_part(1, CHANGEME, aoc.d01.p_1)

    def test_part_two(self):
        self.run_aoc_part(1, CHANGEME, aoc.d01.p_2)
```

Running `make test` from the root project directory will run all regression
tests.

Similarly, running `make lint` will type lint and type check your code and
tests with both `pylint` and `mypy`.