PageRenderTime 47ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/src/webassets/filter/cssrewrite/base.py

https://github.com/mcfletch/webassets
Python | 118 lines | 103 code | 7 blank | 8 comment | 3 complexity | 92de989bf97bcde5aab1ad3e608d0573 MD5 | raw file
Possible License(s): BSD-2-Clause
  1. import os
  2. import re
  3. from os.path import join, normpath
  4. from webassets.filter import Filter
  5. from webassets.utils import common_path_prefix
  6. __all__ = ()
  7. def addsep(path):
  8. """Add a trailing path separator."""
  9. if path and path[-1] != os.path.sep:
  10. return path + os.path.sep
  11. return path
  12. def path2url(path):
  13. """Simple helper for NT systems to replace slash syntax."""
  14. if os.name == 'nt':
  15. return path.replace('\\', '/')
  16. return path
  17. class PatternRewriter(Filter):
  18. """Base class for input filters which want to replace certain patterns.
  19. """
  20. # Define the patterns in the form of:
  21. # method to call -> pattern to call it for (as a compiled regex)
  22. patterns = {}
  23. def input(self, _in, out, **kw):
  24. content = _in.read()
  25. for func, pattern in self.patterns.items():
  26. if not callable(func):
  27. func = getattr(self, func)
  28. # Should this pass along **kw? How many subclasses would need it?
  29. # As is, subclasses needing access need to overwrite input() and
  30. # set class attributes.
  31. content = pattern.sub(func, content)
  32. out.write(content)
  33. urltag_re = re.compile(r"""
  34. url\(
  35. (\s*) # allow whitespace wrapping (and capture)
  36. ( # capture actual url
  37. [^\)\\\r\n]*? # don't allow newlines, closing paran, escape chars (1)
  38. (?:\\. # process all escapes here instead
  39. [^\)\\\r\n]*? # proceed, with previous restrictions (1)
  40. )* # repeat until end
  41. )
  42. (\s*) # whitespace again (and capture)
  43. \)
  44. # (1) non-greedy to let the last whitespace group capture something
  45. # TODO: would it be faster to handle whitespace within _rewrite()?
  46. """, re.VERBOSE)
  47. class CSSUrlRewriter(PatternRewriter):
  48. """Base class for input filters which need to replace url() statements
  49. in CSS stylesheets.
  50. """
  51. patterns = {
  52. 'rewrite_url': urltag_re
  53. }
  54. def input(self, _in, out, **kw):
  55. source, source_path, output, output_path = \
  56. kw['source'], kw['source_path'], kw['output'], kw['output_path']
  57. self.source_path = source_path
  58. self.output_path = output_path
  59. self.source_url = self.ctx.resolver.resolve_source_to_url(
  60. self.ctx, source_path, source)
  61. self.output_url = self.ctx.resolver.resolve_output_to_url(
  62. self.ctx, output)
  63. return super(CSSUrlRewriter, self).input(_in, out, **kw)
  64. def rewrite_url(self, m):
  65. # Get the regex matches; note how we maintain the exact
  66. # whitespace around the actual url; we'll indeed only
  67. # replace the url itself.
  68. text_before = m.groups()[0]
  69. url = m.groups()[1]
  70. text_after = m.groups()[2]
  71. # Normalize the url: remove quotes
  72. quotes_used = ''
  73. if url[:1] in '"\'':
  74. quotes_used = url[:1]
  75. url = url[1:]
  76. if url[-1:] in '"\'':
  77. url = url[:-1]
  78. url = self.replace_url(url) or url
  79. result = 'url(%s%s%s%s%s)' % (
  80. text_before, quotes_used, url, quotes_used, text_after)
  81. return result
  82. def replace_url(self, url):
  83. """Implement this to return a replacement for each URL found."""
  84. raise NotImplementedError()
  85. if __name__ == '__main__':
  86. for text, expect in [
  87. (r' url(icon\)xyz) ', r'url(icon\)xyz)'),
  88. (r' url(icon\\)xyz) ', r'url(icon\\)'),
  89. (r' url(icon\\\)xyz) ', r'url(icon\\\)xyz)'),
  90. ]:
  91. m = urltag_re.search(text)
  92. assert m.group() == expect