'''
This sub-module contains functions to write out an :class:`ImageMetaTag.ImageDict` to a webpage.
The webpages are made up of a single .html file, which is the page to be loaded to view the images.
Alongside this is a short ImageMetaTag javascript library held in a '.js' file
(currently held in a single file) and a .json file contain the :class:`ImageMetaTag.ImageDict`
tree strcuture as a JSON data strcuture.
To reduce file size, the JSON data structure can be compressed using zlib. If this is the case,
then the `pako javascript library <https://github.com/nodeca/pako>`_ is used restore the JSON
data in the browser.
This can either be done using write_full_page, to produce a page with just a set of
selectors to browse the ImageDict, or the different components can be added to a
page as it is being constructed (reading in an html template, for instance).
To write out a full page, use :func:`ImageMetaTag.webpage.write_full_page`.
If the latter, then the following sections are needed:
* :func:`ImageMetaTag.webpage.write_js_to_header` - writes out the javascript information\
to the html header
* :func:`ImageMetaTag.webpage.write_js_placeholders` - writes out the placeholders that\
the javascript will write images to.
* :func:`ImageMetaTag.webpage.write_json` - writes out the :class:`ImageMetaTag.ImageDict`\
as a json.dump to a json file
* :func:`ImageMetaTag.webpage.copy_required_js_css_etc` - copies required javascript library \
to the required location.
An easy example of creating a webpage, using an :class:`ImageMetaTag.ImageDict` is shown in
`simplest_image_dict.py <simple.html>`_
.. TIP:: At present, the only webpage style that can be produced is a set of horizontal dropdown\
menus, but more will hopefully be added soon.
.. moduleauthor:: Melissa Brooks https://github.com/melissaebrooks
(C) Crown copyright Met Office. All rights reserved.
Released under BSD 3-Clause License. See LICENSE for more details.
'''
import os, json, pdb, shutil, tempfile, copy, zlib
from multiprocessing import Pool
import numpy as np
import ImageMetaTag as imt
# single indent to be used on the output webpage
INDENT = ' '
LEN_INDENT = len(INDENT)
# for compressed json files, we use pako to inflate the data back to full size:
PAKO_JS_FILE = 'pako_inflate.js'
PAKO_RELEASE = '1.0.5'
PAKO_SOURCE_TAR = 'https://github.com/nodeca/pako/archive/{}.tar.gz'.format(PAKO_RELEASE)
IMG_COMP_JS_FILE = 'img_comparison_slider_styles.js'
IMG_COMP_STYLE = 'img_comparison_slider_styles.css'
[docs]
def write_full_page(img_dict, filepath, title, page_filename=None, tab_s_name=None,
preamble=None, postamble=None, postamble_no_imt_link=False,
compression=False,
initial_selectors=None, show_selector_names=False,
show_singleton_selectors=True, optgroups=None,
url_type='int', only_show_rel_url=False, verbose=False,
style='horiz dropdowns', write_intmed_tmpfile=False,
description=None, keywords=None, css=None,
load_err_msg=None,
last_img_in_list_is_slider=False,
last_img_still_show=False):
'''
Writes out an :class:`ImageMetaTag.ImageDict` as a webpage, to a given file location.
The files are created as temporary files and when complete they replace any files that
are currently in the specified location.
If the img_dict supplied is None, rather than the appropriate class, then a page will
be produced with the image selectors missing, and a message saying no images are available.
Currently only able to write out a page with horizontal dropdown menus, but other
webpage styles could be added.
Options:
* page_filename - the file name, within the directory (defaults to the name of the file) \
but can be set if tab_s_name is also used.
* tab_s_name : used to denote the name of the page, when it is used as a frame \
of a larger page.
* preamble : html text added at the top of the <body> text, but before the ImageMetaTag \
section. Can be quite extensive.
* postable : html text added after the ImageMetaTag section. A link to the ImageMetaTag \
documentation will be appended unless postamble_no_imt_link is True.
* postamble_no_imt_link : if True, no link to the ImageMetaTag documentation will be added \
to the postamble.
* initial_selectors - A list of initial values for the selectors, to be passed into \
:func:`ImageMetaTag.webpage.write_js_setup`.
* show_selector_names - switches on displaying the selector full names defined by the \
:class:`ImageMetaTag.ImageDict`.full_name_mapping
* show_singleton_selectors - When set to False, selectors that have only one element are \
not displayed (default=True).
* optgroups - The contents of selectors can be grouped together to make large lists \
more readable. This is passed into \
:func:`ImageMetaTag.webpage.write_js_to_header`.
* url_type - determines the type of URL at the bottom of the ImageMetaTag section. Can be \
'int' or 'str'.
* only_show_rel_url - If True, the wepage will only show relative urls in is link section.
* verbose - If True, stdout will be more verbose
* style - the style of output page to write, currently only 'horiz dropdowns' is valid
* write_intmed_tmpfile - If True, files are written out to temporary filenames and then \
moved when completed.
* description - html description metadata
* keywords - html keyword metadata
* compression - default False. If True, then the json data object will be compressed \
using zlib string compression. When read into the browser, we will use \
pako to inflate it (https://github.com/nodeca/pako)
* css - Optional CSS file used to style webpage. By default a small amount of css is \
written out in the page header.
* load_err_msg - additional message to show after 'Please wait while the page is loading'. \
default is None, but very large pages can crash with Internet Explorer so \
a message along the lines of this may be useful: 'If the page does not \
load correctly in Internet Explorer, please try using firefox or Chrome.'
* last_img_in_list_is_slider - for the 'horiz dropdowns' page style, when the image payload \
contains a list of images, then when this is True, the last \
images is used as an on overlay/slider on the other images.
* last_img_still_show - when last_img_in_list_is_slider applies a set of sliders, this \
toggles whether or not the last image is still shown, as a static \
image or not.
Returns a list of files that the the created webpage is dependent upon.
'''
page_dependencies = []
if not (isinstance(img_dict, imt.ImageDict) or img_dict is None):
raise ValueError('write_full_page works on an ImageMetaTag ImageDict.')
if page_filename is None:
page_filename = os.path.basename(filepath)
if not page_filename:
msg = 'filepath ({})" must specify a file (not a directory'
raise ValueError(msg.format(filepath))
# other files involved:
file_dir, file_name = os.path.split(filepath)
page_dependencies.append(file_name)
if img_dict is None:
json_files = []
else:
# now make sure the required javascript library is copied over to the file_dir:
js_css_files = copy_required_js_css_etc(file_dir, style,
compression=compression,
last_img_in_list_is_slider=last_img_in_list_is_slider)
page_dependencies.extend(js_css_files)
# we have real data to work with:
# this tests the dict has uniform_depth, which is needed for all current webpages.
dict_depth = img_dict.dict_depth(uniform_depth=True)
# work out what files we need to create:
file_name_no_ext = os.path.splitext(file_name)[0]
# json file to hold the image_dict branching data etc:
json_file_no_ext = os.path.join(file_dir, file_name_no_ext)
json_files = write_json(img_dict, json_file_no_ext, compression=compression)
# the final page is dependent on the final locations of the json files,
# relative to the html:
page_dependencies.extend([os.path.split(x[1])[1] for x in json_files])
# this is the internal name the different selectors, associated lists for the selectors, and
# the list of files (all with a numbered suffix):
selector_prefix = 'sel'
url_separator = '|'
# now write the actual output file:
if write_intmed_tmpfile:
# get a temporary file:
with tempfile.NamedTemporaryFile('w', suffix='.html', prefix='imt_tmppage_',
dir=file_dir, delete=False) as html_file_obj:
tmp_html_filepath = html_file_obj.name
filepath_to_write = tmp_html_filepath
else:
filepath_to_write = filepath
# start the indent:
ind = ''
# open the file - this is a nice and simple file so just use the with open...
with open(filepath_to_write, 'w') as out_file:
# write out the start of the file:
out_file.write('<!DOCTYPE html>\n')
out_file.write(ind + '<html>\n')
# increase the indent level:
ind = _indent_up_one(ind)
out_file.write(ind + '<head>\n')
ind = _indent_up_one(ind)
if title is not None:
out_file.write('{}<title>{}</title>\n'.format(ind, title))
out_str = ind+'<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">\n'
out_file.write(out_str)
if css:
# copy the css file into the file_dir (unless that's were it already is):
try:
shutil.copy(css, file_dir)
except shutil.Error as sh_err:
if imt.PY3:
if 'are the same file' in sh_err.__str__():
pass
else:
raise sh_err
else:
if 'are the same file' in sh_err.message:
pass
else:
raise sh_err
base_css = os.path.basename(css)
page_dependencies.append(base_css)
out_str = ind+'<link rel="stylesheet" type="text/css" href="{0}">\n'
out_file.write(out_str.format(base_css))
else:
if style == 'horiz dropdowns':
# write out a little css at the top:
css = '''{0}<style>
{0} body {{
{0} background-color: #ffffff;
{0} color: #000000;
{0} }}
{0} body, div, dl, dt, dd, li, h1, h2 {{
{0} margin: 0;
{0} padding: 0;
{0} }}
{0} h3, h4, h5, h6, pre, form, fieldset, input {{'
{0} margin: 0;
{0} padding: 0;
{0} }}
{0} textarea, p, blockquote {{
{0} margin: 0;
{0} padding: 0;
{0} }}
{0} th, td {{
{0} margin: 0;
{0} padding: 0;
{0} vertical-align: top;
{0} }}
{0} fieldset, img {{
{0} border: 0 none;
{0} vertical-align: top;
{0} }}
{0} body {{
{0} font: 12px Myriad,Helvetica,Tahoma,Arial,clean,sans-serif;
{0} *font-size: 75%;
{0} }}
{0} h1 {{
{0} font-size: 1.5em;
{0} font-weight: normal;
{0} line-height: 1em;
{0} margin-top: 1em;
{0} margin-bottom:0;
{0} }}
{0} h2 {{
{0} font-size: 1.1667em;
{0} font-weight: bold;
{0} line-height: 1.286em;
{0} margin-top: 1.929em;
{0} margin-bottom:0.643em;
{0} }}
{0} h3, h4, h5, h6 {{
{0} font-size: 1em;
{0} font-weight: bold;
{0} line-height: 1.5em;
{0} margin-top: 1.5em;
{0} margin-bottom: 0;
{0} }}
{0} p {{
{0} font-size: 1em;
{0} margin-top: 1.5em;
{0} margin-bottom: 1.5em;
{0} line-height: 1.5em;
{0} }}
{0} pre, code {{
{0} font-size:115%;
{0} *font-size:100%;
{0} font-family: Courier, "Courier New";
{0} background-color: #efefef;
{0} border: 1px solid #ccc;
{0} }}
{0} pre {{
{0} border-width: 1px 0;
{0} padding: 1.5em;
{0} }}
{0} table {{
{0} font-size:100%;
{0} }}
{0}</style>
'''
out_file.write(css.format(ind))
# now write out the specific stuff to the html header:
if img_dict is None:
# an empty img_dict needs very little:
write_js_to_header(img_dict,
file_obj=out_file,
pagename=page_filename, tabname=tab_s_name,
ind=ind,
description=description, keywords=keywords)
else:
# the json_files is a list of (tmp_file, final_file) tuples.
# Here we want the final one:
final_json_files = [os.path.split(x[1])[1] for x in json_files]
write_js_to_header(img_dict, initial_selectors=initial_selectors, optgroups=optgroups,
file_obj=out_file, json_files=final_json_files, js_css_files=js_css_files,
pagename=page_filename, tabname=tab_s_name,
selector_prefix=selector_prefix, url_separator=url_separator,
show_singleton_selectors=show_singleton_selectors,
url_type=url_type, only_show_rel_url=only_show_rel_url,
style=style, ind=ind, compression=compression,
last_img_in_list_is_slider=last_img_in_list_is_slider,
last_img_still_show=last_img_still_show,
description=description, keywords=keywords)
# now close the script and head:
ind = _indent_down_one(ind)
out_file.write(ind + '</script>\n')
ind = _indent_down_one(ind)
out_file.write(ind + '</head>\n')
# now start the body:
out_file.write('{}<body>\n'.format(ind))
# the preamble is the first thing to go in the body:
if preamble is not None:
out_file.write(preamble + '\n')
# now the img_dict content:
if img_dict is None:
out_file.write('<p><h1>No images are available for this page.</h1></p>')
else:
# now write out the end, which includes the placeholders for the actual
# stuff that appears on the page:
if show_selector_names:
level_names = img_dict.level_names
else:
level_names = False
# if we're labelling selectors, and we have an animator button, label that too:
if img_dict.selector_animated > 1 and show_selector_names:
anim_level = level_names[img_dict.selector_animated]
else:
anim_level = None
write_js_placeholders(img_dict, file_obj=out_file, dict_depth=img_dict.dict_depth(),
style=style, level_names=level_names,
show_singleton_selectors=show_singleton_selectors,
last_img_in_list_is_slider=last_img_in_list_is_slider,
animated_level=anim_level, load_err_msg=load_err_msg)
# the body is done, so the postamble comes in:
postamble_endline = 'Page created with <a href="{}">ImageMetaTag {}</a>'
postamble_endline = postamble_endline.format(imt.__documentation__, imt.__version__)
if not postamble_no_imt_link:
if postamble is None:
postamble = postamble_endline
else:
postamble = '{}\n{}'.format(postamble, postamble_endline)
if postamble is not None:
out_file.write(postamble + '\n')
# finish the body, and html:
out_file.write(ind + '</body>\n')
out_file.write('\n</html>')
if write_intmed_tmpfile:
tmp_files_to_mv = json_files + [(tmp_html_filepath, filepath)]
else:
tmp_files_to_mv = json_files
for tmp_file_mv in tmp_files_to_mv:
# now move the json, then the html files:
os.chmod(tmp_file_mv[0], 0o644)
shutil.move(tmp_file_mv[0], tmp_file_mv[1])
if verbose:
print('File "%s" complete.' % filepath)
return page_dependencies
# END of the imt specifc header content:
def write_js_setup_defaults(selector_prefix=None, list_prefix=None, file_list_name=None):
'''
this specifies defaults for the internal names the different selectors, associated lists for
the selectors, and the list of files (all with a numbered suffix)
'''
if selector_prefix is None:
selector_prefix = 'sel'
if list_prefix is None:
list_prefix = 'list'
if file_list_name is None:
file_list_name = 'file_list'
return (selector_prefix, list_prefix, file_list_name)
[docs]
def write_json(img_dict, file_name_no_ext, compression=False,
chunk_char_limit=1e7):
'''
Writes a json dump of the :class:`ImageMetaTag.ImageDict` tree strucuture
to a target file path.
Options:
* compression : If True, json is compressed using zlib compresion
* chunk_char_limit : large strings are split into chunks for memory efficency \
in the browser.
Returns a list of json files as (tempfile, final_file) tuples.
'''
def json_from_dict(in_dict):
'returns a json string from an input dict'
return json.dumps(in_dict, separators=(',', ':'))
if isinstance(img_dict, imt.ImageDict):
dict_as_json = json_from_dict(img_dict.dict)
elif isinstance(img_dict, str):
dict_as_json = img_dict
else:
raise ValueError('input img_dict is not an ImageMetaTag.ImageDict or string')
# file suffix:
suffix = '.json'
if compression:
suffix += '.zlib'
# the output files:
out_files = []
tmp_file_dir = os.path.split(file_name_no_ext)[0]
# use the maximum length of a single string per file:
n_chunks = np.ceil(len(dict_as_json) / chunk_char_limit)
n_chunks = int(n_chunks)
if n_chunks == 1:
# easy if it fits into a single file:
json_file = file_name_no_ext + suffix
if compression:
wrt_str, file_mode = compress_string(dict_as_json)
else:
wrt_str = dict_as_json
file_mode = 'w'
with tempfile.NamedTemporaryFile(file_mode, suffix='.json', prefix='imt_',
dir=tmp_file_dir, delete=False) as file_obj:
file_obj.write(wrt_str)
tmp_file_path = file_obj.name
# make a note of the outputs:
out_files.append((tmp_file_path, json_file))
else:
# find the appropriate depth at which to split the dict:
if not isinstance(img_dict, imt.ImageDict):
msg = 'Large data sets need to be supplied as an ImageDict, so they can be split'
raise ValueError(msg)
dict_depth = img_dict.dict_depth(uniform_depth=True)
if len(img_dict.keys) != dict_depth:
raise ValueError('Inconsistent depth and keys. Do the keys need relisting?')
# determine approximately how many splits will be obtained by breaking up the
# dictionary at each level, assuming the tree structure branches uniformly.
n_by_depth = []
for i_depth in range(dict_depth):
if i_depth == 0:
n_by_depth = [len(img_dict.keys[i_depth])]
else:
n_by_depth.append(len(img_dict.keys[i_depth]) * n_by_depth[-1])
if n_by_depth[-1] >= n_chunks:
break
# i_depth is an index, but depth is the number of levels, so needs one adding:
depth = i_depth + 1
# get all the combinations of keys that reach the required depth:
keys, array_inds = img_dict.dict_index_array(maxdepth=depth)
# now loop through the array_inds. Each one contains the indices of a valid path through
# the dict, to the required depth. Each subdict will be written to a separate .json file
paths = []
top_dict = {}
for i_json, path_inds in enumerate(array_inds):
# traverse to the subdict, given by the current path,
# storing the keys of the path along the way
subdict = img_dict.dict
path = []
for level, ind in enumerate(path_inds):
subdict = subdict[keys[level][ind]]
path.append(keys[level][ind])
# convert the subdict to .json
subdict_as_json = json_from_dict(subdict)
# and write this out:
json_file = '{}_{}{}'.format(file_name_no_ext, i_json, suffix)
if compression:
wrt_str, file_mode = compress_string(subdict_as_json)
else:
wrt_str = dict_as_json
file_mode = 'w'
with tempfile.NamedTemporaryFile(file_mode, suffix='.json', prefix='imt_',
dir=tmp_file_dir, delete=False) as file_obj:
file_obj.write(wrt_str)
tmp_file_path = file_obj.name
# make a note of the outputs:
out_files.append((tmp_file_path, json_file))
# now add this to the top level dict structure, so the final
# json file can pull them all togther:
path_dict = {path[-1]: '**FILE_{}**'.format(i_json)}
# go backwards, from the second from last element of the path, to add more;
for key in path[-2::-1]:
path_dict = {key: path_dict}
img_dict.dict_union(top_dict, path_dict)
# add it to the paths, as a cross reference:
paths.append(path)
# now create the final json file that combines the previous
# ones into a single usable object:
i_json += 1
# convert the subdict to .json
subdict_as_json = json_from_dict(top_dict)
# and write this out:
json_file = '{}_{}{}'.format(file_name_no_ext, i_json, suffix)
if compression:
wrt_str, file_mode = compress_string(subdict_as_json)
else:
wrt_str = dict_as_json
file_mode = 'w'
with tempfile.NamedTemporaryFile(file_mode, suffix='.json', prefix='imt_',
dir=tmp_file_dir, delete=False) as file_obj:
file_obj.write(wrt_str)
tmp_file_path = file_obj.name
# make a note of the outputs:
out_files.append((tmp_file_path, json_file))
return out_files
def compress_string(in_str):
'''
Compresses a string using zlib to a format that can be read with pako.
Returns both the compressed string and the file mode to use.
'''
if imt.PY3:
# python3, compress a byte:
#comp_str = str(zlib.compress(bytearray(in_str, 'utf-8')))
comp_str = zlib.compress(in_str.encode(encoding='utf=8'))
file_mode = 'wb'
else:
# python2, compress a string:
comp_str = zlib.compress(in_str)
file_mode = 'w'
return comp_str, file_mode
[docs]
def write_js_placeholders(img_dict, file_obj=None, dict_depth=None,
selector_prefix=None,
style='horiz dropdowns', level_names=False,
show_singleton_selectors=True,
last_img_in_list_is_slider=False,
animated_level=None, load_err_msg=None):
'''
Writes the placeholders into the page body, for the javascript to
manipulate
* file_obj - an open file object to write to
* dict_dept - the depth of the :class:`ImageMetaTag.ImageDict` \
being written
* selector_prefix - prefix for the variable names of the selectors \
(these are visible to people viewing the webpage!)
* style - In future, it would be great to write out different types of \
webpages. For now they are always horizontal dropdown menus.
* show_singleton_selectors - When set to False, selectors that have \
only one element are not displayed \
(default=True).
* level_names - if supplied, this need to be a list of full names, for \
the selectors, of length dict_depth.
* animated_level - if supplied, as a string, this will be used to label \
the animator buttons.
* load_err_msg - additional message to show after 'Please wait while the \
page is loading'. default is None, but very large pages \
can crash with Internet Explorer so a message along the \
lines of this may be useful: 'If the page does not load \
correctly in Internet Explorer, please try using \
firefox or Chrome.'
'''
if not show_singleton_selectors:
# work out which selectors we actually want to show:
show_sel = [len(img_dict.keys[x]) > 1 for x in range(dict_depth)]
if not any(show_sel):
# if there aren't any selectors in this way, that's usually a mistake
# so show them all:
show_sel = [True] * dict_depth
else:
show_sel = [True] * dict_depth
sels_shown = sum(show_sel)
if selector_prefix is None:
selector_prefix, _junk1, _junk2 = write_js_setup_defaults()
apply_level_names = False
if level_names:
if not isinstance(level_names, list):
raise ValueError('level_names needs to be a list of length dict_depth')
if len(level_names) != dict_depth:
raise ValueError('level_names needs to be a list, of length dict_depth')
apply_level_names = True
else:
apply_level_names = False
if style == 'horiz dropdowns':
file_obj.write('''
<!-- Now for some placeholders for the scripts to put content -->
<table border=0 cellspacing=0 cellpadding=0 width=99% align=center>
<tr>
<td>
<font size=3>''')
# a text label for the animator buttons:
if isinstance(animated_level, str):
anim_label = '{}: '.format(animated_level)
else:
anim_label = ''
# for each level of depth in the plot dictionary, add a span to hold the selector:
if apply_level_names:
# if we want labelled selectors, then write out
# a table, with pairs of label, selector, in columns:
file_obj.write('''
<table border=0 cellspacing=0 cellpadding=0 style='border-spacing: 3px 0;'>
<tr>
''')
for level in range(dict_depth):
if show_sel[level]:
file_obj.write(' <td>{} </td>\n'.format(level_names[level]))
file_obj.write(''' </tr>
<tr>
''')
for level in range(dict_depth):
if show_sel[level]:
selp = selector_prefix + str(level)
out_str = ' <td><span id="{}"> </span></td>\n'.format(selp)
file_obj.write(out_str)
file_obj.write(''' </tr>
''')
# add the placeholder for animators buttons:
file_obj.write(''' <tr>
<td colspan={}>
{}<span id="animator1"> </span>
<span id="animator2"> </span>
</td>
</tr>
</table>
'''.format(dict_depth, anim_label))
else:
# simply a set of spans, in a line:
for lev in range(dict_depth):
if show_sel[lev]:
file_obj.write('''
<span id="%s%s"> </span>''' % (selector_prefix, lev))
file_obj.write('\n <br>')
# add the placeholder for animators buttons:
file_obj.write('''
{}<span id="animator1"> </span>
<span id="animator2"> </span>
<br>
'''.format(anim_label))
if last_img_in_list_is_slider:
# adds in the default slider position, if needed:
file_obj.write('''
<div id='slider'>
Default slider position: <input type="range" min="1" max="100" value="80" class="slider" id="slider_default">
</div>
''')
# now add somewhere for the image to go:
if load_err_msg is None:
img_div = ' <div id="the_image">Please wait while the page is loading</div>'
file_obj.write(img_div)
else:
img_div = ' <div id="the_image">Please wait while the page is loading.<br>{}</div>'
file_obj.write(img_div.format(load_err_msg))
file_obj.write('\n <div id="the_url">....</div>')
# and finish off the placeholders:
file_obj.write('''
</font>
</td>
</tr>
</table>
''')
else:
raise ValueError('"%s" tyle of content placeholder not defined' % style)
[docs]
def copy_required_js_css_etc(file_dir, style, compression=False,
last_img_in_list_is_slider=False, overwrite=True):
'''
Copies the required javascript library to the directory
containing the required page (file_dir) for a given webpage style.
If a file is already present it will be checked based it's first line.
If the file is different, it will be overwritten if overwrite is True.
Also copies/obtains required javascript for reading files compressed
with zlib, if compression=True.
'''
if style == 'horiz dropdowns':
imt_js_to_copy = 'imt_dropdown.js'
# get this from the installed ImageMetaTag directory:
file_src_dir = os.path.join(imt.__path__[0], 'javascript')
first_line = '// ImageMetaTag dropdown menu scripting - vn{}\n'.format(imt.__version__)
else:
raise ValueError('Javascript library not set up for style: {}'.format(style))
if not os.path.isfile(os.path.join(file_dir, imt_js_to_copy)):
# file isn't in target dir, so copy it:
shutil.copy(os.path.join(file_src_dir, imt_js_to_copy),
os.path.join(file_dir, imt_js_to_copy))
else:
# the file is there, check it's right:
with open(os.path.join(file_dir, imt_js_to_copy)) as file_obj:
this_first_line = file_obj.readline()
if first_line == this_first_line:
# the file is good, move on:
pass
else:
if overwrite:
shutil.copy(os.path.join(file_src_dir, imt_js_to_copy),
os.path.join(file_dir, imt_js_to_copy))
else:
print('''File: {}/{} differs to the expected contents, but is
not being overwritten. Your webpage may be broken!'''.format(file_dir, imt_js_to_copy))
# make a list of all the required javascript files
js_css_files = [imt_js_to_copy]
# now move on to javascript dependencies from the compression:
if compression:
js_to_copy = PAKO_JS_FILE
js_src = os.path.join(file_src_dir, js_to_copy)
js_dest = os.path.join(file_dir, js_to_copy)
# if the file is already at destination, we're good:
if os.path.isfile(js_dest):
pass
else:
# is the required file in the javascript source directory:
if not os.path.isfile(js_src):
# we need to get the required javascript from source.
#
# if we have permission to write to teh file_src_dir then
# try to do so. This means it's installed for all uses from this
# install of ImageMetaTag:
if os.access(file_src_dir, os.W_OK):
pako_to_dir = file_src_dir
# now get pako:
get_pako(pako_to_dir=pako_to_dir)
# and copy it to where it's needed for this call:
shutil.copy(js_src, js_dest)
else:
# put pako js file into the target dir. At least it will
# be available for subsequent writes to that dir:
pako_to_dir = file_dir
# now get pako to that dir:
get_pako(pako_to_dir=pako_to_dir)
else:
# copy the file:
shutil.copy(js_src, js_dest)
# finally, make a note:
js_css_files.append(js_to_copy)
if last_img_in_list_is_slider:
files_to_copy = [IMG_COMP_STYLE, IMG_COMP_JS_FILE]
for js_to_copy in files_to_copy:
js_src = os.path.join(file_src_dir, js_to_copy)
js_dest = os.path.join(file_dir, js_to_copy)
# if the file is already at destination, we're good:
if os.path.isfile(js_dest):
pass
else:
# copy the file:
shutil.copy(js_src, js_dest)
# finally, make a note:
js_css_files.append(js_to_copy)
return js_css_files
def get_pako(pako_to_dir=None):
'''
Obtains the required pako javascript code from remote host, to a given
javascript directory. If the javascript dir is not supplied, then
the 'javascript' directory alongside the ImageMetaTag python code is used.
'''
import tarfile
from urllib.request import urlopen
# set up pako into the current imt_dir:
if pako_to_dir is None:
pako_to_dir = os.path.join(imt.__path__[0], 'javascript')
# Open the url
pako_urlopen = urlopen(PAKO_SOURCE_TAR)
print("downloading " + PAKO_SOURCE_TAR)
# Open our local file for writing
with tempfile.NamedTemporaryFile('w', suffix='.tar.gz', prefix='pako_',
delete=False) as local_file:
local_file.write(pako_urlopen.read())
targz_file = local_file.name
pako_urlopen.close()
# now extract the file we need:
with tarfile.open(name=targz_file, mode='r:gz') as tgz:
if not tarfile.is_tarfile:
raise ValueError('Downloaded pako tar.gz file cannot be read.')
target = 'pako-{}/dist/{}'.format(PAKO_RELEASE, PAKO_JS_FILE)
target_file = tgz.extractfile(target)
if target_file:
with open(os.path.join(pako_to_dir, PAKO_JS_FILE), 'w') as final_file:
for line in target_file:
final_file.write(line)
os.remove(targz_file)
def _indent_up_one(ind):
'increases the indent level of an input ind by one'
n_indents = int(len(ind) / LEN_INDENT)
return INDENT * (n_indents + 1)
def _indent_down_one(ind):
'decreases the indent level of an input ind by one'
n_indents = int(len(ind) / LEN_INDENT)
return INDENT * max(n_indents - 1, 0)
def _py_to_js_bool(py_bool):
'Converts a python boolean to a string, in javascript bool format (all lower case)'
if py_bool is True:
return 'true'
elif py_bool is False:
return 'false'
raise ValueError('input to _py_to_js_bool is not a boolean, it is: %s' % py_bool)