Ejemplo n.º 1
0
    def test_works(self):

        t = FilterTree()

        t.add([1, 2, 5], 'foo')
        t.add([1, 2, 3, 5], 'bar')
        t.add([7, ], 'baz')

        assert list(t.iter('foo')) == [[1, 2], [5, ]]
        assert list(t.iter('bar')) == [[1, 2], [3, 5]]
        assert list(t.iter('baz')) == [[7, ], ]
Ejemplo n.º 2
0
    def works(self):

        t = FilterTree()

        t.add([1, 2, 5], 'foo')
        t.add([1, 2, 3, 5], 'bar')
        t.add([7, ], 'baz')

        assert list(t.iter('foo')) == [[1, 2], [5, ]]
        assert list(t.iter('bar')) == [[1, 2], [3, 5]]
        assert list(t.iter('baz')) == [[7, ], ]
Ejemplo n.º 3
0
    def __init__(self, path, conf):

        self.filename = path
        self.mtime = os.path.getmtime(path)
        self.props = NestedProperties((k, v) for k, v in conf.iteritems()
            if k in ['author', 'lang', 'encoding', 'date_format', 'permalink_format', 'email'])

        native = conf.get('metastyle', '').lower() == 'native'

        with io.open(path, 'r', encoding=conf['encoding'], errors='replace') as fp:

            if native and any(filter(lambda ext: path.endswith(ext), ['.md', '.mkdown'])):
                i, meta = markdownstyle(fp)
            # elif native and any(filter(lambda ext: path.endswith(ext), ['.rst', '.rest'])):
            #     i, meta = reststyle(fp)
            else:
                i, meta = yamlstyle(fp)

        # remap singular -> plural
        for key, to in {'tag': 'tags', 'filter': 'filters', 'static': 'draft'}.iteritems():
            if key in meta:
                meta[to] = meta[key]
                del meta[key]

        self.offset = i
        self.props.update(meta)

        fx = self.props.get('filters', [])
        if isinstance(fx, basestring):
            fx = [fx]

        self.filters = FilterTree(fx)
Ejemplo n.º 4
0
    def test_edge_cases(self):

        t = FilterTree()

        t.add([1, 2], 'foo')
        t.add([1, 2], 'bar')
        t.add([2, ], 'baz')

        assert list(t.iter('foo')) == [[1, 2], ]
        assert list(t.iter('bar')) == [[1, 2], ]
        assert list(t.iter('baz')) == [[2, ], ]
Ejemplo n.º 5
0
    def edge_cases(self):

        t = FilterTree()

        t.add([1, 2], 'foo')
        t.add([1, 2], 'bar')
        t.add([2, ], 'baz')

        assert list(t.iter('foo')) == [[1, 2], ]
        assert list(t.iter('bar')) == [[1, 2], ]
        assert list(t.iter('baz')) == [[2, ], ]
Ejemplo n.º 6
0
    def __init__(self, filename, conf):
        """parsing FileEntry's YAML header."""

        self.filename = filename
        self.mtime = os.path.getmtime(filename)
        self.props = dict((k, v) for k, v in conf.iteritems()
                        if k in ['author', 'lang', 'encoding', 'date_format',
                                 'permalink_format', 'email'])

        i, props = read(filename, self.props['encoding'],
                        remap={'tag': 'tags', 'filter': 'filters', 'static': 'draft'})
        self.offset = i
        self.props.update(props)

        fx = self.props.get('filters', [])
        if isinstance(fx, basestring):
            fx = [fx]

        self.filters = FilterTree(fx)
Ejemplo n.º 7
0
    def path(self):

        t = FilterTree()
        t.add([1, 3, 4, 7], 'foo')
        assert t.path('foo') == [1, 3, 4, 7]
Ejemplo n.º 8
0
 def setfilters(self, filters):
     if isinstance(filters, string_types):
         filters = [filters]
     self._filters = FilterTree(filters)
Ejemplo n.º 9
0
    def test_path(self):

        t = FilterTree()
        t.add([1, 3, 4, 7], 'foo')
        assert t.path('foo') == [1, 3, 4, 7]
Ejemplo n.º 10
0
class FileEntry:
    """This class gets it's data and metadata from the file specified
    by the filename argument.

    During templating, every (cached) property is available as well as additional
    key, value pairs defined in the YAML header. Note that *tag* is automatically
    mapped to *tags*, *filter* to *filters* and *static* to *draft*.

    If you have something like

    ::

        ---
        title: Foo
        image: /path/to/my/image.png
        ---

    it is available in jinja2 templates as entry.image"""

    __keys__ = ['permalink', 'date', 'year', 'month', 'day', 'filters', 'tags',
                'title', 'author', 'content', 'description', 'lang', 'draft',
                'extension', 'slug']

    def __init__(self, filename, conf):
        """parsing FileEntry's YAML header."""

        self.filename = filename
        self.mtime = os.path.getmtime(filename)
        self.props = dict((k, v) for k, v in conf.iteritems()
                        if k in ['author', 'lang', 'encoding', 'date_format',
                                 'permalink_format', 'email'])

        i, props = read(filename, self.props['encoding'],
                        remap={'tag': 'tags', 'filter': 'filters', 'static': 'draft'})
        self.offset = i
        self.props.update(props)

        fx = self.props.get('filters', [])
        if isinstance(fx, basestring):
            fx = [fx]

        self.filters = FilterTree(fx)

    def __repr__(self):
        return "<FileEntry f'%s'>" % self.filename

    @cached_property
    def permalink(self):
        """Actual permanent link, depends on entry's property and ``permalink_format``.
        If you set permalink in the YAML header, we use this as permalink otherwise
        the URL without trailing *index.html.*"""

        try:
            return self.props['permalink']
        except KeyError:
            return expand(self.props['permalink_format'].rstrip('index.html'), self)

    @cached_property
    def date(self):
        """return :class:`datetime.datetime` object.  Either converted from given key
        and ``date_format`` or fallback to modification timestamp of the file."""

        # alternate formats from pelican.utils, thank you!
        # https://github.com/ametaireau/pelican/blob/master/pelican/utils.py
        formats = ['%Y-%m-%d %H:%M', '%Y/%m/%d %H:%M',
                   '%Y-%m-%d', '%Y/%m/%d',
                   '%d-%m-%Y', '%Y-%d-%m', # Weird ones
                   '%d/%m/%Y', '%d.%m.%Y',
                   '%d.%m.%Y %H:%M', '%Y-%m-%d %H:%M:%S']

        if 'date' not in self.props:
            log.warn("using mtime from %r" % self.filename)
            return datetime.fromtimestamp(self.mtime)

        string = re.sub(' +', ' ', self.props['date'])
        formats.insert(0, self.props['date_format'])

        for date_format in formats:
            try:
                return datetime.strptime(string, date_format)
            except ValueError:
                pass
        else:
            raise AcrylamidException("%r is not a valid date" % string)

    @property
    def year(self):
        """entry's year (Integer)"""
        return self.date.year

    @property
    def month(self):
        """entry's month (Integer)"""
        return self.date.month

    @property
    def day(self):
        """entry's day (Integer)"""
        return self.date.day

    @property
    def tags(self):
        """per-post list of applied tags, if any.  If you applied a single string it
        is used as one-item array."""

        fx = self.props.get('tags', [])
        if isinstance(fx, basestring):
            return [fx]
        return fx

    @property
    def title(self):
        """entry's title."""
        return self.props.get('title', 'No Title!')

    @property
    def author(self):
        """entry's author as set in entry or from conf.py if unset"""
        return self.props['author']

    @property
    def email(self):
        """the author's email address"""
        return self.props['email']

    @property
    def draft(self):
        """If set to True, the entry will not appear in articles, index, feed and tag view."""
        return True if self.props.get('draft', False) else False

    @property
    def lang(self):
        return self.props['lang']

    @property
    def extension(self):
        """filename's extension without leading dot"""
        return os.path.splitext(self.filename)[1][1:]

    @property
    def source(self):
        with io.open(self.filename, 'r', encoding=self.props['encoding'],
        errors='replace') as f:
            return u''.join(f.readlines()[self.offset:]).strip()

    @property
    def content(self):
        """Returns the processed content.  This one of the core functions of
        acrylamid: it compiles incrementally the filter chain using a tree
        representation and saves final output or intermediates to cache, so
        we can rapidly re-compile the whole content.

        The cache is rather dumb: acrylamid can not determine wether it's
        abandoned or differs only in a single character. Thus, to minimize
        the overhead the content is zlib-compressed."""

        # previous value
        pv = None

        # this is our cache filename
        path = join(cache.cache_dir, self.md5)

        # growing dependencies of the filter chain
        deps = []

        for fxs in self.filters.iter(context=self.context):

            # extend dependencies
            deps.extend(fxs)

            # key where we save this filter chain
            key = md5(*deps)

            try:
                rv = cache.get(path, key, mtime=self.mtime)
                if rv is None:
                    res = self.source if pv is None else pv
                    for f in fxs:
                        res = f.transform(res, self, *f.args)
                    pv = cache.set(path, key, res)
                    self.has_changed = True
                else:
                    pv = rv
            except (IndexError, AttributeError):
                # jinja2 will ignore these Exceptions, better to catch them before
                traceback.print_exc(file=sys.stdout)

        return pv

    @property
    def slug(self):
        """ascii safe entry title"""
        return safeslug(self.title)

    @property
    def description(self):
        """first 50 characters from the source"""
        # XXX this is really poor
        return self.source[:50].strip() + '...'

    @cached_property
    def md5(self):
        return md5(self.filename, self.title, self.date)

    @property
    def has_changed(self):
        """Check wether the entry has changed using the following conditions:

        - cache file does not exist -> has changed
        - cache file does not contain required filter intermediate -> has changed
        - entry's file is newer than the cache's one -> has changed
        - otherwise -> not changed
        """

        path = join(cache.cache_dir, self.md5)
        deps = []

        for fxs in self.filters.iter(self.context):

            # extend filter dependencies
            deps.extend(fxs)

            if not cache.has_key(path, md5(*deps)):
                return True
        else:
            return getmtime(self.filename) > cache.getmtime(path)

    def keys(self):
        return list(iter(self))

    def __iter__(self):
        for k in self.__keys__:
            yield k

    def __getitem__(self, key):
        if key in self.__keys__:
            return getattr(self, key)
        return self.props[key]
Ejemplo n.º 11
0
class FileEntry(BaseEntry):

    def __init__(self, path, conf):

        self.filename = path
        self.mtime = os.path.getmtime(path)
        self.props = NestedProperties((k, v) for k, v in conf.iteritems()
            if k in ['author', 'lang', 'encoding', 'date_format', 'permalink_format', 'email'])

        native = conf.get('metastyle', '').lower() == 'native'

        with io.open(path, 'r', encoding=conf['encoding'], errors='replace') as fp:

            if native and any(filter(lambda ext: path.endswith(ext), ['.md', '.mkdown'])):
                i, meta = markdownstyle(fp)
            # elif native and any(filter(lambda ext: path.endswith(ext), ['.rst', '.rest'])):
            #     i, meta = reststyle(fp)
            else:
                i, meta = yamlstyle(fp)

        # remap singular -> plural
        for key, to in {'tag': 'tags', 'filter': 'filters', 'static': 'draft'}.iteritems():
            if key in meta:
                meta[to] = meta[key]
                del meta[key]

        self.offset = i
        self.props.update(meta)

        fx = self.props.get('filters', [])
        if isinstance(fx, basestring):
            fx = [fx]

        self.filters = FilterTree(fx)

    def __repr__(self):
        return "<FileEntry f'%s'>" % self.filename

    @cached_property
    def date(self):
        """parse date value and return :class:`datetime.datetime` object,
        fallback to modification timestamp of the file if unset.
        You can set a ``DATE_FORMAT`` in your :doc:`../conf.py` otherwise
        Acrylamid tries several format strings and throws an exception if
        no pattern works.

        As shortcut you can access ``date.day``, ``date.month``, ``date.year``
        via ``entry.day``, ``entry.month`` and ``entry.year``."""

        # alternate formats from pelican.utils, thank you!
        # https://github.com/ametaireau/pelican/blob/master/pelican/utils.py
        formats = ['%Y-%m-%d %H:%M', '%Y/%m/%d %H:%M',
                   '%Y-%m-%d', '%Y/%m/%d',
                   '%d-%m-%Y', '%Y-%d-%m',  # Weird ones
                   '%d/%m/%Y', '%d.%m.%Y',
                   '%d.%m.%Y %H:%M', '%Y-%m-%d %H:%M:%S']

        if 'date' not in self.props:
            log.warn("using mtime from %r" % self.filename)
            return Date.fromtimestamp(self.mtime)

        string = re.sub(' +', ' ', self.props['date'])
        formats.insert(0, self.props['date_format'])

        for date_format in formats:
            try:
                return Date.strptime(string, date_format)
            except ValueError:
                pass
        else:
            raise AcrylamidException("%r is not a valid date" % string)

    @property
    def extension(self):
        """Filename's extension without leading dot"""
        return os.path.splitext(self.filename)[1][1:]

    @property
    def source(self):
        """Returns the actual, unmodified content."""
        with io.open(self.filename, 'r', encoding=self.props['encoding'],
        errors='replace') as f:
            return u''.join(f.readlines()[self.offset:]).strip()

    @cached_property
    def md5(self):
        return md5(self.filename, self.title, self.date)

    @property
    def content(self):

        # previous value
        res = self.source
        # growing dependencies of the filter chain
        deps = []

        for fxs in self.filters.iter(context=self.context):
            # extend dependencies
            deps.extend(fxs)

            try:
                for f in fxs:
                    res = f.transform(res, self, *f.args)
            except (IndexError, AttributeError):
                # jinja2 will ignore these Exceptions, better to catch them before
                traceback.print_exc(file=sys.stdout)

        return res

    @property
    def has_changed(self):
        return True

    @has_changed.setter
    def has_changed(self, value):
        self._has_changed = value
Ejemplo n.º 12
0
 def setfilters(self, filters):
     if isinstance(filters, basestring):
         filters = [filters]
     self._filters = FilterTree(filters)