././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1732516280.0657144 flask_flatpages-0.8.3/0000755000175100001770000000000014721014670014257 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1732516280.0657144 flask_flatpages-0.8.3/Flask_FlatPages.egg-info/0000755000175100001770000000000014721014670020737 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1732516280.0 flask_flatpages-0.8.3/Flask_FlatPages.egg-info/PKG-INFO0000644000175100001770000000501614721014670022036 0ustar00runnerdockerMetadata-Version: 2.1 Name: Flask-FlatPages Version: 0.8.3 Summary: Provides flat static pages to a Flask application Author: Simon Sapin, Igor Davydenko, Padraic Calpin Maintainer-email: Padraic Calpin Project-URL: Repository, https://github.com/Flask-FlatPages/Flask-FlatPages Project-URL: Documentation, https://flask-flatpages.readthedocs.io/en/latest/ Classifier: Environment :: Web Environment Classifier: Framework :: Flask Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7 Description-Content-Type: text/x-rst License-File: LICENSE Requires-Dist: Flask>1.0 Requires-Dist: Jinja2>=2.10.2 Requires-Dist: Markdown>=2.5 Requires-Dist: PyYAML>5.3.1 Requires-Dist: pytz; python_version == "2.7" Requires-Dist: six Provides-Extra: codehilite Requires-Dist: Pygments>=2.5.2; extra == "codehilite" =============== Flask-FlatPages =============== .. image:: https://github.com/flask-FlatPages/flask-FlatPages/actions/workflows/test.yml/badge.svg?branch=master :target: https://github.com/flask-FlatPages/flask-FlatPages/actions/workflows/test.yml/ .. image:: https://img.shields.io/pypi/v/Flask-FlatPages.svg :target: https://pypi.python.org/pypi/Flask-FlatPages Provides flat static pages to a Flask_ application, based on text files as opposed to a relational database. * Works on Python 2.7 and 3.6+ * BSD licensed * Latest documentation `on Read the Docs`_ * Source, issues and pull requests `on Github`_ * Releases `on PyPI`_ * Install with ``pip install Flask-FlatPages`` .. _Flask: http://flask.pocoo.org/ .. _on Read the Docs: http://flask-flatpages.readthedocs.org/ .. _on Github: https://github.com/SimonSapin/Flask-FlatPages/ .. _on PyPI: http://pypi.python.org/pypi/Flask-FlatPages ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1732516280.0 flask_flatpages-0.8.3/Flask_FlatPages.egg-info/SOURCES.txt0000644000175100001770000000103214721014670022617 0ustar00runnerdockerLICENSE MANIFEST.in README.rst pyproject.toml setup.py Flask_FlatPages.egg-info/PKG-INFO Flask_FlatPages.egg-info/SOURCES.txt Flask_FlatPages.egg-info/dependency_links.txt Flask_FlatPages.egg-info/requires.txt Flask_FlatPages.egg-info/top_level.txt docs/Makefile docs/conf.py docs/index.rst docs/requirements.txt flask_flatpages/__init__.py flask_flatpages/flatpages.py flask_flatpages/imports.py flask_flatpages/page.py flask_flatpages/utils.py tests/test_flask_flatpages.py tests/test_markdown_extensions.py tests/test_temp_directory.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1732516280.0 flask_flatpages-0.8.3/Flask_FlatPages.egg-info/dependency_links.txt0000644000175100001770000000000114721014670025005 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1732516280.0 flask_flatpages-0.8.3/Flask_FlatPages.egg-info/requires.txt0000644000175100001770000000016714721014670023343 0ustar00runnerdockerFlask>1.0 Jinja2>=2.10.2 Markdown>=2.5 PyYAML>5.3.1 six [:python_version == "2.7"] pytz [codehilite] Pygments>=2.5.2 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1732516280.0 flask_flatpages-0.8.3/Flask_FlatPages.egg-info/top_level.txt0000644000175100001770000000002014721014670023461 0ustar00runnerdockerflask_flatpages ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1732516253.0 flask_flatpages-0.8.3/LICENSE0000644000175100001770000000302314721014635015263 0ustar00runnerdockerCopyright (c) 2010-2014 by Simon Sapin, 2013-2014 by Igor Davydenko. Some rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1732516253.0 flask_flatpages-0.8.3/MANIFEST.in0000644000175100001770000000034114721014635016014 0ustar00runnerdockerinclude LICENSE recursive-include docs * recursive-exclude docs *.pyc recursive-exclude docs *.pyo prune docs/_build prune docs/_themes/.git recursive-include tests recursive-exclude tests *.pyc recursive-exclude tests *.pyo ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1732516280.0657144 flask_flatpages-0.8.3/PKG-INFO0000644000175100001770000000501614721014670015356 0ustar00runnerdockerMetadata-Version: 2.1 Name: Flask-FlatPages Version: 0.8.3 Summary: Provides flat static pages to a Flask application Author: Simon Sapin, Igor Davydenko, Padraic Calpin Maintainer-email: Padraic Calpin Project-URL: Repository, https://github.com/Flask-FlatPages/Flask-FlatPages Project-URL: Documentation, https://flask-flatpages.readthedocs.io/en/latest/ Classifier: Environment :: Web Environment Classifier: Framework :: Flask Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7 Description-Content-Type: text/x-rst License-File: LICENSE Requires-Dist: Flask>1.0 Requires-Dist: Jinja2>=2.10.2 Requires-Dist: Markdown>=2.5 Requires-Dist: PyYAML>5.3.1 Requires-Dist: pytz; python_version == "2.7" Requires-Dist: six Provides-Extra: codehilite Requires-Dist: Pygments>=2.5.2; extra == "codehilite" =============== Flask-FlatPages =============== .. image:: https://github.com/flask-FlatPages/flask-FlatPages/actions/workflows/test.yml/badge.svg?branch=master :target: https://github.com/flask-FlatPages/flask-FlatPages/actions/workflows/test.yml/ .. image:: https://img.shields.io/pypi/v/Flask-FlatPages.svg :target: https://pypi.python.org/pypi/Flask-FlatPages Provides flat static pages to a Flask_ application, based on text files as opposed to a relational database. * Works on Python 2.7 and 3.6+ * BSD licensed * Latest documentation `on Read the Docs`_ * Source, issues and pull requests `on Github`_ * Releases `on PyPI`_ * Install with ``pip install Flask-FlatPages`` .. _Flask: http://flask.pocoo.org/ .. _on Read the Docs: http://flask-flatpages.readthedocs.org/ .. _on Github: https://github.com/SimonSapin/Flask-FlatPages/ .. _on PyPI: http://pypi.python.org/pypi/Flask-FlatPages ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1732516253.0 flask_flatpages-0.8.3/README.rst0000644000175100001770000000161414721014635015751 0ustar00runnerdocker=============== Flask-FlatPages =============== .. image:: https://github.com/flask-FlatPages/flask-FlatPages/actions/workflows/test.yml/badge.svg?branch=master :target: https://github.com/flask-FlatPages/flask-FlatPages/actions/workflows/test.yml/ .. image:: https://img.shields.io/pypi/v/Flask-FlatPages.svg :target: https://pypi.python.org/pypi/Flask-FlatPages Provides flat static pages to a Flask_ application, based on text files as opposed to a relational database. * Works on Python 2.7 and 3.6+ * BSD licensed * Latest documentation `on Read the Docs`_ * Source, issues and pull requests `on Github`_ * Releases `on PyPI`_ * Install with ``pip install Flask-FlatPages`` .. _Flask: http://flask.pocoo.org/ .. _on Read the Docs: http://flask-flatpages.readthedocs.org/ .. _on Github: https://github.com/SimonSapin/Flask-FlatPages/ .. _on PyPI: http://pypi.python.org/pypi/Flask-FlatPages ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1732516280.0657144 flask_flatpages-0.8.3/docs/0000755000175100001770000000000014721014670015207 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1732516253.0 flask_flatpages-0.8.3/docs/Makefile0000644000175100001770000001521614721014635016655 0ustar00runnerdocker# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Flask-LazyViews.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Flask-LazyViews.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Flask-LazyViews" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Flask-LazyViews" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1732516253.0 flask_flatpages-0.8.3/docs/conf.py0000644000175100001770000001664414721014635016522 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Flask-FlatPages documentation build configuration file, created by # sphinx-quickstart on Fri Dec 24 15:20:25 2010. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os, re # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) sys.path.append(os.path.abspath('..')) sys.path.append(os.path.abspath('_themes')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'pallets_sphinx_themes', 'reno.sphinxext'] # Intersphinx Mapping intersphinx_mapping = {'flask': ('http://flask.pocoo.org/docs/', None)} # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Flask-FlatPages' copyright = u'2010-2015, Simon Sapin. 2013-2015, Igor Davydenko' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # release = re.search('__version__ = "([^\']+)"', open(os.path.join( os.path.dirname(__file__), '..', 'flask_flatpages', '__init__.py')) .read()).group(1) # The short X.Y version. #version = '0.1' version = re.split('[a-zA-Z]', release)[0] # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. #pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'flask' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} html_theme_options = { 'index_sidebar_logo': False, } # Add any paths that contain custom themes here, relative to this directory. html_theme_path = ['_themes'] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". #html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'Flask-FlatPagesdoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'Flask-FlatPages.tex', u'Flask-FlatPages Documentation', u'Simon Sapin', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'flask-flatpages', u'Flask-FlatPages Documentation', [u'Simon Sapin', u'Igor Davydenko', u'Padraic Calpin'], 1) ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1732516253.0 flask_flatpages-0.8.3/docs/index.rst0000644000175100001770000004102114721014635017047 0ustar00runnerdockerFlask-FlatPages =============== Flask-FlatPages provides a collection of pages to your `Flask`_ application. Pages are built from “flat” text files as opposed to a relational database. * Works on Python 2.7 and 3.6+ * BSD licensed * Latest documentation on `Read the Docs`_ * Source, issues and pull requests `Github`_ * Releases on `PyPI`_ .. _Flask: http://flask.pocoo.org/ .. _Read the Docs: http://flask-flatpages.readthedocs.org/ .. _Github: https://github.com/SimonSapin/Flask-FlatPages/ .. _PyPI: http://pypi.python.org/pypi/Flask-FlatPages Installation ------------ Install the extension with `pip `_:: $ pip install Flask-FlatPages or you can get the `source code from github `_. Configuration ------------- To get started all you need to do is to instantiate a :class:`FlatPages` object after configuring the application:: from flask import Flask from flask_flatpages import FlatPages app = Flask(__name__) app.config.from_pyfile('mysettings.cfg') pages = FlatPages(app) you can also pass the Flask application object later, by calling :meth:`~.FlatPages.init_app`:: pages = FlatPages() def create_app(config='mysettings.cfg'): app = Flask(__name__) app.config.from_pyfile(config) pages.init_app(app) return app Flask-FlatPages accepts the following configuration values. All of them are optional. ``FLATPAGES_ROOT`` Path to the directory where to look for page files. If relative, interpreted as relative to the application root, next to the ``static`` and ``templates`` directories. Defaults to ``pages``. ``FLATPAGES_INSTANCE_RELATIVE`` .. versionadded:: 0.7 If `True`, Flask-Flatpages will interpret the root as relative to the application's `instance folder `_. Defaults to `False`. ``FLATPAGES_EXTENSION`` Filename extension for pages. Files in the ``FLATPAGES_ROOT`` directory without this suffix are ignored. Defaults to ``.html``. .. versionchanged:: 0.6 Support multiple file extensions via sequences, e.g.: ``['.htm', '.html']`` or via comma-separated strings: ``.htm,.html``. ``FLATPAGES_CASE_INSENSITIVE`` .. versionadded:: 0.7 If `True`, the path property of each :class:`Page` instance will be all lower case. Flask-Flatpages will throw a `ValueError` if multiple pages would correspond to the same path. ``FLATPAGES_ENCODING`` Encoding of the pages files. Defaults to ``utf8``. ``FLATPAGES_HTML_RENDERER`` Callable or import string for a callable that takes at least the unicode body of a page, and return its HTML rendering as a unicode string. Defaults to :func:`~.pygmented_markdown`. .. versionchanged:: 0.5 Support for passing the :class:`~.FlatPages` instance as second argument. .. versionchanged:: 0.6 Support for passing the :class:`~.Page` instance as third argument. Renderer functions need to have at least one argument, the unicode body. The use of either :class:`~FlatPages` as second argument or :class:`~FlatPages` and :class:`Page` as second respective third argument is optional, and allows for more advanced renderers. ``FLATPAGES_MARKDOWN_EXTENSIONS`` .. versionadded:: 0.4 List of Markdown extensions to use with default HTML renderer, given as either 'entrypoint' strings or ``markdown.Extension`` objects. Defaults to ``['codehilite']``. .. versionchanged:: 0.7 Markdown 2.5 changed the syntax for passing extensions, and for configuring extensions. In particular, configuring an extension by passing keyword arguments along with the import string is now deprecated. Instead, these options need to be passed in a dict. For more information, see ``FLATPAGES_EXTENSION_CONFIGS``. ``FLATPAGES_EXTENSION_CONFIGS`` .. versionadded:: 0.7 Extension config dictionary for configuring extensions passed by their import string. For each extension, ``FLATPAGES_EXTENSION_CONFIGS`` contains a dict of configuration settings passed as strings. For example, to enable linenums in ``codehilite``: .. code-block:: python FLATPAGES_EXTENSION_CONFIGS = { 'codehilite': { 'linenums': 'True' } } `See the Markdown 3 documentation for more details `_ ``FLATPAGES_AUTO_RELOAD`` Whether to reload pages at each request. See :ref:`laziness-and-caching` for more details. The default is to reload in ``DEBUG`` mode only. ``FLATPAGES_LEGACY_META_PARSER`` .. versionadded:: 0.8 Controls whether to use the newer parser based on tokenising metadata with libyaml. Setting this to true reverts to the simpler method of parsing metadata used in versions <0.8, which requires the metadata block be terminated by a newline, or else if there's no metadata that a leading newline be present. Intended to provide a fallback in case of bugs with the newer parser. Please note that multiple FlatPages instances can be configured by using a name for the FlatPages instance at initializaton time: .. code-block:: python flatpages = FlatPages(name="blog") To configure this instance, you must use modified configuration keys, by adding the uppercase name to the configuration variable names: ``FLATPAGES_BLOG_*`` How it works ------------ When first needed (see :ref:`laziness-and-caching` for more about this), the extension loads all pages from the filesystem: a :class:`Page` object is created for all files in ``FLATPAGES_ROOT`` whose name ends with ``FLATPAGES_EXTENSION``. Each of these objects is associated to a path: the slash-separated (whatever the OS) name of the file it was loaded from, relative to the pages root, and excluding the extension. For example, for an app in ``C:\myapp`` with the default configuration, the path for the ``C:\myapp\pages\lorem\ipsum.html`` is ``lorem/ipsum``. Each file is made of a `YAML`_ mapping of metadata, and the page body:: title: Hello published: 2010-12-22 --- Hello, *World*! Lorem ipsum dolor sit amet, … The body format defaults to `Markdown`_ with `Pygments`_ baked in if available, but depends on the ``FLATPAGES_HTML_RENDERER`` configuration value. .. _YAML: http://www.yaml.org/ .. _Markdown: http://daringfireball.net/projects/markdown/ .. _Pygments: http://pygments.org/ To use Pygments, you need to include the style declarations separately. You can get them with :func:`.pygments_style_defs`:: @app.route('/pygments.css') def pygments_css(): return pygments_style_defs('tango'), 200, {'Content-Type': 'text/css'} and in templates: .. code-block:: html+jinja .. highlight:: YAML Delimiting YAML Metadata ~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 0.8 .. note:: The ``FLATPAGES_LEGACY_META_PARSER`` flag can be used to re-enable the legacy metadata behaviour. In previous versions, YAML metadata was terminated by a newline. This meant it was impossible to use e.g. multi-line strings in the metadata, to import files from other markdown tools like Obsidian, or worse that if your page had no metadata at all it needed to start with an empty line. Starting with v0.8, YAML can now be delimited by wrapping it with ``---``:: --- title: Hello World author: J.L Coolman --- Hello, world! Or using YAML 'end document' specifiers like:: --- title: Hello Again author: J.L Coolman ... Hello, world! In all cases, the leading ``---`` is optional. With this change, it's now possible to have pages with no-metadata by starting them with:: --- --- Hello, this is my page Or:: --- ... Hello, this is a page too Or even just launching in to the body:: Hello, this is also a page! .. warning:: In previous versions, metadata was terminated with a new line. The updated metadata parser attempts to preserve backwards compatability as much as possible, but unexpected behaviour can occur if the page starts with text that looks ambiguously 'YAML-like'. For example:: hello: world This is my blog: We strongly advise using an explicit delimiter. In addition, if your metadata features multi-line blocks, you **must** specify an start and end delimiter, or else the parser may cut the metadata at the first blank line. .. highlight:: python Using custom Markdown extensions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 0.4 By default, Flask-FlatPages renders flatpage body using `Markdown`_ with `Pygments`_ format. This means passing ``['codehilite']`` extensions list to ``markdown.markdown`` function. But sometimes you need to customize things, like using another extension or disable default approach, this possible by passing special config. For example, using another extension:: FLATPAGES_MARKDOWN_EXTENSIONS = ['codehilite', 'headerid'] Or disabling default approach:: FLATPAGES_MARKDOWN_EXTENSIONS = [] Using custom HTML renderers ~~~~~~~~~~~~~~~~~~~~~~~~~~~ As pointed above, by default Flask-FlatPages expects that flatpage body contains `Markdown`_ markup, so uses ``markdown.markdown`` function to render its content. But due to ``FLATPAGES_HTML_RENDERER`` setting you can specify different approach for rendering flatpage body. The most common necessity of using custom HTML renderer is modifyings default Markdown approach (e.g. by pre-rendering Markdown flatpages with Jinja), or using different markup for rendering flatpage body (e.g. ReStructuredText). Examples below introduce how to use custom renderers for those needs. Pre-rendering Markdown flatpages with Jinja ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: from flask import Flask, render_template_string from flask_flatpages import FlatPages from flask_flatpages.utils import pygmented_markdown def my_renderer(text): prerendered_body = render_template_string(text) return pygmented_markdown(prerendered_body) # Or, if you wish to render using the markdown extensions # listed in FLATPAGES_MARKDOWN_EXTENSIONS: #return pygmented_markdown(prerendered_body, flatpages=pages) app = Flask(__name__) app.config['FLATPAGES_HTML_RENDERER'] = my_renderer pages = FlatPages(app) ReStructuredText flatpages ^^^^^^^^^^^^^^^^^^^^^^^^^^ .. note:: For rendering ReStructuredText you need to add `docutils `_ to your project requirements. :: from docuitls.core import publish_parts from flask import Flask from flask_flatpages import FlatPages def rst_renderer(text): parts = publish_parts(source=text, writer_name='html') return parts['fragment'] app = Flask(__name__) app.config['FLATPAGES_HTML_RENDERER'] = rst_renderer pages = FlatPages(app) .. _laziness-and-caching: Laziness and caching -------------------- :class:`.FlatPages` does not hit the filesystem until needed but when it does, it reads all pages from the disk at once. Then, pages are not loaded again unless you explicitly ask for it with :meth:`.FlatPages.reload`, or on new requests depending on the configuration. (See ``FLATPAGES_AUTO_RELOAD``.) This design was decided with `Frozen-Flask`_ in mind but should work even if you don’t use it: you already restart your production server on code changes, you just have to do it on page content change too. This can make sense if the pages are deployed alongside the code in version control. .. _Frozen-Flask: http://packages.python.org/Frozen-Flask/ If you have many pages and loading takes a long time, you can force it at initialization time so that it’s done by the time the first request is served:: pages = FlatPages(app) pages.get('foo') # Force loading now. foo.html may not even exist. Loading everything every time may seem wasteful, but the impact is mitigated by caching: if a file’s modification time hasn’t changed, it is not read again and the previous :class:`.Page` object is re-used. Likewise, the YAML and Markdown parsing is both lazy and cached: not done until needed, and not done again if the file did not change. API --- .. module:: flask_flatpages .. autoclass:: FlatPages :members: init_app, get, get_or_404, __iter__, reload Example usage:: pages = FlatPages(app) @app.route('/') def index(): # Articles are pages with a publication date articles = (p for p in pages if 'published' in p.meta) # Show the 10 most recent articles, most recent first. latest = sorted(articles, reverse=True, key=lambda p: p.meta['published']) return render_template('articles.html', articles=latest[:10]) @app.route('//') def page(path): page = pages.get_or_404(path) template = page.meta.get('template', 'flatpage.html') return render_template(template, page=page) .. autoclass:: Page() :members: With the ``hello.html`` page defined earlier .. code-block:: YAML # hello.html title: Hello published: 2010-12-22 Hello, *World*! Lorem ipsum dolor sit amet, … :: >>> page = pages.get('hello') >>> page.meta # PyYAML converts YYYY-MM-DD to a date object {'title': u'Hello', 'published': datetime.date(2010, 12, 22)} >>> page['title'] u'Hello' >>> page.body u'Hello, *World*!\n\nLorem ipsum dolor sit amet, \u2026' >>> page.html u'

Hello, World!

\n

Lorem ipsum dolor sit amet, \u2026

' .. automethod:: __getitem__ .. automethod:: __html__ .. autofunction:: pygmented_markdown .. autofunction:: pygments_style_defs Changelog --------- .. release-notes:: v0.7.2 ~~~~~~~~~~~~~ Release Date: 2020-04-19 Bug Fixes ^^^^^^^^^ - Fixed a bug arising when the user overrides the default markdown extensions, but does not use Pygments. Apologies to anyone who didn't want codehiliting! (`#73 `_) Documentation Changes ^^^^^^^^^^^^^^^^^^^^^ - Update documentation to use the latest version of the Flask template. - Add towncrier config for auto-generating release notes Other Notes ^^^^^^^^^^^ - This project currently supports Python versions 2.7, and 3.4+. Some dependencies, particularly PyYAML, do not support Python 3.4 in the most recent versions. Thus, for security reasons, we strongly advise using Python 2.7 if no newer version of Python 3 is available. Support for Python 3.4 will be dropped in June 2020. v0.7.1 ~~~~~~~~~~~~~ * Small bump to dependency versions to resolve security alerts. v0.7.0 ~~~~~~~~~~~~~ * Update to Markdown 3.0 with new extension loading syntax. * Added `FLATPAGES_EXTENSION_CONFIGS` for configuring extensions specified by import string. * Add support for loading pages from Flask instance folder * Add a case insensitive loading option v0.6.1 ~~~~~~~~~~~~~ * Update dependencies to `Flask>=1.0` (as Flask 0.12.1 has known vulnerabilities). * Pin `Markdown<=3.0` as the Markdown extension API has changed. v0.6 ~~~~~~~~~~~ Release Date: 2015-02-09 * Python 3 support. * Allow multiple file extensions for FlatPages. * The renderer function now optionally takes a third argument, namely the :class:`Page` instance. * It is now possible to instantiate multiple instances of :class:`FlatPages` with different configurations. This is done by specifying an additional parameter ``name`` to the initializer and adding the same name in uppercase to the respective Flask configuration settings. v0.5 ~~~~~~~~~~~ Release Date: 2013-04-02 * Change behavior of passing ``FLATPAGES_MARKDOWN_EXTENSIONS`` to renderer function, now the :class:`FlatPages` instance is optionally passed as second argument. This allows more robust renderer functions. v0.4 ~~~~~~~~~~~ Release Date: 2013-04-01 * Add ``FLATPAGES_MARKDOWN_EXTENSIONS`` config to setup list of Markdown extensions to use with default HTML renderer. * Fix a bug with non-ASCII filenames. v0.3 ~~~~~~~~~~~ Release Date: 2012-07-03 * Add :meth:`.FlatPages.init_app` * Do not use namespace packages anymore: rename the package from ``flaskext.flatpages`` to ``flask_flatpages`` * Add configuration files for testing with tox and Travis. v0.2 ~~~~~~~~~~~ Release Date: 2011-06-02 Bugfix and cosmetic release. Tests are now installed alongside the code. v0.1 ~~~~~~~~~~~ Release Date: 2011-02-06. First public release. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1732516253.0 flask_flatpages-0.8.3/docs/requirements.txt0000644000175100001770000000007114721014635020472 0ustar00runnerdockerPallets-Sphinx-Themes Pygments>=2.2.0 Sphinx>=1.8.1 reno ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1732516280.0657144 flask_flatpages-0.8.3/flask_flatpages/0000755000175100001770000000000014721014670017405 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1732516253.0 flask_flatpages-0.8.3/flask_flatpages/__init__.py0000644000175100001770000000101714721014635021516 0ustar00runnerdocker""" Flask-FlatPages provides a collection of pages to your Flask application. Pages are built from "flat" text files as opposed to a relational database. :copyright: (c) 2010-2015 by Simon Sapin, 2013-2015 by Igor Davydenko. :license: BSD, see LICENSE for more details. """ from .flatpages import FlatPages # noqa from .page import Page # noqa from .utils import pygmented_markdown, pygments_style_defs # noqa __author__ = "Simon Sapin, Igor Davydenko, Padraic Calpin" __license__ = "BSD License" __version__ = "0.8.3" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1732516253.0 flask_flatpages-0.8.3/flask_flatpages/flatpages.py0000755000175100001770000003524214721014635021737 0ustar00runnerdocker"""Flatpages extension.""" import operator import os import warnings from itertools import takewhile import six from flask import abort from werkzeug.utils import cached_property, import_string from yaml import ( BlockMappingStartToken, BlockSequenceStartToken, DocumentEndToken, DocumentStartToken, FlowMappingStartToken, FlowSequenceStartToken, KeyToken, SafeLoader, ScalarToken, ) from .page import Page from .utils import force_unicode, NamedStringIO, pygmented_markdown if six.PY3: from inspect import getfullargspec else: from inspect import getargspec as getfullargspec START_TOKENS = ( BlockMappingStartToken, BlockSequenceStartToken, DocumentStartToken, FlowMappingStartToken, FlowSequenceStartToken, KeyToken, ) def _check_newline_token(token): return ( isinstance(token, ScalarToken) and token.style is None and "\n" in token.value ) def _check_continue_parsing_tokens(token): return not ( isinstance(token, (DocumentStartToken, DocumentEndToken)) or token is None ) class FlatPages(object): """A collection of :class:`Page` objects.""" #: Default configuration for FlatPages extension default_config = ( ("root", "pages"), ("extension", ".html"), ("encoding", "utf-8"), ("html_renderer", pygmented_markdown), ("markdown_extensions", ["codehilite"]), ("extension_configs", {}), ("auto_reload", "if debug"), ("case_insensitive", False), ("instance_relative", False), ("legacy_meta_parser", False), ) def __init__(self, app=None, name=None): """Initialize FlatPages extension. :param app: Your application. Can be omitted if you call :meth:`init_app` later. :type app: A :class:`~flask.Flask` instance :param name: The name for this FlatPages instance. Used for looking up config values using 'FLATPAGES_%s_%s' % (name.upper(), key) By default, no name is used, so configuration is done by specifying config values using 'FLATPAGES_%s' % (key) Typically, you only need to set this parameter if you want to use multiple :class:`FlatPages instances within the same Flask application. :type name: string .. versionchanged:: 0.6 New parameter `name` to support multiple FlatPages instances. """ self.name = name if name is None: self.config_prefix = "FLATPAGES" else: self.config_prefix = "_".join(("FLATPAGES", name.upper())) #: dict of filename: (page object, mtime when loaded) self._file_cache = {} if app: self.init_app(app) def __iter__(self): """Iterate on all :class:`Page` objects.""" return six.itervalues(self._pages) def config(self, key): """Read actual configuration from Flask application config. :param key: Lowercase config key from :attr:`default_config` tuple """ return self.app.config["_".join((self.config_prefix, key.upper()))] def get(self, path, default=None): """ Return the :class:`Page` object at ``path``. Returns ``default`` if there is no such page. """ # This may trigger the property. Do it outside of the try block. pages = self._pages try: return pages[path] except KeyError: return default def get_or_404(self, path): """ Return the :class:`Page` object at ``path``. Raise Flask's 404 error if there is no such page. """ page = self.get(path) if not page: abort(404) return page def init_app(self, app): """ Use to initialize an application. Ueful for passing an app later and app factory patterns. :param app: your application :type app: a :class:`~flask.Flask` instance """ # Store default config to application for key, value in self.default_config: config_key = "_".join((self.config_prefix, key.upper())) app.config.setdefault(config_key, value) # Register function to forget all pages if necessary app.before_request(self._conditional_auto_reset) # And finally store application to current instance and current # instance to application if "flatpages" not in app.extensions: app.extensions["flatpages"] = {} app.extensions["flatpages"][self.name] = self self._app = app @property def app(self): """ The Flask Application associated with this extension. Accessing the application instance this way is not advised. This property exists to ensure backwards compatability, and in future will wrap `flask.current_app`, the recommended way to access the app instance. """ warnings.warn( "Flask recommends accessing the app instance via " "`flask.current_app`. In future releases, this will wrap " "current_app, and raise a RuntimeError if there is no " "application context present.", DeprecationWarning, ) return self._app @app.setter def app(self, app_instance): warnings.warn( "This attribute should be managed by self.init_app instead. " "This will raise an AttributeError from version 0.9 onwards.", DeprecationWarning, ) self._app = app_instance def reload(self): """Forget all pages. All pages will be reloaded next time they're accessed. """ try: # This will "unshadow" the cached_property. # The property will be re-executed on next access. del self.__dict__["_pages"] except KeyError: pass @property def root(self): """Full path to the directory where pages are looked for. This corresponds to the `FLATPAGES_%(name)s_ROOT` config value, interpreted as relative to the app's root directory, or as relative to the app's instance folder if `FLATPAGES_%(name)s_INSTANCE_RELATIVE` is set to `True`. """ if self.config("instance_relative"): root_dir = os.path.join( self.app.instance_path, self.config("root") ) else: root_dir = os.path.join(self.app.root_path, self.config("root")) return force_unicode(root_dir) def _conditional_auto_reset(self): """Reset if configured to do so on new requests.""" auto = self.config("auto_reload") if auto == "if debug": auto = self.app.debug if auto: self.reload() def _load_file(self, path, filename, rel_path): """ Load file from file system and cache it. We store the result as a tuple of :class:`Path` and the file `mtime`. """ mtime = os.path.getmtime(filename) cached = self._file_cache.get(filename) if cached and cached[1] == mtime: page = cached[0] else: encoding = self.config("encoding") if six.PY3: with open(filename, encoding=encoding) as handler: content = handler.read() else: with open(filename) as handler: content = handler.read().decode(encoding) page = self._parse(content, path, rel_path) self._file_cache[filename] = (page, mtime) return page @cached_property def _pages(self): """ Walk the page root directory and return a dict of pages. Returns a dictionary of pages keyed by their path. """ def _walker(): """ Walk over directory and find all possible flatpages. Returns files which end with the string or sequence given by ``FLATPAGES_%(name)s_EXTENSION``. """ for cur_path, _, filenames in os.walk(self.root): rel_path = cur_path.replace(self.root, "").lstrip(os.sep) path_prefix = tuple(rel_path.split(os.sep)) if rel_path else () for name in filenames: if not name.endswith(extension): continue full_name = os.path.join(cur_path, name) name_without_extension = [ name[: -len(item)] for item in extension if name.endswith(item) ][0] path = "/".join(path_prefix + (name_without_extension,)) if self.config("case_insensitive"): path = path.lower() yield (path, full_name, rel_path) # Read extension from config extension = self.config("extension") # Support for multiple extensions if isinstance(extension, six.string_types): if "," in extension: extension = tuple(extension.split(",")) else: extension = (extension,) elif isinstance(extension, (list, set)): extension = tuple(extension) # FlatPage extension should be a string or a sequence if not isinstance(extension, tuple): raise ValueError( "Invalid value for FlatPages extension. Should be a string or " "a sequence, got {0} instead: {1}".format( type(extension).__name__, extension ) ) pages = {} for path, full_name, rel_path in _walker(): if path in pages: raise ValueError( "Multiple pages found which correspond to the same path. " "This error can arise when using multiple extensions." ) pages[path] = self._load_file(path, full_name, rel_path) return pages def _libyaml_parser(self, content, path): if not six.PY3: content = force_unicode(content) yaml_loader = SafeLoader(NamedStringIO(content, path)) yaml_loader.get_token() # Get stream start token token = yaml_loader.get_token() if not isinstance(token, START_TOKENS): meta = "" content = content.lstrip("\n") else: lines = content.split("\n") if isinstance(token, DocumentStartToken): token = yaml_loader.get_token() newline_token = None while _check_continue_parsing_tokens(token): try: token = yaml_loader.get_token() if _check_newline_token(token) and newline_token is None: newline_token = token except Exception: break if token is None and newline_token is None: meta = content content = "" else: if token is not None: meta_end_line = token.end_mark.line + 1 else: meta_end_line = newline_token.start_mark.line meta_end_line += lines[meta_end_line:].index("") meta = "\n".join(lines[:meta_end_line]) content = "\n".join(lines[meta_end_line:]).lstrip("\n") if not six.PY3: return force_unicode(meta), force_unicode(content) return meta, content def _legacy_parser(self, content): lines = iter(content.split("\n")) # Read lines until an empty line is encountered. meta = "\n".join(takewhile(operator.methodcaller("strip"), lines)) # The rest is the content. `lines` is an iterator so it continues # where `itertools.takewhile` left it. content = "\n".join(lines) return meta, content def _parse(self, content, path, rel_path): """Parse a flatpage file, i.e. read and parse its meta data and body. :return: initialized :class:`Page` instance. """ if self.config("legacy_meta_parser"): meta, content = self._legacy_parser(content) else: meta, content = self._libyaml_parser(content, path) # Now we ready to get HTML renderer function html_renderer = self.config("html_renderer") # If function is not callable yet, import it if not callable(html_renderer): html_renderer = import_string(html_renderer) # Make able to pass custom arguments to renderer function html_renderer = self._smart_html_renderer(html_renderer) # Assign the relative path (to root) for use in the page object folder = rel_path # Initialize and return Page instance return Page(path, meta, content, html_renderer, folder) def _smart_html_renderer(self, html_renderer): """ Wrappper to enable rendering functions with differing signatures. We stay backwards compatible by using reflection, i.e. we inspect the given rendering function's signature in order to find out how many arguments the function takes. .. versionchanged:: 0.6 Support for HTML renderer functions with signature ``f(body, flatpages, page)``, where ``page`` is an instance of :class:`Page`. .. versionchanged:: 0.5 Support for HTML renderer functions with signature ``f(body, flatpages)``, where ``flatpages`` is an instance of :class:`FlatPages`. """ def wrapper(page): """Wrap HTML renderer function. Pass arguments to the renderer based on the number of arguments. * 1 argument -> page body * 2 arguments -> page body, flatpages instance * 3 arguments -> page body, flatpages instance, page instance """ body = page.body try: args_length = len(getfullargspec(html_renderer).args) except TypeError: return html_renderer(body) if args_length == 1: return html_renderer(body) elif args_length == 2: return html_renderer(body, self) elif args_length == 3: return html_renderer(body, self, page) raise ValueError( "HTML renderer function {0!r} not supported by " "Flask-FlatPages, wrong number of arguments: {1}.".format( html_renderer, args_length ) ) return wrapper ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1732516253.0 flask_flatpages-0.8.3/flask_flatpages/imports.py0000644000175100001770000000024114721014635021452 0ustar00runnerdocker"""Conditional imports.""" try: from pygments.formatters import HtmlFormatter as PygmentsHtmlFormatter except ImportError: PygmentsHtmlFormatter = None ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1732516253.0 flask_flatpages-0.8.3/flask_flatpages/page.py0000644000175100001770000000466314721014635020705 0ustar00runnerdocker"""Define flatpage instance.""" from io import StringIO import yaml from werkzeug.utils import cached_property class Page(object): """Simple class to store all necessary information about a flatpage. Main purpose is to render the page's content with a ``html_renderer`` function. """ def __init__(self, path, meta, body, html_renderer, folder): """Initialize Page instance. :param path: Page path. :param meta: Page meta data in YAML format. :param body: Page body. :param html_renderer: HTML renderer function. :param folder: The folder the page is contained in. """ #: Path this page was obtained from, as in ``pages.get(path)`` self.path = path #: Content of the page self._meta = meta self.body = body #: Renderer function self.html_renderer = html_renderer #: The name of the folder the page is contained in. self.folder = folder def __getitem__(self, name): """Shortcut for accessing metadata. ``page['title']`` or, in a template, ``{{ page.title }}`` are equivalent to ``page.meta['title']``. """ return self.meta[name] def __html__(self): """ Return HTML for use in Jinja templates. In a template, ``{{ page }}`` is equivalent to ``{{ page.html|safe }}``. """ return self.html def __repr__(self): """Machine representation of :class:`Page` instance.""" return "" % self.path @cached_property def html(self): """Content of the page, rendered as HTML by the configured renderer.""" return self.html_renderer(self) @cached_property def meta(self): """Store a dict of metadata parsed from the YAML header of the file.""" # meta = yaml.safe_load(self._meta) meta = {} for doc in yaml.safe_load_all(StringIO(self._meta)): if doc is not None: meta.update(doc) # YAML documents can be any type but we want a dict # eg. yaml.safe_load('') -> None # yaml.safe_load('- 1\n- a') -> [1, 'a'] if not meta: return {} if not isinstance(meta, dict): raise ValueError( "Expected a dict in metadata for '{0}', got {1}".format( self.path, type(meta).__name__ ) ) return meta ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1732516253.0 flask_flatpages-0.8.3/flask_flatpages/utils.py0000644000175100001770000000632614721014635021127 0ustar00runnerdocker"""Utility functions to render Markdown text to HTML.""" import markdown import six from markdown.extensions import codehilite from .imports import PygmentsHtmlFormatter if six.PY3: from io import StringIO else: from StringIO import StringIO class NamedStringIO(StringIO, object): """Subclass adding a Name to :class:`StringIO` objects.""" def __init__(self, content, name): """ Initialise the NamedStringIO. :param content: The string to be treated as a stream :param name: The name to attach to the stream. Will be consumed in e.g. ReaderErrors raised by pyyaml. """ if not six.PY3: super(NamedStringIO, self).__init__(content) else: super().__init__(content) self.name = name def force_unicode(value, encoding="utf-8", errors="strict"): """Convert bytes or any other Python instance to string.""" if isinstance(value, six.text_type): return value return value.decode(encoding, errors) def pygmented_markdown(text, flatpages=None): """Render Markdown text to HTML. Uses the `CodeHilite`_ extension only if `Pygments`_ is available. If `Pygments`_ is not available, "codehilite" is removed from list of extensions. If you need other extensions, set them up using the ``FLATPAGES_MARKDOWN_EXTENSIONS`` setting, which should be a sequence of strings or Markdown Extension objects. Extensions specified with entrypoint strings should be configured using ``FLATPAGES_EXTENSION_CONFIGS``. .. _CodeHilite: http://www.freewisdom.org/projects/python-markdown/CodeHilite .. _Pygments: http://pygments.org/ """ if flatpages: extensions = flatpages.config("markdown_extensions") extension_configs = flatpages.config("extension_configs") else: extensions = [] extension_configs = {} if PygmentsHtmlFormatter is None: original_extensions = extensions original_config = extension_configs extensions = [] extension_configs = {} for extension in original_extensions: if ( isinstance(extension, six.string_types) and "codehilite" in extension ): continue elif isinstance(extension, codehilite.CodeHiliteExtension): continue extensions.append(extension) if isinstance(extension, six.string_types): if extension in original_config: extension_configs[extension] = original_config[extension] elif not extensions: extensions = ["codehilite"] return markdown.markdown( text, extensions=extensions, extension_configs=extension_configs ) def pygments_style_defs(style="default"): """:return: the CSS definitions for the `CodeHilite`_ Markdown plugin. :param style: The Pygments `style`_ to use. Only available if `Pygments`_ is. .. _CodeHilite: http://www.freewisdom.org/projects/python-markdown/CodeHilite .. _Pygments: http://pygments.org/ .. _style: http://pygments.org/docs/styles/ """ formatter = PygmentsHtmlFormatter(style=style) return formatter.get_style_defs(".codehilite") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1732516253.0 flask_flatpages-0.8.3/pyproject.toml0000644000175100001770000001000614721014635017171 0ustar00runnerdocker[build-system] requires = [ "setuptools < 45 ; python_version < '3'", "setuptools ; python_version > '2.7'", "wheel", ] build-backend = "setuptools.build_meta" [project] name = "Flask-FlatPages" dynamic = ["version"] authors = [ { name = "Simon Sapin" }, { name = "Igor Davydenko" }, { name = "Padraic Calpin" }, ] maintainers = [{ name = "Padraic Calpin", email = "itsme@padraic.xyz" }] description = "Provides flat static pages to a Flask application" readme = "README.rst" classifiers = [ "Environment :: Web Environment", "Framework :: Flask", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Software Development :: Libraries :: Python Modules", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", ] requires-python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" dependencies = [ "Flask > 1.0", "Jinja2 >= 2.10.2", "Markdown >= 2.5", "PyYAML > 5.3.1", "pytz; python_version=='2.7'", "six", ] [project.urls] Repository = "https://github.com/Flask-FlatPages/Flask-FlatPages" Documentation = "https://flask-flatpages.readthedocs.io/en/latest/" [project.optional-dependencies] codehilite = ["Pygments>=2.5.2"] [dependency-groups] test = ["pytest", "pytest-cov"] [tool.setuptools] packages = ["flask_flatpages"] [tool.setuptools.dynamic] version = { attr = "flask_flatpages.__version__" } [tool.tox] envlist = [ "docs", "lint", "fmt", "py27", "py27-pygments", "py37", "py37-pygments", "py38", "py38-pygments", "py39", "py39-pygments", "py310", "py310-pygments", "py311", "py311-pygments", "py312", "py312-pygments", "py313", "py313-pygments", ] [tool.tox.env_run_base] dependency_groups = ["test"] commands = [ [ "python", "-m", "pytest", "-v", "--cov=flask_flatpages", "--cov-branch", "--cov-report", "term", "{posargs}", ], ] [tool.tox.env.py27-pygments] extras = ["codehilite"] [tool.tox.env.py37-pygments] extras = ["codehilite"] [tool.tox.env.py38-pygments] extras = ["codehilite"] [tool.tox.env.py39-pygments] extras = ["codehilite"] [tool.tox.env.py310-pygments] extras = ["codehilite"] [tool.tox.env.py311-pygments] extras = ["codehilite"] [tool.tox.env.py312-pygments] extras = ["codehilite"] [tool.tox.env.py313-pygments] extras = ["codehilite"] [tool.tox.env.docs] commands = [ [ "sphinx-build", "-d", "{toxworkdir}/docs/_build/_doctree", "docs", "docs/_build/html", "--color", "-W", "-b", "html", ], ] deps = ["-r docs/requirements.txt"] [tool.tox.env.lint] deps = [ "flake8>=2.3.0", "flake8-docstrings>=0.2.1.post1", "flake8-import-order>=0.5.3", "flake8-pyproject", "mccabe>=0.3.0", "pep257>=0.5.0", "pep8>=1.6.2", "pep8-naming>=0.2.2", "pyflakes>=0.8.1", ] commands = [["flake8", "--statistics", "flask_flatpages/"]] [tool.tox.env.fmt] skip_install = true deps = ["black"] commands = [["black", "{posargs:flask_flatpages}"]] [tool.tox.env.reno] skip_install = true deps = ["reno"] commands = [["reno", "{posargs}"]] [tool.flake8] application-import-names = "flask_flatpages" ignore = ["W503"] import-order-style = "smarkets" max-complexity = 20 [tool.pytest] python_files = "*test*.py" [tool.black] line-length = 79 target-version = ["py37", "py38", "py39", "py310", "py311", "py312", "py313"] skip-line-normalization = false ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1732516280.0657144 flask_flatpages-0.8.3/setup.cfg0000644000175100001770000000004614721014670016100 0ustar00runnerdocker[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1732516253.0 flask_flatpages-0.8.3/setup.py0000644000175100001770000000010514721014635015766 0ustar00runnerdockerimport setuptools if __name__ == '__main__': setuptools.setup() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1732516280.0657144 flask_flatpages-0.8.3/tests/0000755000175100001770000000000014721014670015421 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1732516253.0 flask_flatpages-0.8.3/tests/test_flask_flatpages.py0000644000175100001770000005351714721014635022174 0ustar00runnerdocker# coding: utf8 """ Tests for Flask-FlatPages ~~~~~~~~~~~~~~~~~~~~~~~~~ :copyright: (c) 2010 by Simon Sapin. :license: BSD, see LICENSE for more details. """ import datetime import operator import os import shutil import sys import unicodedata import unittest from contextlib import contextmanager import six import yaml import pytest from flask import Flask from flask_flatpages import FlatPages, pygments_style_defs from flask_flatpages.imports import PygmentsHtmlFormatter from werkzeug.exceptions import NotFound from .test_temp_directory import temp_directory if six.PY3: utc = datetime.timezone.utc from unittest.mock import patch else: import pytz utc = pytz.utc from mock import patch @contextmanager def temp_pages(app=None, name=None): """ This context manager gives a FlatPages object configured in a temporary directory with a copy of the test pages. Using a temporary copy allows us to safely write and remove stuff without worrying about undoing our changes. """ with temp_directory() as temp: source = os.path.join(os.path.dirname(__file__), 'pages') # Remove the destination dir as copytree wants it not to exist. # Doing so kind of defeats the purpose of tempfile as it introduces # a race condition, but should be good enough for our purpose. os.rmdir(temp) shutil.copytree(source, temp) app = app or Flask(__name__) if name is None: config_root = 'FLATPAGES_ROOT' else: config_root = 'FLATPAGES_%s_ROOT' % str(name).upper() app.config[config_root] = temp yield FlatPages(app, name) class TestFlatPages(unittest.TestCase): def assert_auto_reset(self, pages): bar = pages.get('foo/bar') self.assertEqual(bar.body, '') filename = os.path.join(pages.root, 'foo', 'bar.html') with open(filename, 'w') as fd: fd.write('\nrewritten') # simulate a request (before_request functions are called) # pages.reload() is not call explicitly with pages.app.test_request_context(): pages.app.preprocess_request() # updated bar2 = pages.get('foo/bar') self.assertEqual(bar2.body, 'rewritten') self.assertTrue(bar2 is not bar) def assert_no_auto_reset(self, pages): bar = pages.get('foo/bar') self.assertEqual(bar.body, '') filename = os.path.join(pages.root, 'foo', 'bar.html') with open(filename, 'w') as fd: fd.write('\nrewritten') # simulate a request (before_request functions are called) with pages.app.test_request_context(): pages.app.preprocess_request() # not updated bar2 = pages.get('foo/bar') self.assertEqual(bar2.body, '') self.assertTrue(bar2 is bar) def assert_unicode(self, pages): hello = pages.get('hello') self.assertEqual(hello.meta, {'title': u'世界', 'template': 'article.html'}) self.assertEqual(hello['title'], u'世界') self.assertEqual(hello.body, u'Hello, *世界*!\n') # Markdown self.assertEqual(hello.html, u'

Hello, 世界!

') def test_caching(self): with temp_pages() as pages: foo = pages.get('foo') bar = pages.get('foo/bar') filename = os.path.join(pages.root, 'foo', 'bar.html') with open(filename, 'w') as fd: fd.write('\nrewritten') pages.reload() foo2 = pages.get('foo') bar2 = pages.get('foo/bar') # Page objects are cached and unmodified files (according to the # modification date) are not parsed again. self.assertTrue(foo2 is foo) self.assertTrue(bar2 is not bar) self.assertTrue(bar2.body != bar.body) def test_configured_auto_reset(self): app = Flask(__name__) app.config['FLATPAGES_AUTO_RELOAD'] = True with temp_pages(app) as pages: self.assert_auto_reset(pages) def test_configured_no_auto_reset(self): app = Flask(__name__) app.debug = True app.config['FLATPAGES_AUTO_RELOAD'] = False with temp_pages(app) as pages: self.assert_no_auto_reset(pages) def test_consistency(self): pages = FlatPages(Flask(__name__)) for page in pages: assert pages.get(page.path) is page def test_debug_auto_reset(self): app = Flask(__name__) app.debug = True with temp_pages(app) as pages: self.assert_auto_reset(pages) def test_default_no_auto_reset(self): with temp_pages() as pages: self.assert_no_auto_reset(pages) def test_extension_comma(self): self.test_extension_sequence('.html,.txt') def test_extension_sequence(self, extension=None): app = Flask(__name__) app.config['FLATPAGES_EXTENSION'] = extension or ['.html', '.txt'] pages = FlatPages(app) self.assertEqual( set(page.path for page in pages), set(['codehilite', 'extra', 'foo', 'foo/42/not_a_page', 'foo/bar', 'foo/lorem/ipsum', 'headerid', 'hello', 'meta_styles/closing_block_only', 'meta_styles/yaml_style', 'meta_styles/jekyll_style', 'meta_styles/multi_line', 'meta_styles/no_meta', 'not_a_page', 'toc']) ) def test_extension_set(self): self.test_extension_sequence(set(['.html', '.txt'])) def test_extension_tuple(self): self.test_extension_sequence(('.html', '.txt')) def test_catch_conflicting_paths(self): app = Flask(__name__) app.config['FLATPAGES_EXTENSION'] = ['.html', '.txt'] with temp_pages(app) as pages: original_file = os.path.join(pages.root, 'hello.html') target_file = os.path.join(pages.root, 'hello.txt') shutil.copyfile(original_file, target_file) pages.reload() self.assertRaises(ValueError, pages.get, 'hello') def test_case_insensitive(self): app = Flask(__name__) app.config['FLATPAGES_EXTENSION'] = ['.html', '.txt'] app.config['FLATPAGES_CASE_INSENSITIVE'] = True with temp_pages(app) as pages: original_file = os.path.join(pages.root, 'hello.html') upper_file = os.path.join(pages.root, 'Hello.html') txt_file = os.path.join(pages.root, 'hello.txt') shutil.move(original_file, upper_file) pages.reload() self.assertEqual( set(page.path for page in pages), set(['codehilite', 'extra', 'foo', 'foo/42/not_a_page', 'foo/bar', 'foo/lorem/ipsum', 'headerid', 'hello', 'meta_styles/closing_block_only', 'meta_styles/yaml_style', 'meta_styles/jekyll_style', 'meta_styles/multi_line', 'meta_styles/no_meta', 'not_a_page', 'toc']) ) shutil.copyfile(upper_file, txt_file) pages.reload() self.assertRaises(ValueError, pages.get, 'hello') def test_get(self): pages = FlatPages(Flask(__name__)) self.assertEqual(pages.get('foo/bar').path, 'foo/bar') self.assertEqual(pages.get('nonexistent'), None) self.assertEqual(pages.get('nonexistent', 42), 42) def test_get_or_404(self): pages = FlatPages(Flask(__name__)) self.assertEqual(pages.get_or_404('foo/bar').path, 'foo/bar') self.assertRaises(NotFound, pages.get_or_404, 'nonexistent') def test_iter(self): pages = FlatPages(Flask(__name__)) self.assertEqual( set(page.path for page in pages), set(['codehilite', 'extra', 'foo', 'foo/bar', 'foo/lorem/ipsum', 'headerid', 'hello', 'meta_styles/closing_block_only', 'meta_styles/yaml_style', 'meta_styles/jekyll_style', 'meta_styles/multi_line', 'meta_styles/no_meta', 'toc']) ) def test_lazy_loading(self): with temp_pages() as pages: bar = pages.get('foo/bar') # bar.html is normally empty self.assertEqual(bar.meta, {}) self.assertEqual(bar.body, '') with temp_pages() as pages: filename = os.path.join(pages.root, 'foo', 'bar.html') # write as pages is already constructed with open(filename, 'a') as fd: fd.write('a: b\n\nc') bar = pages.get('foo/bar') # bar was just loaded from the disk self.assertEqual(bar.meta, {'a': 'b'}) self.assertEqual(bar.body, 'c') def test_markdown(self): pages = FlatPages(Flask(__name__)) foo = pages.get('foo') self.assertEqual(foo.body, 'Foo *bar*\n') self.assertEqual(foo.html, '

Foo bar

') def test_instance_relative(self): with temp_directory() as temp: source = os.path.join(os.path.dirname(__file__), 'pages') dest = os.path.join(temp, 'instance', 'pages') shutil.copytree(source, dest) app = Flask(__name__, instance_path=os.path.join(temp, 'instance')) app.config['FLATPAGES_INSTANCE_RELATIVE'] = True pages = FlatPages(app) bar = pages.get('foo/bar') self.assertTrue(bar is not None) def test_multiple_instance(self): """ This does a very basic test to see if two instances of FlatPages, one default instance and one with a name, do pick up the different config settings. """ app = Flask(__name__) app.debug = True app.config['FLATPAGES_DUMMY'] = True app.config['FLATPAGES_FUBAR_DUMMY'] = False with temp_pages(app) as pages: self.assertEqual(pages.config('DUMMY'), app.config['FLATPAGES_DUMMY']) with temp_pages(app, 'fubar') as pages: self.assertEqual(pages.config('DUMMY'), app.config['FLATPAGES_FUBAR_DUMMY']) @patch('flask_flatpages.flatpages.FlatPages._legacy_parser', return_value=(dict(), 'Foo') ) @patch('flask_flatpages.flatpages.FlatPages._libyaml_parser', side_effect=ValueError('Cannot happen') ) def test_legacy_parser(self, libyaml_mock, legacy_mock): app = Flask(__name__) app.config['FLATPAGES_LEGACY_META_PARSER'] = True pages = FlatPages(app) self.assertEqual( set(page.path for page in pages), set(['codehilite', 'extra', 'foo', 'foo/bar', 'foo/lorem/ipsum', 'headerid', 'hello', 'meta_styles/closing_block_only', 'meta_styles/yaml_style', 'meta_styles/jekyll_style', 'meta_styles/multi_line', 'meta_styles/no_meta', 'toc']) ) libyaml_mock.assert_not_called() assert legacy_mock.call_count == len(list(pages)) def test_other_encoding(self): app = Flask(__name__) app.config['FLATPAGES_ENCODING'] = 'shift_jis' app.config['FLATPAGES_ROOT'] = 'pages_shift_jis' pages = FlatPages(app) self.assert_unicode(pages) def test_other_extension(self): app = Flask(__name__) app.config['FLATPAGES_EXTENSION'] = '.txt' pages = FlatPages(app) self.assertEqual( set(page.path for page in pages), set(['not_a_page', 'foo/42/not_a_page']) ) def test_other_html_renderer(self): def body_renderer(body): return body.upper() def page_renderer(body, pages, page): return page.body.upper() def pages_renderer(body, pages): return pages.get('hello').body.upper() renderers = filter(None, ( operator.methodcaller('upper'), 'string.upper' if not six.PY3 else None, body_renderer, page_renderer, pages_renderer )) for renderer in (renderers): pages = FlatPages(Flask(__name__)) pages.app.config['FLATPAGES_HTML_RENDERER'] = renderer hello = pages.get('hello') self.assertEqual(hello.body, u'Hello, *世界*!\n') # Upper-case, markdown not interpreted self.assertEqual(hello.html, u'HELLO, *世界*!\n') @pytest.mark.skipif(PygmentsHtmlFormatter is None, reason='Pygments not installed') def test_pygments_style_defs(self): styles = pygments_style_defs() self.assertTrue('.codehilite' in styles) def test_reloading(self): with temp_pages() as pages: bar = pages.get('foo/bar') # bar.html is normally empty self.assertEqual(bar.meta, {}) self.assertEqual(bar.body, '') filename = os.path.join(pages.root, 'foo', 'bar.html') # rewrite already loaded page with open(filename, 'w') as fd: # The newline is a separator between the (empty) metadata # and the source 'first' fd.write('\nfirst rewrite') bar2 = pages.get('foo/bar') # the disk is not hit again until requested self.assertEqual(bar2.meta, {}) self.assertEqual(bar2.body, '') self.assertTrue(bar2 is bar) # request reloading pages.reload() # write again with open(filename, 'w') as fd: fd.write('\nsecond rewrite') # get another page pages.get('hello') # write again with open(filename, 'w') as fd: fd.write('\nthird rewrite') # All pages are read at once when any is used bar3 = pages.get('foo/bar') self.assertEqual(bar3.meta, {}) self.assertEqual(bar3.body, 'second rewrite') # not third # Page objects are not reused when a file is re-read. self.assertTrue(bar3 is not bar2) # Removing does not trigger reloading either os.remove(filename) bar4 = pages.get('foo/bar') self.assertEqual(bar4.meta, {}) self.assertEqual(bar4.body, 'second rewrite') self.assertTrue(bar4 is bar3) pages.reload() bar5 = pages.get('foo/bar') self.assertTrue(bar5 is None) # Reloading twice does not trigger an exception pages.reload() pages.reload() def test_unicode(self): pages = FlatPages(Flask(__name__)) self.assert_unicode(pages) def test_unicode_filenames(self): def safe_unicode(sequence): if sys.platform != 'darwin': return sequence return map(lambda item: unicodedata.normalize('NFC', item), sequence) app = Flask(__name__) with temp_pages(app) as pages: self.assertEqual( set(page.path for page in pages), set(['codehilite', 'extra', 'foo', 'foo/bar', 'foo/lorem/ipsum', 'headerid', 'hello', 'meta_styles/closing_block_only', 'meta_styles/yaml_style', 'meta_styles/jekyll_style', 'meta_styles/multi_line', 'meta_styles/no_meta', 'toc']) ) os.remove(os.path.join(pages.root, 'foo', 'lorem', 'ipsum.html')) open(os.path.join(pages.root, u'Unïcôdé.html'), 'w').close() pages.reload() self.assertEqual( set(safe_unicode(page.path for page in pages)), set(['codehilite', 'extra', 'foo', 'foo/bar', 'headerid', 'hello', 'meta_styles/closing_block_only', 'meta_styles/yaml_style', 'meta_styles/jekyll_style', 'meta_styles/multi_line', 'meta_styles/no_meta', 'toc', u'Unïcôdé'])) def test_yaml_meta(self): pages = FlatPages(Flask(__name__)) foo = pages.get('foo') self.assertEqual(foo.meta, { 'title': 'Foo > bar', 'created': datetime.date(2010, 12, 11), 'updated': datetime.datetime(2015, 2, 9, 10, 59, 0), 'updated_iso': datetime.datetime(2015, 2, 9, 10, 59, 0, tzinfo=utc), 'versions': [3.14, 42], }) self.assertEqual(foo['title'], 'Foo > bar') self.assertEqual(foo['created'], datetime.date(2010, 12, 11)) self.assertEqual(foo['updated'], datetime.datetime(2015, 2, 9, 10, 59, 0)) self.assertEqual(foo['updated_iso'], datetime.datetime(2015, 2, 9, 10, 59, 0, tzinfo=utc)) self.assertEqual(foo['versions'], [3.14, 42]) self.assertRaises(KeyError, lambda: foo['nonexistent']) def test_no_meta(self): app = Flask(__name__) with temp_pages(app) as pages: no_meta = pages.get('meta_styles/no_meta') self.assertEqual(no_meta.meta, {}) filename = os.path.join(pages.root, 'meta_styles', 'no_meta.html') with open(filename, 'w') as f_: f_.write("\n Hello, there's no metadata here.") pages.reload() no_meta = pages.get('meta_styles/no_meta') self.assertEqual(no_meta.meta, {}) with open(filename, 'w') as f_: f_.write("---\n---\nHello, there's no metadata here.") pages.reload() no_meta = pages.get('meta_styles/no_meta') self.assertEqual(no_meta.meta, {}) with open(filename, 'w') as f_: f_.write("---\n...\nHello, there's no metadata here.") pages.reload() no_meta = pages.get('meta_styles/no_meta') self.assertEqual(no_meta.meta, {}) with open(filename, 'w') as f_: f_.write("#Hello, there's no metadata here.") pages.reload() no_meta = pages.get('meta_styles/no_meta') self.assertEqual(no_meta.meta, {}) def test_meta_closing_only(self): app = Flask(__name__) with temp_pages(app) as pages: page = pages.get('meta_styles/closing_block_only') self.assertEqual(page.meta, {'hello': 'world'}) filename = os.path.join(pages.root, 'meta_styles', 'closing_block_only.html') with open(filename, 'w') as f: f.write('hello: world\n...\nFoo') pages.reload() page = pages.get('meta_styles/closing_block_only') self.assertEqual(page.meta, {'hello': 'world'}) def test_jekyll_style_meta(self): app = Flask(__name__) with temp_pages(app) as pages: jekyll_style = pages.get('meta_styles/jekyll_style') self.assertEqual(jekyll_style.meta, {'hello': 'world'}) self.assertEqual(jekyll_style.body, 'Foo') filename = os.path.join(pages.root, 'meta_styles', 'jekyll_style.html') with open(filename, 'r') as f_: lines = f_.readlines() with open(filename, 'w') as f_: f_.write('\n'.join(lines[1:])) pages.reload() jekyll_style = pages.get('meta_styles/jekyll_style') self.assertEqual(jekyll_style.meta, {'hello': 'world'}) self.assertEqual(jekyll_style.body, 'Foo') def test_yaml_style_meta(self): app = Flask(__name__) with temp_pages(app) as pages: yaml_style = pages.get('meta_styles/yaml_style') self.assertEqual(yaml_style.meta, {'hello': 'world'}) self.assertEqual(yaml_style.body, 'Foo') filename = os.path.join(pages.root, 'meta_styles', 'yaml_style.html') with open(filename, 'r') as f_: lines = f_.readlines() with open(filename, 'w') as f_: f_.write('\n'.join(lines[1:])) pages.reload() yaml_style = pages.get('meta_styles/yaml_style') self.assertEqual(yaml_style.meta, {'hello': 'world'}) self.assertEqual(yaml_style.body, 'Foo') def test_multi_line(self): pages = FlatPages(Flask(__name__)) multi_line = pages.get('meta_styles/multi_line') self.assertEqual(multi_line.body, 'Foo') self.assertIn('multi_line_string', multi_line.meta) self.assertIn('\n', multi_line.meta['multi_line_string']) def test_parser_error(self): app = Flask(__name__) with temp_pages(app) as pages: with open(os.path.join(pages.root, 'bad_file_test.html'), 'w') as f: if six.PY3: f.write("Hello World \u000B") else: f.write("\x0b".decode('utf-8')) with pytest.raises(yaml.reader.ReaderError, match=r".*bad_file_test.*") as excinfo: pages.get('bad_file_test') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1732516253.0 flask_flatpages-0.8.3/tests/test_markdown_extensions.py0000644000175100001770000001640214721014635023137 0ustar00runnerdocker""" ============================== tests.test_markdown_extensions ============================== Test proper work of various Markdown extensions. """ import sys import unittest import markdown import pytest from flask import Flask from flask_flatpages import FlatPages from flask_flatpages.imports import PygmentsHtmlFormatter from markdown.extensions.toc import TocExtension from six import PY3 class TestMarkdownExtensions(unittest.TestCase): def check_toc_page(self, pages): toc = pages.get('toc') self.assertEqual( toc.html, '\n' '

Page Header

\n' '

Paragraph Header

\n' '

Text

' ) @pytest.mark.skipif(PygmentsHtmlFormatter is None, reason='Pygments not installed') def check_default_codehilite_page(self, pages): codehilite = pages.get('codehilite') body = codehilite.body fixture = markdown.markdown( body, extensions=['codehilite'], ) self.assertEqual( codehilite.html, fixture ) @pytest.mark.skipif(PygmentsHtmlFormatter is None, reason='Pygments not installed') def check_codehilite_with_linenums(self, pages): codehilite = pages.get('codehilite') body = codehilite.body fixture = markdown.markdown( body, extensions=['codehilite'], extension_configs={ 'codehilite': { 'linenums': True } } ) self.assertEqual( codehilite.html, fixture ) def check_extra(self, pages): extra_sep = '\n' if sys.version_info[:2] > (2, 6) else '\n\n' extra = pages.get('extra') self.assertEqual( extra.html, '

This is true markdown text.

\n' '
{0}' '

This is true markdown text.

\n' '
'.format(extra_sep) ) def test_basic(self): pages = FlatPages(Flask(__name__)) hello = pages.get('headerid') self.assertEqual( hello.html, u'

Page Header

\n

Paragraph Header

\n

Text

' ) pages.app.config['FLATPAGES_MARKDOWN_EXTENSIONS'] = [] pages.reload() pages._file_cache = {} hello = pages.get('headerid') self.assertEqual( hello.html, u'

Page Header

\n

Paragraph Header

\n

Text

' ) if PygmentsHtmlFormatter is not None: self.check_default_codehilite_page(pages) @pytest.mark.skipif(PygmentsHtmlFormatter is None, reason='Pygments not installed') def test_codehilite_linenums_disabled(self): #Test explicity disabled app = Flask(__name__) app.config['FLATPAGES_MARKDOWN_EXTENSIONS'] = ['codehilite'] pages = FlatPages(app) self.check_default_codehilite_page(pages) #Test explicity disabled pages.app.config['FLATPAGES_EXTENSION_CONFIGS'] = { 'codehilite': { 'linenums': 'False' } } pages.reload() pages._file_cache = {} self.check_default_codehilite_page(pages) @pytest.mark.skipif(PygmentsHtmlFormatter is None, reason='Pygments not installed') def test_codehilite_linenums_enabled(self): app = Flask(__name__) app.config['FLATPAGES_MARKDOWN_EXTENSIONS'] = ['codehilite'] app.config['FLATPAGES_EXTENSION_CONFIGS'] = { 'codehilite': { 'linenums': 'True' } } pages = FlatPages(app) self.check_codehilite_with_linenums(pages) def test_extra(self): app = Flask(__name__) app.config['FLATPAGES_MARKDOWN_EXTENSIONS'] = ['extra'] pages = FlatPages(app) self.check_extra(pages) def test_toc(self): app = Flask(__name__) app.config['FLATPAGES_MARKDOWN_EXTENSIONS'] = ['toc'] pages = FlatPages(app) self.check_toc_page(pages) def test_headerid_with_toc(self): app = Flask(__name__) pages = FlatPages(app) pages.app.config['FLATPAGES_MARKDOWN_EXTENSIONS'] = [ 'codehilite', 'toc' #headerid is deprecated in Markdown 3.0 ] pages.reload() pages._file_cache = {} hello = pages.get('headerid') self.assertEqual( hello.html, '

Page Header

\n' '

Paragraph Header

\n' '

Text

' ) if PygmentsHtmlFormatter is not None: self.check_default_codehilite_page(pages) #test codehilite also loaded @pytest.mark.skipif(PygmentsHtmlFormatter is None, reason='Pygments not installed') def test_extension_importpath(self): app = Flask(__name__) app.config['FLATPAGES_MARKDOWN_EXTENSIONS'] = [ 'markdown.extensions.codehilite:CodeHiliteExtension' ] pages = FlatPages(app) self.check_default_codehilite_page(pages) app.config['FLATPAGES_EXTENSION_CONFIGS'] = { #Markdown 3 style config 'markdown.extensions.codehilite:CodeHiliteExtension': { 'linenums': True } } pages.reload() pages._file_cache = {} self.check_codehilite_with_linenums(pages) @pytest.mark.skipif(PygmentsHtmlFormatter is None, reason='Pygments not installed') def test_extension_object(self): app = Flask(__name__) from markdown.extensions.codehilite import CodeHiliteExtension codehilite = CodeHiliteExtension() app.config['FLATPAGES_MARKDOWN_EXTENSIONS'] = [codehilite] pages = FlatPages(app) self.check_default_codehilite_page(pages) codehilite = CodeHiliteExtension(linenums='True') #Check config applies app.config['FLATPAGES_MARKDOWN_EXTENSIONS'] = [codehilite] pages.reload() pages._file_cache = {} self.check_codehilite_with_linenums(pages) @pytest.mark.skipif(PygmentsHtmlFormatter is None, reason='Pygments not installed') def test_mixed_extension_types(self): app = Flask(__name__) from markdown.extensions.toc import TocExtension toc = TocExtension() app.config['FLATPAGES_MARKDOWN_EXTENSIONS'] = [ toc, 'codehilite', 'markdown.extensions.extra:ExtraExtension' ] pages = FlatPages(app) self.check_toc_page(pages) self.check_default_codehilite_page(pages) self.check_extra(pages) app.config['FLATPAGES_EXTENSION_CONFIGS'] = { 'codehilite': { 'linenums': 'True' } } pages.reload() pages._file_cache = {} self.check_toc_page(pages) self.check_extra(pages) self.check_codehilite_with_linenums(pages) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1732516253.0 flask_flatpages-0.8.3/tests/test_temp_directory.py0000644000175100001770000000260314721014635022065 0ustar00runnerdocker""" ========================= tests.test_temp_directory ========================= Provide temp directory context manager and tests for it. """ import os import shutil import tempfile import unittest from contextlib import contextmanager @contextmanager def temp_directory(): """ This context manager gives the path to a new temporary directory that is deleted (with all it's content) at the end of the with block. """ directory = tempfile.mkdtemp() try: yield directory finally: shutil.rmtree(directory) class TestTempDirectory(unittest.TestCase): def test_exception(self): try: with temp_directory() as temp: assert os.path.isdir(temp) 1/0 except ZeroDivisionError: pass else: assert False, 'Exception did not propagate' assert not os.path.exists(temp) def test_removed(self): with temp_directory() as temp: assert os.path.isdir(temp) # should be removed now assert not os.path.exists(temp) def test_writing(self): with temp_directory() as temp: filename = os.path.join(temp, 'foo') with open(filename, 'w') as fd: fd.write('foo') assert os.path.isfile(filename) assert not os.path.exists(temp) assert not os.path.exists(filename)