ddplugin-1.0.1/0000755000175000017500000000000013076476460012712 5ustar boutilboutilddplugin-1.0.1/Rakefile0000644000175000017500000000054613076476460014364 0ustar boutilboutilrequire 'rake/testtask' require 'rubocop/rake_task' Rake::TestTask.new do |t| t.libs = %w( lib test ) t.test_files = FileList['test/**/test_*.rb', 'test/**/*_spec.rb'] end RuboCop::RakeTask.new do |task| task.options = %w(--display-cop-names --format simple) task.patterns = ['lib/**/*.rb', 'test/**/*.rb'] end task :default => [:test, :rubocop] ddplugin-1.0.1/Gemfile.lock0000644000175000017500000000176613076476460015146 0ustar boutilboutilPATH remote: . specs: ddplugin (1.0.1) GEM remote: https://rubygems.org/ specs: ast (2.3.0) coveralls (0.8.19) json (>= 1.8, < 3) simplecov (~> 0.12.0) term-ansicolor (~> 1.3) thor (~> 0.19.1) tins (~> 1.6) docile (1.1.5) json (2.0.3) minitest (5.10.1) parser (2.4.0.0) ast (~> 2.2) powerpack (0.1.1) rainbow (2.2.1) rake (12.0.0) rubocop (0.47.1) parser (>= 2.3.3.1, < 3.0) powerpack (~> 0.1) rainbow (>= 1.99.1, < 3.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) ruby-progressbar (1.8.1) simplecov (0.12.0) docile (~> 1.1.0) json (>= 1.8, < 3) simplecov-html (~> 0.10.0) simplecov-html (0.10.0) term-ansicolor (1.4.0) tins (~> 1.0) thor (0.19.4) tins (1.13.2) unicode-display_width (1.1.3) PLATFORMS ruby DEPENDENCIES bundler (~> 1.13) coveralls ddplugin! minitest rake rubocop BUNDLED WITH 1.14.5 ddplugin-1.0.1/ddplugin.gemspec0000644000175000017500000000141513076476460016066 0ustar boutilboutilrequire_relative 'lib/ddplugin/version' Gem::Specification.new do |s| s.name = 'ddplugin' s.version = DDPlugin::VERSION s.homepage = 'http://github.com/ddfreyne/ddplugin/' s.summary = 'Plugins for Ruby apps' s.description = 'Provides plugin management for Ruby projects' s.author = 'Denis Defreyne' s.email = 'denis.defreyne@stoneship.org' s.license = 'MIT' s.required_ruby_version = '>= 2.1.0' s.files = Dir['[A-Z]*'] + Dir['{lib,test}/**/*'] + [ 'ddplugin.gemspec' ] s.require_paths = [ 'lib' ] s.rdoc_options = [ '--main', 'README.md' ] s.extra_rdoc_files = [ 'LICENSE', 'README.md', 'NEWS.md' ] s.add_development_dependency('bundler', '~> 1.13') end ddplugin-1.0.1/README.md0000644000175000017500000000675413076476460014205 0ustar boutilboutil[![Build Status](https://travis-ci.org/ddfreyne/ddplugin.png)](https://travis-ci.org/ddfreyne/ddplugin) [![Code Climate](https://codeclimate.com/github/ddfreyne/ddplugin.png)](https://codeclimate.com/github/ddfreyne/ddplugin) [![Coverage Status](https://coveralls.io/repos/ddfreyne/ddplugin/badge.png?branch=master)](https://coveralls.io/r/ddfreyne/ddplugin) [![Inline docs](http://inch-ci.org/github/ddfreyne/ddplugin.png)](http://inch-ci.org/github/ddfreyne/ddplugin) # ddplugin *ddplugin* is a library for managing plugins. Designing a library so that third parties can easily extend it greatly improves its usefulness. *ddplugin* helps solve this problem using *plugins*, which are classes of a certain type and with a given identifier (Ruby symbol). This code was extracted from Nanoc, where it has been in production for years. ## Use case Many projects can make use of plugins. Here are a few examples: * a **text processing library** with *filters* such as `colorize-syntax`, `markdown` and `smartify-quotes`. * an **image processing library** with *filters* such as `resize`, `desaturate` and `rotate`. * a **database driver abstraction** with *connectors* such as `postgres`, `sqlite3` and `mysql`. * a **document management system** with *data sources* such as `filesystem` and `database`. In *ddplugin*, the filters, connectors and data sources would be *plugin types*, while the actual plugins, such as `markdown`, `rotate`, `postgres` and `database` would be *plugins*. A typical way to use plugins would be to store the plugin names in a configuration file, so that the actual plugin implementations can be discovered at runtime. ## Requirements *ddplugin* requires Ruby 2.1 or higher. ## Versioning *ddplugin* adheres to [Semantic Versioning 2.0.0](http://semver.org). ## Installation If your library where you want to use *ddplugin* has a gemspec, add *ddplugin* as a runtime dependency to the gemspec: ```ruby spec.add_runtime_dependency 'ddplugin', '~> 1.0' ``` If you use Bundler instead, add it to the `Gemfile`: ```ruby gem 'ddplugin', '~> 1.0' ``` ## Usage Plugin type are classes that extend `DDPlugin::Plugin`: ```ruby class Filter extend DDPlugin::Plugin end class DataSource extend DDPlugin::Plugin end ``` To define a plugin, create a class that inherits from the plugin type and sets the identifier: ```ruby class ERBFilter < Filter identifier :erb end class HamlFilter < Filter identifier :haml end class FilesystemDataSource < DataSource identifier :filesystem end class PostgresDataSource < DataSource identifier :postgres end ``` To find a plugin of a given type and with a given identifier, call `.named` on the plugin type, passing an identifier: ```ruby Filter.named(:erb) # => ERBFilter Filter.named(:haml) # => HamlFilter DataSource.named(:filesystem) # => FilesystemDataSource DataSource.named(:postgres) # => PostgresDataSource ``` To get all plugins of a given type, call `.all` on the plugin type: ```ruby Filter.all # => [ ERBFilter, HamlFilter ] DataSource.all # => [ FilesystemDataSource, PostgresDataSource ] ``` To get the identifier of a plugin, call `.identifier`: ```ruby Filter.named(:erb).identifier # => :erb PostgresDataSource.identifier # => :postgres ``` ## Development Pull requests and issues are greatly appreciated. When you submit a pull request, make sure that your change is covered by tests, and that the `README` and [YARD](http://yardoc.org/) source code documentation are still up-to-date. To run the tests, execute `rake`: ``` % rake ``` ddplugin-1.0.1/Gemfile0000644000175000017500000000016013076476460014202 0ustar boutilboutilsource 'https://rubygems.org' gemspec gem 'minitest' gem 'rake' gem 'coveralls', require: false gem 'rubocop' ddplugin-1.0.1/NEWS.md0000644000175000017500000000021113076476460014002 0ustar boutilboutil# ddplugin release notes ## 1.0.1 (2017-02-28) Fixes: * Made `.all` not return duplicates (#3) ## 1.0 (2016-11-27) Initial release. ddplugin-1.0.1/lib/0000755000175000017500000000000013076476460013460 5ustar boutilboutilddplugin-1.0.1/lib/ddplugin/0000755000175000017500000000000013076476460015266 5ustar boutilboutilddplugin-1.0.1/lib/ddplugin/version.rb0000644000175000017500000000005713076476460017302 0ustar boutilboutilmodule DDPlugin VERSION = '1.0.1'.freeze end ddplugin-1.0.1/lib/ddplugin/registry.rb0000644000175000017500000000443213076476460017466 0ustar boutilboutilmodule DDPlugin # The registry is responsible for keeping track of all loaded plugins. class Registry # Returns the shared {DDPlugin::Registry} instance, creating it if none # exists yet. # # @return [DDPlugin::Registry] The shared plugin registry def self.instance @instance ||= new end # @api private def initialize @identifiers_to_classes = Hash.new { |h, k| h[k] = {}.dup } @classes_to_identifiers = Hash.new { |h, k| h[k] = {}.dup } end # Registers the given class as a plugin. # # @param [Class] root_class The root class of the class to register # # @param [Class] klass The class to register # # @param [Symbol] identifiers One or more symbols identifying the class # # @return [void] def register(root_class, klass, *identifiers) identifiers.each do |identifier| @classes_to_identifiers[root_class][klass] ||= [] @identifiers_to_classes[root_class][identifier] = klass @classes_to_identifiers[root_class][klass] << identifier end end # @param [Class] root_class The root class of the class to find the # identifiers for # # @param [Class] klass The class to get the identifiers for # # @return [Array] The identifiers for the given class def identifiers_of(root_class, klass) @classes_to_identifiers[root_class] ||= {} @classes_to_identifiers[root_class].fetch(klass, []) end # Finds the class that is a descendant of the given class and has the given # identifier. # # @param [Class] root_class The root class of the class to return # # @param [Symbol] identifier The identifier of the class to return # # @return [Class, nil] The class with the given identifier def find(root_class, identifier) @identifiers_to_classes[root_class] ||= {} @identifiers_to_classes[root_class][identifier] end # Returns all classes that are registered descendants of the given class. # # @param [Class] root_class The root class of the class to return # # @return [Enumerable] A collection of registered classes def find_all(root_class) @identifiers_to_classes[root_class] ||= {} @identifiers_to_classes[root_class].values.uniq end end end ddplugin-1.0.1/lib/ddplugin/plugin.rb0000644000175000017500000000335213076476460017114 0ustar boutilboutilmodule DDPlugin # A module that contains class methods for plugins. It provides functions # for setting identifiers and finding plugins. Plugin classes should extend # this module. module Plugin # @overload identifiers(*identifiers) # # Sets the identifiers for this class. # # @param [Array] identifiers A list of identifiers to assign to # this class. # # @return [void] # # @overload identifiers # # @return [Array] The identifiers for this class def identifiers(*identifiers) if identifiers.empty? DDPlugin::Registry.instance.identifiers_of(root_class, self) else DDPlugin::Registry.instance.register(root_class, self, *identifiers) end end # @return [Class] The root class for this class def root_class klass = self klass = klass.superclass while klass.superclass.respond_to?(:identifiers) klass end # @overload identifier(identifier) # # Sets the identifier for this class. # # @param [Symbol] identifier The identifier to assign to this class. # # @return [void] # # @overload identifier # # @return [Symbol] The first identifier for this class def identifier(identifier = nil) if identifier identifiers(identifier) else identifiers.first end end # @return [Enumerable] All classes of this type def all DDPlugin::Registry.instance.find_all(self) end # @param [Symbol] identifier The identifier of the class to find # # @return [Class] The class with the given identifier def named(identifier) DDPlugin::Registry.instance.find(self, identifier) end end end ddplugin-1.0.1/lib/ddplugin.rb0000644000175000017500000000012113076476460015605 0ustar boutilboutilrequire 'ddplugin/version' require 'ddplugin/plugin' require 'ddplugin/registry' ddplugin-1.0.1/test/0000755000175000017500000000000013076476460013671 5ustar boutilboutilddplugin-1.0.1/test/helper.rb0000644000175000017500000000014713076476460015477 0ustar boutilboutilrequire 'coveralls' Coveralls.wear! require 'minitest' require 'minitest/autorun' require 'ddplugin' ddplugin-1.0.1/test/test_plugin.rb0000644000175000017500000000377313076476460016565 0ustar boutilboutilrequire 'helper' class DDPlugin::PluginTest < Minitest::Test class IdentifierSample extend DDPlugin::Plugin end class NamedSample extend DDPlugin::Plugin end class AllSample extend DDPlugin::Plugin end class InheritanceSample extend DDPlugin::Plugin end def test_identifier klass = Class.new(IdentifierSample) assert_nil klass.identifier klass.identifier :foo assert_equal :foo, klass.identifier klass.identifier :bar assert_equal :foo, klass.identifier end def test_identifiers klass = Class.new(IdentifierSample) assert_empty klass.identifiers klass.identifiers :foo1, :foo2 assert_equal [:foo1, :foo2], klass.identifiers klass.identifiers :bar1, :bar2 assert_equal [:foo1, :foo2, :bar1, :bar2], klass.identifiers end def test_root superklass = Class.new(InheritanceSample) superklass.identifier :super subklass = Class.new(superklass) subklass.identifiers :sub, :also_sub assert_equal superklass, InheritanceSample.named(:super) assert_equal subklass, InheritanceSample.named(:sub) assert_equal :sub, subklass.identifier assert_equal [:sub, :also_sub], subklass.identifiers assert_equal InheritanceSample, superklass.root_class assert_equal InheritanceSample, subklass.root_class end def test_named klass = Class.new(NamedSample) klass.identifier :named_test assert_nil NamedSample.named(:unknown) assert_equal klass, NamedSample.named(:named_test) end def test_all klass1 = Class.new(AllSample) klass1.identifier :one klass2 = Class.new(AllSample) klass2.identifier :two assert_equal [klass1, klass2], AllSample.all end def test_all_with_multiple_identifiers parent_class = Class.new { extend DDPlugin::Plugin } klass1 = Class.new(parent_class) klass1.identifier :one_a klass1.identifier :one_b klass2 = Class.new(parent_class) klass2.identifier :two assert_equal [klass1, klass2], parent_class.all end end ddplugin-1.0.1/LICENSE0000644000175000017500000000204213076476460013715 0ustar boutilboutilCopyright (c) 2013 Denis Defreyne Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.