class ConfiguredSvgModule(ConfiguredModule, TemplateConverter): """ This module's :class:`configuration object <score.init.ConfiguredModule>`. """ def __init__(self, http, webassets, tpl, css, rootdir, combine, cachedir): super().__init__(__package__) self.http = http self.webassets = webassets self.tpl = tpl self.css = css tpl.renderer.register_format('svg', rootdir, cachedir, self) self.combine = combine self.virtfiles = VirtualAssets() self.virtsvg = self.virtfiles.decorator('svg') self._add_single_svg_route() self._add_single_png_route() self._add_single_resized_png_route() self._add_combined_svg_route() self._add_combined_png_route() if self.combine: @self.css.virtcss def icons(ctx): svgurl = ctx.url('score.svg:combined/svg') pngurl = ctx.url('score.svg:combined/png') return self.sprite(ctx).css(svgurl, pngurl) else: @self.css.virtcss def icons(ctx): styles = [Svg.common_css] for path in self.paths(): svg = self.svg(ctx, path) svgurl = ctx.url('score.svg:single/svg', path) pngurl = ctx.url('score.svg:single/png', path) styles.append('.icon-%s{%s}' % (svg.css_class, svg.css(svgurl, pngurl))) return '\n'.join(styles) def icon(self, ctx, path, size=None): if '.' not in path: path += '.svg' if self.combine: styles = self.sprite(ctx).svg_css(path, size) return '<span class="icon icon-%s" style="%s"></span>' % \ (Svg.path2css(path), styles) if path in self.virtfiles.paths(): svg = Svg(ctx, path, string=self.virtfiles.render(ctx, path)) else: svg = Svg(ctx, path, string=self.render_svg(ctx, path)) if not size: return '<span class="icon icon-%s"></span>' % svg.css_class svgurl = ctx.url('score.svg:single/svg', path) pngurl = ctx.url('score.svg:single/png/resized', path, size) styles = svg.css_resized(svgurl, pngurl, size) return '<span class="icon icon-%s" style="%s"></span>' % \ (Svg.path2css(path), styles) def icon_css(self, ctx, path, size=None): if '.' not in path: path += '.svg' if self.combine and not size: svgurl = ctx.url('score.svg:combined/svg') pngurl = ctx.url('score.svg:combined/png') css = self.sprite(ctx).svg_css(path) css += 'background:url(%s)no-repeat;' % pngurl css += 'background-image:url(%s),none;' % svgurl css += 'display:inline-block;' else: svgurl = ctx.url('score.svg:single/svg', path) pngurl = ctx.url('score.svg:single/png', path) if path in self.virtfiles.paths(): svg = Svg(ctx, path, string=self.virtfiles.render(ctx, path)) else: svg = Svg(ctx, path, string=self.render_svg(ctx, path)) if size: return svg.css_resized(svgurl, pngurl, size) else: return svg.css(svgurl, pngurl) return css def _finalize(self, tpl): tpl.renderer.add_function('scss', 'icon', self.icon_css, escape_output=False) tpl.renderer.add_function('css', 'icon', self.icon_css, escape_output=False) if 'html' in self.tpl.renderer.formats: tpl.renderer.add_function('html', 'icon', self.icon, escape_output=False) def _add_single_svg_route(self): @self.http.newroute('score.svg:single/svg', '/svg/{path>.*}.svg') def single_svg(ctx, path): versionmanager = self.webassets.versionmanager if versionmanager.handle_request(ctx, 'svg', path): return self._svg_response(ctx) path = self._urlpath2path(path) svg = self.render_svg(ctx, path) return self._svg_response(ctx, svg) @single_svg.vars2url def url_single_svg(ctx, path): """ Generates the url to a single svg :term:`path <asset path>`. """ urlpath = self._path2urlpath(path) url = '/svg/%s.svg' % urllib.parse.quote(urlpath) renderer = lambda: self.render_svg(ctx, path).encode('UTF-8') versionmanager = self.webassets.versionmanager if path in self.virtfiles.paths(): hasher = lambda: self.virtfiles.hash(path) else: file = os.path.join(self.rootdir, path) hasher = versionmanager.create_file_hasher(file) hash_ = versionmanager.store('svg', urlpath, hasher, renderer) if hash_: url += '?_v=' + hash_ return url def _add_single_png_route(self): @self.http.newroute('score.svg:single/png', '/svg/{path>.*}.png') def single_png(ctx, path): versionmanager = self.webassets.versionmanager if versionmanager.handle_request(ctx, 'png', path): return self._png_response(ctx) path = self._urlpath2path(path) png = self.render_png(ctx, path) return self._png_response(ctx, png) @single_png.vars2url def url_single_png(ctx, path): urlpath = self._path2urlpath(path) urlpath = urlpath[:-3] + 'png' url = '/svg/%s.png' % urllib.parse.quote(urlpath) renderer = lambda: self.render_png(ctx, path) versionmanager = self.webassets.versionmanager if path in self.virtfiles.paths(): hasher = lambda: self.virtfiles.hash(path) else: file = os.path.join(self.rootdir, path) hasher = versionmanager.create_file_hasher(file) hash_ = versionmanager.store('png', os.path.join('auto', urlpath), hasher, renderer) if hash_: url += '?_v=' + hash_ return url def _add_single_resized_png_route(self): @self.http.newroute('score.svg:single/png/resized', '/svg/{size}/{path>.*}.png') def single_png_resized(ctx, path, size): versionmanager = self.webassets.versionmanager if versionmanager.handle_request(ctx, 'png', os.path.join(size, path)): return self._png_response(ctx) path = self._urlpath2path(path) png = self.render_png(ctx, path, size) return self._png_response(ctx, png) @single_png_resized.vars2url def url_single_png_resized(ctx, path, size): urlpath = self._path2urlpath(path) urlpath = urlpath[:-3] + 'png' url = '/svg/%s/%s.png' % (urllib.parse.quote(size), urllib.parse.quote(urlpath)) renderer = lambda: self.render_png(ctx, path, size) versionmanager = self.webassets.versionmanager if path in self.virtfiles.paths(): hasher = lambda: self.virtfiles.hash(path) else: file = os.path.join(self.rootdir, path) hasher = versionmanager.create_file_hasher(file) hash_ = versionmanager.store('png', os.path.join(size, urlpath), hasher, renderer) if hash_: url += '?_v=' + hash_ return url def _add_combined_svg_route(self): @self.http.newroute('score.svg:combined/svg', '/combined.svg') def svg_combined(ctx): versionmanager = self.webassets.versionmanager if versionmanager.handle_request(ctx, 'svg', '__combined__'): return self._svg_response(ctx) return self._svg_response(ctx, self.render_svg_sprite(ctx)) @svg_combined.vars2url def url_svg_combined(ctx): url = '/combined.svg' files = [] vfiles = [] for path in self.paths(): if path in self.virtfiles.paths(): vfiles.append(path) else: files.append(os.path.join(self.rootdir, path)) versionmanager = self.webassets.versionmanager hashers = [versionmanager.create_file_hasher(files)] hashers += [lambda path: self.virtfiles.hash(path) for path in vfiles] hash_ = versionmanager.store( 'svg', '__combined__', hashers, lambda: self.render_svg_sprite(ctx).encode('UTF-8')) if hash_: url += '?_v=' + hash_ return url def _add_combined_png_route(self): @self.http.newroute('score.svg:combined/png', '/combined.png') def png_combined(ctx): versionmanager = self.webassets.versionmanager if versionmanager.handle_request(ctx, 'png', '__combined__'): return self.png_response(ctx) return self.png_response(ctx, self.render_png_sprite(ctx)) @png_combined.vars2url def url_png_combined(ctx): url = '/combined.png' files = [] vfiles = [] for path in self.paths(): if path in self.virtfiles.paths(): vfiles.append(path) else: files.append(os.path.join(self.rootdir, path)) versionmanager = self.webassets.versionmanager hashers = [versionmanager.create_file_hasher(files)] hashers += [lambda path: self.virtfiles.hash(path) for path in vfiles] hash_ = versionmanager.store( 'png', '__combined__', hashers, lambda: self.render_svg_sprite(ctx).encode('UTF-8')) if hash_: url += '?_v=' + hash_ return url def _path2urlpath(self, path): """ Converts a :term:`path <asset path>` to the corresponding path to use in URLs. """ urlpath = path if not urlpath.endswith('.svg'): urlpath = urlpath[:urlpath.rindex('.')] assert urlpath.endswith('.svg') urlpath = urlpath[:-4] return urlpath def _urlpath2path(self, urlpath): """ Converts a *urlpath*, as passed in via the URL, into the actual :term:`asset path`. """ svgpath = urlpath + '.svg' if svgpath in self.virtfiles.paths(): return svgpath if os.path.isfile(os.path.join(self.rootdir, svgpath)): return svgpath for ext in self.tpl.renderer.engines: file = os.path.join(self.rootdir, svgpath + '.' + ext) if os.path.isfile(file): return svgpath + '.' + ext raise ValueError('Could not determine path for url "%s"' % urlpath) def _svg_response(self, ctx, svg=None): """ Sets appropriate headers on the http response. Will optionally set the response body to the given *svg* string. """ ctx.http.response.content_type = 'image/svg+xml; charset=UTF-8' if svg: ctx.http.response.text = svg return ctx.http.response def _png_response(self, ctx, png=None): """ Sets appropriate headers on the http response. Will optionally set the response body to the given *png* bytes. """ ctx.http.response.content_type = 'image/png' if png: ctx.http.response.body = png return ctx.http.response @property def rootdir(self): """ The configured root folder of svg files. """ return self.tpl.renderer.format_rootdir('svg') @property def cachedir(self): """ The configured root folder of svg files. """ return self.tpl.renderer.format_cachedir('svg') def paths(self, includehidden=False): """ Provides a list of all svg files found in the js root folder as :term:`paths <asset path>`, as well as the paths of all :term:`virtual svg files <virtual asset>`. """ return self.tpl.renderer.paths('svg', self.virtfiles, includehidden) def sprite(self, ctx): """ Provides the :class:`.Sprite` object for this configuration. """ return Sprite(ctx, self) def svg(self, ctx, path): """ Provides an :class:`.Svg` object for given path. Caching behaviour is the same as in :attr:`.sprite`. """ if path.endswith('.svg'): return Svg(ctx, path, file=os.path.join(self.rootdir, path)) return Svg(ctx, path, string=self.tpl.renderer.render_file(ctx, path)) def render_svg(self, ctx, path): """ Retuns the content of the file denoted by :term:`path <asset path>`. """ return self.svg(ctx, path).content def render_png(self, ctx, path, size=None): """ Renders the svg file with given :term:`path <asset path>` in the Portable Network Graphics (png) file format. """ return svg2png(self.svg(ctx, path), size) def render_svg_sprite(self, ctx): """ Renders the :term:`sprite` of this configuration in the svg file format. """ return self.sprite(ctx).content def render_png_sprite(self, ctx, size=None): """ Same as :meth:`.render_svg_sprite`, but returns a png, thus a `bytes` object. """ return svg2png(self.sprite(ctx), size) convert_file = render_svg
class ConfiguredCssModule(ConfiguredModule): """ This module's :class:`configuration object <score.init.ConfiguredModule>`. """ def __init__(self, webassets, tpl, http, rootdir, cachedir, combine, minify): super().__init__(__package__) self.webassets = webassets self.tpl = tpl self.http = http self.rootdir = rootdir self.cachedir = cachedir self.combine = combine self.minify = minify self.css_converter = CssConverter(self) self.scss_converter = ScssConverter(self) tpl.renderer.register_format('css', rootdir, cachedir, self.css_converter) tpl.renderer.register_format('scss', rootdir, cachedir, self.scss_converter) self.virtfiles = VirtualAssets() self.virtcss = self.virtfiles.decorator('css') self._add_single_route() self._add_combined_route() def paths(self, includehidden=False): """ Provides a list of all css files found in the css root folder as :term:`paths <asset path>`, as well as the paths of all :term:`virtual css files <virtual asset>`. """ return ( self.tpl.renderer.paths('css', self.virtfiles, includehidden) + self.tpl.renderer.paths('scss', None, includehidden)) def render_multiple(self, ctx, paths): """ Renders multiple *paths* at once, prefixing the contents of each file with a css comment (only if minification is disabled). """ parts = [] for path in paths: if not self.minify: s = '/*{0}*/\n/*{1:^76}*/\n/*{0}*/'.format('*' * 76, path) parts.append(s) parts.append(self.tpl.renderer.render_file(ctx, path)) return '\n\n'.join(parts) def render_combined(self, ctx): """ Renders the combined css file. """ return self.render_multiple(ctx, self.paths()) def _add_single_route(self): @self.http.newroute('score.css:single', '/css/{path>.*\.css$}') def single(ctx, path): versionmanager = self.webassets.versionmanager if versionmanager.handle_request(ctx, 'css', path): return self._response(ctx) path = self._urlpath2path(path) if path in self.virtfiles.paths(): css = self.virtfiles.render(ctx, path) else: css = self.tpl.renderer.render_file(ctx, path) return self._response(ctx, css) @single.vars2url def url_single(ctx, path): """ Generates the url to a single css *path*. """ urlpath = self._path2urlpath(path) url = '/css/' + urllib.parse.quote(urlpath) versionmanager = self.webassets.versionmanager if path in self.virtfiles.paths(): def hasher(): return self.virtfiles.hash(ctx, path) def renderer(): return self.virtfiles.render(ctx, path).encode('UTF-8') else: files = [os.path.join(self.rootdir, path)] if '.scss' in path: newest_include_file = self.scss_converter.\ _get_newest_include_file(ctx) if newest_include_file: files.append(newest_include_file) hasher = versionmanager.create_file_hasher(files) def renderer(): content = self.tpl.renderer.render_file(ctx, path) return content.encode('UTF-8') hash_ = versionmanager.store('css', urlpath, hasher, renderer) if hash_: url += '?_v=' + hash_ return url def _add_combined_route(self): @self.http.newroute('score.css:combined', '/combined.css') def combined(ctx): versionmanager = self.webassets.versionmanager if versionmanager.handle_request(ctx, 'css', '__combined__'): return self._response(ctx) return self._response(ctx, self.render_multiple(ctx, self.paths())) @combined.vars2url def url_combined(ctx): """ Generates the url to the combined css file. """ url = '/combined.css' files = [] vfiles = [] for path in self.paths(): if path in self.virtfiles.paths(): vfiles.append(path) else: files.append(os.path.join(self.rootdir, path)) versionmanager = self.webassets.versionmanager hashers = [versionmanager.create_file_hasher(files)] for path in vfiles: hashers.append(lambda: self.virtfiles.hash(ctx, path)) hash_ = versionmanager.store( 'css', '__combined__', hashers, lambda: self.render_combined(ctx).encode('UTF-8')) if hash_: url += '?_v=' + hash_ return url def _path2urlpath(self, path): """ Converts a :term:`path <asset path>` to the corresponding path to use in URLs. """ urlpath = path if not (urlpath.endswith('.css') or urlpath.endswith('.scss')): urlpath = urlpath[:urlpath.rindex('.')] if urlpath.endswith('.scss'): urlpath = urlpath[:-4] + 'css' assert urlpath.endswith('.css') return urlpath def _urlpath2path(self, urlpath): """ Converts a *urlpath*, as passed in via the URL, into the actual :term:`asset path`. """ assert urlpath.endswith('.css') csspath = urlpath scsspath = urlpath[:-3] + 'scss' if csspath in self.virtfiles.paths(): return csspath if scsspath in self.virtfiles.paths(): return scsspath if os.path.isfile(os.path.join(self.rootdir, csspath)): return csspath if os.path.isfile(os.path.join(self.rootdir, scsspath)): return scsspath for ext in self.tpl.renderer.engines: file = os.path.join(self.rootdir, csspath + '.' + ext) if os.path.isfile(file): return csspath + '.' + ext file = os.path.join(self.rootdir, scsspath + '.' + ext) if os.path.isfile(file): return scsspath + '.' + ext raise ValueError('Could not determine path for url "%s"' % urlpath) def _finalize(self, tpl): if 'html' in tpl.renderer.formats: tpl.renderer.add_function( 'html', 'css', self._htmlfunc, escape_output=False) def _response(self, ctx, css=None): """ Sets appropriate headers on the http response. Will optionally set the response body to the given *css* string. """ ctx.http.response.content_type = 'text/css; charset=UTF-8' if css: ctx.http.response.text = css return ctx.http.response def _tags(self, ctx, *paths): """ Generates all ``link`` tags necessary to include all css files. It is possible to generate the tags to specific css *paths* only. """ tag = '<link rel="stylesheet" href="%s" type="text/css">' if paths: links = [tag % self.http.url(ctx, 'score.css:single', path) for path in paths] return '\n'.join(links) if self.combine: return tag % self.http.url(ctx, 'score.css:combined') paths = self.paths() if not paths: return '' return self._tags(ctx, *paths) def _htmlfunc(self, ctx, *paths, inline=False): if not inline: return self._tags(ctx, *paths) if not paths: paths = self.paths() return '<style type="text/css">\n' + \ textwrap.indent(self.render_multiple(ctx, paths), ' ' * 4) + \ '\n</style>'
class ConfiguredJsModule(ConfiguredModule, TemplateConverter): """ This module's :class:`configuration object <score.init.ConfiguredModule>`, which is also a :term:`template converter`. """ def __init__(self, http, tpl, webassets, rootdir, cachedir, combine, minifier): super().__init__(__package__) self.http = http self.tpl = tpl self.webassets = webassets self.combine = combine self.minifier = minifier tpl.renderer.register_format('js', rootdir, cachedir, self) self.virtfiles = VirtualAssets() self.virtjs = self.virtfiles.decorator('js') self._add_single_route() self._add_combined_route() def _add_single_route(self): @self.http.newroute('score.js:single', '/js/{path>.*\.js$}') def single(ctx, path): versionmanager = self.webassets.versionmanager if versionmanager.handle_request(ctx, 'js', path): return self._response(ctx) path = self._urlpath2path(path) if path in self.virtfiles.paths(): js = self.virtfiles.render(ctx, path) else: js = self.tpl.renderer.render_file(ctx, path) return self._response(ctx, js) @single.vars2url def url_single(ctx, path): urlpath = self._path2urlpath(path) url = '/js/' + urllib.parse.quote(urlpath) versionmanager = self.webassets.versionmanager if path in self.virtfiles.paths(): def hasher(): return self.virtfiles.hash(ctx, path) def renderer(): return self.virtfiles.render(ctx, path).encode('UTF-8') else: file = os.path.join(self.rootdir, path) hasher = versionmanager.create_file_hasher(file) def renderer(): return self.tpl.renderer.render_file(ctx, path).\ encode('UTF-8') hash_ = versionmanager.store('js', urlpath, hasher, renderer) if hash_: url += '?_v=' + hash_ return url self.route_single = single def _add_combined_route(self): @self.http.newroute('score.js:combined', '/combined.js') def combined(ctx): versionmanager = self.webassets.versionmanager if versionmanager.handle_request(ctx, 'js', '__combined__'): return self._response(ctx) return self._response(ctx, self.render_combined(ctx)) @combined.vars2url def url_combined(ctx): versionmanager = self.webassets.versionmanager hash_ = versionmanager.store( 'js', '__combined__', self.generate_combined_hasher(ctx), lambda: self.render_combined(ctx).encode('UTF-8')) url = '/combined.js' if hash_: url += '?_v=' + hash_ return url self.route_combined = combined def generate_combined_hasher(self, ctx): files = [] vfiles = [] for path in self.paths(): if path in self.virtfiles.paths(): vfiles.append(path) else: files.append(os.path.join(self.rootdir, path)) versionmanager = self.webassets.versionmanager hashers = [versionmanager.create_file_hasher(files)] for path in vfiles: hashers.append(lambda: self.virtfiles.hash(ctx, path)) return hashers def _finalize(self, tpl): if 'html' in tpl.renderer.formats: tpl.renderer.add_function( 'html', 'js', self._tags, escape_output=False) tpl.renderer.add_filter( 'html', 'escape_js', escape, escape_output=False) @property def minify(self): """ Whether javascript content should be minified. """ return bool(self.minifier) @property def rootdir(self): """ The configured root folder of javascript files. """ return self.tpl.renderer.format_rootdir('js') @property def cachedir(self): """ The configured cache folder. """ return self.tpl.renderer.format_cachedir('js') def paths(self, includehidden=False): """ Provides a list of all js files found in the js root folder as :term:`paths <asset path>`, as well as the paths of all :term:`virtual javascript files <virtual asset>`. """ paths = self.tpl.renderer.paths('js', self.virtfiles, includehidden) paths.sort() # FIXME: remove minified javascript files return paths def convert_string(self, ctx, js, path=None): cachefile = None if path and os.path.exists(path) and self.cachedir: cachefile = os.path.join(self.cachedir, path) file = os.path.join(self.rootdir, path) if os.path.isfile(cachefile) and \ os.path.getmtime(cachefile) > os.path.getmtime(file): return open(cachefile, 'r').read() if self.minifier: js = self.minifier.minify_string(js, path=path) if cachefile: os.makedirs(os.path.dirname(cachefile), exist_ok=True) open(cachefile, 'w').write(js) return js def convert_file(self, ctx, path): if path in self.virtfiles.paths(): return self.convert_string( ctx, self.virtfiles.render(ctx, path), path) file = os.path.join(self.rootdir, path) if not os.path.isfile(file): raise AssetNotFound('js', path) if self.minifier and file.endswith('.js'): minfile = file[:-3] + '.min.js' if os.path.isfile(minfile): return open(minfile, 'r', encoding='utf-8-sig').read() js = open(file, 'r', encoding='utf-8-sig').read() return self.convert_string(ctx, js, path) def render_combined(self, ctx): """ Renders the combined js file. """ parts = [] for path in self.paths(): if not self.minify: s = '/*{0}*/\n/*{1:^76}*/\n/*{0}*/'.format('*' * 76, path) parts.append(s) parts.append(self.tpl.renderer.render_file(ctx, path)) return '\n\n'.join(parts) def _response(self, ctx, js=None): """ Sets appropriate headers on the http response. Will optionally set the response body to the given *js* string. """ ctx.http.response.content_type = 'application/javascript; charset=UTF-8' if js: ctx.http.response.text = js return ctx.http.response def _path2urlpath(self, path): """ Converts a :term:`path <asset path>` to the corresponding path to use in URLs. """ urlpath = path if not urlpath.endswith('.js'): urlpath = urlpath[:urlpath.rindex('.')] assert urlpath.endswith('.js') return urlpath def _urlpath2path(self, urlpath): """ Converts a *urlpath*, as passed in via the URL, into the actual :term:`asset path`. """ assert urlpath.endswith('.js') jspath = urlpath if jspath in self.virtfiles.paths(): return jspath if os.path.isfile(os.path.join(self.rootdir, jspath)): return jspath for ext in self.tpl.renderer.engines: file = os.path.join(self.rootdir, jspath + '.' + ext) if os.path.isfile(file): return jspath + '.' + ext raise ValueError('Could not determine path for url "%s"' % urlpath) def _tags(self, ctx, *paths): """ Generates all ``script`` tags necessary to include all javascript files. It is possible to generate the tags to specific js *paths* only. """ tag = '<script src="%s"></script>' if len(paths): return '\n'.join(tag % self.http.url(ctx, 'score.js:single', path) for path in paths) if self.combine: return tag % self.http.url(ctx, 'score.js:combined') if not len(self.paths()): return '' return self._tags(ctx, *self.paths())