Skip to main content

Check code snippets in anki or files via docker.

Project description

snippet-checker

Check code snippets in anki or files via docker.

Quickstart

Install:

uv tool install snippet-checker

Requires Docker.

How to check anki

In ~/.snippet-checker/ or $XDG_CONFIG_HOME/snippet-checker/ write snippet-checker.toml:

# Name of your anki profile.
profile = "cosmo"

# Tell the tool how to extract the code and output from your notes.
[[notes]]
note_type = "Code output"

# The field containing the code.
[notes.code_field]
name = "Code"
# Your field may contain markup, as well as the code.
# The pattern should be a Python regex with a group named "target", which matches just the code.
# The pattern below works for fields like '<pre><code class="lang-python">print(1 + 1)</code></pre>'.
# The markup is added back when the tool writes to anki.
pattern = '(?s)^<pre><code class="lang-\w+">(?P<target>.*)</code></pre>$'

# The field containing the output.
[notes.output_field]
name = "Output"
# As above.
# This pattern works for fields like '<pre><samp>2\n</samp></pre>'.
pattern = "(?s)^<pre><samp>(?P<target>.*)</samp></pre>$"

# Same again for each note type you want to check.

In anki:

  • add a tag to the notes you want to check
    • e.g. check_me
  • add suitable tags snip:image:<image tag> to the notes which have snippets
    • e.g. snip:image:python:3.13
    • sets the image in which the tool runs that note's snippet
    • the image is pulled via docker image pull <image tag>
  • add other tags to customize how the tool treats them
    • snip:no_check_format to skip when checking formatting
    • snip:no_check_output to skip when checking outputs
    • snip:output_verbosity:0 or 1 or 2
    • snip:no_compress to keep double blank lines in code
    • more details on these below

(Anki lets you batch edit tags: select the notes, right click, Notes > Add/Remove Tags.)

Check outputs:

uv tool run snippet-checker --anki output check_me

Check formatting:

uv tool run snippet-checker --anki format check_me

Pass --interactive to fix interactively. Pass --fix to auto-fix (back up your collection first).

Checking files

Structure your directory something like

your_dir
├── a_snippet
│   ├── main.py
│   └── output.txt
├── more_snippets
│   ├── extra_files_anywhere_are_ok
│   ├── a_go_snippet
│   │   ├── go.mod
│   │   ├── main.go
│   │   └── output.txt
│   ├── a_javascript_snippet
│   │   ├── main.js
│   │   └── output.txt
│   └── another_python_snippet
│       ├── main.py
│       └── output.txt

Write a snippet_checker.toml file at your_dir's root:

# Set how tracebacks, panics etc. are abbreviated.
output_verbosity = 0  # Or 1 or 2.

# Set image tags.
[images]
js = "node:22"
rb = "ruby:2.7"
py = "python:3.14"
go = "golang:1.23"
rs = "rust:1.93"

To override a setting for a particular snippet, add another snippet_checker.toml alongside it:

check_format = false

[images]
go = "golang:1.21"

Check outputs:

uv tool run snippet-checker output your_dir

Check formatting:

uv tool run snippet-checker format your_dir

Pass --interactive to fix interactively. Pass --fix to auto-fix (version control your collection first).

Examples

snippet-checker runs the code as though at the command line (python main.py, node main.js, go build main.go then /main, etc) then constructs timed, normalised outputs.

Hello world

print("hello world")
hello world

Trailing newline included.

Timing

from threading import Thread
from time import sleep


def io_bound():
    sleep(3)
    print("done")


thread1 = Thread(target=io_bound)
thread2 = Thread(target=io_bound)
thread1.start()
thread2.start()
print("here")
here
<~3s>
done
done

Timing matters, so it's included in the output. Gaps are rounded to the nearest second, are only included if at least 1s after rounding, and are always included in the form "<~Xs>".

Normalising exceptions

1 / 0

Output verbosity 0:

ZeroDivisionError: division by zero

Output verbosity 1

Traceback (most recent call last):
  ...
ZeroDivisionError: division by zero

Output verbosity 2:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
    1 / 0
    ~~^~~
ZeroDivisionError: division by zero

Similar for exceptions in other languages.

Normalising memory locations

class C:
    pass


class D:
    pass


c = C()
d = D()
print(c)
print(d)
print(c)
<__main__.C object at 0x100>
<__main__.D object at 0x200>
<__main__.C object at 0x100>

Memory addresses vary from run to run. The tool replaces them by consistent, simpler addresses.

Q&A

Which languages does it support?

Python and Go robustly. JavaScript (Node), Ruby and Rust somewhat, but output normalisation is wip.

How sandboxed?

The snippets run in Docker containers. No mounts or volumes.

What to do when snippet-checker complains?

If you agree, then it's done its job and you can update the snippet or output.

If you disagree, then you have options:

  1. open an issue to adapt snippet-checker to handle your snippet
  2. adapt your snippet to something snippet-checker can handle
  3. tag your snippet so snippet-checker ignores it

Some examples.

snippet-checker can't handle

with open("my_file.txt") as f:
    for x in f:
        print(x)

# assuming my_file.txt is "first\nsecond\nthird"

but we can adapt the snippet to something it can handle:

with open("my_file.txt", "w") as f:
    f.write("first\nsecond\nthird"

with open("my_file.txt") as f:
    for x in f:
        print(x)

It can't handle this either

try:
    x = input()
    # user enters Ctrl-C
except Exception:
    print("exception")
finally:
    print("finally")

and I don't see how to adapt the snippet or the tool. Better just add a tag so snippet-checker ignores it.

Or a formatting example:

print("foo" "bar")

ruff formats this as print("foobar"). But if the point of the question is to show implicit string concatenation, again better to add a tag so snippet-checker ignores it.

What formatters does it use?

A fixed formatter with default configuration for each language: ruff, prettier, gofmt, rubocop, rustfmt. I'm thinking about how to make this customizable.

What's no_compress?

ruff likes double blank lines, e.g. between class definitions. But space is at a premium in anki notes. So by default double blanks are replaced by single blanks after formatting. If you don't want this add a snip:no_compress tag.

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

snippet_checker-0.1.0.tar.gz (13.0 kB view details)

Uploaded Source

Built Distribution

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

snippet_checker-0.1.0-py3-none-any.whl (16.6 kB view details)

Uploaded Python 3

File details

Details for the file snippet_checker-0.1.0.tar.gz.

File metadata

  • Download URL: snippet_checker-0.1.0.tar.gz
  • Upload date:
  • Size: 13.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for snippet_checker-0.1.0.tar.gz
Algorithm Hash digest
SHA256 508573e3aa6d01324b88250017e71552426efceced7d23c5cefd2955cb3b1bed
MD5 8631620991417d1e77534dc084071d55
BLAKE2b-256 0d696bbd001c191dd37d12db36e0cd330edae5195ae777f13d6ea87212132833

See more details on using hashes here.

File details

Details for the file snippet_checker-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: snippet_checker-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 16.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for snippet_checker-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9c4f64c2ed3f07a108ce029eb397a69e40d46829c89558501b84031deace1392
MD5 1b4582ad0cb36989d4ab92beb45e8613
BLAKE2b-256 21399a0d5446bf8fcfc5ad9b6de9266a3083d9282721286dc56b76ba53b5dde4

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