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.
 
 
 

246 lines
7.5 KiB

  1. # -*- coding: utf-8 -*-
  2. """
  3. Parses & holds information about the site to be generated.
  4. """
  5. from hyde.fs import File, Folder
  6. from hyde.exceptions import HydeException
  7. import logging
  8. import os
  9. from logging import NullHandler
  10. logger = logging.getLogger('hyde.site')
  11. logger.addHandler(NullHandler())
  12. class Resource(object):
  13. """
  14. Represents any file that is processed by hyde
  15. """
  16. def __init__(self, source_file, node):
  17. super(Resource, self).__init__()
  18. self.source_file = source_file
  19. if not node:
  20. raise HydeException("Resource cannot exist without a node")
  21. if not source_file:
  22. raise HydeException("Source file is required to instantiate a resource")
  23. self.node = node
  24. def __repr__(self):
  25. return self.path
  26. @property
  27. def path(self):
  28. """
  29. Gets the source path of this node.
  30. """
  31. return self.source_file.path
  32. @property
  33. def relative_path(self):
  34. """
  35. Gets the path relative to the root folder (Content, Media, Layout)
  36. """
  37. return self.source_file.get_relative_path(self.node.root.source_folder)
  38. class Node(object):
  39. """
  40. Represents any folder that is processed by hyde
  41. """
  42. def __init__(self, source_folder, parent=None):
  43. super(Node, self).__init__()
  44. if not source_folder:
  45. raise HydeException("Source folder is required to instantiate a node.")
  46. self.root = self
  47. self.module = None
  48. self.site = None
  49. self.source_folder = Folder(str(source_folder))
  50. self.parent = parent
  51. if parent:
  52. self.root = self.parent.root
  53. self.module = self.parent.module if self.parent.module else self
  54. self.site = parent.site
  55. self.child_nodes = []
  56. self.resources = []
  57. def __repr__(self):
  58. return self.path
  59. def add_child_node(self, folder):
  60. """
  61. Creates a new child node and adds it to the list of child nodes.
  62. """
  63. if folder.parent != self.source_folder:
  64. raise HydeException("The given folder [%s] is not a direct descendant of [%s]" %
  65. (folder, self.source_folder))
  66. node = Node(folder, self)
  67. self.child_nodes.append(node)
  68. return node
  69. def add_child_resource(self, afile):
  70. """
  71. Creates a new resource and adds it to the list of child resources.
  72. """
  73. if afile.parent != self.source_folder:
  74. raise HydeException("The given file [%s] is not a direct descendant of [%s]" %
  75. (afile, self.source_folder))
  76. resource = Resource(afile, self)
  77. self.resources.append(resource)
  78. return resource
  79. @property
  80. def path(self):
  81. """
  82. Gets the source path of this node.
  83. """
  84. return self.source_folder.path
  85. @property
  86. def relative_path(self):
  87. """
  88. Gets the path relative to the root folder (Content, Media, Layout)
  89. """
  90. return self.source_folder.get_relative_path(self.root.source_folder)
  91. class RootNode(Node):
  92. """
  93. Represents one of the roots of site: Content, Media or Layout
  94. """
  95. def __init__(self, source_folder, site):
  96. super(RootNode, self).__init__(source_folder)
  97. self.site = site
  98. self.node_map = {}
  99. self.resource_map = {}
  100. def node_from_path(self, path):
  101. """
  102. Gets the node that maps to the given path. If no match is found it returns None.
  103. """
  104. if Folder(path) == self.source_folder:
  105. return self
  106. return self.node_map.get(str(Folder(path)), None)
  107. def node_from_relative_path(self, relative_path):
  108. """
  109. Gets the content node that maps to the given relative path. If no match is found it returns None.
  110. """
  111. return self.node_from_path(self.source_folder.child(str(relative_path)))
  112. def resource_from_path(self, path):
  113. """
  114. Gets the resource that maps to the given path. If no match is found it returns None.
  115. """
  116. return self.resource_map.get(str(File(path)), None)
  117. def resource_from_relative_path(self, relative_path):
  118. """
  119. Gets the content resource that maps to the given relative path. If no match is found it returns None.
  120. """
  121. return self.resource_from_path(self.source_folder.child(str(relative_path)))
  122. def add_node(self, a_folder):
  123. """
  124. Adds a new node to this folder's hierarchy. Also adds to to the hashtable of path to
  125. node associations for quick lookup.
  126. """
  127. folder = Folder(a_folder)
  128. node = self.node_from_path(folder)
  129. if node:
  130. logger.info("Node exists at [%s]" % node.relative_path)
  131. return node
  132. if not folder.is_descendant_of(self.source_folder):
  133. raise HydeException("The given folder [%s] does not belong to this hierarchy [%s]" %
  134. (folder, self.source_folder))
  135. p_folder = folder
  136. parent = None
  137. hierarchy = []
  138. while not parent:
  139. hierarchy.append(p_folder)
  140. p_folder = p_folder.parent
  141. parent = self.node_from_path(p_folder)
  142. hierarchy.reverse()
  143. node = parent if parent else self
  144. for h_folder in hierarchy:
  145. node = node.add_child_node(h_folder)
  146. self.node_map[str(h_folder)] = node
  147. logger.info("Added node [%s] to [%s]" % (node.relative_path, self.source_folder))
  148. return node
  149. def add_resource(self, a_file):
  150. """
  151. Adds a file to the parent node. Also adds to to the hashtable of path to
  152. resource associations for quick lookup.
  153. """
  154. afile = File(a_file)
  155. resource = self.resource_from_path(afile)
  156. if resource:
  157. logger.info("Resource exists at [%s]" % resource.relative_path)
  158. if not afile.is_descendant_of(self.source_folder):
  159. raise HydeException("The given file [%s] does not reside in this hierarchy [%s]" %
  160. (afile, self.content_folder))
  161. node = self.node_from_path(afile.parent)
  162. if not node:
  163. node = self.add_node(afile.parent)
  164. resource = node.add_child_resource(afile)
  165. self.resource_map[str(afile)] = resource
  166. logger.info("Added resource [%s] to [%s]" % (resource.relative_path, self.source_folder))
  167. return resource
  168. def build(self):
  169. """
  170. Walks the `source_folder` and builds the sitemap. Creates nodes and resources,
  171. reads metadata and injects attributes. This is the model for hyde.
  172. """
  173. if not self.source_folder.exists:
  174. raise HydeException("The given source folder[%s] does not exist" % self.source_folder)
  175. with self.source_folder.walk() as walker:
  176. @walker.folder_visitor
  177. def visit_folder(folder):
  178. self.add_node(folder)
  179. @walker.file_visitor
  180. def visit_file(afile):
  181. self.add_resource(afile)
  182. class Site(object):
  183. """
  184. Represents the site to be generated
  185. """
  186. def __init__(self, site_path):
  187. super(Site, self).__init__()
  188. self.site_path = Folder(str(site_path))
  189. # TODO: Get the value from config
  190. content_folder = self.site_path.child_folder('content')
  191. self.content = RootNode(content_folder, self)
  192. self.node_map = {}
  193. self.resource_map = {}
  194. def build(self):
  195. """
  196. Walks the content and media folders to build up the sitemap.
  197. """
  198. self.content.build()