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.
 
 
 

216 lines
6.3 KiB

  1. # -*- coding: utf-8 -*-
  2. """
  3. Contains data structures and utilities for hyde.
  4. """
  5. from hyde.fs import File, Folder
  6. import codecs
  7. import yaml
  8. from datetime import datetime
  9. from UserDict import IterableUserDict
  10. from hyde.util import getLoggerWithNullHandler
  11. logger = getLoggerWithNullHandler('hyde.engine')
  12. class Expando(object):
  13. """
  14. A generic expando class that creates attributes from
  15. the passed in dictionary.
  16. """
  17. def __init__(self, d):
  18. super(Expando, self).__init__()
  19. self.update(d)
  20. def __iter__(self):
  21. """
  22. Returns an iterator for all the items in the
  23. dictionary as key value pairs.
  24. """
  25. return self.__dict__.iteritems()
  26. def update(self, d):
  27. """
  28. Updates the expando with a new dictionary
  29. """
  30. d = d or {}
  31. if isinstance(d, dict):
  32. for key, value in d.items():
  33. self.set_expando(key, value)
  34. elif isinstance(d, Expando):
  35. self.update(d.to_dict())
  36. def set_expando(self, key, value):
  37. """
  38. Sets the expando attribute after
  39. transforming the value.
  40. """
  41. setattr(self, unicode(key).encode('utf-8'), self.transform(value))
  42. def transform(self, primitive):
  43. """
  44. Creates an expando object, a sequence of expando objects or just
  45. returns the primitive based on the primitive's type.
  46. """
  47. if isinstance(primitive, dict):
  48. return Expando(primitive)
  49. elif isinstance(primitive, (tuple, list, set, frozenset)):
  50. seq = type(primitive)
  51. return seq(self.transform(attr) for attr in primitive)
  52. else:
  53. return primitive
  54. def __repr__(self):
  55. return unicode(self.to_dict())
  56. def to_dict(self):
  57. """
  58. Reverse transform an expando to dict
  59. """
  60. result = {}
  61. d = self.__dict__
  62. for k, v in d.items():
  63. if isinstance(v, Expando):
  64. result[k] = v.to_dict()
  65. elif isinstance(v, (tuple, list, set, frozenset)):
  66. seq = type(v)
  67. result[k] = seq(item.to_dict() if isinstance(item, Expando)
  68. else item for item in v)
  69. else:
  70. result[k] = v
  71. return result
  72. class Context(object):
  73. """
  74. Wraps the context related functions and utilities.
  75. """
  76. @staticmethod
  77. def load(sitepath, ctx):
  78. """
  79. Load context from config data and providers.
  80. """
  81. context = {}
  82. try:
  83. context.update(ctx.data.__dict__)
  84. for provider_name, resource_name in ctx.providers.__dict__.items():
  85. res = File(Folder(sitepath).child(resource_name))
  86. if res.exists:
  87. context[provider_name] = Expando(yaml.load(res.read_all()))
  88. except AttributeError:
  89. # No context data found
  90. pass
  91. return context
  92. class Dependents(IterableUserDict):
  93. """
  94. Represents the dependency graph for hyde.
  95. """
  96. def __init__(self, sitepath, depends_file_name='.hyde_deps'):
  97. self.sitepath = Folder(sitepath)
  98. self.deps_file = File(self.sitepath.child(depends_file_name))
  99. self.data = {}
  100. if self.deps_file.exists:
  101. self.data = yaml.load(self.deps_file.read_all())
  102. import atexit
  103. atexit.register(self.save)
  104. def save(self):
  105. """
  106. Saves the dependency graph (just a dict for now).
  107. """
  108. if self.deps_file.parent.exists:
  109. self.deps_file.write(yaml.dump(self.data))
  110. class Config(Expando):
  111. """
  112. Represents the hyde configuration file
  113. """
  114. def __init__(self, sitepath, config_file=None, config_dict=None):
  115. default_config = dict(
  116. mode='production',
  117. content_root='content',
  118. deploy_root='deploy',
  119. media_root='media',
  120. layout_root='layout',
  121. media_url='/media',
  122. base_url="/",
  123. not_found='404.html',
  124. plugins = [],
  125. ignore = [ "*~", "*.bak" ]
  126. )
  127. self.config_file = config_file
  128. self.config_dict = config_dict
  129. self.load_time = datetime.min
  130. self.config_files = []
  131. self.sitepath = Folder(sitepath)
  132. conf = dict(**default_config)
  133. conf.update(self.read_config(config_file))
  134. if config_dict:
  135. conf.update(config_dict)
  136. super(Config, self).__init__(conf)
  137. @property
  138. def last_modified(self):
  139. return max((conf.last_modified for conf in self.config_files))
  140. def needs_refresh(self):
  141. if not self.config_files:
  142. return True
  143. return any((conf.has_changed_since(self.load_time)
  144. for conf in self.config_files))
  145. def read_config(self, config_file):
  146. """
  147. Reads the configuration file and updates this
  148. object while allowing for inherited configurations.
  149. """
  150. conf_file = self.sitepath.child(
  151. config_file if
  152. config_file else 'site.yaml')
  153. conf = {}
  154. if File(conf_file).exists:
  155. self.config_files.append(File(conf_file))
  156. logger.info("Reading site configuration from [%s]", conf_file)
  157. with codecs.open(conf_file, 'r', 'utf-8') as stream:
  158. conf = yaml.load(stream)
  159. if 'extends' in conf:
  160. parent = self.read_config(conf['extends'])
  161. parent.update(conf)
  162. conf = parent
  163. self.load_time = datetime.now()
  164. return conf
  165. @property
  166. def deploy_root_path(self):
  167. """
  168. Derives the deploy root path from the site path
  169. """
  170. return self.sitepath.child_folder(self.deploy_root)
  171. @property
  172. def content_root_path(self):
  173. """
  174. Derives the content root path from the site path
  175. """
  176. return self.sitepath.child_folder(self.content_root)
  177. @property
  178. def media_root_path(self):
  179. """
  180. Derives the media root path from the content path
  181. """
  182. return self.content_root_path.child_folder(self.media_root)
  183. @property
  184. def layout_root_path(self):
  185. """
  186. Derives the layout root path from the site path
  187. """
  188. return self.sitepath.child_folder(self.layout_root)