lektor-feed

Check-in [aa0280bcd8]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Initial commit after forking lektor-atom, clean it up, fix ID generation
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1:aa0280bcd87fb389de44f7ef14629235bb4a1862
User & Date: t73fde 2016-05-22 17:09:27
Context
2017-08-27
12:14
Kreuz -> Stern Leaf check-in: fabf6c22fc user: t73fde tags: trunk
2016-05-22
17:09
Initial commit after forking lektor-atom, clean it up, fix ID generation check-in: aa0280bcd8 user: t73fde tags: trunk
15:52
initial empty check-in check-in: cdfc7f7b62 user: dkreuz tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Added README.md.

















































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# Lektor Feed Plugin

Builds one or more Atom XML feeds or RSS XML feeds for your [Lektor](https://www.getlektor.com/)-based site.

Inspired by the [atom-feed-support](https://github.com/lektor/lektor-website/tree/master/packages/atom-feed-support) plugin Armin Ronacher wrote for the Lektor official blog.

## Installation

Add lektor-feed to your project from command line:

```
lektor plugins add lektor-feed
```

See [the Lektor documentation for more instructions on installing plugins](https://www.getlektor.com/docs/plugins/).

## Configuration

For each feed you want to publish, add a section to `configs/feed.ini`. For example, a blog with a feed of all recent posts, and a feed of recent posts about coffee:

```
[blog]
name = My Blog
source_path = /
url_path = /feed.xml
items = site.query('/').filter(F.type == 'post')
item_model = blog-post

[coffee]
name = My Blog: Articles About Coffee
source_path = /
url_path = /category/coffee/feed.xml
items = site.query('/blog').filter(F.categories.contains('coffee'))
item_model = blog-post
```

The section names, like `blog` and `coffee`, are just used as internal identifiers.

### Options

|Option               | Default    | Description
|---------------------|------------|-------------------------------------------------------------------------
|source\_path         | /          | Where in the content directory to find items' parent source
|name                 |            | Feed name: default is section name
|filename             | feed.xml   | Name of generated feed file
|url\_path            |            | Feed's URL on your site: default is source's URL path plus the filename
|blog\_author\_field  | author     | Name of source's author field
|blog\_summary\_field | summary    | Name of source's summary field
|items                | None       | A query expression: default is the source's children
|limit                | 50         | How many recent items to include
|item\_title\_field   | title      | Name of items' title field
|item\_body\_field    | body       | Name of items' content body field
|item\_author\_field  | author     | Name of items' author field
|item\_date\_field    | pub\_date  | Name of items' publication date field
|item\_model          | None       | Name of items' model

### Customizing the plugin for your models

Use the field options to tell lektor-feed how to read your items. For example, if your site's model is:

```
[model]
name = Blog

[fields.writer]
type = string

[fields.short_description]
type = string
```

Then add to feed.ini:

```
[main]
blog_author_field = writer
blog_summary_field = short_description
```

See `tests/demo-project/configs/feed.ini` for a complete example.

### Filtering items

By default, lektor-feed gets the source at `source_path` and includes all its
children in the feed. If you set `item_model`, lektor-feed includes only the children with that data model.

Set `items` to any query expression to override the default. If `items_model`
is *also* specified, lektor-feed applies it as a filter to `items`.

Added lektor_feed.py.































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# -*- coding: utf-8 -*-
import hashlib
import uuid
from datetime import datetime, date

import pkg_resources
from lektor.build_programs import BuildProgram
from lektor.db import F
from lektor.environment import Expression
from lektor.pluginsystem import Plugin
from lektor.context import get_ctx, url_to
from lektor.sourceobj import VirtualSourceObject
from lektor.utils import build_url

from werkzeug.contrib.atom import AtomFeed
from markupsafe import escape


class AtomFeedSource(VirtualSourceObject):
    def __init__(self, parent, feed_id, plugin):
        VirtualSourceObject.__init__(self, parent)
        self.plugin = plugin
        self.feed_id = feed_id

    @property
    def path(self):
        return self.parent.path + '@atom/' + self.feed_id

    @property
    def url_path(self):
        p = self.plugin.get_feed_config(self.feed_id, 'url_path')
        if p:
            return p

        return build_url([self.parent.url_path, self.filename])

    def __getattr__(self, item):
        try:
            return self.plugin.get_feed_config(self.feed_id, item)
        except KeyError:
            raise AttributeError(item)

    @property
    def feed_name(self):
        return self.plugin.get_feed_config(self.feed_id, 'name') or \
            self.feed_id


def get(item, field, default=None):
    if field in item:
        return item[field]
    return default


def get_id(s):
    return uuid.UUID(bytes=hashlib.md5(s).digest(), version=3).urn


def get_item_title(item, field):
    if field in item:
        return item[field]
    return item.record_label


def get_item_body(item, field):
    if field not in item:
        raise RuntimeError('Body field %r not found in %r' % (field, item))
    with get_ctx().changed_base_url(item.url_path):
        return unicode(escape(item[field]))


def get_item_updated(item, field):
    if field in item:
        rv = item[field]
    else:
        rv = datetime.utcnow()
    if isinstance(rv, date) and not isinstance(rv, datetime):
        rv = datetime(*rv.timetuple()[:3])
    return rv


class AtomFeedBuilderProgram(BuildProgram):
    def produce_artifacts(self):
        self.declare_artifact(
            self.source.url_path,
            sources=list(self.source.iter_source_filenames()))

    def build_artifact(self, artifact):
        ctx = get_ctx()
        feed_source = self.source
        blog = feed_source.parent

        summary = get(blog, feed_source.blog_summary_field) or ''
        subtitle_type = ('html' if hasattr(summary, '__html__') else 'text')
        blog_author = unicode(get(blog, feed_source.blog_author_field) or '')
        generator = ('Lektor Feed Plugin',
                     'https://yoyod.de/p/lektor-feed',
                     pkg_resources.get_distribution('lektor-feed').version)

        project_id = ctx.env.load_config().base_url
        if not project_id:
            project_id = ctx.env.project.id
        feed = AtomFeed(
            title=feed_source.feed_name,
            subtitle=unicode(summary),
            subtitle_type=subtitle_type,
            author=blog_author,
            feed_url=url_to(feed_source, external=True),
            url=url_to(blog, external=True),
            id=get_id(project_id),
            generator=generator)

        if feed_source.items:
            # "feed_source.items" is a string like "site.query('/blog')".
            expr = Expression(ctx.env, feed_source.items)
            items = expr.evaluate(ctx.pad)
        else:
            items = blog.children

        if feed_source.item_model:
            items = items.filter(F._model == feed_source.item_model)

        order_by = '-' + feed_source.item_date_field
        items = items.order_by(order_by).limit(int(feed_source.limit))

        for item in items:
            item_author_field = feed_source.item_author_field
            item_author = get(item, item_author_field) or blog_author

            feed.add(
                get_item_title(item, feed_source.item_title_field),
                get_item_body(item, feed_source.item_body_field),
                xml_base=url_to(item, external=True),
                url=url_to(item, external=True),
                content_type='html',
                id=get_id(u'%s/%s' % (
                    project_id,
                    item['_path'].encode('utf-8'))),
                author=item_author,
                updated=get_item_updated(item, feed_source.item_date_field))

        with artifact.open('wb') as f:
            f.write(feed.to_string().encode('utf-8'))


class FeedPlugin(Plugin):
    name = u'Lektor Feed plugin'

    defaults = {
        'source_path': '/',
        'name': None,
        'url_path': None,
        'filename': 'feed.xml',
        'blog_author_field': 'author',
        'blog_summary_field': 'summary',
        'items': None,
        'limit': 50,
        'item_title_field': 'title',
        'item_body_field': 'body',
        'item_author_field': 'author',
        'item_date_field': 'pub_date',
        'item_model': None,
    }

    def get_feed_config(self, feed_id, key):
        default_value = self.defaults[key]
        return self.get_config().get('%s.%s' % (feed_id, key), default_value)

    def on_setup_env(self, **extra):
        self.env.add_build_program(AtomFeedSource, AtomFeedBuilderProgram)

        @self.env.virtualpathresolver('atom')
        def feed_path_resolver(node, pieces):
            if len(pieces) != 1:
                return

            _id = pieces[0]

            config = self.get_config()
            if _id not in config.sections():
                return

            source_path = self.get_feed_config(_id, 'source_path')
            if node.path == source_path:
                return AtomFeedSource(node, _id, plugin=self)

        @self.env.generator
        def generate_feeds(source):
            for _id in self.get_config().sections():
                if source.path == self.get_feed_config(_id, 'source_path'):
                    yield AtomFeedSource(source, _id, self)

Added setup.py.





































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from setuptools import setup

setup(
    name='lektor-feed',
    version='0.1',
    author=u'Detlef Kreuz',
    author_email='mail-python.org@yodod.de',
    license='MIT',
    py_modules=['lektor_feed'],
    install_requires=['MarkupSafe', 'Lektor'],
    tests_require=['lxml', 'pytest'],
    url='https://yoyod.de/p/lektor-feed',
    entry_points={
        'lektor.plugins': [
            'feed = lektor_feed:FeedPlugin',
        ]
    }
)

Added tests/conftest.py.







































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import os
import pytest
import shutil
import tempfile
from datetime import datetime

from lektor.types import Type


class DatetimeType(Type):
    def value_from_raw(self, raw):
        return datetime.strptime(raw.value, '%Y-%m-%d %H:%M:%S')


@pytest.fixture(scope='function')
def project(request):
    from lektor.project import Project
    return Project.from_path(os.path.join(os.path.dirname(__file__),
                                          'demo-project'))


@pytest.fixture(scope='function')
def env(request, project):
    from lektor.environment import Environment
    e = Environment(project)
    e.types['datetime'] = DatetimeType  # As if we had a datetime plugin.
    return e


@pytest.fixture(scope='function')
def pad(request, env):
    from lektor.db import Database
    return Database(env).new_pad()


def make_builder(request, pad):
    from lektor.builder import Builder
    out = tempfile.mkdtemp()
    b = Builder(pad, out)

    def cleanup():
        try:
            shutil.rmtree(out)
        except (OSError, IOError):
            pass
    request.addfinalizer(cleanup)
    return b


@pytest.fixture(scope='function')
def builder(request, pad):
    return make_builder(request, pad)


@pytest.fixture(scope='function')
def F():
    from lektor.db import F
    return F


@pytest.fixture(scope='function')
def reporter(request, env):
    from lektor.reporter import BufferReporter
    r = BufferReporter(env)
    r.push()
    request.addfinalizer(r.pop)
    return r

Added tests/demo-project/Website.lektorproject.













>
>
>
>
>
>
1
2
3
4
5
6
[project]
name = Demo Project
url = http://x.com

[packages]
lektor-feed

Added tests/demo-project/configs/feed.ini.







































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[feed-one]
name = Feed One
source_path = /typical-blog

[feed-two]
source_path = /typical-blog2

[feed-three]
name = Feed Three
source_path = /custom-blog
filename = atom.xml
blog_author_field = editor
blog_summary_field = description
items = site.query('/custom-blog').filter(F.headline != "I'm filtered out")
item_title_field = headline
item_body_field = contents
item_author_field = writer
item_date_field = published
item_model = custom-blog-post

Added tests/demo-project/content/contents.lr.

Added tests/demo-project/content/custom-blog/contents.lr.











>
>
>
>
>
1
2
3
4
5
_model: custom-blog
---
editor: A. Jesse Jiryu Davis
---
description: My Description

Added tests/demo-project/content/custom-blog/filtered_post/contents.lr.











>
>
>
>
>
1
2
3
4
5
headline: I'm filtered out
---
published: 2015-12-12 15:00:00
---
contents: baz

Added tests/demo-project/content/custom-blog/page/contents.lr.



>
1
_model: page

Added tests/demo-project/content/custom-blog/post1/contents.lr.











>
>
>
>
>
1
2
3
4
5
headline: Post 1
---
published: 2015-12-12 12:34:56
---
contents: foo

Added tests/demo-project/content/custom-blog/post2/contents.lr.















>
>
>
>
>
>
>
1
2
3
4
5
6
7
headline: Post 2
---
writer: Armin Ronacher
---
published: 2015-12-13 00:00:00
---
contents: bar

Added tests/demo-project/content/typical-blog/contents.lr.













>
>
>
>
>
>
1
2
3
4
5
6
_model: blog
---
author: A. Jesse Jiryu Davis
---
summary: My Summary
---

Added tests/demo-project/content/typical-blog/post1/contents.lr.











>
>
>
>
>
1
2
3
4
5
title: Post 1
---
pub_date: 2015-12-12
---
body: foo

Added tests/demo-project/content/typical-blog/post2/contents.lr.















>
>
>
>
>
>
>
1
2
3
4
5
6
7
title: Post 2
---
author: Armin Ronacher
---
pub_date: 2015-12-13
---
body: bar

Added tests/demo-project/content/typical-blog2/contents.lr.













>
>
>
>
>
>
1
2
3
4
5
6
_model: blog
---
author: A. Jesse Jiryu Davis
---
summary: My Summary
---

Added tests/demo-project/models/blog-post.ini.





























>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[model]
name = Blog Post

[fields.title]
type = string

[fields.author]
type = string

[fields.pub_date]
type = date

[fields.body]
type = markdown

Added tests/demo-project/models/blog.ini.







































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[model]
name = Blog
label = Blog
hidden = yes

[fields.author]
type = string

[fields.summary]
type = string

[children]
model = blog-post
order_by = -pub_date, title

[pagination]
enabled = yes
per_page = 10
items = this.children.filter(F._model == 'blog-post')

Added tests/demo-project/models/custom-blog-post.ini.





























>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[model]
name = Blog Post

[fields.headline]
type = string

[fields.writer]
type = string

[fields.published]
type = datetime

[fields.contents]
type = markdown

Added tests/demo-project/models/custom-blog.ini.







































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[model]
name = Blog
label = Blog
hidden = yes

[fields.editor]
type = string

[fields.description]
type = markdown

[children]
model = custom-blog-post
order_by = -pub_date, title

[pagination]
enabled = yes
per_page = 10
items = this.children.filter(F._model == 'blog-post')

Added tests/demo-project/models/page.ini.

Added tests/demo-project/templates/blog-post.html.

Added tests/demo-project/templates/blog.html.

Added tests/demo-project/templates/custom-blog-post.html.

Added tests/demo-project/templates/custom-blog.html.

Added tests/demo-project/templates/page.html.

Added tests/test_lektor_feed.py.

























































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import os

from lxml import objectify


def test_typical_feed(pad, builder):
    failures = builder.build_all()
    assert not failures
    feed_path = os.path.join(builder.destination_path, 'typical-blog/feed.xml')
    feed = objectify.parse(open(feed_path)).getroot()

    assert 'Feed One' == feed.title
    assert 'My Summary' == feed.subtitle
    assert 'text' == feed.subtitle.attrib['type']
    assert 'A. Jesse Jiryu Davis' == feed.author.name
    assert 'http://x.com/typical-blog/' == feed.link[0].attrib['href']
    assert 'http://x.com/typical-blog/feed.xml' == feed.link[1].attrib['href']
    assert 'self' == feed.link[1].attrib['rel']
    assert 'Lektor Feed Plugin' == feed.generator

    assert 2 == len(feed.entry)
    post2, post1 = feed.entry      # Most recent first.

    assert 'Post 2' == post2.title
    assert '2015-12-13T00:00:00Z' == post2.updated
    assert '<p>bar</p>' == str(post2.content).strip()
    assert 'html' == post2.content.attrib['type']
    assert 'http://x.com/typical-blog/post2/' == post2.link.attrib['href']
    base = post2.attrib['{http://www.w3.org/XML/1998/namespace}base']
    assert 'http://x.com/typical-blog/post2/' == base
    assert 'Armin Ronacher' == post2.author.name

    assert 'Post 1' == post1.title
    assert '2015-12-12T00:00:00Z' == post1.updated
    assert '<p>foo</p>' == str(post1.content).strip()
    assert 'html' == post1.content.attrib['type']
    assert 'http://x.com/typical-blog/post1/' == post1.link.attrib['href']
    base = post1.attrib['{http://www.w3.org/XML/1998/namespace}base']
    assert 'http://x.com/typical-blog/post1/' == base
    assert 'A. Jesse Jiryu Davis' == post1.author.name


def test_custom_feed(pad, builder):
    failures = builder.build_all()
    assert not failures
    feed_path = os.path.join(builder.destination_path, 'custom-blog/atom.xml')
    feed = objectify.parse(open(feed_path)).getroot()

    assert 'Feed Three' == feed.title
    assert '<p>My Description</p>' == str(feed.subtitle).strip()
    assert 'html' == feed.subtitle.attrib['type']
    assert 'A. Jesse Jiryu Davis' == feed.author.name
    assert 'http://x.com/custom-blog/' == feed.link[0].attrib['href']
    assert 'http://x.com/custom-blog/atom.xml' == feed.link[1].attrib['href']
    assert 'self' == feed.link[1].attrib['rel']
    assert 'Lektor Feed Plugin' == feed.generator

    assert 2 == len(feed.entry)
    post2, post1 = feed.entry      # Most recent first.

    assert 'Post 2' == post2.title
    assert '2015-12-13T00:00:00Z' == post2.updated
    assert '<p>bar</p>' == str(post2.content).strip()
    assert 'html' == post2.content.attrib['type']
    assert 'http://x.com/custom-blog/post2/' == post2.link.attrib['href']
    base = post2.attrib['{http://www.w3.org/XML/1998/namespace}base']
    assert 'http://x.com/custom-blog/post2/' == base
    assert 'Armin Ronacher' == post2.author.name

    assert 'Post 1' == post1.title
    assert '2015-12-12T12:34:56Z' == post1.updated
    assert '<p>foo</p>' == str(post1.content).strip()
    assert 'html' == post1.content.attrib['type']
    assert 'http://x.com/custom-blog/post1/' == post1.link.attrib['href']
    base = post1.attrib['{http://www.w3.org/XML/1998/namespace}base']
    assert 'http://x.com/custom-blog/post1/' == base
    assert 'A. Jesse Jiryu Davis' == post1.author.name


def test_virtual_resolver(pad, builder):
    feed = pad.get('typical-blog@atom/feed-one')
    assert feed and feed.feed_name == 'Feed One'
    url_path = pad.get('typical-blog/post1').url_to(feed)
    assert url_path == '../../typical-blog/feed.xml'

    feed = pad.get('typical-blog2@atom/feed-two')
    assert feed and feed.feed_name == 'feed-two'

    feed = pad.get('custom-blog@atom/feed-three')
    assert feed and feed.feed_name == 'Feed Three'
    url_path = pad.get('custom-blog/post1').url_to(feed)
    assert url_path == '../../custom-blog/atom.xml'


def test_dependencies(pad, builder, reporter):
    reporter.clear()
    builder.build(pad.get('typical-blog@atom/feed-one'))

    assert set(reporter.get_recorded_dependencies()) == set([
        'Website.lektorproject',
        'content/typical-blog',
        'content/typical-blog/contents.lr',
        'content/typical-blog/post1/contents.lr',
        'content/typical-blog/post2/contents.lr',
        'models/blog.ini',
        'models/blog-post.ini',
        'configs/feed.ini',
    ])

Added tox.ini.























































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[pytest]
norecursedirs = .tox 
addopts = -rs

[tox]
envlist = source,py27

[testenv]
deps = pytest 
    lxml
commands = py.test {posargs}

[testenv:source]
deps = pep257
    flake8
    dodgy
commands = - pep257 -v -e lektor_feed.py tests
    flake8 lektor_feed.py tests
    dodgy

[testenv:pylint]
deps = pylint
commands = - pylint lektor_feed.py

[testenv:pylinttest]
deps = pylint
commands = - pylint tests