diff options
Diffstat (limited to 'venv/lib/python3.9/site-packages/altair/sphinxext/utils.py')
-rw-r--r-- | venv/lib/python3.9/site-packages/altair/sphinxext/utils.py | 199 |
1 files changed, 199 insertions, 0 deletions
diff --git a/venv/lib/python3.9/site-packages/altair/sphinxext/utils.py b/venv/lib/python3.9/site-packages/altair/sphinxext/utils.py new file mode 100644 index 00000000..44b5074b --- /dev/null +++ b/venv/lib/python3.9/site-packages/altair/sphinxext/utils.py @@ -0,0 +1,199 @@ +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: + # gradient from gray to white + 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() + # change from Windows format to UNIX for uniformity + 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 the category comment + find_category = re.compile(r"^#\s*category:\s*(.*)$", re.MULTILINE) + match = find_category.search(content) + if match is not None: + category = match.groups()[0] + # remove this comment from the content + 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: + # In python 3.7 module knows its docstring. + # Everything else will raise an attribute error + docstring = node.docstring + + import tokenize + from io import BytesIO + + ts = tokenize.tokenize(BytesIO(content).readline) + ds_lines = 0 + # find the first string according to the tokenizer and get + # it's end row + for tk in ts: + if tk.exact_type == 3: + ds_lines, _ = tk.end + break + # grab the rest of the file + rest = "\n".join(content.split("\n")[ds_lines:]) + lineno = ds_lines + 1 + + except AttributeError: + # this block can be removed when python 3.6 support is dropped + 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 + # python2.7: Code was read in bytes needs decoding to utf-8 + # unless future unicode_literals is imported in source which + # make ast output unicode strings + if hasattr(docstring, "decode") and not isinstance(docstring, str): + docstring = docstring.decode("utf-8") + # python3.8: has end_lineno + lineno = ( + getattr(docstring_node, "end_lineno", None) or docstring_node.lineno + ) # The last line of the string. + # This get the content of the file after the docstring last line + # Note: 'maxsplit' argument is not a keyword argument in python2 + 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() |