diff --git a/cal_tools/cal_tools/tools.py b/cal_tools/cal_tools/tools.py index d1912490bdc4bedf9a2a67a90b50d636ad65e203..2943381d897d1bc437bfb0720021a994caf5a801 100644 --- a/cal_tools/cal_tools/tools.py +++ b/cal_tools/cal_tools/tools.py @@ -1,15 +1,11 @@ from collections import OrderedDict import datetime from glob import glob -from importlib.machinery import SourceFileLoader import json -from os import chdir, environ, listdir, makedirs, path, remove, stat -from os.path import isdir, isfile, splitext +from os import environ, listdir, path, stat +from os.path import isfile, splitext from queue import Queue import re -from shutil import copy, copytree, move, rmtree -from subprocess import CalledProcessError, check_call, check_output -from textwrap import dedent from time import sleep from urllib.parse import urljoin @@ -20,318 +16,9 @@ from metadata_client.metadata_client import MetadataClient from notebook.notebookapp import list_running_servers import numpy as np import requests -import tabulate -from jinja2 import Template from .mdc_config import MDC_config from .ana_tools import save_dict_to_hdf5 -from xfel_calibrate import settings - - -def atoi(text): - """ - Convert string to integer is possible - - :param text: string to be converted - :return: integer value or input string - """ - - return int(text) if text.isdigit() else text - - -def natural_keys(text): - """ - Decompose string to list of integers and sub-strings - """ - return [atoi(c) for c in re.split(r'(\d+)', text)] - - -def combine_report(run_path, calibration): - sphinx_path = "{}/sphinx_rep".format(path.abspath(run_path)) - makedirs(sphinx_path) - direntries = listdir(run_path) - direntries.sort(key=natural_keys) - - for entry in direntries: - - if isfile("{}/{}".format(run_path, entry)): - name, ext = splitext("{}".format(entry)) - - if ext == ".rst": - comps = name.split("__") - if len(comps) >= 3: - group, name_param, conc_param = "_".join(comps[:-2]), \ - comps[-2], comps[-1] - else: - group, name_param, conc_param = comps[0], "None", "None" - - with open("{}/{}.rst".format(sphinx_path, group), - "a") as gfile: - if conc_param != "None": - title = "{}, {} = {}".format(calibration, name_param, - conc_param) - gfile.write(title + "\n") - gfile.write("=" * len(title) + "\n") - gfile.write("\n") - with open("{}/{}".format(run_path, entry), "r") as ifile: - skip_next = False - for line in ifile.readlines(): - if skip_next: - skip_next = False - continue - if conc_param == "None": - gfile.write(line) - elif " ".join(calibration.split()) != " ".join( - line.split()): - gfile.write(line) - else: - skip_next = True - - gfile.write("\n\n") - if isdir("{}/{}".format(run_path, entry)): - copytree("{}/{}".format(run_path, entry), - "{}/{}".format(sphinx_path, entry)) - return sphinx_path - - -def prepare_plots(run_path, threshold=1000000): - """ - Convert svg file to pdf or png to be used for latex - - Conversion of svg to vector graphics pdf is performed using svglib3. - This procedure is CPU consuming. In order to speed up process - large svg files are converted to png format. - - The links in the rst files are adapted accordingly to the - converted image files. - - :param run_path: Run path of the slurm job - :param threshold: Max svg file size (in bytes) to be converted to pdf - """ - print('Convert svg to pdf and png') - run_path = path.abspath(run_path) - - rst_files = glob('{}/*rst'.format(run_path)) - for rst_file in rst_files: - rst_file_name = path.basename(rst_file) - rst_file_name = path.splitext(rst_file_name)[0] - - svg_files = glob( - '{}/{}_files/*svg'.format(run_path, rst_file_name)) - for f_path in svg_files: - f_name = path.basename(f_path) - f_name = path.splitext(f_name)[0] - - if (stat(f_path)).st_size < threshold: - check_call(["svg2pdf", "{}".format(f_path)], shell=False) - new_ext = 'pdf' - else: - check_call(["convert", "{}".format(f_path), - "{}.png".format(f_name)], shell=False) - new_ext = 'png' - - check_call(["sed", - "-i", - "s/{}.svg/{}.{}/g".format(f_name, f_name, new_ext), - rst_file], - shell=False) - - -def make_timing_summary(run_path, joblist): - """ - Create an rst file with timing summary of executed slurm jobs - - :param run_path: Run path of the slurm job - :param joblist: List of slurm jobs - """ - print('Prepare timing summary') - run_path = path.abspath(run_path) - pars_vals = [] - pars = 'JobID,Elapsed,Suspended' - pars_name = pars.split(',') - - for job in joblist: - out = check_output(['sacct', '-j', job, - '--format={}'.format(pars)], - shell=False) - lines = str(out).split('\\n') - - # loop over output lines, skip first two lines with header. - for line in lines[2:]: - s = line.split() - if len(s) == len(pars_name): - pars_vals.append(s) - break - - tmpl = Template(''' - Runtime summary - ============== - - .. math:: - {% for line in table %} - {{ line }} - {%- endfor %} - ''') - - with open("{}/timing_summary.rst".format(run_path), "w+") as gfile: - - if len(pars_vals) > 0: - table = tabulate.tabulate(pars_vals, tablefmt='latex', - headers=pars_name) - gfile.write(dedent(tmpl.render(table=table.split('\n')))) - - -def make_report(run_path, tmp_path, out_path, project, author, version, - report_to): - """ - Create calibration report (pdf file) - - Automatically generated report document results, produced by - jupyter-notebooks. - - :param run_path: Path to sphinx run directory - :param tmp_path: Run path of the slurm job - :param out_path: Output directory for report. - Overwritten if path to report is given in `report_to` - :param project: Project title - :param author: Author of the notebook - :param version: Version of the notebook - :param report_to: Name or path of the report file - """ - run_path = path.abspath(run_path) - report_path, report_name = path.split(report_to) - if report_path != '': - out_path = report_path - - try: - check_call(["sphinx-quickstart", - "--quiet", - "--project='{}'".format(project), - "--author='{}'".format(author), - "-v", str(version), - "--suffix=.rst", - "--master=index", - "--ext-intersphinx", - "--ext-mathjax", - "--makefile", - "--no-batchfile", run_path]) - - except CalledProcessError: - raise Exception("Failed to run sphinx-quickbuild. Is sphinx installed?" - "Generated simple index.rst instead") - - # quickbuild went well we need to edit the index.rst and conf.py files - module_path = "{}".format(path.abspath(path.dirname(__file__))) - - conf = SourceFileLoader("conf", - "{}/conf.py".format(run_path)).load_module() - l_var = [v for v in dir(conf) if not v.startswith('__')] - - with open("{}/conf.py.tmp".format(run_path), "w") as mf: - latex_elements = {'extraclassoptions': ',openany, oneside', - 'preamble': r'\usepackage{longtable}', - 'maketitle': r'\input{titlepage.tex.txt}'} - mf.write("latex_elements = {}\n".format(latex_elements)) - mf.write("latex_logo = '{}/{}'\n".format(module_path, - settings.logo_path)) - mf.write("latex_additional_files = ['titlepage.tex.txt']\n") - - for var in l_var: - if var in ['latex_elements', 'latex_logo', - 'latex_additional_files']: - continue - tmpl = '{} = {}\n' - v = getattr(conf, var, None) - if isinstance(v, str): - tmpl = '{} = "{}"\n' - - # Set name of the latex document - if var == 'latex_documents' and len(v[0]) > 1: - v[0] = v[0][:1] + ('{}.tex'.format(report_name), ) + v[0][2:] - - mf.write(tmpl.format(var, v)) - - remove("{}/conf.py".format(run_path)) - move("{}/conf.py.tmp".format(run_path), "{}/conf.py".format(run_path)) - - direntries = listdir(run_path) - files_to_handle = [] - for entry in direntries: - if isfile("{}/{}".format(run_path, entry)): - name, ext = splitext("{}".format(entry)) - if ext == ".rst" and "index" not in name: - files_to_handle.append(name.strip()) - - index_tmp = Template(''' - Calibration report - ================== - - .. toctree:: - :maxdepth: 2 - {% for k in keys %} - {{ k }} - {%- endfor %} - ''') - - with open("{}/index.rst".format(run_path), "w+") as mf: - mf.write(dedent(index_tmp.render(keys=files_to_handle))) - - # finally call the make scripts - chdir(run_path) - try: - check_call(["make", "latexpdf"]) - - except CalledProcessError: - print("Failed to make pdf documentation") - print("Temp files will not be deleted and " + - "can be inspected at: {}".format(run_path)) - return - print("Moving report to final location: {}".format(out_path)) - copy('{}/_build/latex/{}.pdf'.format(run_path, report_name), out_path) - print("Removing temporary files at: {}".format(tmp_path)) - rmtree(tmp_path) - - -def make_titlepage(sphinx_path, project, data_path, version): - """ - Create title page for report using template - - :param sphinx_path: path to sphinx run directory - :param project: title of the project - :param data_path: path to input data sample used for notebook - :param version: Version of the pycalibration tool - """ - module_path = "{}".format(path.abspath(path.dirname(__file__))) - with open('{}/titlepage.tmpl'.format(module_path)) as file_: - title_tmp = Template(file_.read()) - - with open("{}/titlepage.tex.txt".format(sphinx_path), "w+") as mf: - mf.write(dedent(title_tmp.render(project=tex_escape(project), - data_path=tex_escape(data_path), - version=tex_escape(version)))) - - -def finalize(joblist, finaljob, run_path, out_path, project, calibration, - author, version, report_to, data_path='Unknown'): - print("Waiting on jobs to finish: {}".format(joblist)) - while True: - found_jobs = set() - output = check_output(['squeue']).decode('utf8') - for line in output.split("\n"): - for job in joblist: - if str(job) in line: - found_jobs.add(job) - if len(found_jobs) == 0: - break - sleep(10) - - prepare_plots(run_path) - make_timing_summary(run_path, joblist + [str(finaljob)]) - sphinx_path = combine_report(run_path, calibration) - make_titlepage(sphinx_path, project, data_path, version) - make_report(sphinx_path, run_path, out_path, project, author, version, - report_to) - def parse_runs(runs, return_type=str): pruns = [] @@ -716,30 +403,3 @@ def get_constant_from_db_and_time(device, constant, condition, empty_constant, return data, m.calibration_constant_version.begin_at else: return data, None - - -def tex_escape(text): - """ - Escape latex special characters found in the text - - :param text: a plain text message - :return: the message escaped to appear correctly in LaTeX - """ - conv = { - '&': r'\&', - '%': r'\%', - '$': r'\$', - '#': r'\#', - '_': r'\_', - '{': r'\{', - '}': r'\}', - '~': r'\textasciitilde{}', - '^': r'\^{}', - '\\': r'\textbackslash{}', - '<': r'\textless{}', - '>': r'\textgreater{}', - } - - key_list = sorted(conv.keys(), key=lambda item: - len(item)) - regex = re.compile('|'.join(re.escape(str(key)) for key in key_list)) - return regex.sub(lambda match: conv[match.group()], text) diff --git a/cal_tools/setup.py b/cal_tools/setup.py index e1150aae3ad5b9b063a8e9dad3af3d5ddd77b803..37538c3bbf3a1e82006bee390c5ddb18ef5a204e 100644 --- a/cal_tools/setup.py +++ b/cal_tools/setup.py @@ -4,7 +4,6 @@ setup( name='calTools', version='0.1', packages=['cal_tools',], - package_data={'': ['titlepage.tmpl', 'xfel.pdf']}, url='', license='(c) European XFEL GmbH', author='Steffen Hauf', diff --git a/setup.py b/setup.py index 391d522db505b3e17d87e34e4e37f335c12f3aee..ee79e5426858d55ea47fff3b9be7d2525c7786eb 100644 --- a/setup.py +++ b/setup.py @@ -52,7 +52,8 @@ setup( 'xfel_calibrate.notebooks': 'xfel_calibrate/notebooks', }, package_data={ - 'xfel_calibrate': ['bin/*.sh']+data_files + 'xfel_calibrate': ['bin/*.sh'] + data_files + ['titlepage.tmpl', + 'xfel.pdf'] }, cmdclass={ diff --git a/xfel_calibrate/calibrate.py b/xfel_calibrate/calibrate.py index 0bed5ab99a5cd7f5dbd122d4d7560443e79645a4..08311e921820fe795d142e3c42f575d96508d681 100755 --- a/xfel_calibrate/calibrate.py +++ b/xfel_calibrate/calibrate.py @@ -19,9 +19,10 @@ import warnings from .settings import * from .notebooks import notebooks from jinja2 import Template +import stat import textwrap -from cal_tools.tools import tex_escape +from .finalize import tex_escape # Add a class combining raw description formatting with @@ -491,6 +492,36 @@ def set_figure_format(nb, enable_vector_format): cell.source += "\n%config InlineBackend.figure_formats = ['svg']\n" +def create_finalize_script(fmt_args, temp_path, job_list): + """ + Create a finalize script to produce output report + :param fmt_args: Dictionary of fmt arguments + :param temp_path: Path to temopary folder to run slurm job + :param job_list: List of slurm jobs + """ + tmpl = Template(''' + #!/bin/tcsh + source /etc/profile.d/modules.sh + module load texlive + echo 'Running finalize script' + python3 -c "from xfel_calibrate.finalize import finalize; + finalize({{joblist}}, $1, '{{run_path}}', '{{out_path}}', + '{{project}}', '{{calibration}}', '{{author}}', + '{{version}}', '{{report_to}}', '{{in_folder}}' )" + + ''') + + fmt_args['joblist'] = job_list + f_name = "{}/finalize.sh".format(temp_path) + with open(f_name, "w") as finfile: + finfile.write(textwrap.dedent(tmpl.render(**fmt_args))) + + # change rights of the file to be: + # executed and writable for user, readable for user, group and others + all_stats = stat.S_IXUSR | stat.S_IWUSR | stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH + os.chmod(f_name, all_stats) + + def get_launcher_command(args, temp_path, dependent, job_list): """ Return a slurm launcher command @@ -529,16 +560,16 @@ def get_launcher_command(args, temp_path, dependent, job_list): launcher_slurm += " --mem {}G".format(args.get('slurm_mem', '500')) if dependent: - srun_dep = "--dependency=afterok" + srun_dep = " --dependency=afterok" for jobid in job_list: srun_dep += ":{}".format(jobid) - launcher_slurm += [srun_dep] + launcher_slurm += srun_dep return launcher_slurm.split() def concurrent_run(temp_path, nb, nbname, args, cparm=None, cval=None, - final_job=False, job_list=[], fmtcmd="", cluster_cores=8, + final_job=False, job_list=[], fmt_args={}, cluster_cores=8, sequential=False, dependent=False, show_title=True): """ Launch a concurrent job on the cluster via SLURM @@ -570,16 +601,7 @@ def concurrent_run(temp_path, nb, nbname, args, cparm=None, cval=None, # add finalization to the last job if final_job: - import stat - with open("{}/finalize.sh".format(temp_path), "w") as finfile: - finfile.write("#!/bin/tcsh\n") - finfile.write("source /etc/profile.d/modules.sh\n") - finfile.write("module load texlive\n") - finfile.write("echo 'Running finalize script'\n") - finfile.write( - "python3 -c {}\n".format(fmtcmd.format(joblist=job_list))) - all_stats = stat.S_IXUSR | stat.S_IWUSR | stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH - os.chmod("{}/finalize.sh".format(temp_path), all_stats) + create_finalize_script(fmt_args, temp_path, job_list) # then run an sbatch job srun_base = [] @@ -780,10 +802,6 @@ def run(): out_path)) os.makedirs(out_path, exist_ok=True) - cmd = ('"from cal_tools.tools import finalize; ' + - 'finalize({{joblist}}, $1, \'{run_path}\', \'{out_path}\', ' + - '\'{project}\', \'{calibration}\', \'{author}\', ' - '\'{version}\', \'{report_to}\', \'{in_folder}\' )"') report_to = title.replace(" ", "") if args["report_to"] is not None: @@ -791,10 +809,14 @@ def run(): folder = get_par_attr(parms, 'in_folder', 'value', '') - fmtcmd = cmd.format(run_path=run_tmp_path, out_path=out_path, - project=title, calibration=title, - author=author, version=version, - report_to=report_to, in_folder=folder) + fmt_args = {'run_path': run_tmp_path, + 'out_path': out_path, + 'project': title, + 'calibration': title, + 'author': author, + 'version': version, + 'report_to': report_to, + 'in_folder': folder} joblist = [] if concurrency.get("parameter", None) is None: @@ -803,7 +825,7 @@ def run(): jobid = concurrent_run(run_tmp_path, nb, os.path.basename(notebook), args, final_job=True, job_list=joblist, - fmtcmd=fmtcmd, + fmt_args=fmt_args, cluster_cores=cluster_cores, sequential=sequential) @@ -865,7 +887,7 @@ def run(): jobid = concurrent_run(run_tmp_path, nb, notebook, args, cvar, cval, final_job, - joblist, fmtcmd, + joblist, fmt_args, cluster_cores=cluster_cores, sequential=sequential, show_title=show_title) @@ -882,7 +904,7 @@ def run(): os.path.basename(notebook), args, final_job=final_job, - job_list=joblist, fmtcmd=fmtcmd, + job_list=joblist, fmt_args=fmt_args, cluster_cores=cluster_cores, sequential=sequential, dependent=True) diff --git a/xfel_calibrate/finalize.py b/xfel_calibrate/finalize.py new file mode 100644 index 0000000000000000000000000000000000000000..d37b83f6cfa6403852378181a62af8a7fad5c06b --- /dev/null +++ b/xfel_calibrate/finalize.py @@ -0,0 +1,345 @@ +from glob import glob +from importlib.machinery import SourceFileLoader +from os import chdir, listdir, makedirs, path, remove, stat +from os.path import isdir, isfile, splitext +import re +from shutil import copy, copytree, move, rmtree +from subprocess import CalledProcessError, check_call, check_output +from textwrap import dedent +from time import sleep + +import tabulate +from jinja2 import Template + +from .settings import * + +def atoi(text): + """ + Convert string to integer is possible + + :param text: string to be converted + :return: integer value or input string + """ + + return int(text) if text.isdigit() else text + + +def natural_keys(text): + """ + Decompose string to list of integers and sub-strings + """ + return [atoi(c) for c in re.split(r'(\d+)', text)] + + +def combine_report(run_path, calibration): + sphinx_path = "{}/sphinx_rep".format(path.abspath(run_path)) + makedirs(sphinx_path) + direntries = listdir(run_path) + direntries.sort(key=natural_keys) + + for entry in direntries: + + if isfile("{}/{}".format(run_path, entry)): + name, ext = splitext("{}".format(entry)) + + if ext == ".rst": + comps = name.split("__") + if len(comps) >= 3: + group, name_param, conc_param = "_".join(comps[:-2]), \ + comps[-2], comps[-1] + else: + group, name_param, conc_param = comps[0], "None", "None" + + with open("{}/{}.rst".format(sphinx_path, group), + "a") as gfile: + if conc_param != "None": + title = "{}, {} = {}".format(calibration, name_param, + conc_param) + gfile.write(title + "\n") + gfile.write("=" * len(title) + "\n") + gfile.write("\n") + with open("{}/{}".format(run_path, entry), "r") as ifile: + skip_next = False + for line in ifile.readlines(): + if skip_next: + skip_next = False + continue + if conc_param == "None": + gfile.write(line) + elif " ".join(calibration.split()) != " ".join( + line.split()): + gfile.write(line) + else: + skip_next = True + + gfile.write("\n\n") + if isdir("{}/{}".format(run_path, entry)): + copytree("{}/{}".format(run_path, entry), + "{}/{}".format(sphinx_path, entry)) + return sphinx_path + + +def prepare_plots(run_path, threshold=1000000): + """ + Convert svg file to pdf or png to be used for latex + + Conversion of svg to vector graphics pdf is performed using svglib3. + This procedure is CPU consuming. In order to speed up process + large svg files are converted to png format. + + The links in the rst files are adapted accordingly to the + converted image files. + + :param run_path: Run path of the slurm job + :param threshold: Max svg file size (in bytes) to be converted to pdf + """ + print('Convert svg to pdf and png') + run_path = path.abspath(run_path) + + rst_files = glob('{}/*rst'.format(run_path)) + for rst_file in rst_files: + rst_file_name = path.basename(rst_file) + rst_file_name = path.splitext(rst_file_name)[0] + + svg_files = glob( + '{}/{}_files/*svg'.format(run_path, rst_file_name)) + for f_path in svg_files: + f_name = path.basename(f_path) + f_name = path.splitext(f_name)[0] + + if (stat(f_path)).st_size < threshold: + check_call(["svg2pdf", "{}".format(f_path)], shell=False) + new_ext = 'pdf' + else: + check_call(["convert", "{}".format(f_path), + "{}.png".format(f_name)], shell=False) + new_ext = 'png' + + check_call(["sed", + "-i", + "s/{}.svg/{}.{}/g".format(f_name, f_name, new_ext), + rst_file], + shell=False) + + +def make_timing_summary(run_path, joblist): + """ + Create an rst file with timing summary of executed slurm jobs + + :param run_path: Run path of the slurm job + :param joblist: List of slurm jobs + """ + print('Prepare timing summary') + run_path = path.abspath(run_path) + pars_vals = [] + pars = 'JobID,Elapsed,Suspended' + pars_name = pars.split(',') + + for job in joblist: + out = check_output(['sacct', '-j', job, + '--format={}'.format(pars)], + shell=False) + lines = str(out).split('\\n') + + # loop over output lines, skip first two lines with header. + for line in lines[2:]: + s = line.split() + if len(s) == len(pars_name): + pars_vals.append(s) + break + + tmpl = Template(''' + Runtime summary + ============== + + .. math:: + {% for line in table %} + {{ line }} + {%- endfor %} + ''') + + with open("{}/timing_summary.rst".format(run_path), "w+") as gfile: + + if len(pars_vals) > 0: + table = tabulate.tabulate(pars_vals, tablefmt='latex', + headers=pars_name) + gfile.write(dedent(tmpl.render(table=table.split('\n')))) + + +def make_report(run_path, tmp_path, out_path, project, author, version, + report_to): + """ + Create calibration report (pdf file) + + Automatically generated report document results, produced by + jupyter-notebooks. + + :param run_path: Path to sphinx run directory + :param tmp_path: Run path of the slurm job + :param out_path: Output directory for report. + Overwritten if path to report is given in `report_to` + :param project: Project title + :param author: Author of the notebook + :param version: Version of the notebook + :param report_to: Name or path of the report file + """ + run_path = path.abspath(run_path) + report_path, report_name = path.split(report_to) + if report_path != '': + out_path = report_path + + try: + check_call(["sphinx-quickstart", + "--quiet", + "--project='{}'".format(project), + "--author='{}'".format(author), + "-v", str(version), + "--suffix=.rst", + "--master=index", + "--ext-intersphinx", + "--ext-mathjax", + "--makefile", + "--no-batchfile", run_path]) + + except CalledProcessError: + raise Exception("Failed to run sphinx-quickbuild. Is sphinx installed?" + "Generated simple index.rst instead") + + # quickbuild went well we need to edit the index.rst and conf.py files + module_path = "{}".format(path.abspath(path.dirname(__file__))) + + conf = SourceFileLoader("conf", + "{}/conf.py".format(run_path)).load_module() + l_var = [v for v in dir(conf) if not v.startswith('__')] + + with open("{}/conf.py.tmp".format(run_path), "w") as mf: + latex_elements = {'extraclassoptions': ',openany, oneside', + 'preamble': r'\usepackage{longtable}', + 'maketitle': r'\input{titlepage.tex.txt}'} + mf.write("latex_elements = {}\n".format(latex_elements)) + mf.write("latex_logo = '{}/{}'\n".format(module_path, + logo_path)) + mf.write("latex_additional_files = ['titlepage.tex.txt']\n") + + for var in l_var: + if var in ['latex_elements', 'latex_logo', + 'latex_additional_files']: + continue + tmpl = '{} = {}\n' + v = getattr(conf, var, None) + if isinstance(v, str): + tmpl = '{} = "{}"\n' + + # Set name of the latex document + if var == 'latex_documents' and len(v[0]) > 1: + v[0] = v[0][:1] + ('{}.tex'.format(report_name), ) + v[0][2:] + + mf.write(tmpl.format(var, v)) + + remove("{}/conf.py".format(run_path)) + move("{}/conf.py.tmp".format(run_path), "{}/conf.py".format(run_path)) + + direntries = listdir(run_path) + files_to_handle = [] + for entry in direntries: + if isfile("{}/{}".format(run_path, entry)): + name, ext = splitext("{}".format(entry)) + if ext == ".rst" and "index" not in name: + files_to_handle.append(name.strip()) + + index_tmp = Template(''' + Calibration report + ================== + + .. toctree:: + :maxdepth: 2 + {% for k in keys %} + {{ k }} + {%- endfor %} + ''') + + with open("{}/index.rst".format(run_path), "w+") as mf: + mf.write(dedent(index_tmp.render(keys=files_to_handle))) + + # finally call the make scripts + chdir(run_path) + try: + check_call(["make", "latexpdf"]) + + except CalledProcessError: + print("Failed to make pdf documentation") + print("Temp files will not be deleted and " + + "can be inspected at: {}".format(run_path)) + return + print("Moving report to final location: {}".format(out_path)) + copy('{}/_build/latex/{}.pdf'.format(run_path, report_name), out_path) + print("Removing temporary files at: {}".format(tmp_path)) + rmtree(tmp_path) + + +def make_titlepage(sphinx_path, project, data_path, version): + """ + Create title page for report using template + + :param sphinx_path: path to sphinx run directory + :param project: title of the project + :param data_path: path to input data sample used for notebook + :param version: Version of the pycalibration tool + """ + module_path = "{}".format(path.abspath(path.dirname(__file__))) + with open('{}/titlepage.tmpl'.format(module_path)) as file_: + title_tmp = Template(file_.read()) + + with open("{}/titlepage.tex.txt".format(sphinx_path), "w+") as mf: + mf.write(dedent(title_tmp.render(project=tex_escape(project), + data_path=tex_escape(data_path), + version=tex_escape(version)))) + + +def tex_escape(text): + """ + Escape latex special characters found in the text + + :param text: a plain text message + :return: the message escaped to appear correctly in LaTeX + """ + conv = { + '&': r'\&', + '%': r'\%', + '$': r'\$', + '#': r'\#', + '_': r'\_', + '{': r'\{', + '}': r'\}', + '~': r'\textasciitilde{}', + '^': r'\^{}', + '\\': r'\textbackslash{}', + '<': r'\textless{}', + '>': r'\textgreater{}', + } + + key_list = sorted(conv.keys(), key=lambda item: - len(item)) + regex = re.compile('|'.join(re.escape(str(key)) for key in key_list)) + return regex.sub(lambda match: conv[match.group()], text) + + +def finalize(joblist, finaljob, run_path, out_path, project, calibration, + author, version, report_to, data_path='Unknown'): + print("Waiting on jobs to finish: {}".format(joblist)) + while True: + found_jobs = set() + output = check_output(['squeue']).decode('utf8') + for line in output.split("\n"): + for job in joblist: + if str(job) in line: + found_jobs.add(job) + if len(found_jobs) == 0: + break + sleep(10) + + prepare_plots(run_path) + make_timing_summary(run_path, joblist + [str(finaljob)]) + sphinx_path = combine_report(run_path, calibration) + make_titlepage(sphinx_path, project, data_path, version) + make_report(sphinx_path, run_path, out_path, project, author, version, + report_to) diff --git a/cal_tools/cal_tools/titlepage.tmpl b/xfel_calibrate/titlepage.tmpl similarity index 100% rename from cal_tools/cal_tools/titlepage.tmpl rename to xfel_calibrate/titlepage.tmpl diff --git a/cal_tools/cal_tools/xfel.pdf b/xfel_calibrate/xfel.pdf similarity index 100% rename from cal_tools/cal_tools/xfel.pdf rename to xfel_calibrate/xfel.pdf