diff --git a/docs/plugins.md b/docs/plugins.md index 35d180ed..b728ca48 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -3,14 +3,33 @@ # Extending jrnl -jrnl can be extended with custom importers and exporters. +*jrnl* can be extended with custom importers and exporters. Note that custom importers and exporters can be given the same name as a built-in importer or exporter to override it. Custom Importers and Exporters are traditional Python packages, and are -installed (into jrnl) simply by installing them so they are available to the -Python interpreter that is running jrnl. +installed (into *jrnl*) simply by installing them so they are available to the +Python interpreter that is running *jrnl*. + +Exporter are also used as "formatters" when entries are written to the command +line. + +## Rational + +I added this feature because *jrnl* was overall working well for me, but I +found myself maintaining a private fork so I could have a slightly customized +export format. Implemeneting (import and) export plugins was seen as a way to +maintain my custom exporter without the need to maintaining my private fork. + +This implementation tries to keep plugins as light as possible, and as free of +boilerplate code as reasonable. As well, internal importers and exporters are +implemented in almost exactly the same way as custom importers and exporters, +and so it is hoped that plugins can be moved from "contributed" to "internal" +easily, or that internal plugins can serve as a base and/or a demonstration for +external plugins. + +-- @MinchinWeb, May 2021 ## Entry Class @@ -63,7 +82,9 @@ Some implementation notes: - The importer class must be named **Importer**, and should sub-class **jrnl.plugins.base.BaseImporter**. - The importer module must be within the **jrnl.contrib.importer** namespace. -- The importer must not have any `__init__.py` files in the base directories. +- The importer must not have any `__init__.py` files in the base directories + (but you can have one for your importer base directory if it is in a + directory rather than a single file). - The importer must be installed as a Python package available to the same Python interpreter running jrnl. - The importer must expose at least the following the following members: @@ -85,7 +106,7 @@ generator or blogging engine. An exporter take either a whole journal or a specific entry and exports it. Below is a basic JSON Exporter; note that a more extensive JSON exporter is -included in jrnl and so this (if installed) would override the built in +included in *jrnl* and so this (if installed) would override the built in exporter. ~~~ python @@ -103,7 +124,9 @@ Some implementation notes: - the exporter class must be named **Exporter** and should sub-class **jrnl.plugins.base.BaseExporter**. - the exporter module must be within the **jrnl.contrib.exporter** namespace. -- The exporter must not have any `__init__.py` files in the base directories. +- The exporter must not have any `__init__.py` files in the base directories + (but you can have one for your exporter base directory if it is in a + directory rather than a single file). - The exporter must be installed as a Python package available to the same Python interpreter running jrnl. - the exporter should expose at least the following the following members @@ -118,11 +141,16 @@ Some implementation notes: - **extension** (string): the file extention used on exported entries. - **export_entry(entry)**: given an entry, returns a string of the formatted, exported entry. - - **export_journal(journal)**: given a journal, returns a string of the - formatted, exported entries of the journal. + - **export_journal(journal)**: (optional) given a journal, returns a string + of the formatted, exported entries of the journal. If not implemented, + *jrnl* will call **export_entry()** on each entry in turn and then + concatenate the results together. ## Development Tips - editable installs (`pip install -e ...`) don't seem to play nice with the namespace layout. If your plugin isn't appearing, try a non-editable - install of both jrnl and your plugin. + install of both *jrnl* and your plugin. +- for examples, you can look to the *jrnl*'s internal importers and exporters. + As well, there are some basic external examples included in *jrnl*'s git repo + at `tests/external_plugins_src` (including the example code above). diff --git a/jrnl/plugins/meta.py b/jrnl/plugins/meta.py index 2f6ba463..abb88415 100644 --- a/jrnl/plugins/meta.py +++ b/jrnl/plugins/meta.py @@ -1,5 +1,20 @@ #!/usr/bin/env python # encoding: utf-8 +# Copyright (C) 2012-2021 jrnl contributors +# License: https://www.gnu.org/licenses/gpl-3.0.html + +""" +This file ("meta") uses that title in the reflexive sense; i.e. it is the +collection of code that allows plugins to deal with themselves. + +In particular, the code here collects the list of imports and exporters, both +internal and external, and tells the main program which plugins are available. +Actual calling of the plugins is done directly and works because given plugin +functions are importable/callable at predetermined (code) locations. + +Internal plugins are located in the `jrnl.plugins` namespace, and external +plugins are located in the `jrnl.contrib` namespace. +""" import importlib import pkgutil @@ -64,10 +79,15 @@ __importer_types = { } EXPORT_FORMATS = sorted(__exporter_types.keys()) +"""list of stings: all available export formats.""" IMPORT_FORMATS = sorted(__importer_types.keys()) +"""list of stings: all available import formats.""" def get_exporter(format): + """ + Given an export format, returns the (callable) class of the corresponding exporter. + """ # print('get_exporter') # print(__exporter_types) for exporter_name, exporter_class in __exporter_types.items(): @@ -82,6 +102,9 @@ def get_exporter(format): def get_importer(format): + """ + Given an import format, returns the (callable) class of the corresponding importer. + """ for importer_name, importer_class in __importer_types.items(): if ( hasattr(importer_class, "Importer") diff --git a/tests/external_plugins_src/jrnl/contrib/exporter/testing.py b/tests/external_plugins_src/jrnl/contrib/exporter/testing.py index 76982c2a..92941eb6 100644 --- a/tests/external_plugins_src/jrnl/contrib/exporter/testing.py +++ b/tests/external_plugins_src/jrnl/contrib/exporter/testing.py @@ -4,7 +4,14 @@ # License: https://www.gnu.org/licenses/gpl-3.0.html """ -Exporter for testing and experimentation purposes +Exporter for testing and experimentation purposes. + +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