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.
 
 
 

187 lines
5.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 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, 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. return d
  62. class Context(object):
  63. """
  64. Wraps the context related functions and utilities.
  65. """
  66. @staticmethod
  67. def load(sitepath, ctx):
  68. """
  69. Load context from config data and providers.
  70. """
  71. context = {}
  72. try:
  73. context.update(ctx.data.__dict__)
  74. for provider_name, resource_name in ctx.providers.__dict__.items():
  75. res = File(Folder(sitepath).child(resource_name))
  76. if res.exists:
  77. context[provider_name] = yaml.load(res.read_all())
  78. except AttributeError:
  79. # No context data found
  80. pass
  81. return context
  82. class Dependents(IterableUserDict):
  83. """
  84. Represents the dependency graph for hyde.
  85. """
  86. def __init__(self, sitepath, depends_file_name='.hyde_deps'):
  87. self.sitepath = Folder(sitepath)
  88. self.deps_file = File(self.sitepath.child(depends_file_name))
  89. self.data = {}
  90. if self.deps_file.exists:
  91. self.data = yaml.load(self.deps_file.read_all())
  92. import atexit
  93. atexit.register(self.save)
  94. def save(self):
  95. """
  96. Saves the dependency graph (just a dict for now).
  97. """
  98. if self.deps_file.parent.exists:
  99. self.deps_file.write(yaml.dump(self.data))
  100. class Config(Expando):
  101. """
  102. Represents the hyde configuration file
  103. """
  104. def __init__(self, sitepath, config_file=None, config_dict=None):
  105. default_config = dict(
  106. mode='production',
  107. content_root='content',
  108. deploy_root='deploy',
  109. media_root='media',
  110. layout_root='layout',
  111. media_url='/media',
  112. site_url='/',
  113. not_found='404.html',
  114. plugins = []
  115. )
  116. conf = dict(**default_config)
  117. self.sitepath = Folder(sitepath)
  118. conf.update(self.read_config(config_file))
  119. if config_dict:
  120. conf.update(config_dict)
  121. super(Config, self).__init__(conf)
  122. def read_config(self, config_file):
  123. """
  124. Reads the configuration file and updates this
  125. object while allowing for inherited configurations.
  126. """
  127. conf_file = self.sitepath.child(
  128. config_file if
  129. config_file else 'site.yaml')
  130. conf = {}
  131. if File(conf_file).exists:
  132. logger.info("Reading site configuration from [%s]", conf_file)
  133. with codecs.open(conf_file, 'r', 'utf-8') as stream:
  134. conf = yaml.load(stream)
  135. if 'extends' in conf:
  136. parent = self.read_config(conf['extends'])
  137. parent.update(conf)
  138. conf = parent
  139. return conf
  140. @property
  141. def deploy_root_path(self):
  142. """
  143. Derives the deploy root path from the site path
  144. """
  145. return self.sitepath.child_folder(self.deploy_root)
  146. @property
  147. def content_root_path(self):
  148. """
  149. Derives the content root path from the site path
  150. """
  151. return self.sitepath.child_folder(self.content_root)
  152. @property
  153. def media_root_path(self):
  154. """
  155. Derives the media root path from the site path
  156. """
  157. return self.sitepath.child_folder(self.media_root)
  158. @property
  159. def layout_root_path(self):
  160. """
  161. Derives the layout root path from the site path
  162. """
  163. return self.sitepath.child_folder(self.layout_root)