-
Notifications
You must be signed in to change notification settings - Fork 741
Discoverable Python plugin support #4642
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
This pull request introduces 1 alert when merging 503d1f5 into db455c1 - view on LGTM.com new alerts:
|
Here are some sample plugins, both source and installable archives (in As the name suggests these aren't polished systems but quick adaptions of scripts I had lying around. |
We need more information.
|
There is a UI for configuring how the plugins installed on your own system interact with your instance of FontForge. You bring it up with a menu option under "File". That stuff is described in the documentation added in this PR. Your other questions relate to the discussion/disagreement in #4062. Note that I'm not marking that issue as being closed by this. This is a mechanism that allows user-developers to more easily share their FontForge extensions with the user base. My suggestion that we address "the contrib question" with a meta-package is just that, the project needn't go that route. There are a number of improvements to the Python API I plan to pursue in the gap between this being merged and the next release, whenever that is. (This is assuming I have the time to pursue them, of course.) I don't want to get into that "here" because they're not directly related; they would be just as much benefit to an "init script" developer as a plugin developer. However, one is a limited means of specifying multi-parameter dialogs, similar to the "could we just use the Params dialog template?" idea that @ctrlcctrlv and I discussed a while back. It's a compromise that lets a user bring up a dialog with a lot of different options without a lot of control over how they're spatially presented. I think it will work for 90% of the cases and in a way that's far less likely to rot over time. This will be a bit of work: In addition to just the general UI foo (I think?) I'll need to build a wrapping layout manager analogous to (Another improvement is adding an "Apply protocol" callback option to the ask-like dialogs, including the new one, so that you make them work like the current Transform dialog.) |
Oh okay, I understand the scope of this change now. Tkinter can be provided as a requirements.txt entry, so I'm just noting that can happen with Pip installable packages. If you consider contrib a separate problem, I agree, so we can put a pin in that for now :) |
I'm thinking the best way for me to test this is to convert ctrlcctrlv/QuickAnchors into a |
There are somewhat separate questions of "Is this functionality worth adding?" (testing the idea) and "Does this do what it says it does without crashing?" (testing the code). The first question also closely relates to what documentation we should write and what further steps, if any, we should take with the python dev and user communities. So what I would say is: That's absolutely a great idea relative to the first question and I strongly encourage it. If your plugin doesn't have or need preferences you might get by with just the (I don't yet say anything about dependencies, and the docs probably should. In many cases you might get away with just an If the plugin does have preferences or you want to add them, well, I should add something about the preferences callback anyway -- it's quite simple. (However, before there's more development work on the "ask' dialogs you might want to TkInter yourself a window for that.) Anyway, this could take you as little as 15 minutes or so, if you just copy and adapt the current MidContour config into your https://github.com/ctrlcctrlv/QuickAnchors repo. (I would skip PyPI for now, both because it's more work and because people may get confused if they start seeing plugins listed before a released version of FontForge can support them.) For breadth of testing, which I've already done some of, we can mix your plugin with some of the fake ones I uploaded and folks can play around with them. So: a benefit but not a solution. |
Oh -- @jtanx I forgot a lingering issue in this code. If you were to glance at
One thing you may be happy about is that I looked in our X Resources stuff and:
Do you have any thoughts on this? Best practices? Implementation help? I can also just abandon the idea ... |
Your guess is as good as mine to be honest. You could probably add the concept in if you spent enough time, but I'm not sure it's worth the effort. I'd be fine for hard-coding it for now, even if it is perhaps not the most ideal.
This is purely a packaging decision, like your Python distribution either comes installed with tkinter or it doesn't, you can't pip install it. I am OK with that, I don't think the plugin system should prescribe whether or not it's required. If your plugin depends on something and it isn't available, too bad (of course since these are basically just python packages, if they can install dependencies via pip then fine). We have control over the binaries we ship, and can choose to include or exclude whatever we like, but that doesn't prevent distros/users from doing what they like, and if they choose to install python without tkinter then so be it. |
The only thing I would add to this is that it would probably be a good idea to define (or, more likely, pick) an exception type that, when returned from |
Is there really enough traffic on the FF dev list to warrant a separate
list?
Great work!!!
|
@davelab6 It's not so much about the current amount of traffic as the potential burst in traffic and who may be subscribed. If folks signed up thinking the list was for direct contributions and not helping people with their plugins and sorting out the API then they could get annoyed. But it could also be fine, I don't really know. |
I just checked the archives and traffic has been at such a low level that we can probably just send a note warning about the change in emphasis on fontforge-devel so people can unsubscribe if they want. Seems easiest. |
This isn't really our problem because |
Expand documentation
I added a warning for enabled plugins that aren't found and significantly expanded the documentation. |
Back when I got subscription change notifications, we did indeed get a wave of cancellations whenever somebody hijacked the announcement list for a user or engineering issue. I imagine that the same holds true for the user list. If something is beyond the understanding or interest of a casual user, I think that we need to keep it on the engineering list. |
@frank-trampe: So maybe one announcement on fontforge-users noting that there's going to be a phase of discussion about contributed features on fontforge-dev. One warning on fontforge-dev in case anyone wants to unsubscribe in the fact of increased traffic? |
That sounds generally right to me, although I'm inclined to push as much of the substantive engineering discussion to the issue tracker. I propose the following. The users list gets a notification that we are working on a new plug-in interface, with discussion happening here. The engineering list gets the same notification with the additional note that discussion of new plug-ins may happen on the engineering list and that we will announce significant plug-ins (likely in conjunction with mainstream releases) on the announcement list. |
fontforge/plugin.c
Outdated
@@ -370,7 +372,7 @@ static bool DiscoverPlugins(int do_import) { | |||
Py_DECREF(tmp); | |||
if (PyIter_Check(tmp2)) { | |||
while ((str = PyIter_Next(tmp2))) { | |||
tmp = PyUnicode_AsASCIIString(str); | |||
tmp = PyUnicode_AsUTF8String(str); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should be able to use PyUnicode_AsUTF8
and skip having to manage temporary python objects/freeing anything yourself
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
fontforge/plugin.c
Outdated
pe->summary); | ||
PyList_Append(r, t); | ||
entry = PyDict_New(); | ||
tmp = PyUnicode_FromString(pe->name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you need to Py_DECREF
these. Might be easier to still use Py_BuildValue
? e.g.
Py_BuildValue("{s:i,s:i}",
"abc", 123, "def", 456) {'abc': 123, 'def': 456}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK ... done
Was just playing around with this on Windows - had a couple of questions/notes
|
Delete is primarily for removing the entry of a plugin that is no longer installed. I suppose I could just enable it in that case -- there's no particular reason a user should be able to put an entry back in "new/ask" mode.
I'll look at that.
Yes.
Yeah, I'm probably not going to get into that for this release.
It's the usual problem that 80% of the stuff will only be wanted by 20% of the users, but the latter will want those things.
I'll think about that. People who don't care about ordering will expect that. People who do care about ordering might not. |
Offer to load newly enabled plugins
I updated the code so that only an undiscovered plugin can be deleted. I also now offer (in a button choice dialog) to load a plugin that is newly enabled.
This is likely no longer relevant but I'm not sure how it would have been happening. If you still see something along these lines please describe a repeatable test case. |
Since you removed the ability to 'Delete' a discoverable plugin (so you can't cancel the action) it's no longer possible. |
What are your views on init scripts going forward? Keep it? Deprecate it? Let me know if I'm missing anything, but I see it as the functionality being basically the same as an init script except you add on better discoverability(assuming people follow the conventions when uploading to pypi)+version management and that you've added a mechanism to load them from system python. |
If someone is just making a little extension for themselves and doesn't want to get into Python packaging an init script still seems like a fine mechanism. It's a bit unfortunately hidden at this point -- the location where they need to be isn't documented very well/accurately. But I still expect future plugins will start their lives as init scripts. I guess my view is that init scripts will continue to be for "personal use" and plugins will be for when someone generalizes a feature enough to make it worth sharing. The biggest advantage of plugins is PyPI and, once things get going, the https://pypi.org/search/?q=%22FontForge_plugin%22 link. Rather than building its own database, distribution, and installation system the FontForge project can just use Python's. That's the main thing this buys. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a comprehensive review by any means, but I think some things you just find easier to discover/fix once it's out there.
Seems like a pretty decent change, nice work.
fontforge/plugin.c
Outdated
return pe; | ||
} | ||
|
||
static char *getPluginDirName() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My preference would be to at least keep the casing consistent within a file, especially new files; with pascal case for functions unless replacing stuff that's already following some other pattern.
Using a plugin is as easy as installing the package (often with | ||
`pip <https:pypi.org/project/pip/>`_) and, when necessary, "configuring" | ||
the plugin. Almost any plugin of general interest will be listed in | ||
the `PyPI package database <https:pypi.org/search/?q=%22FontForge_plugin%22>`_. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's sad that there isn't proper support for custom tags (like you just have to have it as part of the description), but oh well, that's a pip/pypi limitation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I switched the new functions to Pascal case.
|
||
First we create a new directory for our project with the following files: | ||
|
||
1. ``fontforge_midpoint.py`` (The script itself as a single module file.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing you can do is to create something like a cookiecutter template that allows you to quickly bootstrap a python package. It's what I use at work, and it's pretty handy in saving you from doing a lot of the boilerplate.
Capitalize new functions
Any remaining thoughts before this is merged? @frank-trampe? This would be my initial guidance on "communication", which it might be better to delay kicking off until #4671 and maybe the
I don't have discussion category edit privileges. Can someone maybe do https://docs.github.com/en/discussions/managing-discussions-for-your-community/managing-categories-for-discussions-in-your-repository#creating-a-category to add "Plugin Q&A" and "Plugin Show and Tell" categories? |
Alright, if there are no updates before Thursday evening I'll merge this. |
@skef, This sounds really exciting. Thanks for all your amazing work on this and all the recent improved python support. I don't think I'm the only one who will welcome some improvements in the plugin realm. I feel quite like a dunce, though, not able to understand how I can use this on Windows or how it differs from "the old way" of creating plugins/scripts. Do you (or someone) plan on putting together a step-by-step tutorial (with pictures, preferably)? That would help me tremendously and I think would spur more plugin development from the userbase. |
The windows version of FontForge (at least the one that we compile) has a built-in python interpreter separate from the system one (assuming one is installed). But I believe it bundles pip, which can be used to install PyPI packages. As far as plugin development goes, the plugin interface itself doesn't make that much different. The multiask dialog and the improved menu support (assuming that gets merged) do improve the options. The plugin idea is more about user discovery and distribution. Sharing FontForge scripts never really took off, partly because there was no master list and "installing" them can be confusing. Now people will be able to search on a keyword in PyPI and install additional FontForge functionality using the python package ecosystem. We will probably set up a "blank plugin" repository as a starting point for interested developers at some point (@jtanx's suggestion). |
I see now. Thanks for the additional explanation. I have developed quite a library of functions I use regularly through the plugin framework. This could entice me to break out what I've done into separate, shareable plugins. I'd certainly be interested to know if there are many others who have successfully gotten their own plugins to work to enhance FF's functionality because it can be very frustrating at times. It's been a huge blessing for me, though, to have the ones I've created. |
@tisimst I doubt there are many, because this isn't in a release yet, but there is also me. If you meant, “have many people managed to do all sorts of things via FontForge Python API (before it included pip-packaged plugins)?" the answer is certainly yes. |
Well, I created all of my plugins well before this functionality manifested. I think you're probably right, though, that there aren't a lot of people who've taken the plunge and gotten very far before throwing their hands up in frustration. I certainly almost did, but was able to develop a few very much needed helper functions that could adequately tell me when I was doing something wrong. Anyway, still, if the ability to pass plugins along via pip works well, I'd consider figuring out how to package mine to share with others. |
This is the deluxe version of the discoverable plugin interface I discuss in #4062. At the bottom level the idea is quite simple -- provide a way for python packages to identify themselves as plugins (the Entry-points mechanism), then import each package and call a prearranged function on its identified object (usually the module itself).
Once this is in place a developer can adapt an "init script" into python package and upload it to PyPI. A user who finds and installs that package can then simply enable it in the "Configure Plugins..." dialog under the File menu (or choose that all new plugins are automatically enabled).
Most of the hoo-hah is UI-related, as usual. It seemed inevitable that users would complain if they couldn't control the initialization order, and therefore the Tools menu order, so I provided that ability. There may also be some cases where security is a worry so a user need not enable new plugins by default and can turn some off. It also seemed good to provide (optional) preferences configuration (yet to be described in the documentation but fairly clear from the code) in a central window rather than cluttering Tools menus with it. Same with the project URLs.
I don't know how much code sharing this will wind up facilitating but it does provide these key advantages:
https://pypi.org/search/?q=%22FontForge_plugin%22
. (I've specified that each plugin should contain the string "FontForge_plugin" in its package metadatadescription
field.)contrib
conventions #4062 the project can just maintain a meta-package (with no contents but dependencies on each of the plugins we consider "key" contributions). We can then encourage distros to provide that package in their usual way, and (perhaps?) install it automatically in our embedded pythons (presumably using pip over the web in the normal way). Users can then upgrade those packages as they like also in the normal way, so no code-rot in our repos.I expect this PR will take some time to review and will change a bit before it is merged, but I think it's more or less ready except for the documentation. Those docs, however, should be developed in part in response to developers' feedback.
I also want to have a discussion about those users. I'm thinking a new fontforge-plugin-dev mailing list would be good (it doesn't seem like that's what fontforge-dev is for), along with the offer (once this actually ships in an official release) to announce significant new plugins on fontforge-announce.
I'll upload a couple silly plugins to play with shortly.
This work has been supported by the Tug libre fonts fund, and also, I guess, by myself, considering that it took longer than I expected. @davelab6 should take a look.
Closes #4581