Mercurial > piecrust2
diff piecrust/appconfig.py @ 681:894d286b348f
internal: Refactor config loading some more.
* Remove fixup code in the app to make the app config class more standalone.
* Remove support for old-style variants... maybe bring it back later.
* Try and fix various bugs introduced by subtle config value overriding order
changes.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Tue, 08 Mar 2016 01:07:34 -0800 |
parents | 3df808b133f8 |
children | ec384174b8b2 |
line wrap: on
line diff
--- a/piecrust/appconfig.py Tue Mar 08 01:05:39 2016 -0800 +++ b/piecrust/appconfig.py Tue Mar 08 01:07:34 2016 -0800 @@ -28,88 +28,63 @@ variant_name)) -def _make_variant_fixup(variant_name, raise_if_not_found): - def _variant_fixup(index, config): - if index != -1: - return - try: - try: - v = get_dict_value(config, 'variants/%s' % variant_name) - except KeyError: - raise VariantNotFoundError(variant_name) - if not isinstance(v, dict): - raise VariantNotFoundError( - variant_name, - "Configuration variant '%s' is not an array. " - "Check your configuration file." % variant_name) - merge_dicts(config, v) - except VariantNotFoundError: - if raise_if_not_found: - raise - - return _variant_fixup - - class PieCrustConfiguration(Configuration): - def __init__(self, paths=None, cache=None, values=None, validate=True, - theme_config=False): + def __init__(self, *, path=None, theme_path=None, values=None, + cache=None, validate=True, theme_config=False): super(PieCrustConfiguration, self).__init__() - self._paths = paths + self._path = path + self._theme_path = theme_path self._cache = cache or NullCache() - self._fixups = [] + self._custom_paths = [] + self._post_fixups = [] self.theme_config = theme_config # Set the values after we set the rest, since our validation needs # our attributes. if values: self.setAll(values, validate=validate) - def addFixup(self, f): + def addPath(self, p): self._ensureNotLoaded() - self._fixups.append(f) - - def addPath(self, p, first=False): - self._ensureNotLoaded() - if not first: - self._paths.append(p) - else: - self._paths.insert(0, p) + self._custom_paths.append(p) def addVariant(self, variant_path, raise_if_not_found=True): self._ensureNotLoaded() if os.path.isfile(variant_path): self.addPath(variant_path) - else: - name, _ = os.path.splitext(os.path.basename(variant_path)) - fixup = _make_variant_fixup(name, raise_if_not_found) - self.addFixup(fixup) + elif raise_if_not_found: + logger.error( + "Configuration variants should now be `.yml` files " + "located in the `configs/` directory of your website.") + raise VariantNotFoundError(variant_path) - logger.warning( - "Configuration variants should now be `.yml` files located " - "in the `configs/` directory of your website.") - logger.warning( - "Variants defined in the site configuration will be " - "deprecated in a future version of PieCrust.") def addVariantValue(self, path, value): - def _fixup(index, config): + def _fixup(config): set_dict_value(config, path, value) - self.addFixup(_fixup) + self._post_fixups.append(_fixup) def _ensureNotLoaded(self): if self._values is not None: raise Exception("The configurations has been loaded.") def _load(self): - if self._paths is None: + paths_and_fixups = [] + if self._theme_path: + paths_and_fixups.append((self._theme_path, self._fixupThemeConfig)) + if self._path: + paths_and_fixups.append((self._path, None)) + paths_and_fixups += [(p, None) for p in self._custom_paths] + + if not paths_and_fixups: self._values = self._validateAll({}) return - path_times = [os.path.getmtime(p) for p in self._paths] + path_times = [os.path.getmtime(p[0]) for p in paths_and_fixups] cache_key_hash = hashlib.md5( ("version=%s&cache=%d" % ( APP_VERSION, CACHE_VERSION)).encode('utf8')) - for p in self._paths: + for p, _ in paths_and_fixups: cache_key_hash.update(("&path=%s" % p).encode('utf8')) cache_key = cache_key_hash.hexdigest() @@ -127,27 +102,29 @@ logger.debug("Outdated cache key '%s' (expected '%s')." % ( actual_cache_key, cache_key)) - logger.debug("Loading configuration from: %s" % self._paths) + logger.debug("Loading configuration from: %s" % + ', '.join([p[0] for p in paths_and_fixups])) values = {} try: - for i, p in enumerate(self._paths): + for p, f in paths_and_fixups: with open(p, 'r', encoding='utf-8') as fp: loaded_values = yaml.load( fp.read(), Loader=ConfigurationLoader) if loaded_values is None: loaded_values = {} - for fixup in self._fixups: - fixup(i, loaded_values) + if f: + f(loaded_values) merge_dicts(values, loaded_values) - for fixup in self._fixups: - fixup(-1, values) + for f in self._post_fixups: + f(values) self._values = self._validateAll(values) except Exception as ex: - raise Exception("Error loading configuration from: %s" % - ', '.join(self._paths)) from ex + raise Exception( + "Error loading configuration from: %s" % + ', '.join([p[0] for p in paths_and_fixups])) from ex logger.debug("Caching configuration...") self._values['__cache_key'] = cache_key @@ -160,22 +137,18 @@ if values is None: values = {} - # Add the loaded values to the default configuration. - values = merge_dicts(copy.deepcopy(default_configuration), values) - - # Set the theme site flag. - if self.theme_config: - values['site']['theme_site'] = True + # We 'prepend' newer values to default values, so we need to do + # things in the following order so that the final list is made of: + # (1) user values, (2) site values, (3) theme values. + # Still, we need to do a few theme-related things before generating + # the default site content model. + values = self._preValidateThemeConfig(values) + values = self._validateSiteConfig(values) + values = self._validateThemeConfig(values) - # Figure out if we need to generate the configuration for the - # default content model. - sitec = values.setdefault('site', {}) - gen_default_model = bool(sitec.get('use_default_content')) - if gen_default_model: - logger.debug("Generating default content model...") - values = self._generateDefaultContentModel(values) - - # Add a section for our cached information. + # Add a section for our cached information, and start visiting + # the configuration tree, calling validation functions as we + # find them. cachec = collections.OrderedDict() values['__cache'] = cachec cache_writer = _ConfigCacheWriter(cachec) @@ -199,24 +172,74 @@ return values - def _generateDefaultContentModel(self, values): - dcmcopy = copy.deepcopy(default_content_model_base) - values = merge_dicts(dcmcopy, values) + def _fixupThemeConfig(self, values): + # Make all sources belong to the "theme" realm. + sitec = values.get('site') + if sitec: + srcc = sitec.get('sources') + if srcc: + for sn, sc in srcc.items(): + sc['realm'] = REALM_THEME + + def _preValidateThemeConfig(self, values): + if not self._theme_path: + return values - blogsc = values['site'].get('blogs') - if blogsc is None: - blogsc = ['posts'] - values['site']['blogs'] = blogsc + sitec = values.setdefault('site', collections.OrderedDict()) + gen_default_theme_model = bool(sitec.setdefault( + 'use_default_theme_content', True)) + if gen_default_theme_model: + pmcopy = copy.deepcopy(default_theme_content_pre_model) + values = merge_dicts(pmcopy, values) + return values + + + def _validateThemeConfig(self, values): + if not self._theme_path: + return values - is_only_blog = (len(blogsc) == 1) - for blog_name in blogsc: - blog_cfg = get_default_content_model_for_blog( - blog_name, is_only_blog, values, - theme_site=self.theme_config) - values = merge_dicts(blog_cfg, values) + # Create the default theme content model if needed. + sitec = values.setdefault('site', collections.OrderedDict()) + gen_default_theme_model = bool(sitec.setdefault( + 'use_default_theme_content', True)) + if gen_default_theme_model: + logger.debug("Generating default theme content model...") + dcmcopy = copy.deepcopy(default_theme_content_model_base) + values = merge_dicts(dcmcopy, values) + return values + + + def _validateSiteConfig(self, values): + # Add the loaded values to the default configuration. + dccopy = copy.deepcopy(default_configuration) + values = merge_dicts(dccopy, values) + + # Set the theme site flag. + sitec = values['site'] + if self.theme_config: + sitec['theme_site'] = True - dcm = get_default_content_model(values) - values = merge_dicts(dcm, values) + # Create the default content model if needed. + gen_default_model = bool(sitec['use_default_content']) + if gen_default_model: + logger.debug("Generating default content model...") + dcmcopy = copy.deepcopy(default_content_model_base) + values = merge_dicts(dcmcopy, values) + + blogsc = values['site'].get('blogs') + if blogsc is None: + blogsc = ['posts'] + values['site']['blogs'] = blogsc + + is_only_blog = (len(blogsc) == 1) + for blog_name in blogsc: + blog_cfg = get_default_content_model_for_blog( + blog_name, is_only_blog, values, + theme_site=self.theme_config) + values = merge_dicts(blog_cfg, values) + + dcm = get_default_content_model(values) + values = merge_dicts(dcm, values) return values @@ -276,6 +299,38 @@ }) +default_theme_content_pre_model = collections.OrderedDict({ + 'site': collections.OrderedDict({ + 'theme_tag_page': 'theme_pages:_tag.%ext%', + 'theme_category_page': 'theme_pages:_category.%ext%' + }) + }) + + +default_theme_content_model_base = collections.OrderedDict({ + 'site': collections.OrderedDict({ + 'sources': collections.OrderedDict({ + 'theme_pages': { + 'type': 'default', + 'ignore_missing_dir': True, + 'fs_endpoint': 'pages', + 'data_endpoint': 'site.pages', + 'default_layout': 'default', + 'item_name': 'page', + 'realm': REALM_THEME + } + }), + 'routes': [ + { + 'url': '/%path:slug%', + 'source': 'theme_pages', + 'func': 'pcurl(slug)' + } + ] + }) + }) + + def get_default_content_model(values): default_layout = values['site']['default_page_layout'] return collections.OrderedDict({