File: prepare_gallery.py

package info (click to toggle)
dipy 1.11.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 17,144 kB
  • sloc: python: 92,240; makefile: 272; pascal: 183; sh: 162; ansic: 106
file content (344 lines) | stat: -rw-r--r-- 11,539 bytes parent folder | download | duplicates (2)
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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
from dataclasses import dataclass, field
import fnmatch
import os
from os.path import join as pjoin
from pathlib import Path
import shutil
import sys

from sphinx.util import logging

try:
    import tomllib
except ImportError:
    import tomli as tomllib

logger = logging.getLogger(__name__)

sphx_glr_sep = '########################################'
sphx_glr_sep += '#######################################'
sphx_glr_sep_2 = '#%%'


@dataclass(order=True)
class examplesConfig:
    sort_index: int = field(init=False)
    readme: str
    position: int
    enable: bool
    files: list
    folder_name: str

    def __post_init__(self):
        self.sort_index = self.position


def abort(error):
    print(f'*WARNING* Examples Revamp not generated: \n\n{error}')
    exit()


def already_converted(content):
    """Check if the example file is already converted to sphinx-gallery format.

    Parameters
    ----------
    content : list of str
        example file content

    Returns
    -------
    bool
        _description_
    """
    return content.count(sphx_glr_sep) >= 2 or \
        content.count(sphx_glr_sep_2) >= 2


def convert_to_sphinx_gallery_format(content):
    """Convert the example file to sphinx-gallery format.

    Parameters
    ----------
    content : list of str
        example file content

    Returns
    -------
    list of str
        example file content converted to sphinx-gallery format
    """
    inheader = True
    indocs = False

    new_content = ''
    for line in content:

        if inheader:
            if not indocs and (line.startswith('"""') or line.startswith("'''") or
               line.startswith('r"""') or line.startswith("r'''")):
                new_content += line
                if len(line.rstrip()) < 5:
                    indocs = True
                else:
                    # single line doc
                    inheader = False
                continue

            if line.rstrip().endswith('"""') or line.rstrip().endswith("'''"):
                inheader = False
                indocs = False

            new_content += line
            continue

        if indocs \
           or (line.startswith('"""') or line.startswith("'''") or
               line.startswith('r"""') or line.startswith("r'''")):

            if not indocs:
                # guaranteed to start with """
                if len(line.rstrip()) > 4 \
                  and (line.rstrip().endswith('"""') or line.rstrip().endswith("'''")):
                    # single line doc
                    tmp_line = line.replace('"""', '').replace("'''", '')
                    new_content += f'{sphx_glr_sep}\n'
                    new_content += f'# {tmp_line}'
                else:
                    # must be start of multiline block
                    indocs = True
                    new_content += f'{sphx_glr_sep}\n'
            else:
                # we are already in the docs
                # handle doc end
                if line.rstrip().endswith('"""') or line.rstrip().endswith("'''"):
                    # remove quotes
                    # import ipdb; ipdb.set_trace()
                    indocs = False
                    new_content += '\n'
                else:
                    # import ipdb; ipdb.set_trace()
                    new_content += f'# {line}'
                    # has to be documentation
            continue

        new_content += line

    return new_content


def folder_explicit_order():
    srcdir = os.path.abspath(os.path.dirname(__file__))
    examples_dir = os.path.join(srcdir, '..', 'examples')

    f_example_desc = Path(examples_dir, '_valid_examples.toml')
    if not f_example_desc.exists():
        msg = f'No valid examples description file found in {examples_dir}'
        msg += "(e.g '_valid_examples.toml')"
        abort(msg)

    with open(f_example_desc, 'rb') as fobj:
        try:
            desc_examples = tomllib.load(fobj)
        except Exception as e:
            msg = f'Error Loading examples description file: {e}.\n\n'
            msg += 'Please check the file format.'
            abort(msg)

    if 'main' not in desc_examples.keys():
        msg = 'No main section found in examples description file'
        abort(msg)

    try:
        folder_list = sorted(
            [examplesConfig(folder_name=k.lower(), **v)
             for k, v in desc_examples.items()]
        )
    except Exception as e:
        msg = f'Error parsing examples description file: {e}.\n\n'
        msg += 'Please check the file format.'
        abort(msg)

    return [f.folder_name for f in folder_list if f.enable]


def preprocess_include_directive(input_rst, output_rst):
    """
    Process include directives from input RST, and write the output to a new RST.

    Parameters
    ----------
    input_rst : str
        Path to the input RST file containing the include directive.
    output_rst : str
        Path to the output RST file with the include content expanded.

    """
    with open(input_rst, "r") as infile, open(output_rst, "w") as outfile:
        for line in infile:
            if line.strip().startswith(".. include::"):
                # Extract the included file's path
                included_file_path = line.strip().split(" ")[-1]
                included_file_path = os.path.normpath(
                    os.path.join(os.path.dirname(input_rst), included_file_path)
                )

                # Check if the included file exists and read its content
                if os.path.isfile(included_file_path):
                    with open(included_file_path, "r") as included_file:
                        included_content = included_file.read()
                        # Write the included file's content to the output file
                        outfile.write(included_content)
                else:
                    print(f"Warning: Included file '{included_file_path}' not found.")
            else:
                # Write the line as-is if it's not an include directive
                outfile.write(line)


def prepare_gallery(app=None):
    srcdir = app.srcdir if app else os.path.abspath(pjoin(
        os.path.dirname(__file__), '..'))
    examples_dir = os.path.join(srcdir, 'examples')
    examples_revamp_dir = os.path.join(srcdir, 'examples_revamped')
    os.makedirs(examples_revamp_dir, exist_ok=True)

    f_example_desc = Path(examples_dir, '_valid_examples.toml')
    if not f_example_desc.exists():
        msg = f'No valid examples description file found in {examples_dir}'
        msg += "(e.g '_valid_examples.toml')"
        abort(msg)

    with open(f_example_desc, 'rb') as fobj:
        try:
            desc_examples = tomllib.load(fobj)
        except Exception as e:
            msg = f'Error Loading examples description file: {e}.\n\n'
            msg += 'Please check the file format.'
            abort(msg)

    if 'main' not in desc_examples.keys():
        msg = 'No main section found in examples description file'
        abort(msg)

    try:
        examples_config = sorted(
            [examplesConfig(folder_name=k.lower(), **v)
             for k, v in desc_examples.items()]
        )
    except Exception as e:
        msg = f'Error parsing examples description file: {e}.\n\n'
        msg += 'Please check the file format.'
        abort(msg)

    if examples_config[0].position != 0:
        msg = 'Main section must be first in examples description file with'
        msg += 'position=0'
        abort(msg)
    elif examples_config[0].folder_name != 'main':
        msg = "Main section must be named 'main' in examples description file"
        abort(msg)
    elif examples_config[0].enable is False:
        msg = 'Main section must be enabled in examples description file'
        abort(msg)

    disable_examples_section = []
    included_examples = []
    for example in examples_config:
        if not example.enable:
            disable_examples_section.append(example.folder_name)
            continue

        # Create folder for each example
        if example.position != 0:
            folder = Path(
                examples_revamp_dir, f'{example.folder_name}')
        else:
            folder = Path(examples_revamp_dir)

        if not folder.exists():
            os.makedirs(folder)

        # Create readme file
        if example.readme.startswith('file:'):
            filename = example.readme.split('file:')[1].strip()
            preprocess_include_directive(Path(examples_dir, filename),
                                         Path(folder, "README.rst"))
        else:
            with open(Path(folder, "tmp_readme.rst"), "w", encoding="utf8") as fi:
                fi.write(example.readme)
            preprocess_include_directive(Path(folder, "tmp_readme.rst"),
                                         Path(folder, "README.rst"))
            os.remove(Path(folder, "tmp_readme.rst"))

        # Copy files to folder
        if not example.files:
            continue

        for filename in example.files:
            if not Path(examples_dir, filename).exists():
                msg = f'\tFile {filename} not found in examples folder:  '
                msg += f'{examples_dir}.Please, Add the file or remove it '
                msg += 'from the description file.'
                logger.warning(msg)
                continue

            with open(Path(examples_dir, filename), encoding="utf8") as f:
                xfile = f.readlines()

            new_name = None
            if filename in included_examples:
                # file need to be renamed to make it unique for sphinx-gallery
                occurrences = included_examples.count(fi)
                new_name = f'{filename[:-3]}_{occurrences+1}.py'
            if already_converted(xfile):
                shutil.copy(Path(examples_dir, filename),
                            Path(folder, new_name or filename))
            else:
                with open(Path(folder, new_name or filename), 'w',
                          encoding="utf8") as fi:
                    fi.write(convert_to_sphinx_gallery_format(xfile))
            # Add additional link_names
            with open(Path(folder, new_name or filename), 'r+',
                      encoding="utf8") as fi:
                content = fi.read()
                fi.seek(0, 0)
                link_name = f'\n{sphx_glr_sep}\n'
                link_name += '# .. include:: ../../links_names.inc\n#\n'
                fi.write(content + link_name)

            included_examples.append(filename)

    # Check if all python examples are in the description file
    files_in_config = [fi for ex in examples_config for fi in ex.files]
    all_examples = fnmatch.filter(os.listdir(examples_dir), '*.py')
    for all_ex in all_examples:
        if all_ex in files_in_config:
            continue
        msg = f'File {all_ex} not found in examples '
        msg += f"description file: {f_example_desc}"
        logger.warning(msg)


def setup(app):
    """Install the plugin.
    Parameters
    ----------
    app: Sphinx application context.
    """
    logger.info('Initializing Examples folder revamp plugin...')

    app.connect('builder-inited', prepare_gallery)
    # app.connect('build-finished', summarize_failing_examples)

    metadata = {'parallel_read_safe': True, 'version': app.config.version}

    return metadata


if __name__ == '__main__':
    gallery_name = sys.argv[1]
    outdir = sys.argv[2]

    print(folder_explicit_order())
    prepare_gallery(app=None)