slim.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import os
  2. import sys
  3. import logging
  4. from pkg_resources import EntryPoint
  5. from jinja2 import Template, TemplateNotFound
  6. try:
  7. import plim
  8. except ImportError:
  9. plim = None
  10. try:
  11. import mako.lookup
  12. except ImportError:
  13. mako = None
  14. try:
  15. from bs4 import BeautifulSoup as bs
  16. except ImportError:
  17. bs = None
  18. try:
  19. from htmlmin import minify
  20. except ImportError:
  21. minify = None
  22. from pelican.writers import Writer, is_selected_for_writing
  23. from pelican.paginator import Paginator
  24. from pelican import signals
  25. logger = logging.getLogger(__name__)
  26. def get_writer(sender):
  27. class PlimWriter(Writer):
  28. def write_file(self, name, template, context, relative_urls=False,
  29. paginated=None, override_output=False, **kwargs):
  30. """Render the template and write the file.
  31. :param name: name of the file to output
  32. :param template: template to use to generate the content
  33. :param context: dict to pass to the templates.
  34. :param relative_urls: use relative urls or absolutes ones
  35. :param paginated: dict of article list to paginate - must have the
  36. same length (same list in different orders)
  37. :param override_output: boolean telling if we can override previous
  38. output with the same name (and if next files written with the same
  39. name should be skipped to keep that one)
  40. :param **kwargs: additional variables to pass to the templates
  41. """
  42. if name is False or name == "" or\
  43. not is_selected_for_writing(self.settings,\
  44. os.path.join(self.output_path, name)):
  45. return
  46. elif not name:
  47. # other stuff, just return for now
  48. return
  49. def _render_using_plim(filename, localcontext):
  50. """Render the template using Plim."""
  51. root_dir = os.path.dirname(os.path.abspath(filename))
  52. template_file = os.path.basename(filename)
  53. lookup = mako.lookup.TemplateLookup(
  54. directories=[root_dir],
  55. input_encoding='utf-8',
  56. output_encoding='utf-8',
  57. preprocessor=plim.preprocessor,
  58. strict_undefined=True,
  59. default_filters=['trim'])
  60. output = lookup.get_template(template_file).render_unicode(
  61. **localcontext)
  62. if ('SLIM_OPTIONS' in self.settings and
  63. 'PRETTYIFY' in self.settings['SLIM_OPTIONS'] and
  64. self.settings['SLIM_OPTIONS']['PRETTYIFY']):
  65. output = bs(output).prettify() # prettify the html
  66. else:
  67. output = minify(output) # minify the html
  68. return output
  69. def _write_file(template, localcontext, output_path, name, override):
  70. """Render the template write the file."""
  71. # set localsiteurl for context so that Contents can adjust links
  72. if localcontext['localsiteurl']:
  73. context['localsiteurl'] = localcontext['localsiteurl']
  74. output = _render_using_plim(template.filename, localcontext)
  75. # output = template.render(localcontext) # render using jinja2
  76. path = os.path.join(output_path, name)
  77. try:
  78. os.makedirs(os.path.dirname(path))
  79. except Exception:
  80. pass
  81. with self._open_w(path, 'utf-8', override=override) as f:
  82. f.write(output)
  83. logger.info('Writing %s', path)
  84. # Send a signal to say we're writing a file with some specific
  85. # local context.
  86. signals.content_written.send(path, context=localcontext)
  87. def _get_localcontext(context, name, kwargs, relative_urls):
  88. localcontext = context.copy()
  89. localcontext['localsiteurl'] = localcontext.get(
  90. 'localsiteurl', None)
  91. if relative_urls:
  92. relative_url = path_to_url(get_relative_path(name))
  93. localcontext['SITEURL'] = relative_url
  94. localcontext['localsiteurl'] = relative_url
  95. localcontext['output_file'] = name
  96. localcontext.update(kwargs)
  97. return localcontext
  98. # pagination
  99. if paginated:
  100. # pagination needed, init paginators
  101. paginators = {key: Paginator(name, val, self.settings)
  102. for key, val in paginated.items()}
  103. # generated pages, and write
  104. for page_num in range(list(paginators.values())[0].num_pages):
  105. paginated_kwargs = kwargs.copy()
  106. for key in paginators.keys():
  107. paginator = paginators[key]
  108. previous_page = paginator.page(page_num) \
  109. if page_num > 0 else None
  110. page = paginator.page(page_num + 1)
  111. next_page = paginator.page(page_num + 2) \
  112. if page_num + 1 < paginator.num_pages else None
  113. paginated_kwargs.update(
  114. {'%s_paginator' % key: paginator,
  115. '%s_page' % key: page,
  116. '%s_previous_page' % key: previous_page,
  117. '%s_next_page' % key: next_page})
  118. localcontext = _get_localcontext(context, page.save_as,
  119. paginated_kwargs, relative_urls)
  120. _write_file(template, localcontext, self.output_path,
  121. page.save_as, override_output)
  122. else:
  123. # no pagination
  124. localcontext = _get_localcontext(context, name, kwargs,
  125. relative_urls)
  126. _write_file(template, localcontext, self.output_path, name,
  127. override_output)
  128. return PlimWriter
  129. def register():
  130. """Plugin registration."""
  131. if not plim:
  132. logger.warning('`slim` failed to load dependency `plim`. '
  133. '`slim` plugin not loaded.')
  134. return
  135. if not mako:
  136. logger.warning('`slim` failed to load dependency `mako`. '
  137. '`slim` plugin not loaded.')
  138. return
  139. if not bs:
  140. logger.warning('`slim` failed to load dependency `BeautifulSoup4`. '
  141. '`slim` plugin not loaded.')
  142. return
  143. if not minify:
  144. logger.warning('`slim` failed to load dependency `htmlmin`. '
  145. '`slim` plugin not loaded.')
  146. return
  147. signals.get_writer.connect(get_writer)