Use implicit namespace plugins for import and export (#1216)

* behavior outline
* FIrst pass at allow external plugins
* remove template exporter
* Add listing of active plugins to '--version' output
* Documentation for plugins
* [Docs] add custom imports and exporters to site TOC
* [Docs] better linewrapping
* enforce positive initial linewrap
  Check column widths
  update gitignore
  throw error when linewrap too small
  simply check for large enough linewrap value
* delete unused error message
* PR feedback
  make exception more informative
  update check_linewrap signature in src and test
  make check_linewrap a free function
* delete unused function
* delete else..pass block
* newline for make format
* Include dates_exporter
* Use Base classes for importer and exporters.
* [Docs] improve documentation of custom Importers and Exporters
* [Testing] separate run with external plugin!
* basic behavior test
* prototype unittest for JSON Exporter
  test for unimplemented method
* make format
  delete unused imports
* Remove 'importer' or 'exporter' from filenames where not needed
* [Test] run different tests with or without the external plugins installed
* [Test] move test rot13 plugin into git tree
  from 0dc912af82
* consolidate demo plugins to common package
* [Docs] name page for plugins
* [Docs] include the sample plug in code files directly
* style fixes
* [test] determine whether to run external plug in tests based on installed packages
* improved code documentation
* style fixes for GitHub actions
* Convert "short" and "pretty" (and "default") formaters to plugins
  further to https://github.com/jrnl-org/jrnl/pull/1177
* more code clean up
  tests pass locally...now for GitHub...
* [tests] dynamically determine jrnl version for plugin tests
* [GitHub Actions] direct install of testing plugins
* Remove template code
* [plugins] meta --> collector
* [Docs] create scripted entries using an custom importer
* (closer to) being able to run behave tests outside project root directory
* We already know when exporter to use
  Don't re-calculate it!
* [Tests] don't name test plugin 'testing"
  If so named, pip won't install it.
* [Test] run behave tests with test plugins outside project root
* [Test] behave tests pass locally
* [Docs] fix typo
* [GitHub Actions] run test commands from poetry's shell
* black-ify code
* [GitHub Actions] move downstream (rather than up) to run tests
* [GitHub Actions] set shell to poetry
* [GitHub Workflows] Manually activate virtual environment
* [GitHub Actions] Skip Windows & Python 3.8
  Can't seem to find Python exe?
* [GiotHub Actions] explicitly use virtual env
* [GitHub Actions] create virutal env directly
* [GitHub Actions] better activate of Windows virtual env
* [GitHub Actions] create virtual env on Mac
* [Github Actions] install wheel and upgrade pip
* [GitHub Actions] skip virtual environments altogether
* [GitHub Actions] change directory for behave test
* Remove Windows exclusions from CI as per note -- they should be working now

Co-authored-by: Suhas <sugas182@gmail.com>
Co-authored-by: Micah Jerome Ellison <micah.jerome.ellison@gmail.com>
This commit is contained in:
MinchinWeb 2021-05-29 18:21:45 -06:00 committed by Jonathan Wren
parent cef3a98b4e
commit 4392e29742
45 changed files with 1021 additions and 383 deletions

View file

@ -0,0 +1,9 @@
# Rot13 Custom Exporter for Jrnl
This is a custom exporter to demostrate how to write customer exporters for
[jrnl](https://github.com/jrnl-org/jrnl). It is also used by *jrnl* in its
tests to ensure the feature works as expected.
This plugin applies a [Caeser
cipher](https://en.wikipedia.org/wiki/Caesar_cipher) (specifically the
[ROT13](https://en.wikipedia.org/wiki/ROT13)) to output text.

View file

@ -0,0 +1,37 @@
# pelican\contrib\exporter\custom_json.py
import json
from jrnl.plugins.base import BaseExporter
__version__ = "v1.0.0"
class Exporter(BaseExporter):
"""
This basic Exporter can convert entries and journals into JSON.
"""
names = ["json"]
extension = "json"
version = __version__
@classmethod
def entry_to_dict(cls, entry):
return {
"title": entry.title,
"body": entry.body,
"date": entry.date.strftime("%Y-%m-%d"),
}
@classmethod
def export_entry(cls, entry):
"""Returns a json representation of a single entry."""
return json.dumps(cls.entry_to_dict(entry), indent=2) + "\n"
@classmethod
def export_journal(cls, journal):
"""Returns a json representation of an entire journal."""
result = {
"entries": [cls.entry_to_dict(e) for e in journal.entries],
}
return json.dumps(result, indent=2)

View file

@ -0,0 +1,25 @@
#!/usr/bin/env python
# encoding: utf-8
# Copyright (C) 2012-2021 jrnl contributors
# License: https://www.gnu.org/licenses/gpl-3.0.html
"""
Exporter for testing and experimentation purposes.
It is not called "testing" because then it's not installed.
The presence of this plugin is also used as a "switch" by the test suite to
decide on whether or not to run the "vanilla" test suite, or the test suite
for external plugins.
The `export_entry` and `export_journal` methods are both purposely not
implemented to confirm behavior on plugins that don't implement them.
"""
from jrnl.plugins.base import BaseExporter
class Exporter(BaseExporter):
names = ["testing", "test"]
version = "v0.0.1"
extension = "test"

View file

@ -0,0 +1,15 @@
import codecs
from jrnl.plugins.base import BaseExporter
__version__ = "v1.0.0"
class Exporter(BaseExporter):
names = ["rot13", "txt"]
extension = "txt"
version = __version__
@classmethod
def export_entry(cls, entry):
return codecs.encode(str(entry), "rot_13")

View file

@ -0,0 +1,50 @@
# pelican\contrib\importer\sample_json.py
import json
import sys
from jrnl import Entry
from jrnl.plugins.base import BaseImporter
__version__ = "v1.0.0"
class Importer(BaseImporter):
"""JSON Importer for jrnl."""
names = ["json"]
version = __version__
@staticmethod
def import_(journal, input=None):
"""
Given a nicely formatted JSON file, will add the
contained Entries to the journal.
"""
old_cnt = len(journal.entries)
if input:
with open(input, "r", encoding="utf-8") as f:
data = json.loads(f)
else:
try:
data = sys.stdin.read()
except KeyboardInterrupt:
print(
"[Entries NOT imported into journal.]",
file=sys.stderr,
)
sys.exit(0)
for json_entry in data:
raw = json_entry["title"] + "/n" + json_entry["body"]
date = json_entry["date"]
entry = Entry.Entry(journal, date, raw)
journal.entries.append(entry)
new_cnt = len(journal.entries)
print(
"[{} entries imported to '{}' journal]".format(
new_cnt - old_cnt, journal.name
),
file=sys.stderr,
)

View file

@ -0,0 +1,35 @@
import os
import re
try:
from setuptools import setup
except ImportError:
from distutils.core import setup
base_dir = os.path.dirname(os.path.abspath(__file__))
def get_version(filename="jrnl/contrib/exporter/rot13.py"):
with open(os.path.join(base_dir, filename), encoding="utf-8") as initfile:
for line in initfile.readlines():
m = re.match("__version__ *= *['\"](.*)['\"]", line)
if m:
return m.group(1)
setup(
name="jrnl-demo-plugins",
version=get_version(),
description="Demonstration custom plugins for jrnl",
long_description="\n\n".join([open(os.path.join(base_dir, "README.md")).read()]),
long_description_content_type="text/markdown",
author="W. Minchin",
author_email="w_minchin@hotmail.com",
url="https://github.com/jrnl-org/jrnl/tree/develop/tests/external_plugins_src",
packages=["jrnl", "jrnl.contrib", "jrnl.contrib.exporter", "jrnl.contrib.importer"],
include_package_data=True,
install_requires=[
"jrnl",
],
zip_safe=False, # use wheels instead
)

46
tests/test_plugin.py Normal file
View file

@ -0,0 +1,46 @@
from datetime import date
import json
import pytest
from jrnl import Entry
from jrnl import Journal
from jrnl.plugins.exporter import json as json_exporter
try:
from jrnl.contrib.exporter import testing as testing_exporter
except:
testing_exporter = None
if testing_exporter:
@pytest.fixture()
def create_entry():
entry = Entry.Entry(
journal=Journal.Journal(),
text="This is the entry text",
date=date(year=2001, month=1, day=1),
starred=True,
)
yield entry
class TestBaseExporter(testing_exporter.Exporter):
def test_unimplemented_export(self, create_entry):
entry = create_entry
with pytest.raises(NotImplementedError):
self.export_entry(entry)
class TestJsonExporter(json_exporter.Exporter):
def test_json_exporter_name(self):
assert "json" in self.names
def test_export_entry(self, create_entry):
entry = create_entry
fake_uuid = "ewqf09-432p9p0433-243209" # generated by mashing keys
entry.uuid = fake_uuid
exported = self.export_entry(entry)
deserialized_export = json.loads(exported)
assert deserialized_export["title"] == "This is the entry text"
assert deserialized_export["date"] == "2001-01-01"
assert "uuid" in deserialized_export.keys()

View file

@ -9,7 +9,7 @@ from jrnl.jrnl import _display_search_results
# fmt: off
# see: https://github.com/psf/black/issues/664
@pytest.mark.parametrize("export_format", [ "pretty", "short","markdown"])
@pytest.mark.parametrize("export_format", [ "pretty", "short", "markdown"])
#fmt: on
@mock.patch.object(argparse, "Namespace", return_value={"export": "markdown", "filename": "irrele.vant"})
def test_export_format(mock_args, export_format):