|
import ast |
|
import hashlib |
|
import itertools |
|
import json |
|
import re |
|
|
|
|
|
def create_thumbnail(image_filename, thumb_filename, window_size=(280, 160)): |
|
"""Create a thumbnail whose shortest dimension matches the window""" |
|
from PIL import Image |
|
|
|
im = Image.open(image_filename) |
|
im_width, im_height = im.size |
|
width, height = window_size |
|
|
|
width_factor, height_factor = width / im_width, height / im_height |
|
|
|
if width_factor > height_factor: |
|
final_width = width |
|
final_height = int(im_height * width_factor) |
|
else: |
|
final_height = height |
|
final_width = int(im_width * height_factor) |
|
|
|
thumb = im.resize((final_width, final_height), Image.ANTIALIAS) |
|
thumb.save(thumb_filename) |
|
|
|
|
|
def create_generic_image(filename, shape=(200, 300), gradient=True): |
|
"""Create a generic image""" |
|
from PIL import Image |
|
import numpy as np |
|
|
|
assert len(shape) == 2 |
|
|
|
arr = np.zeros((shape[0], shape[1], 3)) |
|
if gradient: |
|
|
|
arr += np.linspace(128, 255, shape[1])[:, None] |
|
im = Image.fromarray(arr.astype("uint8")) |
|
im.save(filename) |
|
|
|
|
|
SYNTAX_ERROR_DOCSTRING = """ |
|
SyntaxError |
|
=========== |
|
Example script with invalid Python syntax |
|
""" |
|
|
|
|
|
def _parse_source_file(filename): |
|
"""Parse source file into AST node |
|
|
|
Parameters |
|
---------- |
|
filename : str |
|
File path |
|
|
|
Returns |
|
------- |
|
node : AST node |
|
content : utf-8 encoded string |
|
|
|
Notes |
|
----- |
|
This function adapted from the sphinx-gallery project; license: BSD-3 |
|
https://github.com/sphinx-gallery/sphinx-gallery/ |
|
""" |
|
|
|
with open(filename, "r", encoding="utf-8") as fid: |
|
content = fid.read() |
|
|
|
content = content.replace("\r\n", "\n") |
|
|
|
try: |
|
node = ast.parse(content) |
|
except SyntaxError: |
|
node = None |
|
return node, content |
|
|
|
|
|
def get_docstring_and_rest(filename): |
|
"""Separate ``filename`` content between docstring and the rest |
|
|
|
Strongly inspired from ast.get_docstring. |
|
|
|
Parameters |
|
---------- |
|
filename: str |
|
The path to the file containing the code to be read |
|
|
|
Returns |
|
------- |
|
docstring: str |
|
docstring of ``filename`` |
|
category: list |
|
list of categories specified by the "# category:" comment |
|
rest: str |
|
``filename`` content without the docstring |
|
lineno: int |
|
the line number on which the code starts |
|
|
|
Notes |
|
----- |
|
This function adapted from the sphinx-gallery project; license: BSD-3 |
|
https://github.com/sphinx-gallery/sphinx-gallery/ |
|
""" |
|
node, content = _parse_source_file(filename) |
|
|
|
|
|
find_category = re.compile(r"^#\s*category:\s*(.*)$", re.MULTILINE) |
|
match = find_category.search(content) |
|
if match is not None: |
|
category = match.groups()[0] |
|
|
|
content = find_category.sub("", content) |
|
else: |
|
category = None |
|
|
|
if node is None: |
|
return SYNTAX_ERROR_DOCSTRING, category, content, 1 |
|
|
|
if not isinstance(node, ast.Module): |
|
raise TypeError( |
|
"This function only supports modules. " |
|
"You provided {}".format(node.__class__.__name__) |
|
) |
|
try: |
|
|
|
|
|
docstring = node.docstring |
|
|
|
import tokenize |
|
from io import BytesIO |
|
|
|
ts = tokenize.tokenize(BytesIO(content).readline) |
|
ds_lines = 0 |
|
|
|
|
|
for tk in ts: |
|
if tk.exact_type == 3: |
|
ds_lines, _ = tk.end |
|
break |
|
|
|
rest = "\n".join(content.split("\n")[ds_lines:]) |
|
lineno = ds_lines + 1 |
|
|
|
except AttributeError: |
|
|
|
if ( |
|
node.body |
|
and isinstance(node.body[0], ast.Expr) |
|
and isinstance(node.body[0].value, (ast.Str, ast.Constant)) |
|
): |
|
docstring_node = node.body[0] |
|
docstring = docstring_node.value.s |
|
|
|
|
|
|
|
if hasattr(docstring, "decode") and not isinstance(docstring, str): |
|
docstring = docstring.decode("utf-8") |
|
|
|
lineno = ( |
|
getattr(docstring_node, "end_lineno", None) or docstring_node.lineno |
|
) |
|
|
|
|
|
rest = content.split("\n", lineno)[-1] |
|
lineno += 1 |
|
else: |
|
docstring, rest = "", "" |
|
|
|
if not docstring: |
|
raise ValueError( |
|
( |
|
'Could not find docstring in file "{0}". ' |
|
"A docstring is required for the example gallery." |
|
).format(filename) |
|
) |
|
return docstring, category, rest, lineno |
|
|
|
|
|
def prev_this_next(it, sentinel=None): |
|
"""Utility to return (prev, this, next) tuples from an iterator""" |
|
i1, i2, i3 = itertools.tee(it, 3) |
|
next(i3, None) |
|
return zip(itertools.chain([sentinel], i1), i2, itertools.chain(i3, [sentinel])) |
|
|
|
|
|
def dict_hash(dct): |
|
"""Return a hash of the contents of a dictionary""" |
|
serialized = json.dumps(dct, sort_keys=True) |
|
|
|
try: |
|
m = hashlib.md5(serialized) |
|
except TypeError: |
|
m = hashlib.md5(serialized.encode()) |
|
|
|
return m.hexdigest() |
|
|