From 9e5af50a4bb7f921eab58dfc917faba35b28dfc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=9A=D0=B0=D1=81=D0=B8?= =?UTF-8?q?=D0=BC=D0=BE=D0=B2?= Date: Tue, 21 May 2019 20:31:11 +0300 Subject: [PATCH] =?UTF-8?q?=D0=97=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=B3=D0=BB=D0=BE=D0=B1=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B?= =?UTF-8?q?=D1=85=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D1=85=20[closes=20#29]=20=D0=97=D0=B0=D0=B3=D1=80=D1=83=D0=B7?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=BC=D0=B5=D0=BD=D1=8E=20=D0=BF=D0=BE-=D1=83?= =?UTF-8?q?=D0=BC=D0=BE=D0=BB=D1=87=D0=B0=D0=BD=D0=B8=D1=8E=20=D1=87=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=B7=20=D1=81=D0=BE=D0=B1=D1=8B=D1=82=D0=B8=D1=8F?= =?UTF-8?q?=20[closes=20#27]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flatfilecms/__init__.py | 40 ++++++++----------- flatfilecms/events.py | 18 +++++++++ flatfilecms/filters.py | 18 +++++---- flatfilecms/models.py | 39 ++++++++++++------ flatfilecms/resources.py | 25 ++++-------- flatfilecms/templates/atom.jinja2 | 8 ++-- flatfilecms/templates/sitemap.jinja2 | 2 +- flatfilecms/views/__init__.py | 10 ----- flatfilecms/views/pages.py | 12 +----- tests/conftest.py | 22 +++++++++- tests/data/globals.yaml | 1 + tests/pages/blog/blogpost1.md | 2 +- tests/pages/blog/blogpost2.yaml | 2 +- tests/pages/blog/blogpost_with_future_date.md | 2 +- tests/pages/menu.yaml | 2 + tests/pages/sitemap.xml.yaml | 2 +- tests/templates/test_menu.jinja2 | 1 + tests/test_events.py | 8 ++++ tests/test_pages.py | 30 +++++--------- 19 files changed, 132 insertions(+), 112 deletions(-) create mode 100644 tests/data/globals.yaml create mode 100644 tests/pages/menu.yaml create mode 100644 tests/templates/test_menu.jinja2 diff --git a/flatfilecms/__init__.py b/flatfilecms/__init__.py index 5f49a18..358cdec 100644 --- a/flatfilecms/__init__.py +++ b/flatfilecms/__init__.py @@ -5,26 +5,26 @@ from pyramid.path import AssetResolver from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler -from flatfilecms.resources import Root +from flatfilecms.resources import Root, root_factory, roots class PagesEventHandler(FileSystemEventHandler): - def __init__(self, root, root_path): + def __init__(self, root_path): super(PagesEventHandler, self).__init__() - self.root = root self.root_path = root_path + self.abspath = AssetResolver().resolve(root_path).abspath() def find_dir(self, path): - folder = self.root - path = path.relative_to(self.root_path) + folder = roots.setdefault(self.root_path, Root(self.root_path)) + path = path.relative_to(self.abspath) for name in path.parts[:-1]: folder = folder[name] return folder def on_created(self, event): super(PagesEventHandler, self).on_created(event) - path = Path(event.dest_path if hasattr(event, 'dest_path') - else event.src_path) + path = Path( + event.dest_path if hasattr(event, 'dest_path') else event.src_path) folder = self.find_dir(path) if event.is_directory: folder.create_dir(path) @@ -51,30 +51,24 @@ def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ from pyramid.settings import asbool - watchdog = asbool(settings.get( - 'watchdog', 'false')) + watchdog = asbool(settings.get('watchdog', 'false')) settings['watchdog'] = watchdog settings.setdefault('pages_path', 'pages') settings.setdefault('data_path', 'data') + default_jinja2_filters = """markdown = flatfilecms.filters:markdown2html + fileglob = flatfilecms.filters:fileglob + merge_dict = flatfilecms.filters.merge_dict + join_url = flatfilecms.filters.join_url""" + settings['jinja2.filters'] = default_jinja2_filters + settings.get( + 'jinja2.filters', '') - pages = Root(settings['pages_path'], settings['data_path']) - - def factory(request): - return pages - - config = Configurator(settings=settings, - root_factory=factory) + config = Configurator(settings=settings, root_factory=root_factory) config.include('pyramid_jinja2') config.include('.routes') config.scan() if watchdog: - path = AssetResolver().resolve( - settings['pages_path']).abspath() observer = Observer() - event_handler = PagesEventHandler(pages, path) - observer.schedule( - event_handler, - path, - recursive=True) + event_handler = PagesEventHandler(settings['pages_path']) + observer.schedule(event_handler, event_handler.abspath, recursive=True) observer.start() return config.make_wsgi_app() diff --git a/flatfilecms/events.py b/flatfilecms/events.py index 7516fe1..7589f71 100644 --- a/flatfilecms/events.py +++ b/flatfilecms/events.py @@ -4,6 +4,8 @@ from datetime import date from pyramid.events import subscriber from pyramid.events import BeforeRender +from .models import load_yaml + @subscriber(BeforeRender) def add_global(event): @@ -13,3 +15,19 @@ def add_global(event): 'format_datetime': dates.format_datetime, 'today': date.today(), } + + +@subscriber(BeforeRender) +def add_data(event): + try: + event['data'] = load_yaml('globals.yaml', True) + except FileNotFoundError: + event['data'] = {} + + +@subscriber(BeforeRender) +def add_menu(event): + try: + event['menu'] = load_yaml('menu/default.yaml', True) + except FileNotFoundError: + event['menu'] = [] diff --git a/flatfilecms/filters.py b/flatfilecms/filters.py index 5fa5685..5e253ac 100644 --- a/flatfilecms/filters.py +++ b/flatfilecms/filters.py @@ -27,19 +27,17 @@ class Jinja2Processor(BlockProcessor): class IgnoreExtension(Extension): def extendMarkdown(self, md, md_globals): - md.parser.blockprocessors.add( - 'jinja2', - Jinja2Processor(md.parser), - ">hashheader") + md.parser.blockprocessors.add('jinja2', Jinja2Processor(md.parser), + ">hashheader") @contextfilter def markdown2html(context, text, render=True): result = markdown.markdown( - text, - extensions=[IgnoreExtension(), 'markdown.extensions.extra'], - output_format='html5', - tab_length=2) + text, + extensions=[IgnoreExtension(), 'markdown.extensions.extra'], + output_format='html5', + tab_length=2) if render: result = context.environment.from_string(result).render(context) if context.eval_ctx.autoescape: @@ -55,3 +53,7 @@ def merge_dict(a, b): def fileglob(path, root): return [p.relative_to(root) for p in Path(root).glob(path)] + + +def join_url(a, b): + return a.rstrip('/') + '/' + b.lstrip('/') diff --git a/flatfilecms/models.py b/flatfilecms/models.py index e8412d2..272f496 100644 --- a/flatfilecms/models.py +++ b/flatfilecms/models.py @@ -5,6 +5,7 @@ import markdown from datetime import datetime from pyramid.path import AssetResolver from zope.interface import (Interface, implementer) +from pyramid.threadlocal import get_current_registry from flatfilecms.filters import IgnoreExtension @@ -67,6 +68,27 @@ class CustomYAMLHandler(frontmatter.YAMLHandler): return yaml.load(fm, self.loader) +def load_yaml(path, data=False): + registry = get_current_registry() + settings = registry.settings + data_path = settings['data_path'] + if data: + path = str(PurePath(data_path) / path) + return yaml.load( + AssetResolver().resolve(path).stream(), + LoaderFactory(data_path)) + + +def load_frontmatter(path): + registry = get_current_registry() + settings = registry.settings + data_path = settings['data_path'] + return frontmatter.load( + AssetResolver().resolve(path).stream(), + handler=CustomYAMLHandler(data_path), + ).to_dict() + + class HiddenFile(Exception): pass @@ -76,8 +98,6 @@ class Folder(dict): self.path = path self.name = name self.__parent__ = parent - if parent is not None: - self.data_path = parent.data_path for entry in AssetResolver().resolve(path).listdir(): asset = f"{path}/{entry}" if AssetResolver().resolve(asset).isdir(): @@ -124,7 +144,6 @@ class Document(object): self.name = name self.__parent__ = parent self.path = path - self.data_path = parent.data_path class IMarkdown(Interface): @@ -135,25 +154,19 @@ class IMarkdown(Interface): class Markdown(Document): def __init__(self, name, parent, path): super(Markdown, self).__init__('', parent, path) - self.page = frontmatter.load( - AssetResolver().resolve(self.path).stream(), - handler=CustomYAMLHandler(self.data_path), - ).to_dict() + self.page = load_frontmatter(path) if 'published' in self.page and \ - str(datetime.now()) < str(self.page['published']): + self.page['published'] > datetime.now(): raise HiddenFile - @implementer(IMarkdown) class YAML(Document): def __init__(self, name, parent, path): super(YAML, self).__init__('', parent, path) - self.page = yaml.load( - AssetResolver().resolve(self.path).stream(), - LoaderFactory(self.data_path)) + self.page = load_yaml(path) if 'published' in self.page and \ - str(datetime.now()) < str(self.page['published']): + self.page['published'] > datetime.now(): raise HiddenFile diff --git a/flatfilecms/resources.py b/flatfilecms/resources.py index f224347..9b769e4 100644 --- a/flatfilecms/resources.py +++ b/flatfilecms/resources.py @@ -1,23 +1,14 @@ from flatfilecms.models import Folder -def flat(d, path): - structure = [] - for k, v in d.items(): - if isinstance(v, dict): - structure.extend(flat(v, f"{path}/{k}")) - else: - if k == 'index': - structure.append(f"{path}/") - else: - structure.append(f"{path}/{k}") - return structure - - class Root(Folder): - def __init__(self, path, data_path): - self.data_path = data_path + def __init__(self, path): super(Root, self).__init__('', None, path) - def structure(self, base_dir=''): - return flat(self, base_dir) + +roots = {} + + +def root_factory(request): + path = request.registry.settings['pages_path'] + return roots.setdefault(path, Root(path)) diff --git a/flatfilecms/templates/atom.jinja2 b/flatfilecms/templates/atom.jinja2 index ea80056..85cc7ed 100644 --- a/flatfilecms/templates/atom.jinja2 +++ b/flatfilecms/templates/atom.jinja2 @@ -1,12 +1,12 @@ - + {{title}} {%- if subtitle %} {{subtitle}} {%- endif %} - + {%- if 'links' in feed and feed['index'] %} - + {%- endif %} {{(pages[0][1].page['updated'] or pages[0][1].page['published']).isoformat()}}+00:00 urn:uuid:{{feed['id']}} @@ -20,7 +20,7 @@ {%- for item in pages %} {{item[1].page['title']}} - + urn:uuid:{{item[1].page['id']}} {{(item[1].page['updated'] or item[1].page['published']).isoformat()}}+00:00 {{item[1].page['published'].isoformat()}}+00:00 diff --git a/flatfilecms/templates/sitemap.jinja2 b/flatfilecms/templates/sitemap.jinja2 index 02b5ddd..0298f2a 100644 --- a/flatfilecms/templates/sitemap.jinja2 +++ b/flatfilecms/templates/sitemap.jinja2 @@ -2,7 +2,7 @@ {%- for item, lastmod in pages %} - {{data.base_url}}/{{item}} + {{data['base_url']|join_url(item)}} {%- if lastmod %} {{lastmod}} {%- endif %} diff --git a/flatfilecms/views/__init__.py b/flatfilecms/views/__init__.py index a051d61..7b64a8b 100644 --- a/flatfilecms/views/__init__.py +++ b/flatfilecms/views/__init__.py @@ -1,9 +1,5 @@ import re -import yaml from pyramid.renderers import render_to_response -from pyramid.path import AssetResolver -from pathlib import PurePath -from flatfilecms.models import LoaderFactory from pyramid.httpexceptions import HTTPFound from pyramid.traversal import (find_root, find_resource) @@ -30,12 +26,6 @@ def blog(self, options={}): post = self.context.page.copy() post['pages'] = sorted( postlist, key=lambda t: t[1].page['published'], reverse=True) - if 'menu' not in post: - post['menu'] = yaml.load( - AssetResolver().resolve( - str(PurePath(self.context.data_path) / - 'menu/default.yaml')).stream(), - LoaderFactory(self.context.data_path)) if format == 'atom': set_content_type(self, 'application/atom+xml') return render_to_response( diff --git a/flatfilecms/views/pages.py b/flatfilecms/views/pages.py index d4602fb..1972406 100644 --- a/flatfilecms/views/pages.py +++ b/flatfilecms/views/pages.py @@ -6,11 +6,8 @@ from pyramid.renderers import render_to_response from pyramid.httpexceptions import HTTPNotFound from pyramid.response import FileResponse from pyramid.path import (DottedNameResolver, AssetResolver) -from pathlib import PurePath -import yaml -from flatfilecms.models import (Folder, Document, IMarkdown, Jinja2, - LoaderFactory) +from flatfilecms.models import (Folder, Document, IMarkdown, Jinja2) class PagesView: @@ -55,13 +52,6 @@ class PagesView: if response: return response post = self.context.page - if 'menu' not in post: - post['menu'] = yaml.load( - AssetResolver().resolve( - str( - PurePath(self.context.data_path) / - 'menu/default.yaml')).stream(), - LoaderFactory(self.context.data_path)) return render_to_response( '{0}.jinja2'.format(post.get('template', 'default')), post, diff --git a/tests/conftest.py b/tests/conftest.py index 98b334e..ee932e2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,9 +1,27 @@ import pytest +@pytest.fixture +def current_directory(): + from pathlib import Path + return Path(__file__).parent + + +@pytest.fixture +def config(current_directory): + from pyramid.testing import testConfig + settings = { + 'data_path': current_directory / 'data', + 'jinja2.filters': 'join_url = flatfilecms.filters.join_url' + } + with testConfig(settings=settings) as config: + config.include('pyramid_jinja2') + config.add_jinja2_search_path('tests:templates') + config.add_jinja2_search_path('flatfilecms:templates') + yield config + + @pytest.fixture def fake_request(): from pyramid.testing import DummyRequest return DummyRequest() - - diff --git a/tests/data/globals.yaml b/tests/data/globals.yaml new file mode 100644 index 0000000..53b22f7 --- /dev/null +++ b/tests/data/globals.yaml @@ -0,0 +1 @@ +base_url: https://flatfilecms.test/ diff --git a/tests/pages/blog/blogpost1.md b/tests/pages/blog/blogpost1.md index 528096e..4923a01 100644 --- a/tests/pages/blog/blogpost1.md +++ b/tests/pages/blog/blogpost1.md @@ -2,7 +2,7 @@ title: Блоговая запись 1 в формате markdown description: Тест записей блогов template: blogpost -published: 2019-02-01 +published: 2019-02-01T12:00:00+00:00 --- Hello World! diff --git a/tests/pages/blog/blogpost2.yaml b/tests/pages/blog/blogpost2.yaml index 0589b55..6c87a5e 100644 --- a/tests/pages/blog/blogpost2.yaml +++ b/tests/pages/blog/blogpost2.yaml @@ -1,4 +1,4 @@ title: Блоговая запись 2 в формате YAML template: blogpost -published: 2019-02-02 +published: 2019-02-02T11:00:00+3:00 content: Hello World! 2 diff --git a/tests/pages/blog/blogpost_with_future_date.md b/tests/pages/blog/blogpost_with_future_date.md index 01439a8..81fe13b 100644 --- a/tests/pages/blog/blogpost_with_future_date.md +++ b/tests/pages/blog/blogpost_with_future_date.md @@ -2,7 +2,7 @@ title: Блоговая запись 1 в формате markdown description: Тест записей блогов template: blogpost -published: 3019-02-01 +published: 3019-02-01T23:59:59+0:00 --- Hello World! diff --git a/tests/pages/menu.yaml b/tests/pages/menu.yaml new file mode 100644 index 0000000..a6a7c11 --- /dev/null +++ b/tests/pages/menu.yaml @@ -0,0 +1,2 @@ +template: tests:templates/test_menu +menu: 'Menu - OK' diff --git a/tests/pages/sitemap.xml.yaml b/tests/pages/sitemap.xml.yaml index d830aed..c26e6fd 100644 --- a/tests/pages/sitemap.xml.yaml +++ b/tests/pages/sitemap.xml.yaml @@ -3,4 +3,4 @@ view: ignore: - blog/.*$ data: - base_url: https://flatfilecms.test + base_url: https://flatfilecms.test/ diff --git a/tests/templates/test_menu.jinja2 b/tests/templates/test_menu.jinja2 new file mode 100644 index 0000000..0d18b89 --- /dev/null +++ b/tests/templates/test_menu.jinja2 @@ -0,0 +1 @@ +{{menu}} diff --git a/tests/test_events.py b/tests/test_events.py index 9439091..c1bed42 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -5,3 +5,11 @@ def test_globals(fake_request): assert 'globals' in event assert 'format_date' in event['globals'] assert 'format_datetime' in event['globals'] + + +def test_data(config): + from flatfilecms.events import add_data + event = {} + add_data(event) + assert 'data' in event + assert event['data']['base_url'] == 'https://flatfilecms.test/' diff --git a/tests/test_pages.py b/tests/test_pages.py index 8e1308d..95770ea 100644 --- a/tests/test_pages.py +++ b/tests/test_pages.py @@ -2,27 +2,10 @@ import pytest @pytest.fixture -def current_directory(): - from pathlib import Path - return Path(__file__).parent - - -@pytest.fixture -def pages(current_directory): +def pages(current_directory, config): from flatfilecms.resources import Root return Root( - str(current_directory / 'pages'), - str(current_directory / 'data')) - - -@pytest.fixture -def config(current_directory): - from pyramid.testing import testConfig - with testConfig() as config: - config.include('pyramid_jinja2') - config.add_jinja2_search_path('tests:templates') - config.add_jinja2_search_path('flatfilecms:templates') - yield config + str(current_directory / 'pages')) def test_loading(pages): @@ -83,5 +66,14 @@ def test_sitemap(pages, fake_request, config): https://flatfilecms.test/view-with-options + + https://flatfilecms.test/menu + """ assert response.content_type == 'text/xml' + + +def test_menu(pages, fake_request, config): + from flatfilecms.views.pages import PagesView + response = PagesView(pages['menu'], fake_request).process_yaml() + assert response.text == 'Menu - OK'