diff piecrust/sources/posts.py @ 3:f485ba500df3

Gigantic change to basically make PieCrust 2 vaguely functional. - Serving works, with debug window. - Baking works, multi-threading, with dependency handling. - Various things not implemented yet.
author Ludovic Chabant <ludovic@chabant.com>
date Sun, 10 Aug 2014 23:43:16 -0700
parents
children 474c9882decf
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/piecrust/sources/posts.py	Sun Aug 10 23:43:16 2014 -0700
@@ -0,0 +1,230 @@
+import os
+import os.path
+import re
+import glob
+import logging
+import datetime
+from piecrust import CONTENT_DIR
+from piecrust.sources.base import (PageSource, IPreparingSource,
+        PageNotFoundError, InvalidFileSystemEndpointError,
+        PageFactory, MODE_CREATING)
+
+
+logger = logging.getLogger(__name__)
+
+
+class PostsSource(PageSource, IPreparingSource):
+    PATH_FORMAT = None
+
+    def __init__(self, app, name, config):
+        super(PostsSource, self).__init__(app, name, config)
+        self.fs_endpoint = config.get('fs_endpoint', name)
+        self.fs_endpoint_path = os.path.join(self.root_dir, CONTENT_DIR, self.fs_endpoint)
+        self.supported_extensions = app.config.get('site/auto_formats').keys()
+
+    @property
+    def path_format(self):
+        return self.__class__.PATH_FORMAT
+
+    def resolveRef(self, ref_path):
+        return os.path.join(self.fs_endpoint_path, ref_path)
+
+    def findPagePath(self, metadata, mode):
+        year = metadata.get('year')
+        month = metadata.get('month')
+        day = metadata.get('day')
+        slug = metadata.get('slug')
+
+        ext = metadata.get('ext')
+        if ext is None:
+            if len(self.supported_extensions) == 1:
+                ext = self.supported_extensions[0]
+
+        replacements = {
+                'year': year,
+                'month': month,
+                'day': day,
+                'slug': slug,
+                'ext': ext
+                }
+        needs_recapture = False
+        if year is None:
+            needs_recapture = True
+            replacements['year'] = '????'
+        if month is None:
+            needs_recapture = True
+            replacements['month'] = '??'
+        if day is None:
+            needs_recapture = True
+            replacements['day'] = '??'
+        if slug is None:
+            needs_recapture = True
+            replacements['slug'] = '*'
+        if ext is None:
+            needs_recapture = True
+            replacements['ext'] = '*'
+        path = os.path.join(self.fs_endpoint_path, self.path_format % replacements)
+
+        if needs_recapture:
+            if mode == MODE_CREATING:
+                raise ValueError("Not enough information to find a post path.")
+            possible_paths = glob.glob(path)
+            if len(possible_paths) != 1:
+                raise PageNotFoundError()
+            path = possible_paths[0]
+        elif not os.path.isfile(path):
+            raise PageNotFoundError()
+
+        regex_repl = {
+                'year': '(?P<year>\d{4})',
+                'month': '(?P<month>\d{2})',
+                'day': '(?P<day>\d{2})',
+                'slug': '(?P<slug>.*)',
+                'ext': '(?P<ext>.*)'
+                }
+        pattern = os.path.join(self.fs_endpoint_path, self.path_format) % regex_repl
+        m = re.match(pattern, path)
+        if not m:
+            raise Exception("Expected to be able to match path with path "
+                            "format: %s" % path)
+        fac_metadata = {
+                'year': m.group('year'),
+                'month': m.group('month'),
+                'day': m.group('day'),
+                'slug': m.group('slug')
+                }
+
+        return path, fac_metadata
+
+    def setupPrepareParser(self, parser, app):
+        parser.add_argument('-d', '--date', help="The date of the post, "
+                "in `year/month/day` format (defaults to today).")
+        parser.add_argument('slug', help="The URL slug for the new post.")
+
+    def buildMetadata(self, args):
+        today = datetime.date.today()
+        year, month, day = today.year, today.month, today.day
+        if args.date:
+            year, month, day = filter(
+                    lambda s: int(s),
+                    args.date.split('/'))
+        return {'year': year, 'month': month, 'day': day, 'slug': args.slug}
+
+    def _checkFsEndpointPath(self):
+        if not os.path.isdir(self.fs_endpoint_path):
+            raise InvalidFileSystemEndpointError(self.name, self.fs_endpoint_path)
+
+    def _makeFactory(self, path, slug, year, month, day):
+        timestamp = datetime.date(year, month, day)
+        metadata = {
+                'slug': slug,
+                'year': year,
+                'month': month,
+                'day': day,
+                'date': timestamp}
+        return PageFactory(self, path, metadata)
+
+
+class FlatPostsSource(PostsSource):
+    SOURCE_NAME = 'posts/flat'
+    PATH_FORMAT = '%(year)s-%(month)s-%(day)s_%(slug)s.%(ext)s'
+
+    def __init__(self, app, name, config):
+        super(FlatPostsSource, self).__init__(app, name, config)
+
+    def buildPageFactories(self):
+        logger.debug("Scanning for posts (flat) in: %s" % self.fs_endpoint_path)
+        pattern = re.compile(r'(\d{4})-(\d{2})-(\d{2})_(.*)\.(\w+)$')
+        _, __, filenames = next(os.walk(self.fs_endpoint_path))
+        for f in filenames:
+            match = pattern.match(f)
+            if match is None:
+                name, ext = os.path.splitext(f)
+                logger.warning("'%s' is not formatted as 'YYYY-MM-DD_slug-title.%s' "
+                        "and will be ignored. Is that a typo?" % (f, ext))
+                continue
+            yield self._makeFactory(
+                    f,
+                    match.group(4),
+                    int(match.group(1)),
+                    int(match.group(2)),
+                    int(match.group(3)))
+
+
+class ShallowPostsSource(PostsSource):
+    SOURCE_NAME = 'posts/shallow'
+    PATH_FORMAT = '%(year)s/%(month)s-%(day)s_%(slug)s.%(ext)s'
+
+    def __init__(self, app, name, config):
+        super(ShallowPostsSource, self).__init__(app, name, config)
+
+    def buildPageFactories(self):
+        logger.debug("Scanning for posts (shallow) in: %s" % self.fs_endpoint_path)
+        year_pattern = re.compile(r'(\d{4})$')
+        file_pattern = re.compile(r'(\d{2})-(\d{2})_(.*)\.(\w+)$')
+        _, year_dirs, __ = next(os.walk(self.fs_endpoint_path))
+        year_dirs = filter(lambda d: year_pattern.match(d), year_dirs)
+        for yd in year_dirs:
+            if year_pattern.match(yd) is None:
+                logger.warning("'%s' is not formatted as 'YYYY' and will be ignored. "
+                        "Is that a typo?")
+                continue
+            year = int(yd)
+            year_dir = os.path.join(self.fs_endpoint_path, yd)
+
+            _, __, filenames = os.walk(year_dir)
+            for f in filenames:
+                match = file_pattern.match(f)
+                if match is None:
+                    name, ext = os.path.splitext(f)
+                    logger.warning("'%s' is not formatted as 'MM-DD_slug-title.%s' "
+                            "and will be ignored. Is that a typo?" % (f, ext))
+                    continue
+                yield self._makeFactory(
+                        os.path.join(yd, f),
+                        match.group(3),
+                        year,
+                        int(match.group(1)),
+                        int(match.group(2)))
+
+
+class HierarchyPostsSource(PostsSource):
+    SOURCE_NAME = 'posts/hierarchy'
+    PATH_FORMAT = '%(year)s/%(month)s/%(day)s_%(slug)s.%(ext)s'
+
+    def __init__(self, app, name, config):
+        super(HierarchyPostsSource, self).__init__(app, name, config)
+
+    def buildPageFactories(self):
+        logger.debug("Scanning for posts (hierarchy) in: %s" % self.fs_endpoint_path)
+        year_pattern = re.compile(r'(\d{4})$')
+        month_pattern = re.compile(r'(\d{2})$')
+        file_pattern = re.compile(r'(\d{2})_(.*)\.(\w+)$')
+        _, year_dirs, __ = next(os.walk(self.fs_endpoint_path))
+        year_dirs = filter(lambda d: year_pattern.match(d), year_dirs)
+        for yd in year_dirs:
+            year = int(yd)
+            year_dir = os.path.join(self.fs_endpoint_path, yd)
+
+            _, month_dirs, __ = next(os.walk(year_dir))
+            month_dirs = filter(lambda d: month_pattern.match(d), month_dirs)
+            for md in month_dirs:
+                month = int(md)
+                month_dir = os.path.join(year_dir, md)
+
+                _, __, filenames = next(os.walk(month_dir))
+                for f in filenames:
+                    match = file_pattern.match(f)
+                    if match is None:
+                        name, ext = os.path.splitext(f)
+                        logger.warning("'%s' is not formatted as 'DD_slug-title.%s' "
+                                "and will be ignored. Is that a typo?" % (f, ext))
+                        continue
+                    rel_name = os.path.join(yd, md, f)
+                    yield self._makeFactory(
+                            rel_name,
+                            match.group(2),
+                            year,
+                            month,
+                            int(match.group(1)))
+