diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..86343c5 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,56 @@ +name: Publish Python Package + +on: + release: + types: [created] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.5, 3.6, 3.7, 3.8, 3.9] + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - uses: actions/cache@v2 + name: Configure pip caching + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: Install dependencies + run: | + pip install -e '.[test]' + - name: Run tests + run: python test.py + deploy: + runs-on: ubuntu-latest + needs: [test] + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: "3.8" + - uses: actions/cache@v2 + name: Configure pip caching + with: + path: ~/.cache/pip + key: ${{ runner.os }}-publish-pip-${{ hashFiles('**/setup.py') }} + restore-keys: | + ${{ runner.os }}-publish-pip- + - name: Install dependencies + run: | + pip install setuptools wheel twine + - name: Publish + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} + run: | + python setup.py sdist bdist_wheel + twine upload --verbose dist/* diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..e439c1b --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,29 @@ +name: Test + +on: [push] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.5, 3.6, 3.7, 3.8, 3.9] + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - uses: actions/cache@v2 + name: Configure pip caching + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: Install dependencies + run: | + pip install -e '.[test]' + - name: Run tests + run: | + python test.py diff --git a/.gitignore b/.gitignore index f380c2d..3a71124 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,4 @@ docs/_build/ target/ .DS_Store +.vscode/ \ No newline at end of file diff --git a/.pipignore b/.pipignore deleted file mode 100644 index 7e6ec6b..0000000 --- a/.pipignore +++ /dev/null @@ -1,5 +0,0 @@ -gnureadline -ipython -pip-tools -readline -python-frontmatter \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 79c9eed..bcaec6c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,15 @@ language: python dist: xenial python: - - "2.7" - - "3.4" - "3.5" - "3.6" - "3.7" + - "3.8" + - "3.9" # command to install dependencies install: # use pyaml for tests above py26 - pip install -r requirements.txt - "python setup.py install" # command to run tests -script: python setup.py test +script: python test.py diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index de288e1..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "python.formatting.provider": "black" -} \ No newline at end of file diff --git a/README.md b/README.md index cce1709..6dd0dd2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -Python Frontmatter -================== +# Python Frontmatter [Jekyll](http://jekyllrb.com/)-style YAML front matter offers a useful way to add arbitrary, structured metadata to text documents, regardless of type. @@ -7,14 +6,11 @@ This is a small package to load and parse files (or just text) with YAML front m [![Build Status](https://travis-ci.org/eyeseast/python-frontmatter.svg?branch=master)](https://travis-ci.org/eyeseast/python-frontmatter) -Install: --------- +## Install: pip install python-frontmatter - -Usage: ------- +## Usage: ```python import frontmatter @@ -31,6 +27,7 @@ Or a file (or file-like object): ```python >>> with open('tests/hello-world.markdown') as f: ... post = frontmatter.load(f) + ``` Or load from text: @@ -38,6 +35,7 @@ Or load from text: ```python >>> with open('tests/hello-world.markdown') as f: ... post = frontmatter.loads(f.read()) + ``` Access content: @@ -49,6 +47,7 @@ Well, hello there, world. # this works, too >>> print(post) Well, hello there, world. + ``` Use metadata (metadata gets proxied as post keys): @@ -56,6 +55,7 @@ Use metadata (metadata gets proxied as post keys): ```python >>> print(post['title']) Hello, world! + ``` Metadata is a dictionary, with some handy proxies: @@ -68,6 +68,7 @@ Metadata is a dictionary, with some handy proxies: >>> post['excerpt'] = 'tl;dr' >>> pprint(post.metadata) {'excerpt': 'tl;dr', 'layout': 'post', 'title': 'Hello, world!'} + ``` If you don't need the whole post object, just parse: @@ -77,6 +78,7 @@ If you don't need the whole post object, just parse: ... metadata, content = frontmatter.parse(f.read()) >>> print(metadata['title']) Hello, world! + ``` Write back to plain text, too: @@ -90,6 +92,8 @@ title: Hello, world! --- Well, hello there, world. +``` + Or write to a file (or file-like object): ```python @@ -103,5 +107,5 @@ layout: post title: Hello, world! --- Well, hello there, world. -``` +``` diff --git a/frontmatter/__init__.py b/frontmatter/__init__.py index 9108fdb..032ac76 100644 --- a/frontmatter/__init__.py +++ b/frontmatter/__init__.py @@ -2,12 +2,10 @@ """ Python Frontmatter: Parse and manage posts with YAML frontmatter """ -from __future__ import unicode_literals import codecs import re -import six from .util import u from .default_handlers import YAMLHandler, JSONHandler, TOMLHandler @@ -38,7 +36,7 @@ def detect_format(text, handlers): ``text`` should be unicode text about to be parsed. - ``handlers`` is a dictionary where keys are opening delimiters + ``handlers`` is a dictionary where keys are opening delimiters and values are handler instances. """ for pattern, handler in handlers.items(): @@ -93,9 +91,9 @@ def parse(text, encoding="utf-8", handler=None, **defaults): def check(fd, encoding="utf-8"): """ - Check if a file-like object or filename has a frontmatter, + Check if a file-like object or filename has a frontmatter, return True if exists, False otherwise. - + If it contains a frontmatter but it is empty, return True as well. :: @@ -118,7 +116,7 @@ def checks(text, encoding="utf-8"): """ Check if a text (binary or unicode) has a frontmatter, return True if exists, False otherwise. - + If it contains a frontmatter but it is empty, return True as well. :: @@ -134,7 +132,7 @@ def checks(text, encoding="utf-8"): def load(fd, encoding="utf-8", handler=None, **defaults): """ - Load and parse a file-like object or filename, + Load and parse a file-like object or filename, return a :py:class:`post `. :: @@ -202,12 +200,12 @@ def dump(post, fd, encoding="utf-8", handler=None, **kwargs): def dumps(post, handler=None, **kwargs): """ - Serialize a :py:class:`post ` to a string and return text. + Serialize a :py:class:`post ` to a string and return text. This always returns unicode text, which can then be encoded. Passing ``handler`` will change how metadata is turned into text. A handler - passed as an argument will override ``post.handler``, with - :py:class:`YAMLHandler ` used as + passed as an argument will override ``post.handler``, with + :py:class:`YAMLHandler ` used as a default. :: @@ -239,15 +237,15 @@ def dumps(post, handler=None, **kwargs): class Post(object): """ A post contains content and metadata from Front Matter. This is what gets - returned by :py:func:`load ` and :py:func:`loads `. - Passing this to :py:func:`dump ` or :py:func:`dumps ` + returned by :py:func:`load ` and :py:func:`loads `. + Passing this to :py:func:`dump ` or :py:func:`dumps ` will turn it back into text. - For convenience, metadata values are available as proxied item lookups. + For convenience, metadata values are available as proxied item lookups. """ def __init__(self, content, handler=None, **metadata): - self.content = u(content) + self.content = str(content) self.metadata = metadata self.handler = handler @@ -271,11 +269,6 @@ def __bytes__(self): return self.content.encode("utf-8") def __str__(self): - if six.PY2: - return self.__bytes__() - return self.content - - def __unicode__(self): return self.content def get(self, key, default=None): diff --git a/frontmatter/default_handlers.py b/frontmatter/default_handlers.py index 3b50da8..0fe4773 100644 --- a/frontmatter/default_handlers.py +++ b/frontmatter/default_handlers.py @@ -112,7 +112,6 @@ """ -from __future__ import unicode_literals import json import re @@ -141,7 +140,7 @@ class BaseHandler(object): """ - BaseHandler lays out all the steps to detecting, splitting, parsing and + BaseHandler lays out all the steps to detecting, splitting, parsing and exporting front matter metadata. All default handlers are subclassed from BaseHandler. @@ -169,7 +168,7 @@ def detect(self, text): Decide whether this handler can parse the given ``text``, and return True or False. - Note that this is *not* called when passing a handler instance to + Note that this is *not* called when passing a handler instance to :py:func:`frontmatter.load ` or :py:func:`loads `. """ if self.FM_BOUNDARY.match(text): @@ -207,7 +206,7 @@ class YAMLHandler(BaseHandler): def load(self, fm, **kwargs): """ - Parse YAML front matter. This uses yaml.SafeLoader by default. + Parse YAML front matter. This uses yaml.SafeLoader by default. """ kwargs.setdefault("Loader", SafeLoader) return yaml.load(fm, **kwargs) diff --git a/frontmatter/util.py b/frontmatter/util.py index 13e60b6..602f8fd 100644 --- a/frontmatter/util.py +++ b/frontmatter/util.py @@ -2,13 +2,12 @@ """ Utilities for handling unicode and other repetitive bits """ -import six def u(text, encoding="utf-8"): "Return unicode text, no matter what" - if isinstance(text, six.binary_type): + if isinstance(text, bytes): text = text.decode(encoding) # it's already unicode diff --git a/requirements.txt b/requirements.txt index 3106a37..eb10adb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ PyYAML -six -toml \ No newline at end of file +pyaml +toml diff --git a/setup.py b/setup.py index 7de427f..6b14e4b 100644 --- a/setup.py +++ b/setup.py @@ -11,8 +11,6 @@ readme = f.read() -requirements = ["PyYAML", "six"] - VERSION = "0.5.0" @@ -27,7 +25,9 @@ url="https://github.com/eyeseast/python-frontmatter", packages=["frontmatter"], include_package_data=True, - install_requires=requirements, + install_requires=["PyYAML"], + extras_require={"test": ["pytest", "toml"]}, + tests_require=["python-frontmatter[test]"], license="MIT", zip_safe=False, keywords="frontmatter", @@ -36,13 +36,12 @@ "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Natural Language :: English", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", ], test_suite="test", ) diff --git a/test.py b/test.py index 47197bf..77c481a 100644 --- a/test.py +++ b/test.py @@ -1,8 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from __future__ import unicode_literals -from __future__ import print_function import codecs import doctest @@ -15,8 +13,6 @@ import textwrap import unittest -import six - import frontmatter from frontmatter.default_handlers import YAMLHandler, JSONHandler, TOMLHandler @@ -56,7 +52,7 @@ def test_unicode_post(self): output = frontmatter.dumps(chinese) zh = "中文" - self.assertTrue(isinstance(chinese.content, six.text_type)) + self.assertTrue(isinstance(chinese.content, str)) # check that we're dumping out unicode metadata, too self.assertTrue(zh in output) @@ -88,7 +84,7 @@ def test_no_frontmatter(self): def test_empty_frontmatter(self): "Frontmatter, but no metadata" post = frontmatter.load("tests/empty-frontmatter.txt") - content = six.text_type("I have frontmatter but no metadata.") + content = "I have frontmatter but no metadata." self.assertEqual(post.metadata, {}) self.assertEqual(post.content, content) @@ -96,9 +92,7 @@ def test_empty_frontmatter(self): def test_extra_space(self): "Extra space in frontmatter delimiter" post = frontmatter.load("tests/extra-space.txt") - content = six.text_type( - "This file has an extra space on the opening line of the frontmatter." - ) + content = "This file has an extra space on the opening line of the frontmatter." self.assertEqual(post.content, content) metadata = {"something": "else", "test": "tester"} @@ -121,13 +115,13 @@ def test_to_string(self): # test unicode and bytes text = "Well, hello there, world." - self.assertEqual(six.text_type(post), text) - self.assertEqual(six.binary_type(post), text.encode("utf-8")) + self.assertEqual(str(post), text) + self.assertEqual(bytes(post), text.encode("utf-8")) def test_pretty_dumping(self): "Use pyaml to dump nicer" # pyaml only runs on 2.7 and above - if sys.version_info > (2, 6) and pyaml is not None: + if pyaml is not None: with codecs.open("tests/unpretty.md", "r", "utf-8") as f: data = f.read() @@ -142,8 +136,6 @@ def test_pretty_dumping(self): self.assertTrue(yaml in dump) def test_with_crlf_string(self): - import codecs - markdown_bytes = b'---\r\ntitle: "my title"\r\ncontent_type: "post"\r\npublished: no\r\n---\r\n\r\nwrite your content in markdown here' loaded = frontmatter.loads(markdown_bytes, "utf-8") self.assertEqual(loaded["title"], "my title") @@ -281,18 +273,17 @@ def setUp(self): } def read_from_tests(self): - with open(self.data["filename"]) as fil: - return fil.read() + with open(self.data["filename"]) as f: + return f.read() def test_external(self): filename = self.data["filename"] content = self.data["content"] metadata = self.data["metadata"] - content_stripped = content.strip() post = frontmatter.load(filename) - self.assertEqual(post.content, content_stripped) + self.assertEqual(post.content, content.strip()) for k, v in metadata.items(): self.assertEqual(post[k], v) @@ -420,7 +411,7 @@ def setUp(self): if __name__ == "__main__": - doctest.testfile("README.md") + doctest.testfile("README.md", extraglobs={"frontmatter": frontmatter}) doctest.testmod( frontmatter.default_handlers, extraglobs={"frontmatter": frontmatter} )