File size: 5,859 Bytes
ba2f5d6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
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()