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.
 
 
 

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