コード例 #1
0
ファイル: _init.py プロジェクト: score-framework/py.svg
 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)
コード例 #2
0
ファイル: _init.py プロジェクト: score-framework/py.js
 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()
コード例 #3
0
ファイル: _init.py プロジェクト: score-framework/py.css
 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()
コード例 #4
0
ファイル: _init.py プロジェクト: score-framework/py.css
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>'
コード例 #5
0
ファイル: _init.py プロジェクト: score-framework/py.svg
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
コード例 #6
0
ファイル: _init.py プロジェクト: score-framework/py.js
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())