GitHub - Grayson/pluginmanager: Several classes for Objective-C/Cocoa to help write plugins using common scripting languages. (original) (raw)

The PluginManager

I really like scriptable applications. I especially like applications that provide a plugin architecture. I am ecstatic to find applications that provide script-based plugins. Let's face it, writing a Cocoa bundle is kind of a pain in the ass even when using Python or Ruby. Being able to read plain text files as plugins is not only convenient, but it drastically lowers the barrier to entry for writing plugins (no dealing with Xcode).

Unfortunately, very few applications provide this feature. The PluginManager attempts to resolve this issue. It's a series of classes that provides support for a vast number of scripting languages as well as standard Cocoa bundles. It is designed based on AddressBook's applescript plugin support but can be used in a wide number of ways. If nothing else, this project should provide an example into calling of scripts that you can adapt to your own applications.

The Basic Design pattern

There are two basic design patterns at work here. The first is the PluginManager pattern. The PluginManager can be accessed from the PluginManager with basic agnosticism to what scripts are running underneath. This is achieved by sub-managers registering themselves with the umbrella PluginManager. For example, in the NuPluginManager's +load method, it calls [PluginManager registerManager:[[self new] autorelease]]; to add itself to the umbrella PluginManager's list of registered submanagers. All interactions should be done through this umbrella class.

The second design pattern is in using the actual plugins. When a sub-manager loads, it generally finds all of the scripts that it can read and loads them into memory (perhaps inefficient, but the initial release was not coded for strict memory efficiency... this could change in future releases). Then, when the application calls pluginsForProperty:forValue:withValue:, it returns an NSArray of NSDictionaries which describes the plugin. At least one key will be relevant to the plugin (usually the actual plugin in memory), and one key will be title (as returned from the plugin's actionTitle function. Other keys may refer to information necessary for internal use by the plugin manager or to provide additional information about the plugin to the application. When the application is ready to run the plugin, it should call runPlugin:forValue:withValue and return the dictionary that was originally returned from pluginsForProperty....

As a third option, you can usually just call runScriptAtPath: to execute a script at a particular path. This won't work for Cocoa bundles at the moment, but I've got an idea or two.

Please note the following

At present, the plugin managers are not optimized for performance in any sense. They inherited a half-thought out design pattern and my first instinct was to just get them to work. I'll go back and make them more memory-efficient in the near future, but you should certain test for memory usage before shipping. Right now, I consider this code to be proof of concept rather than shipping quality.

Todo

A short list of things that I'll be trying to take care of relatively quickly.

Necessary frameworks

The Python, Ruby, Applescript, and Cocoa bundle submanagers only use frameworks that are instaled by default in OS X 10.5. The Javascript, Nu, Perl, and Lua submanagers require additional downloads. These frameworks can either be linked into the application or bundled with the app.

Contact information

Patches, questions, comments, and any other communication can be sent via email to:
Grayson Hansard
info@fromconcentratesoftware.com

Patches, forks and other git stuff can be sent to PluginManager's github page at http://github.com/Grayson/pluginmanager/. However, I sometimes don't immediately notice pushes and whatnot so feel free to email me as well, especially if I don't incorporate or respond to changes in a day or two.

Simple inquiries or comments can also be sent to my Twitter account: @Grayson.

License information

A lot of the frameworks that PluginManager relies upon have their own licenses. Make sure that if you bundle them with your application that you are following their license and giving credit where credit is due. As for PluginManager, I have not added a license at this time. I don't really feel the need but I would appreciate the following considerations:

Special thanks

I skimmed a lot of mailing lists and documentation to figure how to make some of this stuff work. Other times, I was fortunate enough to have direct access to the developers or other people with direct knowledge of how the necessary frameworks worked. And then there was stuff I just had to figure out on my own. I've tried to thank everyone that I could remember to thank, but if I forgot someone, I apologize.

A note about MacRuby

I tried really hard to make RubyCocoa support work. I really did. But nothing seemed to go right. There were a ton of weird bugs and crashes that I got tired of trying to work around. I actually like RubyCocoa and would have preferred it to MacRuby for a few reasons (no garbage collection requirement, installed on OSX, better convenience methods), but I couldn't make it work. Maybe I'll return to it and try again in the future.

However, I went with MacRuby. It has a lot going for it and it worked for me. Unfortunately, it has one rather sizeable issue: it require's Objective-C's new garbage collection. That may not sound like a bit deal, but it can be a problem if you're integrating plugins into a large codebase. You'll need to turn on garbage collection support (-fobjc-gc) for your project (which isn't too big of a deal), but you'll also need to make sure all of your frameworks, loadable bundles, and other stuff are also garbage collection-supported. For small projects, this may not be a big deal. For larger ones, this could be a deal breaker. Regardless, expect to see a lot of junk in the console when you turn on garbage collection and expect to crash until you update all of the compiled bits that your app depends on.

For those of you who want to include Perl and Ruby in one project, I have bad news for you. I tried to find a way to compile CamelBones with garbage collection. As may be expected, that did not work very well. I haven't exactly racked my brain trying to figure this out, but I also don't really see many options here. This may be an instance where you have to pick one. Since CamelBones is kind of iffy anyway (you'll need to compile your own version for Leopard) and, perhaps, dying code, you may want to side with Ruby here. However, since Ruby support means turning on garbage collection support, it may be easier to just go with Perl. Again, I'll continue to evaluate RubyCocoa and incorporate it when possible.

The Perl/Ruby problem also affects Python. With garbage collection turned in your application, you cannot import the objc module into your script. This means that custom objects can't cross the bridge. Sure, I've written some code to allow for the conversion of basic types, but your custom classes can't be used in scripts. This has something to do with the objc module. It was compiled without garbage collection turned on and therefore won't be loaded by the application if it has garbage collection support. Long story short, you can't use MacRuby and the default install of Python.