diff --git a/README.md b/README.md index d4366c2f1a77d70fce9d513910bcd79af230e694..1a374a3f9b457b31329eed6f7114ed1285bd3041 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ -# slider +# Slider -Slide overlay software based on beamer and inkscape \ No newline at end of file +Slide overlay software based on beamer and inkscape. This project is currently used in coursebox. +The software also offers a package for jinja2 (jinjafy) which offers a handful of convenient extensions. \ No newline at end of file diff --git a/inkscape2tex.py b/inkscape2tex.py new file mode 100644 index 0000000000000000000000000000000000000000..0b7e68465555c97f1f4f3169a8eb0e905e7aa13d --- /dev/null +++ b/inkscape2tex.py @@ -0,0 +1,183 @@ +# #!/usr/bin/env python +# """Convert inkscape SVG files to TeX input. +# +# - SVG to PDF or EPS with inkscape, optionally with LaTeX output. +# - DOT to SVG +# +# Skips conversion if PDF file found newer than SVG source. +# Requires `inkscape` in path. +# """ +# # Copyright 2010-2017 by Ioannis Filippidis +# # All rights reserved. Licensed under BSD-2. +# # +# import argparse +# import datetime +# import fnmatch +# import logging +# import os +# import shlex +# import subprocess +# import time +# +# import humanize +# +# import svg2latex as convert +# # from svglatex import convert +# +# +# log = logging.getLogger(__name__) +# +# +# def main(): +# """Start from here.""" +# args = parse_args() +# f = '{name}.svg'.format(name=args.input_file) +# out_type = args.method +# if './img/' in f: +# files = [f] +# else: +# files = locate(f, './img') +# svg = None +# for svg in files: +# log.info('Will convert SVG file "{f}" to {t}'.format( +# f=svg, t=out_type)) +# convert_if_svg_newer(svg, out_type) +# if svg is None: +# raise Exception( +# 'SVG file "{f}" not found! ' +# 'Cannot export to PDF.'.format(f=f)) +# +# +# def parse_args(): +# """Parse command-line arguments using.""" +# parser = argparse.ArgumentParser() +# parser.add_argument( +# '-i', '--input-file', type=str, +# help=( +# 'Name (w/o extension) of SVG file. ' +# 'Either file name to search for under `./img`, ' +# 'or path that starts with `./img`.')) +# choices = [ +# 'latex-pdf', 'pdf', +# 'latex-eps', 'eps'] +# parser.add_argument( +# '-m', '--method', type=str, choices=choices, +# help=( +# 'Export to this file type. ' +# 'The prefix "latex" produces also a file `*.pdf_tex` ' +# 'that contains the text from the SVG. ' +# 'The command `\includesvgpdf` passes `pdf`, ' +# 'and `\includesvg` passes `latex-pdf`.')) +# args = parser.parse_args() +# return args +# +# +# def convert_if_svg_newer(svg, out_type): +# """Convert SVG file to PDF or EPS.""" +# base, ext = os.path.splitext(svg) +# assert ext == '.svg', ext +# if 'pdf' in out_type: +# out = base + '.pdf' +# elif 'eps' in out_type: +# out = base + '.eps' +# else: +# raise ValueError(out_type) +# if not os.access(svg, os.F_OK): +# raise FileNotFoundError( +# 'No SVG file "{f}"'.format(f=svg)) +# fresh = is_newer(out, svg) +# if out_type == 'latex-pdf': +# pdf_tex = base + '.pdf_tex' +# fresh &= is_newer(pdf_tex, svg) +# if fresh: +# log.info('No update needed, target newer than SVG.') +# return +# log.info('File not found or old. Converting from SVG...') +# convert_svg(svg, out, out_type) +# +# +# def is_newer(target, source): +# """Return `True` if `target` newer than `source` file.""" +# assert os.path.isfile(source), source +# if not os.path.isfile(target): +# return False +# t_src = os.stat(source)[8] +# t_tgt = os.stat(target)[8] +# _print_dates(source, target, t_src, t_tgt) +# return t_src < t_tgt +# +# +# def _print_dates(source, target, t_src, t_tgt): +# s = _format_time(t_src) +# t = _format_time(t_tgt) +# log.info(( +# 'last modification dates:\n' +# ' Source ({source}): {s}\n' +# ' Target ({target}): {t}').format( +# source=source, target=target, +# s=s, t=t)) +# +# +# def _format_time(t): +# """Return time readable by humans.""" +# return humanize.naturaltime( +# datetime.datetime.fromtimestamp(t)) +# +# +# def convert_svg(svg, out, out_type): +# """Convert from SVG to output format.""" +# # TODO: implement options `latex-eps`, `eps` +# assert out_type in ('latex-pdf', 'pdf'), out_type +# if out_type == 'latex-pdf': +# convert.main(svg) +# elif out_type == 'pdf': +# inkscape = convert.which_inkscape() +# svg_path = os.path.realpath(svg) +# out_path = os.path.realpath(out) +# args = [ +# inkscape, +# '--without-gui', +# '--export-area-drawing', +# '--export-ignore-filters', +# '--export-dpi={dpi}'.format(dpi=96), +# '--export-pdf={out}'.format(out=out_path), +# svg_path] +# r = subprocess.call(args) +# if r != 0: +# raise Exception('Conversion error') +# +# +# def convert_svg_using_inkscape(svg, out, out_type): +# """Convert from SVG to output format.""" +# # inkscape need be called with an absolute path on OS X +# # http://wiki.inkscape.org/wiki/index.php/MacOS_X +# symlink_relpath = 'bin/inkscape' +# home = os.path.expanduser('~') +# symlink_abspath = os.path.join(home, symlink_relpath) +# inkscape_abspath = os.path.realpath(symlink_abspath) +# svg_abspath = os.path.realpath(svg) +# args = ['{inkscape_abspath} -z -D --file={svg}'.format( +# inkscape_abspath=inkscape_abspath, svg=svg_abspath)] +# if 'pdf' in out_type: +# args.append('--export-pdf={pdf}'.format(pdf=out)) +# if 'eps' in out_type: +# args.append('--export-eps={eps}'.format(eps=out)) +# if 'latex' in out_type: +# args.append('--export-latex') +# args = shlex.split(' '.join(args)) +# r = subprocess.call(args) +# if r != 0: +# raise Exception( +# 'conversion from "{svg}" to "{out}" failed'.format( +# svg=svg, out=out)) +# +# +# def locate(pattern, root=os.curdir): +# """Locate all files matching supplied filename pattern under `root`.""" +# for path, dirs, files in os.walk(os.path.abspath(root)): +# for filename in fnmatch.filter(files, pattern): +# yield os.path.join(path, filename) +# +# +# if __name__ == '__main__': +# main() diff --git a/setup.py b/setup.py index 0ebabc692c7c2d72bbd0aa9ad88dcd7a418dd6bf..0076be64071cdafef02c2781ce15af8de75b86af 100644 --- a/setup.py +++ b/setup.py @@ -27,5 +27,5 @@ setuptools.setup( package_dir={"": "src"}, packages=setuptools.find_packages(where="src"), python_requires=">=3.8", - install_requires=['jinja2',], + install_requires=['jinja2', 'numpy', 'scipy', 'bs4', 'lxml', 'codecs', 'optparse', 'PyPDF2', 'pickle'], ) diff --git a/src/jinjafy/__init__.py b/src/jinjafy/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8a00d8cd422222546dfd99ba2f052660bc562784 --- /dev/null +++ b/src/jinjafy/__init__.py @@ -0,0 +1,26 @@ +from jinjafy.jinjafy import jinjafy_comment +from jinjafy.jinjafy import jinjafy_template +from jinjafy.jinja_matlab_load import matlab_load +from jinjafy.textools import mat2table +import subprocess +# from subprocess import subprocess + +# def get_system_name(): +# if is_win(): +# return "Win" +# if is_compute(): +# return "thinlinc.compute.dtu.dk" +# if is_cogsys_cluster(): +# return "cogys cluster" + +def execute_command(command, shell=True): + if not isinstance(command, list): + command = [command] + # if not is_compute(): + # result = subprocess.run(command, stdout=subprocess.PIPE, shell=shell) + # out = result.stdout + # else: + out = subprocess.check_output(command, shell=shell) + s = out.decode("utf-8") + OK = True + return s, OK \ No newline at end of file diff --git a/src/jinjafy/cache/__init__.py b/src/jinjafy/cache/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a2e522c2f3baf4762462cd8dac312e446a3e4323 --- /dev/null +++ b/src/jinjafy/cache/__init__.py @@ -0,0 +1,8 @@ +from thtools.cache.simplecache import cache_update_str as cache_update_str +from thtools.cache.simplecache import cache_contains_str as cache_contains_str + +from thtools.cache.simplecache import cache_update_file as cache_update_file +from thtools.cache.simplecache import cache_contains_file as cache_contains_file + +from thtools.cache.simplecache import cache_update_dir as cache_update_dir +from thtools.cache.simplecache import cache_contains_dir as cache_contains_dir \ No newline at end of file diff --git a/src/jinjafy/cache/simplecache.py b/src/jinjafy/cache/simplecache.py new file mode 100644 index 0000000000000000000000000000000000000000..e24ab194c44642e727a0eae4ce6e281f3a607fd8 --- /dev/null +++ b/src/jinjafy/cache/simplecache.py @@ -0,0 +1,94 @@ +from hashlib import md5 +import os +import pickle +import glob + +def dir_content_cache_(dir, pattern="*"): + fl = glob.glob(dir + "/" + pattern) + s = ''.join(fl) + key = "key_"+dir + return fl, s,key + +def cache_contains_dir(cache_base, dir, pattern="*"): + # fl = glob.glob(dir) + fl,s,key = dir_content_cache_(dir, pattern=pattern) + + v = [cache_contains_file(cache_base, f) for f in fl] + if all(v) and cache_contains_str(cache_base, key, s): + return True + return False + +def cache_update_dir(cache_base, dir, pattern="*"): + fl, s, key = dir_content_cache_(dir, pattern=pattern) + cache_update_str(cache_base, key, s) + for f in fl: + cache_update_file(cache_base, f) + + +def cache_contains_str(cache_base,key=None,value=None): + assert(key or value) + value = hash_binary_(value.encode()) + if not key: key = value + return cache_contains_hash(cache_base, key, value) + +def cache_update_str(cache_base,key,value): + assert(key or value) + value = hash_binary_(value.encode()) + if not key: key = value + return cache_update_hash(cache_base, key, value) + + +def cache_contains_file(cache_base,file): + key = os.path.abspath(file) + if not os.path.exists(file): + return False + value = hash_file_(file) + return cache_contains_hash(cache_base, key, value) + +def hash_file_(file): + import hashlib + hasher = hashlib.md5() + with open(file, 'rb') as afile: + buf = afile.read() + hasher.update(buf) + return hasher.hexdigest() + +def cache_update_file(cache_base, file): + key = os.path.abspath(file) + value = hash_file_(file) + return cache_update_hash(cache_base, key, value) + + +def cache_contains_hash(cache_base,key,hash_val): + cc = load_cache(cache_base) + return cc.get(key,"Not found") == hash_val + +def cache_update_hash(cache_base,key,hash_val): + cc = load_cache(cache_base) + cc[key] = hash_val + save_cache(cache_base, cc) + + +def hash_binary_(str_bin): + return md5(str_bin).hexdigest() + + +def cache_file(cache_base): + return os.path.join(cache_base, "cache.pkl") + +def save_cache(cache_base, cache): + with open(cache_file(cache_base), 'wb') as f: + pickle.dump(cache,f) + +def load_cache(cache_base): + if not os.path.exists(cache_file(cache_base)): + save_cache(cache_base, {'default' : 42}) + return load_cache(cache_base) + with open(cache_file(cache_base), 'rb') as f: + return pickle.load(f) + + +if __name__ == "__main__": + cache_base = "./" + + print("Hello World") diff --git a/src/jinjafy/jinja_env.py b/src/jinjafy/jinja_env.py new file mode 100644 index 0000000000000000000000000000000000000000..102ef966eba2420c374279e5d1d32d67cdfaa4ba --- /dev/null +++ b/src/jinjafy/jinja_env.py @@ -0,0 +1,136 @@ +import numpy as np +from fractions import Fraction +import jinja2 + + +def format_list_symbols(list, pattern, symbol="x", seperator=",\ "): + return format_join(list, pattern=symbol+"_{%i}", seperator=seperator) + + +def n2w(i): + w = {0: 'zero', 1: 'one', 2: 'two', 3: 'three', 4: 'four', 5: 'five', 6: 'six', 7: 'seven', 8: 'eight', + 9: 'nine', 10: 'ten'} + return i if i < 0 or i > 10 else w[i] + + +def format_list(list_, pattern): + list_ = tolist(list_) + return [pattern % s for s in list_] + + +def format_join(list, pattern, seperator=",\ ",withand=False,withor=False,lastsep=None): + ls = format_list(list, pattern) + if withand: + return seperator.join(ls[:-1]) + "$, and $" + ls[-1] + if withor: + return seperator.join(ls[:-1]) + "$, or $" + ls[-1] + return seperator.join(ls) + + +def format_join_enum(list, pattern="x_{%i}=%g", seperator=",\ "): + list = tolist(list) + return seperator.join(format_list( zip( range(1,len(list)+1 ), list), pattern)) + + +def as_set(l, symbol='f_{%i}'): + if type(l) != list and type(l) != np.ndarray: + l = [l] + l = tolist(l) + s = [symbol%(i,) for i in l] + s = '\{' + ", ".join(s) + "\}" + return s + + +def as_set_list(ll, symbol='%g'): + s = [] + for l in ll.flat: + l = tolist(l) + s.append(as_set(l, symbol)) + s = ["$"+ds+"$" for ds in s] + s = ", ".join(s) + return s + + +def infty(n,tol=10^8): + if n > tol: + s = '\infty' + else: + s = str(n) + return s + + +def flatten(ar): + v = [] + if type(ar) is np.ndarray or type(ar) is np.array: + for x in ar.flat: + m = flatten(x) + if type(m) == list: + v = v + m + else: + v.append(m) + else: + v = ar + return v + + +def tolist(l): + if type(l) == np.ndarray: + l2 = [] + for x in l.flat: + l2.append( x.tolist() if isinstance(x,np.ndarray) else x ) + l = l2 + + elif type(l) == list or hasattr(l, '__iter__'): + pass + else: + l = [l] + return l + + +def jget(A,n=0): + A = flatten(A) + return A[n] + + +def as_rational(x, output='tex', always_frac=False): + if type(x) == jinja2.runtime.Undefined: + return "UNDEFINED(jinja2)" + b = Fraction.from_float(x).limit_denominator(10000) + s = "output_error_in_as_rational_filter" + if output == 'tex': + if (b.denominator == 1 or b.numerator == 0) and not always_frac: + s = '%i'%b.numerator + else: + s = "\\frac{%i}{%i}"%(b.numerator, b.denominator) + return s + + +def mylen(l): + if isinstance(l, np.ndarray): + sz = l.size + else: + sz = len(l) + return sz + + +def permute_exam_answers(section,permutation): + v = section.split("\\item") + v = v[:5] + v[-1:] + assert(len(v) == 6) + permutation = [0] + permutation + [5] + v[0] = "\\begin{answer}[%i]\n"%permutation.index(1) + v2 = "\\item".join( [v[i] for i in permutation] ) + return v2 + + +def startswithvowel(value): + if value.lower().startswith(("a", "e", "i", "o","u")): + return True + else: + return False + + +def aan(s): + if s.startswith("no "): + return "" + return "an" if startswithvowel(s) else "a" diff --git a/src/jinjafy/jinja_matlab_load.py b/src/jinjafy/jinja_matlab_load.py new file mode 100644 index 0000000000000000000000000000000000000000..c67581542aaf2fc407f65489a2db9a1713770335 --- /dev/null +++ b/src/jinjafy/jinja_matlab_load.py @@ -0,0 +1,149 @@ +import numpy as np +import scipy.io as spio + +def matlab_load(mfile): + j = mfile.rfind('.') + if j > -1: + ex = mfile[j + 1:] + base = mfile[:j] + else: + ex = '' + base = mfile + mat = loadmat(base + '.mat') + mat = uuroll(mat) + mat = fix_1_arrays(mat) + mat = fix_strings(mat) + mat = fix_simple_lists(mat) + return mat + + +def loadmat(filename): + ''' + this function should be called instead of direct spio.loadmat + as it cures the problem of not properly recovering python dictionaries + from mat files. It calls the function check keys to cure all entries + which are still mat-objects + ''' + data = spio.loadmat(filename,struct_as_record=False) + data2 = _check_keys(data) + return data2 + + +def _check_keys(dd): + ''' + checks if entries in dictionary are mat-objects. If yes + todict is called to change them to nested dictionaries + ''' + if isinstance(dd, spio.matlab.mio5_params.mat_struct): + dd = _check_keys(_todict(dd)) + elif type(dd) == dict: + for key in dd: + kv = flist(dd[key]) + if type( kv ) == spio.matlab.mio5_params.mat_struct: + dd[key] = _check_keys(kv) + else: + dd[key] = _check_keys(dd[key]) + elif type(dd) == list: + dd = [_check_keys(l) for l in dd] + elif type(dd) == np.ndarray: + if dd.dtype.str == '|O' and dd.size > 0: + if type( flist(dd.flat[0]) ) == spio.matlab.mio5_params.mat_struct: + for i in range( dd.size ): + dd.flat[i] = _check_keys( flist( dd.flat[i]) ) + else: + for i in range(dd.size): + dd.flat[i] = _check_keys(dd.flat[i]) + + return dd + +def fix_simple_lists(l): + if type(l) == dict: + for k,v in l.items(): + l[k] = fix_simple_lists(v) + elif type(l) == np.ndarray and l.dtype.name == "uint8" and l.shape[0] == 1 and l.ndim == 2: + # l = l.tolist() + l = l.tolist()[0] + return l + +def apply_recursively(l, myfun): + if type(l) == dict: + for k,v in l.items(): + l[k] = apply_recursively(v, myfun) + elif type(l) == np.ndarray and l.dtype.str == '|O' and l.size > 0: + for i in range( l.size ): + l.flat[i] = apply_recursively( l.flat[i], myfun) + else: + l = myfun(l) + return l + + +def fix_1_arrays(l): + def _fix_1_arrays(l): + if type(l) == np.ndarray and l.size == 1 and np.issubdtype(l.dtype, np.number): + l = l.flat[0] + return l + l = apply_recursively(l, _fix_1_arrays) + return l + + +def fix_strings(l): + if type(l) == dict: + for k,v in l.items(): + l[k] = fix_strings(v) + elif type(l) == np.ndarray and l.size > 0: + tp = type(superpop(l.flat[0])) + if tp == str or tp == np.str_: + l = [superpop(x) for x in l.flat ] + if len(l) == 1: + l = l.pop() + return l + + +def superpop(l): + if type(l) == list and len(l) == 1: + return superpop(l[0]) + if type(l) == np.ndarray and l.size == 1: + return superpop(l.tolist()) + return l + + +def flist(l): + if type(l) == list and len(l) == 1: + l = flist( l.pop() ) + + if type(l) == np.ndarray and l.dtype.name == "object": + l3 = [flist(v) for v in l.flat] + l = flist( l3 ) + return l + + +def _todict(matobj): + ''' + A recursive function which constructs from matobjects nested dictionaries + ''' + dict = {} + for strg in matobj._fieldnames: + elem = matobj.__dict__[strg] + if isinstance(elem, spio.matlab.mio5_params.mat_struct): + dict[strg] = _todict(elem) + else: + dict[strg] = elem + return dict + + +def uuroll(v): + if type(v) is dict: + for key,val in v.items(): + v[key] = uuroll(val) + if type(v) is np.ndarray or type(v) is np.array: + for j in range(v.size): + v.flat[j] = uuroll(v.flat[j]) + return v + + +def uroll(mat): + for k in mat.keys(): + v = mat[k] + v = uuroll(v) + mat[k] = v + return mat \ No newline at end of file diff --git a/src/jinjafy/jinjafy.py b/src/jinjafy/jinjafy.py new file mode 100644 index 0000000000000000000000000000000000000000..4eee3d35cfeeb307daf3016d8dc24fbe5dcbf391 --- /dev/null +++ b/src/jinjafy/jinjafy.py @@ -0,0 +1,215 @@ +import inspect +import jinja2 +from math import floor, log10 +import os +import numpy as np +from jinjafy import jinja_env + + +def jinjafy_template(data,file_in,file_out=None, filters={},template_searchpath=None): + if template_searchpath: + file_in = os.path.relpath(file_in, template_searchpath) + + return jinjafy_comment(data, file_in=file_in, file_out=file_out,jinja_tag=None, filters=filters,template_searchpath=template_searchpath) + + +def jinjafy_comment(data,file_in=None,file_out=None,jinja_tag="jinja",jinja_code=None,trim_whitespace=True,trim_comments=True,comment_char="#", + filters={},template_searchpath=None): + # Extract all comments from the given file and jinjafy them. + if file_in is None: + frame = inspect.stack()[1] + module = inspect.getmodule(frame[0]) + file_in = module.__file__ + elif not jinja_tag: + trim_comments=False + trim_whitespace=False + + if not template_searchpath: + with open(file_in,'r') as f: + s = f.read() + if jinja_tag: + stag = "<" + jinja_tag + ">" + etag = "</" + jinja_tag + ">" + + i_start = s.find(stag) + i_end = s.find(etag) + s = s[i_start+len(stag):i_end] + ss = [s] + if trim_comments: + ss = [ds.strip()[1:] for ds in s.splitlines() if len(ds.strip()) > 0 and ds.strip()[0] in ["#", "%"] ] + if trim_whitespace: + ss = [ds.strip() for ds in ss] + + jinja_code = '\n'.join(ss) + + from thtools.jinjafy.snipper import SnipperExtension + extensions = [SnipperExtension] + if template_searchpath: + if not isinstance(template_searchpath, list): + template_searchpath = [template_searchpath] + template_searchpath = [ts.replace("\\", "/") for ts in template_searchpath] + templateLoader = jinja2.FileSystemLoader(searchpath=template_searchpath) + env = jinja2.Environment(lstrip_blocks=True, trim_blocks=True,loader=templateLoader, extensions=extensions) + else: + env = jinja2.Environment(lstrip_blocks=True, trim_blocks=True, extensions=extensions) + + import math + env.globals['exp'] = math.exp + env.globals['sqrt'] = math.sqrt + env.globals['cos'] = math.cos + env.globals['sin'] = math.sin + + env.globals['mround'] = mround + env.globals['bold'] = bold + env.globals['fmat'] = fmat + env.globals['enumerate'] = enumerate + env.globals['zip'] = zip + env.globals['ensure_numpy'] = ensure_numpy + env.globals['transpose'] = transpose + import math + env.globals['ceil'] = math.ceil + env.globals['floor'] = math.floor + + import thtools + if not thtools.is_cluster(): + from pylatexenc import latexencode + env.globals['utf8tolatex'] = latexencode.utf8tolatex + env.globals['as_set'] = jinja_env.as_set + env.globals['as_set_list'] = jinja_env.as_set_list + env.globals['len'] = jinja_env.mylen + env.globals['get'] = jinja_env.jget + env.globals['tolist'] = jinja_env.tolist + + filters['as_set'] = jinja_env.as_set + filters['format_list'] =jinja_env.format_list + filters['format_join'] = jinja_env.format_join + filters['format_join_enum'] = jinja_env.format_join_enum + filters['pm'] = lambda x: f" {x}" if x < 0 else f"+{x}" + filters['bold'] = bold + filters['capfirst'] = lambda x: (x[0].upper() + x[1:] if len(x) > 1 else x.upper()) if x != None and isinstance(x, str) else x + filters['lowerfirst'] = lambda x: (x[0].lower() + x[1:] if len(x) > 1 else x.lower()) if x != None and isinstance(x, str) else x + filters['infty'] = jinja_env.infty + filters['n2w'] = jinja_env.n2w + def latex_url(url): + if not isinstance(url, str): + return url + url = url.replace("%", r"\%") + return url + filters['latex_url'] = latex_url + filters['format_list_symbols'] = jinja_env.format_list_symbols + filters['mround'] = mround + def eround(val,l): + x = str(mround(val, l)) + if l == 0: + return x + if '.' not in x: + x = x + "." + n = l - (len(x) - x.find(".") - 1) + if n > 0: + x = x + "0"*n + return x + + filters['eround'] = eround + filters['get'] = jinja_env.jget + filters['flatten'] = jinja_env.flatten + filters['aan'] = jinja_env.aan + filters['bracket'] = bracket + filters['tolist'] = jinja_env.tolist + filters['rational'] = jinja_env.as_rational + filters['permute_exam_answers'] = jinja_env.permute_exam_answers + env.filters.update(filters) + + data['block_start_string'] = '{%' + if not template_searchpath: + jinja_out = env.from_string(jinja_code).render(data) + else: + file_in = file_in.replace("\\", "/") + template = env.get_template(file_in) + jinja_out = template.render(data) + + if file_out is not None: + with open(file_out,'w',encoding='utf-8') as f: + # jinja_out = jinja_out.encode('utf-8') + + f.write(jinja_out) + print("Writing to: " + file_out) + + return jinja_out + + +def bold(bob,d=True) : + if not isinstance(bob, str) : + bob = str(bob) + if d : + bob = '\\textbf{' + bob +"}" + return bob + + +def fmat(bob,l=2,dobold=False) : + bob = mround(bob,l) + bob = bold(bob, dobold) + return bob + +def bracket(s): + return "{"+str(s)+"}" + +def un2str(x, xe, precision=2): + """pretty print nominal value and uncertainty + + x - nominal value + xe - uncertainty + precision - number of significant digits in uncertainty + + returns shortest string representation of `x +- xe` either as + x.xx(ee)e+xx + or as + xxx.xx(ee)""" + # base 10 exponents + x_exp = int(floor(log10(x))) + xe_exp = int(floor(log10(xe))) + + # uncertainty + un_exp = xe_exp - precision + 1 + un_int = round(xe * 10 ** (-un_exp)) + + # nominal value + no_exp = un_exp + no_int = round(x * 10 ** (-no_exp)) + + # format - nom(unc)exp + fieldw = x_exp - no_exp + fmt = '%%.%df' % fieldw + result1 = (fmt + '(%.0f)e%d') % (no_int * 10 ** (-fieldw), un_int, x_exp) + + # format - nom(unc) + fieldw = max(0, -no_exp) + fmt = '%%.%df' % fieldw + result2 = (fmt + '(%.0f)') % (no_int * 10 ** no_exp, un_int * 10 ** max(0, un_exp)) + + # return shortest representation + if len(result2) <= len(result1): + return result2 + else: + return result1 + + +def mround(val, l=2): + if not isinstance(l, int): + return un2str(val, l, 1) + else: + if isinstance(val, np.ndarray): + return np.round(val * 10 ** l) / (10 ** l) + else: + return round(val * 10 ** l) / (10 ** l) + + +def transpose(X): + return np.transpose( ensure_numpy( X) ) + + +def ensure_numpy(X): + if type(X) != np.ndarray: + X = np.asarray(X) + if X.ndim == 1: + X = np.transpose( np.expand_dims(X,1) ) + return X \ No newline at end of file diff --git a/src/jinjafy/snipper.py b/src/jinjafy/snipper.py new file mode 100644 index 0000000000000000000000000000000000000000..a0f8fd02f8517cb2f204446426914a6778f38d40 --- /dev/null +++ b/src/jinjafy/snipper.py @@ -0,0 +1,90 @@ +from jinja2 import nodes +from jinja2.ext import Extension +import os +import thtools + + +class SnipperExtension(Extension): + # a set of names that trigger the extension. + tags = set(['snipper']) + + def __init__(self, environment): + super(SnipperExtension, self).__init__(environment) + + # add the defaults to the environment + environment.extend( + fragment_cache_prefix='', + fragment_cache=None + ) + self.ofile = "" + + def parse(self, parser): + # the first token is the token that started the tag. In our case + # we only listen to ``'cache'`` so this will be a name token with + # `cache` as value. We get the line number so that we can give + # that line number to the nodes we create by hand. + lineno = next(parser.stream).lineno + + # now we parse a single expression that is used as cache key. + args = [parser.parse_expression()] + ofile = os.path.join(os.path.dirname(parser.filename), args[0].value) + args[0].value = ofile + thtools.ensure_dir_exists(os.path.dirname(ofile)) + self.ofile = ofile + print("Snipper args", args, "ofile", ofile) + + # if there is a comma, the user provided a timeout. If not use + # None as second parameter. + if parser.stream.skip_if('comma'): + args.append(parser.parse_expression()) + else: + args.append(nodes.Const(None)) + + # now we parse the body of the cache block up to `endcache` and + # drop the needle (which would always be `endcache` in that case) + body = parser.parse_statements(['name:endsnipper'], drop_needle=True) + + # now return a `CallBlock` node that calls our _cache_support + # helper method on this extension. + return nodes.CallBlock(self.call_method('_snip_method', args), + [], [], body).set_lineno(lineno) + + # parser.environment.loader.searchpath + + # parser.parse_statements(body) + return body + + def _snip_method(self, name, timeout, caller): + # rv = 0 + # key = self.environment.fragment_cache_prefix + name + + # try to load the block from the cache + # if there is no fragment in the cache, render it and store + # it in the cache. + # rv = self.environment.fragment_cache.get(key) + # if rv is not None: + # return rv + rv = caller() + outfile = name + print("Actually snipping to ", self.ofile, "name", name, "timeout", timeout) + with open(name, 'w') as f: + f.write(rv) + # print("Actually snipping to ", self.ofile, 'writing', rv) + + # self.environment.fragment_cache.add(key, rv, timeout) + return rv + + + def _cache_support(self, name, timeout, caller): + """Helper callback.""" + key = self.environment.fragment_cache_prefix + name + + # try to load the block from the cache + # if there is no fragment in the cache, render it and store + # it in the cache. + rv = self.environment.fragment_cache.get(key) + if rv is not None: + return rv + rv = caller() + self.environment.fragment_cache.add(key, rv, timeout) + return rv \ No newline at end of file diff --git a/src/jinjafy/textools.py b/src/jinjafy/textools.py new file mode 100644 index 0000000000000000000000000000000000000000..662f393640a5140d3c6c2837ce0773a45f528e31 --- /dev/null +++ b/src/jinjafy/textools.py @@ -0,0 +1,187 @@ +from jinjafy import jinjafy_comment +import numpy as np + +#"<jinja1>" +#\begin{tabular}{ {{cc}} } +# {% if bookstabs %}\toprule{% endif %} +# {% if vvlabels %} +# {% for vl in vvlabels %} +# {% if loop.index > 1 %} & {% endif %} \multicolumn{ {{vl[0]}} }{ {{vl[2]}} }{ {{vl[1]}} } +# {% endfor %} \\ +# {% for vl in vvlabels %} +# {% if vl[3] %} +# \cmidrule(r){ {{vl[3]}} } +# {% endif %} +# {% endfor %} +# {% endif %} +# {% for row in X %} +# {% if bookstabs and loop.index == 2%}\midrule{% endif %} +# {% for c in row %} +# {% if loop.index > 1 %} & {% endif %} {{ c['tex'] }} {% if loop.index == W %} \\ {% endif %} +# {% endfor %} +# {% endfor %} +# {% if bookstabs %}\bottomrule{% endif %} +#\end{tabular} +#</jinja1> +# Convert a matrix to a table super quickly +def mat2table(X,vlabels=None,hlabels=None,file_out = None, bookstabs=True, vvlabels=None,plot=False,pdf_out=None, standalone=False): + X, Xx, Xerr,Xdl = fmat_X2dict(X) + if pdf_out: plot = True + #%% + if plot: + import matplotlib.pyplot as plt + #plt.style.use('ggplot') + plt.style.use('seaborn') + fig = plt.figure() + ax = fig.gca() + #ax = plt.gca() + ls = [] + for j in range(X.shape[0]): + ls.append(ax.plot(Xx[j, :]).pop() ) + + if Xerr[j]: + plt.errorbar(range(X.shape[1]), Xx[j,:], yerr=Xerr[j], color=ls[j].get_color()) + + for i in range( X.shape[1] ): + if 'xs' in X[j,i]: + plt.plot([i]*len(X[j,i]['xs']), X[j,i]['xs'], '.', color=ls[j].get_color()) + + if vlabels: + plt.legend(ls, vlabels, bbox_to_anchor=(1.04, 1), loc="upper left") + if hlabels: + plt.xticks(range(X.shape[1]), hlabels[1:]) + #plt.subplots_adjust(right=0.5) + plt.tight_layout(rect=[0, 0, 1, 1]) + plt.show() + #if pdf_out: + # fig.savefig(pdf_out, bbox_inches='tight') + + + if vlabels: + vltex = [{'tex': v} for v in vlabels] + for i in range(len(Xdl)): + Xdl[i] = [vltex[i]] + Xdl[i] + + if hlabels: + Xdl = [ [{'tex': h} for h in hlabels] ] + Xdl + + if vvlabels: + cc = 1 + for i in range(len(vvlabels)): + if len(vvlabels[i]) < 3: + vvlabels[i].append("c") + dl = vvlabels[i][0] + if dl == 1: + a = None + else: + a = "%i-%i"%(cc, cc+dl-1) + cc = cc + dl + vvlabels[i] = vvlabels[i] + [a] + + H = len(Xdl) + W = len(Xdl[0]) + cc = ["c" for i in range(W)] + if vlabels: + cc[0] = "l" + cc = "".join(cc) + + def fmat(x): + if isinstance(x, int): + x = str(x) + if isinstance(x, float): + x = "%2.3f"%x + return x + + #X = [ [fmat(x) for x in row] for row in X] + + data = {'X' : Xdl, 'hlabels': hlabels, 'vlabels': vlabels, 'cc': cc, 'H':H, 'W': W, 'bookstabs': bookstabs, + 'vvlabels': vvlabels} + + from thtools.jinjafy.jinjafy import jinjafy_comment + s = jinjafy_comment(data,jinja_tag="jinja1") + if file_out: + print("Writing to: " + file_out) + + if standalone: + s = jinjafy_comment({"s": s}, jinja_tag="jinja3") + + with open(file_out, 'w') as f: + f.write(s) + if standalone: + from thtools import latexmk + latexmk(file_out) + + + + return s +# "<jinja3>" +# \documentclass[crop]{standalone} +# \usepackage{booktabs} +# \usepackage{siunitx} +# \begin{document} +# {{s}} +# \end{document} +# </jinja3> + +def fmat_X2dict(X): + X = np.asarray(X, dtype=np.object) + if len(X.shape) > 2: + X2 = np.ndarray(X.shape[:2], dtype=np.object) + for i in range(X.shape[0]): + for j in range(X.shape[1]): + X2[i, j] = X[i, j, :].squeeze() + X = X2 + X = np.reshape(X, X.shape[:2]) + + for i in range(X.shape[0]): + for j in range(X.shape[1]): + dx = X[i,j] + if isinstance(dx, (list, np.ndarray)): + dx = [x for x in np.ravel(dx)] + + if not isinstance(dx, dict): + dx = {'x': dx} + elif not isinstance(dx['x'], str): + x = dx['x'] + # if isinstance(x, np.ndarray): + if 'tex' not in dx: + dx['std'] = np.std(x) + dx['std_mean'] = np.std(x) / np.sqrt( len(x)) + dx['xs'] = x + dx['x'] = np.mean(x) + x2, u2 = mround( dx['x'], dx['std_mean'] ) + + dx['tex'] = '\\SI{%g\\pm %.2f}{}'%(x2, u2) + + if 'tex' not in dx: + dx['tex'] = dx['x'] + + X[i,j] = dx + + Xerr = [None] * X.shape[0] + Xx = np.zeros(X.shape) + + for i in range(X.shape[0]): + if "std" in X[0,0]: + Xerr[i] = [dx['std_mean'] for dx in X[i]] + + for j in range(X.shape[1]): + Xx[i,j] = X[i,j]['x'] + + Xdl = [] + for i in range(X.shape[0]): + dx = [] + for j in range(X.shape[1]): + dx.append(X[i,j]) + Xdl.append(dx) + + + return X,Xx,Xerr,Xdl + +import math +def mround(x,u): + n = np.floor(np.log10(x)+1) + dx = np.round(x / np.power(10.0, n), 2) + du = np.round(u / np.power(10.0, n), 2) + return dx * np.power(10, n), du * np.power(10.0,n) + diff --git a/src/slider/convert.py b/src/slider/convert.py index 6737d3c5884ffe624df5d92a2f0d403312ab64e2..f4b00969f75d835da5715ac9487e5b1bc7a33d74 100644 --- a/src/slider/convert.py +++ b/src/slider/convert.py @@ -1,4 +1,7 @@ -from thtools import execute_command +from jinjafy import execute_command +import os +from bs4 import BeautifulSoup +# import thtools def svg2pdf(fin, fout=None, crop=True, text_to_path=False, export_area_page=True): """ @@ -65,9 +68,6 @@ def pdfcrop(fin, fout=None): execute_command(cmd.split()) -import os -from bs4 import BeautifulSoup -import thtools def svg_edit_to_importable(svg_edit_file,verbose=False, keep_background_layer=True): """ @@ -114,7 +114,7 @@ def svg_edit_to_importable(svg_edit_file,verbose=False, keep_background_layer=Tr f2.write(s2.encode("UTF-8")) cmd = ['inkscape', '-C', '-T', '--without-gui', '--file=%s'%svg_fonts_layers[-1], '--export-pdf=%s' % pdf_nofonts_layers[-1]] - thtools.execute_command(cmd) + execute_command(cmd) if verbose: print("svg_edit_to_importable called. Converting svg file\n > %s\nto files:"%svg_edit_file) diff --git a/src/slider/inkscape2tex.py b/src/slider/inkscape2tex.py deleted file mode 100644 index 989f495ad4a46c8c9991f3a1fc376264ea912cdf..0000000000000000000000000000000000000000 --- a/src/slider/inkscape2tex.py +++ /dev/null @@ -1,183 +0,0 @@ -#!/usr/bin/env python -"""Convert inkscape SVG files to TeX input. - -- SVG to PDF or EPS with inkscape, optionally with LaTeX output. -- DOT to SVG - -Skips conversion if PDF file found newer than SVG source. -Requires `inkscape` in path. -""" -# Copyright 2010-2017 by Ioannis Filippidis -# All rights reserved. Licensed under BSD-2. -# -import argparse -import datetime -import fnmatch -import logging -import os -import shlex -import subprocess -import time - -import humanize - -import svg2latex as convert -# from svglatex import convert - - -log = logging.getLogger(__name__) - - -def main(): - """Start from here.""" - args = parse_args() - f = '{name}.svg'.format(name=args.input_file) - out_type = args.method - if './img/' in f: - files = [f] - else: - files = locate(f, './img') - svg = None - for svg in files: - log.info('Will convert SVG file "{f}" to {t}'.format( - f=svg, t=out_type)) - convert_if_svg_newer(svg, out_type) - if svg is None: - raise Exception( - 'SVG file "{f}" not found! ' - 'Cannot export to PDF.'.format(f=f)) - - -def parse_args(): - """Parse command-line arguments using.""" - parser = argparse.ArgumentParser() - parser.add_argument( - '-i', '--input-file', type=str, - help=( - 'Name (w/o extension) of SVG file. ' - 'Either file name to search for under `./img`, ' - 'or path that starts with `./img`.')) - choices = [ - 'latex-pdf', 'pdf', - 'latex-eps', 'eps'] - parser.add_argument( - '-m', '--method', type=str, choices=choices, - help=( - 'Export to this file type. ' - 'The prefix "latex" produces also a file `*.pdf_tex` ' - 'that contains the text from the SVG. ' - 'The command `\includesvgpdf` passes `pdf`, ' - 'and `\includesvg` passes `latex-pdf`.')) - args = parser.parse_args() - return args - - -def convert_if_svg_newer(svg, out_type): - """Convert SVG file to PDF or EPS.""" - base, ext = os.path.splitext(svg) - assert ext == '.svg', ext - if 'pdf' in out_type: - out = base + '.pdf' - elif 'eps' in out_type: - out = base + '.eps' - else: - raise ValueError(out_type) - if not os.access(svg, os.F_OK): - raise FileNotFoundError( - 'No SVG file "{f}"'.format(f=svg)) - fresh = is_newer(out, svg) - if out_type == 'latex-pdf': - pdf_tex = base + '.pdf_tex' - fresh &= is_newer(pdf_tex, svg) - if fresh: - log.info('No update needed, target newer than SVG.') - return - log.info('File not found or old. Converting from SVG...') - convert_svg(svg, out, out_type) - - -def is_newer(target, source): - """Return `True` if `target` newer than `source` file.""" - assert os.path.isfile(source), source - if not os.path.isfile(target): - return False - t_src = os.stat(source)[8] - t_tgt = os.stat(target)[8] - _print_dates(source, target, t_src, t_tgt) - return t_src < t_tgt - - -def _print_dates(source, target, t_src, t_tgt): - s = _format_time(t_src) - t = _format_time(t_tgt) - log.info(( - 'last modification dates:\n' - ' Source ({source}): {s}\n' - ' Target ({target}): {t}').format( - source=source, target=target, - s=s, t=t)) - - -def _format_time(t): - """Return time readable by humans.""" - return humanize.naturaltime( - datetime.datetime.fromtimestamp(t)) - - -def convert_svg(svg, out, out_type): - """Convert from SVG to output format.""" - # TODO: implement options `latex-eps`, `eps` - assert out_type in ('latex-pdf', 'pdf'), out_type - if out_type == 'latex-pdf': - convert.main(svg) - elif out_type == 'pdf': - inkscape = convert.which_inkscape() - svg_path = os.path.realpath(svg) - out_path = os.path.realpath(out) - args = [ - inkscape, - '--without-gui', - '--export-area-drawing', - '--export-ignore-filters', - '--export-dpi={dpi}'.format(dpi=96), - '--export-pdf={out}'.format(out=out_path), - svg_path] - r = subprocess.call(args) - if r != 0: - raise Exception('Conversion error') - - -def convert_svg_using_inkscape(svg, out, out_type): - """Convert from SVG to output format.""" - # inkscape need be called with an absolute path on OS X - # http://wiki.inkscape.org/wiki/index.php/MacOS_X - symlink_relpath = 'bin/inkscape' - home = os.path.expanduser('~') - symlink_abspath = os.path.join(home, symlink_relpath) - inkscape_abspath = os.path.realpath(symlink_abspath) - svg_abspath = os.path.realpath(svg) - args = ['{inkscape_abspath} -z -D --file={svg}'.format( - inkscape_abspath=inkscape_abspath, svg=svg_abspath)] - if 'pdf' in out_type: - args.append('--export-pdf={pdf}'.format(pdf=out)) - if 'eps' in out_type: - args.append('--export-eps={eps}'.format(eps=out)) - if 'latex' in out_type: - args.append('--export-latex') - args = shlex.split(' '.join(args)) - r = subprocess.call(args) - if r != 0: - raise Exception( - 'conversion from "{svg}" to "{out}" failed'.format( - svg=svg, out=out)) - - -def locate(pattern, root=os.curdir): - """Locate all files matching supplied filename pattern under `root`.""" - for path, dirs, files in os.walk(os.path.abspath(root)): - for filename in fnmatch.filter(files, pattern): - yield os.path.join(path, filename) - - -if __name__ == '__main__': - main() diff --git a/src/slider/legacy_importer.py b/src/slider/legacy_importer.py index fd19de6decd4de9864a3b672e25b5cdc1f8aa543..983f3fce5b47114c78af0fe2304d4b5bd735c199 100644 --- a/src/slider/legacy_importer.py +++ b/src/slider/legacy_importer.py @@ -3,10 +3,11 @@ # https://github.com/eea/odfpy import os import shutil -import thtools -from thtools.jinjafy import jinjafy_comment +# import thtools +from jinjafy import jinjafy_comment from bs4 import BeautifulSoup import glob +from jinjafy import execute_command CDIR = os.path.dirname(os.path.realpath(__file__)) CDIR = CDIR.replace('\\','/') @@ -26,7 +27,7 @@ def join_pdfs(slide_deck_pdf, outfile): files = [os.path.relpath(os.path.dirname(pdf), start=dn) + "/" + os.path.basename(pdf) for pdf in slide_deck_pdf] outf = os.path.relpath(os.path.dirname(outfile), start=dn) + "/" + os.path.basename(outfile) cmd = "cd " + dn + " && pdftk " + " ".join(files) + " cat output " + outf - thtools.execute_command(cmd.split()) + execute_command(cmd.split()) def li_import(slide_deck_pdf, tex_output_path=None, num_to_take=None, force=False, svg_pfix="osvg", svg_height=743.75, svg_width=992.5, diff --git a/src/slider/slider.py b/src/slider/slider.py index fe7b51fc41339ecf382331a46c224a0ef275e7ae..bbf3721a68a1e4f75a3b2cad005cfc413587cdea 100644 --- a/src/slider/slider.py +++ b/src/slider/slider.py @@ -1,11 +1,11 @@ -from thtools.slider import legacy_importer +from slider import legacy_importer import PyPDF2 import os -import thtools -from thtools.slider.legacy_importer import SVG_EDIT_RELPATH, SVG_TMP_RELPATH, move_template_files, DTU_beamer_base -from thtools.cache import cache_update_str, cache_contains_str, cache_contains_file, cache_update_file +# import thtools +from slider.legacy_importer import SVG_EDIT_RELPATH, SVG_TMP_RELPATH, move_template_files, DTU_beamer_base +from jinjafy.cache import cache_update_str, cache_contains_str, cache_contains_file, cache_update_file import shutil -from thtools.slider.slide_fixer import check_svg_file_and_fix_if_broken +from slider.slide_fixer import check_svg_file_and_fix_if_broken dc = "\\documentclass" def fix_handout(s):