A fork of hyde, the static site generation. Some patches will be pushed upstream.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

229 lines
7.9 KiB

  1. """
  2. The generator class and related utility functions.
  3. """
  4. from hyde.exceptions import HydeException
  5. from hyde.fs import File
  6. from hyde.plugin import Plugin
  7. from hyde.template import Template
  8. from contextlib import contextmanager
  9. from hyde.util import getLoggerWithNullHandler
  10. logger = getLoggerWithNullHandler('hyde.engine')
  11. class Generator(object):
  12. """
  13. Generates output from a node or resource.
  14. """
  15. def __init__(self, site):
  16. super(Generator, self).__init__()
  17. self.site = site
  18. self.generated_once = False
  19. self.__context__ = dict(site=site)
  20. self.template = None
  21. Plugin.load_all(site)
  22. class PluginProxy(object):
  23. """
  24. A proxy class to raise events in registered plugins
  25. """
  26. def __init__(self, site):
  27. super(PluginProxy, self).__init__()
  28. self.site = site
  29. def __getattr__(self, method_name):
  30. if hasattr(Plugin, method_name):
  31. def __call_plugins__(*args):
  32. res = None
  33. if self.site.plugins:
  34. for plugin in self.site.plugins:
  35. if hasattr(plugin, method_name):
  36. function = getattr(plugin, method_name)
  37. res = function(*args)
  38. if res:
  39. targs = list(args)
  40. last = None
  41. if len(targs):
  42. last = targs.pop()
  43. targs.append(res if res else last)
  44. args = tuple(targs)
  45. return res
  46. return __call_plugins__
  47. raise HydeException(
  48. "Unknown plugin method [%s] called." % method_name)
  49. self.events = PluginProxy(self.site)
  50. @contextmanager
  51. def context_for_resource(self, resource):
  52. """
  53. Context manager that intializes the context for a given
  54. resource and rolls it back after the resource is processed.
  55. """
  56. # TODO: update metadata and other resource
  57. # specific properties here.
  58. self.__context__.update(resource=resource)
  59. yield self.__context__
  60. self.__context__.update(resource=None)
  61. def load_template_if_needed(self):
  62. """
  63. Loads and configures the template environement from the site
  64. configuration if its not done already.
  65. """
  66. if not self.template:
  67. logger.info("Generating site at [%s]" % self.site.sitepath)
  68. self.template = Template.find_template(self.site)
  69. logger.info("Using [%s] as the template",
  70. self.template.__class__.__name__)
  71. logger.info("Configuring the template environment")
  72. self.template.configure(self.site.config)
  73. self.events.template_loaded(self.template)
  74. def initialize(self):
  75. """
  76. Start Generation. Perform setup tasks and inform plugins.
  77. """
  78. logger.info("Begin Generation")
  79. self.events.begin_generation()
  80. def load_site_if_needed(self):
  81. """
  82. Checks if the site requries a reload and loads if
  83. necessary.
  84. """
  85. #TODO: Perhaps this is better suited in Site
  86. if not len(self.site.content.child_nodes):
  87. logger.info("Reading site contents")
  88. self.site.load()
  89. def finalize(self):
  90. """
  91. Generation complete. Inform plugins and cleanup.
  92. """
  93. logger.info("Generation Complete")
  94. self.events.generation_complete()
  95. def generate_all(self):
  96. """
  97. Generates the entire website
  98. """
  99. logger.info("Reading site contents")
  100. self.load_template_if_needed()
  101. self.initialize()
  102. self.load_site_if_needed()
  103. self.events.begin_site()
  104. logger.info("Generating site to [%s]" %
  105. self.site.config.deploy_root_path)
  106. self.__generate_node__(self.site.content)
  107. self.events.site_complete()
  108. self.finalize()
  109. self.generated_once = True
  110. def generate_node_at_path(self, node_path=None):
  111. """
  112. Generates a single node. If node_path is non-existent or empty,
  113. generates the entire site.
  114. """
  115. if not self.generated_once:
  116. return self.generate_all()
  117. self.load_template_if_needed()
  118. self.load_site_if_needed()
  119. node = None
  120. if node_path:
  121. node = self.site.content.node_from_path(node_path)
  122. self.generate_node(node)
  123. def generate_node(self, node=None):
  124. """
  125. Generates the given node. If node is invalid, empty or
  126. non-existent, generates the entire website.
  127. """
  128. if not node or not self.generated_once:
  129. return self.generate_all()
  130. self.load_template_if_needed()
  131. self.initialize()
  132. self.load_site_if_needed()
  133. try:
  134. self.__generate_node__(node)
  135. self.finalize()
  136. except HydeException:
  137. self.generate_all()
  138. def generate_resource_at_path(self, resource_path=None):
  139. """
  140. Generates a single resource. If resource_path is non-existent or empty,
  141. generats the entire website.
  142. """
  143. if not self.generated_once:
  144. return self.generate_all()
  145. self.load_template_if_needed()
  146. self.load_site_if_needed()
  147. resource = None
  148. if resource_path:
  149. resource = self.site.content.resource_from_path(resource_path)
  150. self.generate_resource(resource)
  151. def generate_resource(self, resource=None):
  152. """
  153. Generates the given resource. If resource is invalid, empty or
  154. non-existent, generates the entire website.
  155. """
  156. if not resource or not self.generated_once:
  157. return self.generate_all()
  158. self.load_template_if_needed()
  159. self.initialize()
  160. self.load_site_if_needed()
  161. try:
  162. self.__generate_resource__(resource)
  163. self.finalize()
  164. except HydeException:
  165. self.generate_all()
  166. def __generate_node__(self, node):
  167. logger.info("Generating [%s]", node)
  168. for node in node.walk():
  169. self.events.begin_node(node)
  170. for resource in node.resources:
  171. self.__generate_resource__(resource)
  172. self.events.node_complete(node)
  173. def __generate_resource__(self, resource):
  174. if not resource.is_processable:
  175. logger.info("Skipping [%s]", resource)
  176. return
  177. logger.info("Processing [%s]", resource)
  178. with self.context_for_resource(resource) as context:
  179. if resource.source_file.is_text:
  180. text = resource.source_file.read_all()
  181. text = self.events.begin_text_resource(resource, text) or text
  182. if resource.uses_template:
  183. logger.info("Rendering [%s]", resource)
  184. text = self.template.render(text, context)
  185. text = self.events.text_resource_complete(
  186. resource, text) or text
  187. target = File(self.site.config.deploy_root_path.child(
  188. resource.relative_deploy_path))
  189. target.parent.make()
  190. target.write(text)
  191. else:
  192. logger.info("Copying binary file [%s]", resource)
  193. self.events.begin_binary_resource(resource)
  194. target = File(self.site.config.deploy_root_path.child(
  195. resource.relative_deploy_path))
  196. target.parent.make()
  197. resource.source_file.copy_to(target)
  198. self.events.binary_resource_complete(resource)