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.
 
 
 

209 lines
6.2 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.__dict__)
  36. def set_expando(self, key, value):
  37. """
  38. Sets the expando attribute after
  39. transforming the value.
  40. """
  41. setattr(self, 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 to_dict(self):
  55. """
  56. Reverse transform an expando to dict
  57. """
  58. d = self.__dict__
  59. for k, v in d.iteritems():
  60. if isinstance(v, Expando):
  61. d[k] = v.to_dict()
  62. elif isinstance(v, (tuple, list, set, frozenset)):
  63. seq = type(v)
  64. d[k] = seq(item.to_dict() if isinstance(item, Expando)
  65. else item for item in v)
  66. return d
  67. class Context(object):
  68. """
  69. Wraps the context related functions and utilities.
  70. """
  71. @staticmethod
  72. def load(sitepath, ctx):
  73. """
  74. Load context from config data and providers.
  75. """
  76. context = {}
  77. try:
  78. context.update(ctx.data.__dict__)
  79. for provider_name, resource_name in ctx.providers.__dict__.items():
  80. res = File(Folder(sitepath).child(resource_name))
  81. if res.exists:
  82. context[provider_name] = yaml.load(res.read_all())
  83. except AttributeError:
  84. # No context data found
  85. pass
  86. return context
  87. class Dependents(IterableUserDict):
  88. """
  89. Represents the dependency graph for hyde.
  90. """
  91. def __init__(self, sitepath, depends_file_name='.hyde_deps'):
  92. self.sitepath = Folder(sitepath)
  93. self.deps_file = File(self.sitepath.child(depends_file_name))
  94. self.data = {}
  95. if self.deps_file.exists:
  96. self.data = yaml.load(self.deps_file.read_all())
  97. import atexit
  98. atexit.register(self.save)
  99. def save(self):
  100. """
  101. Saves the dependency graph (just a dict for now).
  102. """
  103. if self.deps_file.parent.exists:
  104. self.deps_file.write(yaml.dump(self.data))
  105. class Config(Expando):
  106. """
  107. Represents the hyde configuration file
  108. """
  109. def __init__(self, sitepath, config_file=None, config_dict=None):
  110. default_config = dict(
  111. mode='production',
  112. content_root='content',
  113. deploy_root='deploy',
  114. media_root='media',
  115. layout_root='layout',
  116. media_url='/media',
  117. base_url="/",
  118. not_found='404.html',
  119. plugins = [],
  120. ignore = [ "*~", "*.bak" ]
  121. )
  122. self.config_file = config_file
  123. self.config_dict = config_dict
  124. self.load_time = datetime.min
  125. self.config_files = []
  126. self.sitepath = Folder(sitepath)
  127. conf = dict(**default_config)
  128. conf.update(self.read_config(config_file))
  129. if config_dict:
  130. conf.update(config_dict)
  131. super(Config, self).__init__(conf)
  132. @property
  133. def last_modified(self):
  134. return max((conf.last_modified for conf in self.config_files))
  135. def needs_refresh(self):
  136. if not self.config_files:
  137. return True
  138. return any((conf.has_changed_since(self.load_time)
  139. for conf in self.config_files))
  140. def read_config(self, config_file):
  141. """
  142. Reads the configuration file and updates this
  143. object while allowing for inherited configurations.
  144. """
  145. conf_file = self.sitepath.child(
  146. config_file if
  147. config_file else 'site.yaml')
  148. conf = {}
  149. if File(conf_file).exists:
  150. self.config_files.append(File(conf_file))
  151. logger.info("Reading site configuration from [%s]", conf_file)
  152. with codecs.open(conf_file, 'r', 'utf-8') as stream:
  153. conf = yaml.load(stream)
  154. if 'extends' in conf:
  155. parent = self.read_config(conf['extends'])
  156. parent.update(conf)
  157. conf = parent
  158. self.load_time = datetime.now()
  159. return conf
  160. @property
  161. def deploy_root_path(self):
  162. """
  163. Derives the deploy root path from the site path
  164. """
  165. return self.sitepath.child_folder(self.deploy_root)
  166. @property
  167. def content_root_path(self):
  168. """
  169. Derives the content root path from the site path
  170. """
  171. return self.sitepath.child_folder(self.content_root)
  172. @property
  173. def media_root_path(self):
  174. """
  175. Derives the media root path from the content path
  176. """
  177. return self.content_root_path.child_folder(self.media_root)
  178. @property
  179. def layout_root_path(self):
  180. """
  181. Derives the layout root path from the site path
  182. """
  183. return self.sitepath.child_folder(self.layout_root)