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
- e.g.
- add 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>
- e.g.
- add other tags to customize how the tool treats them
snip:no_check_formatto skip when checking formattingsnip:no_check_outputto skip when checking outputssnip:output_verbosity:0or1or2snip:no_compressto 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:
snippet-checker --anki output check_me
Check formatting:
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:
snippet-checker output your_dir
Check formatting:
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 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:
- open an issue to adapt
snippet-checkerto handle your snippet - adapt your snippet to something
snippet-checkercan handle - tag your snippet so
snippet-checkerignores it
Some examples.
snippet-checker can't handle
# assume my_file.txt is "first\nsecond\nthird\n"
with open("my_file.txt") as f:
for x in f:
print(x)
but we can adapt the snippet to something it can handle:
with open("my_file.txt", "w") as f:
f.write("first\nsecond\nthird\n"
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?
Some formatters like double blank lines, e.g. between class definitions.
But space is at a premium in anki notes.
So by default when formatting anki double blanks are replaced by single blanks.
To keep double blanks add a snip:no_compress tag.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file snippet_checker-0.1.2.tar.gz.
File metadata
- Download URL: snippet_checker-0.1.2.tar.gz
- Upload date:
- Size: 13.9 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2af526edc382f34130d3e1b0bb8857f2fc7148c6c6cbb3352994252f70b423f6
|
|
| MD5 |
29c72ca711fdd926779d25f812fd2511
|
|
| BLAKE2b-256 |
62680091357d9c528c2db41438cf77b205ea73eaddb101e590bcd9422a84a7e2
|
File details
Details for the file snippet_checker-0.1.2-py3-none-any.whl.
File metadata
- Download URL: snippet_checker-0.1.2-py3-none-any.whl
- Upload date:
- Size: 17.5 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0587441d90948f10a08402e1ecb1fb45669ef48486d87a73f695f2c7ff4de9f7
|
|
| MD5 |
3ef43568b6af3d3207c2c07bd15989e3
|
|
| BLAKE2b-256 |
4ea8ed883d55af1e9a48488b5af002ef43fda77ec1438bfa310bf97244fc03fd
|