Coverage for ase / utils / sphinx_create_png.py: 12.58%
159 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-21 15:52 +0000
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-21 15:52 +0000
1import os
2import re
3import runpy
4import traceback
5import warnings
6from os.path import join
7from pathlib import Path
8from stat import ST_MTIME
9from subprocess import DEVNULL, CalledProcessError, check_call
11from docutils import nodes
12from docutils.parsers.rst.roles import set_classes
15def png_scraper(block, block_vars, gallery_conf):
16 import shutil
18 from sphinx_gallery.scrapers import figure_rst
20 src_file = Path(block_vars['src_file'])
21 src_dir = src_file.parent
23 pngs = sorted(src_dir.glob('*.png'))
25 image_names = []
26 image_path_iterator = block_vars['image_path_iterator']
28 for png in pngs:
29 this_image_path = image_path_iterator.next()
30 image_names.append(this_image_path)
31 shutil.move(png, this_image_path)
33 return figure_rst(image_names, gallery_conf['src_dir'])
36def mol_role(role, rawtext, text, lineno, inliner, options={}, content=[]):
37 n = []
38 t = ''
39 while text:
40 if text[0] == '_':
41 n.append(nodes.inline(text=t))
42 t = ''
43 m = re.match(r'\d+', text[1:])
44 if m is None:
45 raise RuntimeError('Expected one or more digits after "_"')
46 digits = m.group()
47 n.append(nodes.subscript(text=digits))
48 text = text[1 + len(digits) :]
49 else:
50 t += text[0]
51 text = text[1:]
52 n.append(nodes.inline(text=t))
53 return n, []
56def git_role_tmpl(
57 urlroot, role, rawtext, text, lineno, inliner, options={}, content=[]
58):
59 env = inliner.document.settings.env
60 srcdir = Path(env.srcdir)
61 project_root = srcdir.parent
62 # The asserts below are commented out because this role template
63 # is also used by other projects, such as GPAW and Asap.
64 # assert srcdir.name == 'doc'
65 # assert project_root.name == 'ase'
67 if text[-1] == '>':
68 i = text.index('<')
69 name = text[: i - 1]
70 text = text[i + 1 : -1]
71 else:
72 name = text
73 if name[0] == '~':
74 name = name.split('/')[-1]
75 text = text[1:]
76 if '?' in name:
77 name = name[: name.index('?')]
79 # Check if the link is broken
80 is_tag = text.startswith('..') # Tags are like :git:`3.19.1 <../3.19.1>`
81 path = project_root / text
83 if not (is_tag or path.exists()):
84 msg = f'Broken link: {rawtext}: Non-existing path: {path}'
85 msg = inliner.reporter.error(msg, line=lineno)
86 prb = inliner.problematic(rawtext, rawtext, msg)
87 return [prb], [msg]
88 ref = urlroot + text
89 set_classes(options)
90 node = nodes.reference(rawtext, name, refuri=ref, **options)
91 return [node], []
94def git_role(role, rawtext, text, lineno, inliner, options={}, content=[]):
95 return git_role_tmpl(
96 'https://gitlab.com/ase/ase/blob/master/',
97 role,
98 rawtext,
99 text,
100 lineno,
101 inliner,
102 options,
103 content,
104 )
107def setup(app):
108 app.add_role('mol', mol_role)
109 app.add_role('git', git_role)
110 create_png_files()
113def creates():
114 """Generator for Python scripts and their output filenames."""
115 for dirpath, dirnames, filenames in sorted(os.walk('.')):
116 if dirpath.startswith('./build'):
117 # Skip files in the build/ folder
118 continue
120 for filename in sorted(filenames):
121 if filename.endswith('.py'):
122 path = join(dirpath, filename)
123 with open(path) as fd:
124 lines = fd.readlines()
125 if len(lines) == 0:
126 continue
127 if 'coding: utf-8' in lines[0]:
128 lines.pop(0)
129 outnames = []
130 for line in lines:
131 if line.startswith('# creates:'):
132 outnames.extend(
133 [file.rstrip(',') for file in line.split()[2:]]
134 )
135 else:
136 break
137 if outnames:
138 yield dirpath, filename, outnames
141def create_png_files(raise_exceptions=False):
142 from ase.utils import workdir
144 try:
145 check_call(['povray', '-h'], stderr=DEVNULL)
146 except (FileNotFoundError, CalledProcessError):
147 warnings.warn('No POVRAY!')
148 # Replace write_pov with write_png:
149 from ase.io import pov
150 from ase.io.png import write_png
152 def write_pov(
153 filename,
154 atoms,
155 povray_settings={},
156 isosurface_data=None,
157 **generic_projection_settings,
158 ):
159 write_png(
160 Path(filename).with_suffix('.png'),
161 atoms,
162 **generic_projection_settings,
163 )
165 class DummyRenderer:
166 def render(self):
167 pass
169 return DummyRenderer()
171 pov.write_pov = write_pov
173 for dir, pyname, outnames in creates():
174 path = join(dir, pyname)
175 t0 = os.stat(path)[ST_MTIME]
176 run = False
177 for outname in outnames:
178 try:
179 t = os.stat(join(dir, outname))[ST_MTIME]
180 except OSError:
181 run = True
182 break
183 else:
184 if t < t0:
185 run = True
186 break
187 if run:
188 print('running:', path)
189 with workdir(dir):
190 import matplotlib.pyplot as plt
192 plt.figure()
193 try:
194 runpy.run_path(pyname)
195 except KeyboardInterrupt:
196 return
197 except Exception:
198 if raise_exceptions:
199 raise
200 else:
201 traceback.print_exc()
203 for n in plt.get_fignums():
204 plt.close(n)
206 for outname in outnames:
207 print(dir, outname)
210def clean():
211 """Remove all generated files."""
212 for dir, pyname, outnames in creates():
213 for outname in outnames:
214 if os.path.isfile(os.path.join(dir, outname)):
215 os.remove(os.path.join(dir, outname))
218def visual_inspection():
219 """Manually inspect generated files."""
220 import subprocess
222 images = []
223 text = []
224 pdf = []
225 for dir, pyname, outnames in creates():
226 for outname in outnames:
227 path = os.path.join(dir, outname)
228 ext = path.rsplit('.', 1)[1]
229 if ext == 'pdf':
230 pdf.append(path)
231 elif ext in ['csv', 'txt', 'out', 'css', 'LDA', 'rst']:
232 text.append(path)
233 else:
234 images.append(path)
235 subprocess.call(['eog'] + images)
236 subprocess.call(['evince'] + pdf)
237 subprocess.call(['more'] + text)
240if __name__ == '__main__':
241 import argparse
243 parser = argparse.ArgumentParser(description='Process generated files.')
244 parser.add_argument(
245 'command',
246 nargs='?',
247 default='list',
248 choices=['list', 'inspect', 'clean', 'run'],
249 )
250 args = parser.parse_args()
251 if args.command == 'clean':
252 clean()
253 elif args.command == 'list':
254 for dir, pyname, outnames in creates():
255 for outname in outnames:
256 print(os.path.join(dir, outname))
257 elif args.command == 'run':
258 create_png_files(raise_exceptions=True)
259 else:
260 visual_inspection()