PageRenderTime 33ms CodeModel.GetById 13ms app.highlight 16ms RepoModel.GetById 1ms app.codeStats 0ms

/hyde/model.py

http://github.com/hyde/hyde
Python | 261 lines | 146 code | 6 blank | 109 comment | 6 complexity | e7bc73a84915237b3cb00983e1ce7a47 MD5 | raw file
  1# -*- coding: utf-8 -*-
  2"""
  3Contains data structures and utilities for hyde.
  4"""
  5import codecs
  6import yaml
  7from datetime import datetime
  8
  9from commando.util import getLoggerWithNullHandler
 10from fswrap import File, Folder
 11
 12from hyde._compat import iteritems, str, UserDict
 13
 14logger = getLoggerWithNullHandler('hyde.engine')
 15
 16SEQS = (tuple, list, set, frozenset)
 17
 18
 19def make_expando(primitive):
 20    """
 21    Creates an expando object, a sequence of expando objects or just
 22    returns the primitive based on the primitive's type.
 23    """
 24    if isinstance(primitive, dict):
 25        return Expando(primitive)
 26    elif isinstance(primitive, SEQS):
 27        seq = type(primitive)
 28        return seq(make_expando(attr) for attr in primitive)
 29    else:
 30        return primitive
 31
 32
 33class Expando(object):
 34
 35    """
 36    A generic expando class that creates attributes from
 37    the passed in dictionary.
 38    """
 39
 40    def __init__(self, d):
 41        super(Expando, self).__init__()
 42        self.update(d)
 43
 44    def __iter__(self):
 45        """
 46        Returns an iterator for all the items in the
 47        dictionary as key value pairs.
 48        """
 49        return iteritems(self.__dict__)
 50
 51    def update(self, d):
 52        """
 53        Updates the expando with a new dictionary
 54        """
 55        d = d or {}
 56        if isinstance(d, dict):
 57            for key, value in d.items():
 58                self.set_expando(key, value)
 59        elif isinstance(d, Expando):
 60            self.update(d.to_dict())
 61
 62    def set_expando(self, key, value):
 63        """
 64        Sets the expando attribute after
 65        transforming the value.
 66        """
 67        setattr(self, str(key), make_expando(value))
 68
 69    def __repr__(self):
 70        return str(self.to_dict())
 71
 72    def to_dict(self):
 73        """
 74        Reverse transform an expando to dict
 75        """
 76        result = {}
 77        d = self.__dict__
 78        for k, v in d.items():
 79            if isinstance(v, Expando):
 80                result[k] = v.to_dict()
 81            elif isinstance(v, SEQS):
 82                seq = type(v)
 83                result[k] = seq(item.to_dict()
 84                                if isinstance(item, Expando)
 85                                else item for item in v
 86                                )
 87            else:
 88                result[k] = v
 89        return result
 90
 91    def get(self, key, default=None):
 92        """
 93        Dict like get helper method
 94        """
 95        return self.__dict__.get(key, default)
 96
 97
 98class Context(object):
 99
100    """
101    Wraps the context related functions and utilities.
102    """
103
104    @staticmethod
105    def load(sitepath, ctx):
106        """
107        Load context from config data and providers.
108        """
109        context = {}
110        try:
111            context.update(ctx.data.__dict__)
112        except AttributeError:
113            # No context data found
114            pass
115
116        providers = {}
117        try:
118            providers.update(ctx.providers.__dict__)
119        except AttributeError:
120            # No providers found
121            pass
122
123        for provider_name, resource_name in providers.items():
124            res = File(Folder(sitepath).child(resource_name))
125            if res.exists:
126                data = make_expando(yaml.load(res.read_all()))
127                context[provider_name] = data
128
129        return context
130
131
132class Dependents(UserDict):
133
134    """
135    Represents the dependency graph for hyde.
136    """
137
138    def __init__(self, sitepath, depends_file_name='.hyde_deps'):
139        self.sitepath = Folder(sitepath)
140        self.deps_file = File(self.sitepath.child(depends_file_name))
141        self.data = {}
142        if self.deps_file.exists:
143            self.data = yaml.load(self.deps_file.read_all())
144        import atexit
145        atexit.register(self.save)
146
147    def save(self):
148        """
149        Saves the dependency graph (just a dict for now).
150        """
151        if self.deps_file.parent.exists:
152            self.deps_file.write(yaml.dump(self.data))
153
154
155def _expand_path(sitepath, path):
156    child = sitepath.child_folder(path)
157    return Folder(child.fully_expanded_path)
158
159
160class Config(Expando):
161
162    """
163    Represents the hyde configuration file
164    """
165
166    def __init__(self, sitepath, config_file=None, config_dict=None):
167        self.default_config = dict(
168            mode='production',
169            simple_copy=[],
170            content_root='content',
171            deploy_root='deploy',
172            media_root='media',
173            layout_root='layout',
174            media_url='/media',
175            base_url="/",
176            encode_safe=None,
177            not_found='404.html',
178            plugins=[],
179            ignore=["*~", "*.bak", ".hg", ".git", ".svn"],
180            meta={
181                "nodemeta": 'meta.yaml'
182            }
183        )
184        self.config_file = config_file
185        self.config_dict = config_dict
186        self.load_time = datetime.min
187        self.config_files = []
188        self.sitepath = Folder(sitepath)
189        super(Config, self).__init__(self.load())
190
191    @property
192    def last_modified(self):
193        return max((conf.last_modified for conf in self.config_files))
194
195    def needs_refresh(self):
196        if not self.config_files:
197            return True
198        return any((conf.has_changed_since(self.load_time)
199                    for conf in self.config_files))
200
201    def load(self):
202        conf = dict(**self.default_config)
203        conf.update(self.read_config(self.config_file))
204        if self.config_dict:
205            conf.update(self.config_dict)
206        return conf
207
208    def reload(self):
209        if not self.config_file:
210            return
211        self.update(self.load())
212
213    def read_config(self, config_file):
214        """
215        Reads the configuration file and updates this
216        object while allowing for inherited configurations.
217        """
218        conf_file = self.sitepath.child(
219            config_file if
220            config_file else 'site.yaml')
221        conf = {}
222        if File(conf_file).exists:
223            self.config_files.append(File(conf_file))
224            logger.info("Reading site configuration from [%s]", conf_file)
225            with codecs.open(conf_file, 'r', 'utf-8') as stream:
226                conf = yaml.load(stream)
227                if 'extends' in conf:
228                    parent = self.read_config(conf['extends'])
229                    parent.update(conf)
230                    conf = parent
231        self.load_time = datetime.now()
232        return conf
233
234    @property
235    def deploy_root_path(self):
236        """
237        Derives the deploy root path from the site path
238        """
239        return _expand_path(self.sitepath, self.deploy_root)
240
241    @property
242    def content_root_path(self):
243        """
244        Derives the content root path from the site path
245        """
246        return _expand_path(self.sitepath, self.content_root)
247
248    @property
249    def media_root_path(self):
250        """
251        Derives the media root path from the content path
252        """
253        path = Folder(self.content_root).child(self.media_root)
254        return _expand_path(self.sitepath, path)
255
256    @property
257    def layout_root_path(self):
258        """
259        Derives the layout root path from the site path
260        """
261        return _expand_path(self.sitepath, self.layout_root)