diff --git a/PLUGINS.md b/PLUGINS.md index d389339a..c0afa177 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -1,6 +1,6 @@ # Developer Plugin Documentation -Plugins allow you to customise and extend the behavior of EDMC. +Plugins allow you to customise and extend the behavior of EDMarketConnector. ## Installing a Plugin @@ -12,28 +12,31 @@ wiki. Check [Releasing.md](docs/Releasing.md#environment) to be sure of the current version of Python that we've tested against. -Plugins are loaded when EDMC starts up. +Plugins are loaded when EDMarketConnector starts up. Each plugin has it's own folder in the `plugins` directory: - Windows: `%LOCALAPPDATA%\EDMarketConnector\plugins` - Mac: `~/Library/Application Support/EDMarketConnector/plugins` -- Linux: `$XDG_DATA_HOME/EDMarketConnector/plugins`, or `~/.local/share/EDMarketConnector/plugins` if `$XDG_DATA_HOME` is unset. +- Linux: `$XDG_DATA_HOME/EDMarketConnector/plugins`, or + `~/.local/share/EDMarketConnector/plugins` if `$XDG_DATA_HOME` is unset. Plugins are python files. The plugin folder must have a file named `load.py` that must provide one module level function and optionally provide a few others. -If you're running from source (which allows for debugging with e.g. [PyCharm](https://www.jetbrains.com/pycharm/features/)) +If you're running from source (which allows for debugging with e.g. +[PyCharm](https://www.jetbrains.com/pycharm/features/)) then you'll need to be using an appropriate version of Python. The current -version is listed in the [Environment section of Releasing.md](https://github.com/EDCD/EDMarketConnector/blob/main/docs/Releasing.md#environment). +version is listed in the +[Environment section of Releasing.md](https://github.com/EDCD/EDMarketConnector/blob/main/docs/Releasing.md#environment). If you're developing your plugin simply against an install of EDMarketConnector then you'll be relying on the bundled version of Python (it's baked into the .exe via the py2exe build process). Please be sure to read the [Avoiding potential pitfalls](#avoiding-potential-pitfalls) -section, else you might inadvertently cause issues for the core EDMC code -including whole application crashes. +section, else you might inadvertently cause issues for the core +EDMarketConnector code including whole application crashes. It is highly advisable to ensure you are aware of all EDMarketConnector releases, including the pre-releases. The -beta and -rc changelogs will @@ -118,9 +121,9 @@ the packaged executeable all output is redirected to a log file. See [Reporting a problem](https://github.com/EDCD/EDMarketConnector/wiki/Troubleshooting#reporting-a-problem) for the location of this log file. -EDMC now implements proper logging using the Python `logging` module. Plugin -developers should now use the following code instead of simple `print(...)` -statements. +EDMarketConnector now implements proper logging using the Python `logging` +module. Plugin developers should now use the following code instead of simple +`print(...)` statements. Insert this at the top-level of your load.py file (so not inside `plugin_start3()`): @@ -160,13 +163,14 @@ exactly the folder name. Our custom `qualname` and `class` formatters won't work with a 'bare' logger, and will cause your code to throw exceptions if you're not using our supplied logger. -If running with 4.1.0-beta1 or later of EDMC the logging setup happens in -the core code and will include the extra logfile destinations. If your -plugin is run under a pre-4.1.0 version of EDMC then the above will set up -basic logging only to the console (and thus redirected to the log file). +If running with 4.1.0-beta1 or later of EDMarketConnector the logging setup +happens in the core code and will include the extra logfile destinations. If +your plugin is run under a pre-4.1.0 version of EDMarketConnector then the +above will set up basic logging only to the console (and thus redirected to +the log file). -If you're certain your plugin will only be run under EDMC 4.1.0 or newer then -you can remove the `if` clause. +If you're certain your plugin will only be run under EDMarketConnector 4.1.0 +or newer then you can remove the `if` clause. Replace all `print(...)` statements with one of the following: @@ -226,26 +230,26 @@ from config import appversion # Yes, just blow up if config.appverison is neither str or callable - logger.info(f'Core EDMC version: {core_version}') + logger.info(f'Core EDMarketConnector version: {core_version}') # And then compare like this if core_version < semantic_version.Version('5.0.0-beta1'): - logger.info('EDMC core version is before 5.0.0-beta1') + logger.info('EDMarketConnector core version is before 5.0.0-beta1') else: - logger.info('EDMC core version is at least 5.0.0-beta1') + logger.info('EDMarketConnector core version is at least 5.0.0-beta1') ``` --- ## Startup -EDMC will import the `load.py` file as a module and then call the +EDMarketConnector will import the `load.py` file as a module and then call the `plugin_start3()` function. ```python def plugin_start3(plugin_dir: str) -> str: """ - Load this plugin into EDMC + Load this plugin into EDMarketConnector """ print(f"I am loaded! My plugin folder is {plugin_dir}") return "Test" @@ -267,8 +271,8 @@ Mac, and `$TMP/EDMarketConnector.log` on Linux. ## Avoiding potential pitfalls There are a number of things that your code should either do or avoiding -doing so as to play nicely with the core EDMC code and not risk causing -application crashes or hangs. +doing so as to play nicely with the core EDMarketConnector code and not risk +causing application crashes or hangs. ### Be careful about the name of your plugin directory @@ -344,15 +348,17 @@ So instead use: ### Configuration If you want your plugin to be configurable via the GUI you can define a frame -(panel) to be displayed on its own tab in EDMC's settings dialog. The tab -title will be the value that you returned from `plugin_start3`. Use widgets -from EDMC's myNotebook.py for the correct look-and-feel. You can be notified -when the settings dialog is closed so you can save your settings. +(panel) to be displayed on its own tab in EDMarketConnector's settings dialog. +The tab title will be the value that you returned from `plugin_start3`. Use +widgets from EDMarketConnector's myNotebook.py for the correct look-and-feel. +You can be notified when the settings dialog is closed, so you can save your +settings. You can use `set()` and `get_$type()` (where type is one of: `int`, `bool`, -`str`, `list`) from EDMC's `config.config` object to retrieve your plugin's -settings in a platform-independent way. Previously this was done with a single -set and two get methods, the new methods provide better type safety. +`str`, `list`) from EDMarketConnector's `config.config` object to retrieve +your plugin's settings in a platform-independent way. Previously this was done +with a single set and two get methods, the new methods provide better type +safety. If you want to maintain compatibility with pre-5.0.0 versions of this application (please encourage plugin users to update!) then you'll need to @@ -377,12 +383,12 @@ if not hasattr(config, 'get_list'): ``` **Be sure to use a unique prefix for any settings you save so as not to clash -with core EDMC or other plugins.** +with core EDMarketConnector or other plugins.** -Use `number_from_string()` from EDMC's `l10n.Locale` object to parse input -numbers in a locale-independent way. NB: the old CamelCase versions of -`number_from_string` and `string_from_number` do still exist, but are -deprecated. They will continue to work, but will throw warnings. +Use `number_from_string()` from EDMarketConnector's `l10n.Locale` object to +parse input numbers in a locale-independent way. NB: the old CamelCase +versions of `number_from_string` and `string_from_number` do still exist, but +are deprecated. They will continue to work, but will throw warnings. Note that in the following example the function signature defines that it returns `Optional[tk.Frame]` only because we need to allow for `None` if @@ -401,7 +407,7 @@ my_setting: Optional[tk.IntVar] = None def plugin_prefs(parent: nb.Notebook, cmdr: str, is_beta: bool) -> Optional[tk.Frame]: """ - Return a TK Frame for adding to the EDMC settings dialog. + Return a TK Frame for adding to the EDMarketConnector settings dialog. """ global my_setting my_setting = tk.IntVar(value=config.get_int("MyPluginSetting")) # Retrieve saved value from config @@ -438,15 +444,15 @@ def prefs_changed(cmdr: str, is_beta: bool) -> None: ### Display -You can also have your plugin add an item to the EDMC main window and update -from your event hooks. This works in the same way as `plugin_prefs()`. For a -simple one-line item return a `tk.Label` widget or a 2 tuple of widgets. -For a more complicated item create a tk.Frame widget and populate it with other -ttk widgets. Return `None` if you just want to use this as a callback after the -main window and all other plugins are initialised. +You can also have your plugin add an item to the EDMarketConnector main window +and update from your event hooks. This works in the same way as +`plugin_prefs()`. For a simple one-line item return a `tk.Label` widget or a 2 +tuple of widgets. For a more complicated item create a tk.Frame widget and +populate it with other ttk widgets. Return `None` if you just want to use this +as a callback after the main window and all other plugins are initialised. -You can use `string_from_number()` from EDMC's `l10n.Locale` object to format -numbers in your widgets in a locale-independent way. +You can use `string_from_number()` from EDMarketConnector's `l10n.Locale` +object to format numbers in your widgets in a locale-independent way. ```python from typing import Optional, Tuple @@ -457,7 +463,7 @@ status: Optional[tk.Label] def plugin_app(parent: tk.Frame) -> Tuple[tk.Label, tk.Label]: """ - Create a pair of TK widgets for the EDMC main window + Create a pair of TK widgets for the EDMarketConnector main window """ global status label = tk.Label(parent, text="Status:") # By default widgets inherit the current theme's colors @@ -473,7 +479,7 @@ def some_other_function() -> None: | Parameter | Type | Description | | :-------- | :---------------------------------------------: | :---------------------------------------------------------- | -| `parent` | `tk.Frame` | The root EDMC window | +| `parent` | `tk.Frame` | The root EDMarketConnector window | | `RETURN` | `Union[tk.Widget, Tuple[tk.Widget, tk.Widget]]` | A widget to add to the main window. See below for more info | The return from `plugin_app()` can either be any widget (`Frame`, `Label`, @@ -496,7 +502,7 @@ frame: Optional[tk.Frame] = None def plugin_app(parent: tk.Frame) -> tk.Frame: """ - Create a frame for the EDMC main window + Create a frame for the EDMarketConnector main window """ global frame frame = tk.Frame(parent) @@ -519,9 +525,10 @@ See [Avoiding potential pitfalls](#avoiding-potential-pitfalls). ### Events -Once you have created your plugin and EDMC has loaded it there are four other -functions you can define to be notified by EDMC when something happens: -`journal_entry()`, `journal_entry_cqc()`, `dashboard_entry()` and `cmdr_data()`. +Once you have created your plugin and EDMarketConnector has loaded it there +are four other functions you can define to be notified by EDMarketConnector +when something happens: `journal_entry()`, `journal_entry_cqc()`, +`dashboard_entry()` and `cmdr_data()`. Your events all get called on the main Tkinter loop so be sure not to block for very long or the app will appear to freeze. If you have a long running @@ -551,7 +558,7 @@ def journal_entry( logger.info(f'Arrived at {entry["StarSystem"]}') ``` -This gets called when EDMC sees a new entry in the game's journal. +This gets called when EDMarketConnector sees a new entry in the game's journal. | Parameter | Type | Description | | :-------- | :--------------: | :--------------------------------------------------------------------- | @@ -682,10 +689,10 @@ ___ ##### Synthetic Events -A special "StartUp" entry is sent if EDMC is started while the game is already -running. In this case you won't receive initial events such as "LoadGame", -"Rank", "Location", etc. However the `state` dictionary will reflect the -cumulative effect of these missed events. +A special "StartUp" entry is sent if EDMarketConnector is started while the +game is already running. In this case you won't receive initial events such as +"LoadGame", "Rank", "Location", etc. However the `state` dictionary will +reflect the cumulative effect of these missed events. **NB: Any of the values in this might be `None` if the Cmdr has loaded into Arena (CQC) from the Main Menu.** @@ -702,7 +709,7 @@ between the two scenarios. **NB: Any of these events are passing to `journal_entry_cqc` rather than to `journal_entry` if player has loaded into Arena (CQC).** -This event is not sent when EDMC is running on a different +This event is not sent when EDMarketConnector is running on a different machine so you should not *rely* on receiving this event. --- @@ -782,7 +789,7 @@ This gets called when the user closes the program: ```python def plugin_stop() -> None: """ - EDMC is closing + EDMarketConnector is closing """ print("Farewell cruel world!") ``` @@ -926,23 +933,23 @@ time after the corresponding `journal_entry()` event. ## Error messages -You can display an error in EDMC's status area by returning a string from your -`journal_entry()`, `dashboard_entry()` or `cmdr_data()` function, or -asynchronously (e.g. from a "worker" thread that is performing a long running -operation) by calling `plug.show_error()`. Either method will cause the "bad" -sound to be played (unless the user has muted sound). +You can display an error in EDMarketConnector's status area by returning a +string from your `journal_entry()`, `dashboard_entry()` or `cmdr_data()` +function, or asynchronously (e.g. from a "worker" thread that is performing a +long-running operation) by calling `plug.show_error()`. Either method will +cause the "bad" sound to be played (unless the user has muted sound). -The status area is shared between EDMC itself and all other plugins, so your -message won't be displayed for very long. Create a dedicated widget if you need -to display routine status information. +The status area is shared between EDMarketConnector itself and all other +plugins, so your message won't be displayed for very long. Create a dedicated +widget if you need to display routine status information. --- ## Localisation -You can localise your plugin to one of the languages that EDMC itself supports. -Add the following boilerplate near the top of each source file that contains -strings that needs translating: +You can localise your plugin to one of the languages that EDMarketConnector +itself supports. Add the following boilerplate near the top of each source +file that contains strings that needs translating: ```python import l10n @@ -956,9 +963,9 @@ Wrap each string that needs translating with the `_()` function, e.g.: status["text"] = _('Happy!') # Main window status ``` -If you display localized strings in EDMC's main window you should refresh them -in your `prefs_changed` function in case the user has changed their preferred -language. +If you display localized strings in EDMarketConnector's main window you should +refresh them in your `prefs_changed` function in case the user has changed +their preferred language. Translation files should reside in folder named `L10n` inside your plugin's folder. Files must be in macOS/iOS ".strings" format, encoded as UTF-8. You can @@ -967,7 +974,7 @@ in your plugin's folder. This extracts all the translatable strings from Python files in your plugin's folder and places them in a file named `en.template` in the `L10n` folder. Rename this file as `.strings` and edit it. -See EDMC's own [`L10n`](https://github.com/EDCD/EDMarketConnector/tree/main/L10n) +See EDMarketConnector's own [`L10n`](https://github.com/EDCD/EDMarketConnector/tree/main/L10n) folder for the list of supported language codes and for example translation files. @@ -976,9 +983,9 @@ files. ## Python Package Plugins A _Package Plugin_ is both a standard Python package (i.e. contains an -`__init__.py` file) and an EDMC plugin (i.e. contains a `load.py` file -providing at minimum a `plugin_start3()` function). These plugins are loaded -before any non-Package plugins. +`__init__.py` file) and an EDMarketConnector plugin (i.e. contains a `load.py` +file providing at minimum a `plugin_start3()` function). These plugins are +loaded before any non-Package plugins. Other plugins can access features in a Package Plugin by `import`ing the package by name in the usual way. @@ -1085,16 +1092,20 @@ step for the extra module(s) until it works. You can debug your http post requests using the builtin debug webserver. -To add support for said debug webserver to your plugin, you need to check `config.debug_senders` (`list[str]`) for -some indicator string for your plugin. `debug_senders` is generated from args to `--debug-sender` on the invocation -command line. +To add support for said debug webserver to your plugin, you need to check +`config.debug_senders` (`list[str]`) for some indicator string for your +plugin. `debug_senders` is generated from args to `--debug-sender` on the +invocation command line. If said string exists, `DEBUG_WEBSERVER_HOST` and `DEBUG_WEBSERVER_PORT` in -`edmc_data` will contain the host and port for the currently running local webserver. Simply redirect your requests -there, and your requests will be logged to disk. For organisation, rewrite your request path to simply be `/pluginname`. +`edmc_data` will contain the host and port for the currently running local +webserver. Simply redirect your requests there, and your requests will be +logged to disk. For organisation, rewrite your request path to simply be +`/pluginname`. -Logs exist in `$TEMP/EDMarketConnector/http_debug/$path.log`. If somehow you manage to cause a directory traversal, your -data will not be saved to disk at all. You will see this in EDMCs log. +Logs exist in `$TEMP/EDMarketConnector/http_debug/$path.log`. If somehow you +manage to cause a directory traversal, your data will not be saved to disk at +all. You will see this in EDMarketConnectors log. The simplest way to go about adding support is: @@ -1110,15 +1121,16 @@ if 'my_plugin' in debug_senders: ``` -For returned data, you can modify `debug_webserver.DEFAULT_RESPONSES` (`dict[str, Union[Callable[[str], str]], str])` -with either a function that accepts a single string (the raw post data) and returns a single string -(the response to send), or with a string if your required response is simple. +For returned data, you can modify `debug_webserver.DEFAULT_RESPONSES` +(`dict[str, Union[Callable[[str], str]], str])` with either a function that +accepts a single string (the raw post data) and returns a single string (the +response to send), or with a string if your required response is simple. ## Disable a plugin -EDMC now lets you disable a plugin without deleting it, simply rename the -plugin folder to append ".disabled". Eg, -"SuperSpaceHelper" -> "SuperSpaceHelper.disabled" +EDMarketConnector now lets you disable a plugin without deleting it, simply +rename the plugin folder to append ".disabled". Eg, "SuperSpaceHelper" -> +"SuperSpaceHelper.disabled" Disabled and enabled plugins are listed on the "Plugins" Settings tab @@ -1126,18 +1138,19 @@ Disabled and enabled plugins are listed on the "Plugins" Settings tab ## Migration from Python 2.7 -Starting with pre-release 3.5 EDMC used Python 3.7. The first full -release under Python 3.7 was 4.0.0.0. The 4.2.x series was the last to use -Python 3.7, with releases moving on to the latest Python 3.9.x after that. +Starting with pre-release 3.5 EDMarketConnector used Python 3.7. The first +full release under Python 3.7 was 4.0.0.0. The 4.2.x series was the last to +use Python 3.7, with releases moving on to the latest Python 3.9.x after that. This is a brief outline of the steps required to migrate a plugin from earlier -versions of EDMC: +versions of EDMarketConnector: - Rename the function `plugin_start` to `plugin_start3(plugin_dir)`. - Plugins without a `plugin_start3` function are listed as disabled on EDMC's - "Plugins" tab and a message like "plugin SuperSpaceHelper needs migrating" - appears in the log. Such plugins are also listed in a section "Plugins - Without Python 3.x Support:" on the Settings > Plugins tab. + Plugins without a `plugin_start3` function are listed as disabled on + EDMarketConnector's "Plugins" tab and a message like "plugin + SuperSpaceHelper needs migrating" appears in the log. Such plugins are + also listed in a section "Plugins Without Python 3.x Support:" on the + `Settings` > `Plugins` tab. - Check that callback functions `plugin_prefs`, `prefs_changed`, `journal_entry`, `dashboard_entry` and `cmdr_data`, if used, are declared @@ -1150,5 +1163,5 @@ versions of EDMC: We advise *against* making any attempt to have a plugin's code work under both Python 2.7 and 3.x. We no longer maintain the Python 2.7-based -versions of this application and you shouldn't support use of them with +versions of this application, and you shouldn't support use of them with your plugin.