changeset 259:adb066ffb363

Merge docs.
author Ludovic Chabant <ludovic@chabant.com>
date Sun, 22 Feb 2015 22:03:54 -0800
parents 7ec06ec14247 (diff) ef8e5f9efdbd (current diff)
children 07b4b8484c0a
files
diffstat 6 files changed, 108 insertions(+), 59 deletions(-) [+]
line wrap: on
line diff
--- a/piecrust/app.py	Sun Feb 22 21:53:53 2015 -0800
+++ b/piecrust/app.py	Sun Feb 22 22:03:54 2015 -0800
@@ -108,9 +108,7 @@
                 'default_template_engine': DEFAULT_TEMPLATE_ENGINE,
                 'enable_gzip': True,
                 'pretty_urls': False,
-                'slugify': 'transliterate|lowercase',
-                'timezone': False,
-                'locale': False,
+                'trailing_slash': False,
                 'date_format': DEFAULT_DATE_FORMAT,
                 'auto_formats': collections.OrderedDict([
                     ('html', ''),
@@ -121,7 +119,6 @@
                 'plugins_sources': [DEFAULT_PLUGIN_SOURCE],
                 'themes_sources': [DEFAULT_THEME_SOURCE],
                 'cache_time': 28800,
-                'display_errors': True,
                 'enable_debug_info': True,
                 'show_debug_info': False,
                 'use_default_content': True
@@ -139,20 +136,36 @@
 
         # Cache auto-format regexes.
         if not isinstance(sitec['auto_formats'], dict):
-            raise ConfigurationError("The 'site/auto_formats' setting must be a dictionary.")
+            raise ConfigurationError("The 'site/auto_formats' setting must be "
+                                     "a dictionary.")
         html_auto_format = sitec['auto_formats']
         if not html_auto_format:
             sitec['auto_formats']['html'] = sitec['default_format']
         cachec['auto_formats_re'] = r"\.(%s)$" % (
                 '|'.join(
-                        [re.escape(i) for i in list(sitec['auto_formats'].keys())]))
+                        [re.escape(i) for i in
+                            list(sitec['auto_formats'].keys())]))
         if sitec['default_auto_format'] not in sitec['auto_formats']:
-            raise ConfigurationError("Default auto-format '%s' is not declared." % sitec['default_auto_format'])
+            raise ConfigurationError("Default auto-format '%s' is not "
+                                     "declared." %
+                                     sitec['default_auto_format'])
 
-        # Cache pagination suffix regex.
-        pgn_suffix = re.escape(sitec['pagination_suffix'])
-        pgn_suffix = pgn_suffix.replace("\\%num\\%", "(?P<num>\\d+)") + '$'
-        cachec['pagination_suffix_re'] = pgn_suffix
+        # Cache pagination suffix regex and format.
+        pgn_suffix = sitec['pagination_suffix']
+        if len(pgn_suffix) == 0 or pgn_suffix[0] != '/':
+            raise ConfigurationError("The 'site/pagination_suffix' setting "
+                                     "must start with a slash.")
+        if '%num%' not in pgn_suffix:
+            raise ConfigurationError("The 'site/pagination_suffix' setting "
+                                     "must contain the '%num%' placeholder.")
+
+        pgn_suffix_fmt = pgn_suffix.replace('%num%', '%(num)d')
+        cachec['pagination_suffix_format'] = pgn_suffix_fmt
+
+        pgn_suffix_re = re.escape(pgn_suffix)
+        pgn_suffix_re = (pgn_suffix_re.replace("\\%num\\%", "(?P<num>\\d+)") +
+                         '$')
+        cachec['pagination_suffix_re'] = pgn_suffix_re
 
         # Make sure plugins and theme sources are lists.
         if not isinstance(sitec['plugins_sources'], list):
@@ -283,9 +296,11 @@
         if not routesc:
             raise ConfigurationError("There are no routes defined.")
         if not isinstance(sourcesc, dict):
-            raise ConfigurationError("The 'site/sources' setting must be a dictionary.")
+            raise ConfigurationError("The 'site/sources' setting must be a "
+                                     "dictionary.")
         if not isinstance(routesc, list):
-            raise ConfigurationError("The 'site/routes' setting must be a list.")
+            raise ConfigurationError("The 'site/routes' setting must be a "
+                                     "list.")
 
         # Add the theme page source if no sources were defined in the theme
         # configuration itself.
@@ -310,7 +325,8 @@
         # of other default values for other configuration stuff.
         for sn, sc in sourcesc.items():
             if not isinstance(sc, dict):
-                raise ConfigurationError("All sources in 'site/sources' must be dictionaries.")
+                raise ConfigurationError("All sources in 'site/sources' must "
+                                         "be dictionaries.")
             sc.setdefault('type', 'default')
             sc.setdefault('fs_endpoint', sn)
             sc.setdefault('ignore_missing_dir', False)
@@ -325,17 +341,19 @@
         # values, etc.
         for rc in routesc:
             if not isinstance(rc, dict):
-                raise ConfigurationError("All routes in 'site/routes' must be dictionaries.")
+                raise ConfigurationError("All routes in 'site/routes' must be "
+                                         "dictionaries.")
             rc_url = rc.get('url')
             if not rc_url:
-                raise ConfigurationError("All routes in 'site/routes' must have an 'url'.")
+                raise ConfigurationError("All routes in 'site/routes' must "
+                                         "have an 'url'.")
             if rc_url[0] != '/':
                 raise ConfigurationError("Route URLs must start with '/'.")
             if rc.get('source') is None:
                 raise ConfigurationError("Routes must specify a source.")
             if rc['source'] not in list(sourcesc.keys()):
-                raise ConfigurationError("Route is referencing unknown source: %s" %
-                        rc['source'])
+                raise ConfigurationError("Route is referencing unknown "
+                                         "source: %s" % rc['source'])
             rc.setdefault('taxonomy', None)
             rc.setdefault('page_suffix', '/%num%')
 
@@ -358,7 +376,6 @@
                         "Source '%s' is using a reserved endpoint name: %s" %
                         (name, endpoint))
 
-
         # Done validating!
         return values
 
--- a/piecrust/baking/single.py	Sun Feb 22 21:53:53 2015 -0800
+++ b/piecrust/baking/single.py	Sun Feb 22 22:03:54 2015 -0800
@@ -33,34 +33,6 @@
         self.pretty_urls = app.config.get('site/pretty_urls')
         self.pagination_suffix = app.config.get('site/pagination_suffix')
 
-    def getOutputUri(self, uri, num):
-        suffix = self.pagination_suffix.replace('%num%', str(num))
-        if self.pretty_urls:
-            # Output will be:
-            # - `uri/name`
-            # - `uri/name/2`
-            # - `uri/name.ext`
-            # - `uri/name.ext/2`
-            if num <= 1:
-                return uri
-            return uri + suffix
-        else:
-            # Output will be:
-            # - `uri/name.html`
-            # - `uri/name/2.html`
-            # - `uri/name.ext`
-            # - `uri/name/2.ext`
-            if uri == '/':
-                if num <= 1:
-                    return '/'
-                return '/' + suffix.lstrip('/')
-            else:
-                if num <= 1:
-                    return uri
-                #TODO: watch out for tags with dots in them.
-                base_uri, ext = os.path.splitext(uri)
-                return base_uri + suffix + ext
-
     def getOutputPath(self, uri):
         bake_path = [self.out_dir]
         decoded_uri = urllib.parse.unquote(uri.lstrip('/'))
@@ -109,7 +81,8 @@
 
         # Generate the URL using the route.
         page = factory.buildPage()
-        uri = route.getUri(route_metadata, page, include_site_root=False)
+        uri = route.getUri(route_metadata, provider=page,
+                           include_site_root=False)
 
         override = self.record.getOverrideEntry(factory, uri)
         if override is not None:
@@ -161,7 +134,8 @@
                     invalidate_formatting = True
 
         while has_more_subs:
-            sub_uri = self.getOutputUri(uri, cur_sub)
+            sub_uri = route.getUri(route_metadata, sub_num=cur_sub,
+                                   provider=page, include_site_root=False)
             out_path = self.getOutputPath(sub_uri)
 
             # Check for up-to-date outputs.
--- a/piecrust/data/base.py	Sun Feb 22 21:53:53 2015 -0800
+++ b/piecrust/data/base.py	Sun Feb 22 22:03:54 2015 -0800
@@ -125,7 +125,7 @@
         route = page.app.getRoute(page.source.name, page.source_metadata)
         if route is None:
             raise Exception("Can't get route for page: %s" % page.path)
-        return route.getUri(page.source_metadata, page)
+        return route.getUri(page.source_metadata, provider=page)
 
     def _loadCustom(self):
         page_url = self._get_uri()
--- a/piecrust/data/linker.py	Sun Feb 22 21:53:53 2015 -0800
+++ b/piecrust/data/linker.py	Sun Feb 22 22:03:54 2015 -0800
@@ -23,7 +23,6 @@
     @property
     def children(self):
         self._linker._load()
-        print("GOT ", self._linker._items.keys())
         if self._linker._self_item is None:
             return None
         return self._linker._self_item.config.get('__linker_child')
--- a/piecrust/routing.py	Sun Feb 22 21:53:53 2015 -0800
+++ b/piecrust/routing.py	Sun Feb 22 22:03:54 2015 -0800
@@ -1,4 +1,5 @@
 import re
+import os.path
 import logging
 
 
@@ -8,6 +9,7 @@
 route_re = re.compile(r'%((?P<qual>path):)?(?P<name>\w+)%')
 template_func_re = re.compile(r'^(?P<name>\w+)\((?P<first_arg>\w+)(?P<other_args>.*)\)\s*$')
 template_func_arg_re = re.compile(r',\s*(?P<arg>\w+)')
+ugly_url_cleaner = re.compile(r'\.html$')
 
 
 class IRouteMetadataProvider(object):
@@ -22,15 +24,22 @@
     """
     def __init__(self, app, cfg):
         self.app = app
+
+        self.pretty_urls = app.config.get('site/pretty_urls')
+        self.trailing_slash = app.config.get('site/trailing_slash')
+        self.pagination_suffix_format = app.config.get(
+                '__cache/pagination_suffix_format')
+        self.uri_root = app.config.get('site/root').rstrip('/') + '/'
+
         uri = cfg['url']
-        self.uri_root = app.config.get('site/root').rstrip('/') + '/'
         self.uri_pattern = uri.lstrip('/')
         self.uri_format = route_re.sub(self._uriFormatRepl, self.uri_pattern)
         if app.config.get('site/show_debug_info'):
             self.uri_format += '?!debug'
 
         # Get the straight-forward regex for matching this URI pattern.
-        p = route_re.sub(self._uriPatternRepl, self.uri_pattern) + '$'
+        re_suffix = '$'
+        p = route_re.sub(self._uriPatternRepl, self.uri_pattern) + re_suffix
         self.uri_re = re.compile(p)
 
         # If the URI pattern has a 'path'-type component, we'll need to match
@@ -76,6 +85,11 @@
         return self.required_source_metadata.issubset(source_metadata.keys())
 
     def matchUri(self, uri):
+        if not self.pretty_urls:
+            uri = ugly_url_cleaner.sub('', uri)
+        elif self.trailing_slash:
+            uri = uri.rstrip('/')
+
         m = self.uri_re.match(uri)
         if m:
             return m.groupdict()
@@ -85,17 +99,50 @@
                 return m.groupdict()
         return None
 
-    def getUri(self, source_metadata, provider=None, include_site_root=True):
+    def getUri(self, source_metadata, *, sub_num=1, provider=None,
+               include_site_root=True):
         if provider:
             source_metadata = dict(source_metadata)
             source_metadata.update(provider.getRouteMetadata())
+
         #TODO: fix this hard-coded shit
         for key in ['year', 'month', 'day']:
             if key in source_metadata and isinstance(source_metadata[key], str):
                 source_metadata[key] = int(source_metadata[key])
+
         uri = self.uri_format % source_metadata
+        suffix = None
+        if sub_num > 1:
+            # Note that we know the pagination suffix starts with a slash.
+            suffix = self.pagination_suffix_format % sub_num
+
+        if self.pretty_urls:
+            # Output will be:
+            # - `subdir/name`
+            # - `subdir/name/2`
+            # - `subdir/name.ext`
+            # - `subdir/name.ext/2`
+            if suffix:
+                uri = uri.rstrip('/') + suffix
+            if self.trailing_slash:
+                uri = uri.rstrip('/') + '/'
+        else:
+            # Output will be:
+            # - `subdir/name.html`
+            # - `subdir/name/2.html`
+            # - `subdir/name.ext`
+            # - `subdir/name/2.ext`
+            base_uri, ext = os.path.splitext(uri)
+            if not ext:
+                ext = '.html'
+            if suffix:
+                uri = base_uri + suffix + ext
+            else:
+                uri = base_uri + ext
+
         if include_site_root:
             uri = self.uri_root + uri
+
         return uri
 
     def _uriFormatRepl(self, m):
--- a/piecrust/serving.py	Sun Feb 22 21:53:53 2015 -0800
+++ b/piecrust/serving.py	Sun Feb 22 22:03:54 2015 -0800
@@ -8,6 +8,7 @@
 import os.path
 import hashlib
 import logging
+import datetime
 import threading
 from werkzeug.exceptions import (
         NotFound, MethodNotAllowed, InternalServerError, HTTPException)
@@ -130,7 +131,6 @@
         # Create the app for this request.
         app = PieCrust(root_dir=self.root_dir, debug=self.debug)
         app.config.set('site/root', '/')
-        app.config.set('site/pretty_urls', True)
         app.config.set('server/is_serving', True)
         if (app.config.get('site/enable_debug_info') and
                 '!debug' in request.args):
@@ -170,7 +170,7 @@
             full_path = os.path.join(mount, rel_req_path)
             try:
                 response = self._make_wrapped_file_response(
-                        environ, full_path)
+                        environ, request, full_path)
                 return response
             except OSError:
                 pass
@@ -203,7 +203,7 @@
 
         try:
             response = self._make_wrapped_file_response(
-                    environ, full_path)
+                    environ, request, full_path)
             return response
         except OSError:
             pass
@@ -217,7 +217,7 @@
         if not os.path.isfile(full_path):
             return None
 
-        return self._make_wrapped_file_response(environ, full_path)
+        return self._make_wrapped_file_response(environ, request, full_path)
 
     def _try_serve_page(self, app, environ, request):
         # Try to find what matches the requested URL.
@@ -353,11 +353,23 @@
 
         return response
 
-    def _make_wrapped_file_response(self, environ, path):
+    def _make_wrapped_file_response(self, environ, request, path):
         logger.debug("Serving %s" % path)
+
+        # Check if we can return a 304 status code.
+        mtime = os.path.getmtime(path)
+        etag_str = '%s$$%s' % (path, mtime)
+        etag = hashlib.md5(etag_str.encode('utf8')).hexdigest()
+        if etag in request.if_none_match:
+            response = Response()
+            response.status_code = 304
+            return response
+
         wrapper = wrap_file(environ, open(path, 'rb'))
         response = Response(wrapper)
         _, ext = os.path.splitext(path)
+        response.set_etag(etag)
+        response.last_modified = datetime.datetime.fromtimestamp(mtime)
         response.mimetype = self._mimetype_map.get(
                 ext.lstrip('.'), 'text/plain')
         return response