ddplugin-1.0.2/0000755000004100000410000000000013330104055013342 5ustar www-datawww-dataddplugin-1.0.2/Gemfile.lock0000644000004100000410000000203313330104055015562 0ustar www-datawww-dataPATH remote: . specs: ddplugin (1.0.2) GEM remote: https://rubygems.org/ specs: ast (2.4.0) coveralls (0.8.21) json (>= 1.8, < 3) simplecov (~> 0.14.1) term-ansicolor (~> 1.3) thor (~> 0.19.4) tins (~> 1.6) docile (1.1.5) json (2.1.0) minitest (5.11.3) parallel (1.12.1) parser (2.5.0.5) ast (~> 2.4.0) powerpack (0.1.1) rainbow (3.0.0) rake (12.3.1) rubocop (0.54.0) parallel (~> 1.10) parser (>= 2.5) powerpack (~> 0.1) rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) ruby-progressbar (1.9.0) simplecov (0.14.1) docile (~> 1.1.0) json (>= 1.8, < 3) simplecov-html (~> 0.10.0) simplecov-html (0.10.2) term-ansicolor (1.6.0) tins (~> 1.0) thor (0.19.4) tins (1.16.3) unicode-display_width (1.3.0) PLATFORMS ruby DEPENDENCIES bundler (~> 1.13) coveralls ddplugin! minitest rake rubocop BUNDLED WITH 1.16.1 ddplugin-1.0.2/test/0000755000004100000410000000000013330104055014321 5ustar www-datawww-dataddplugin-1.0.2/test/test_plugin.rb0000644000004100000410000000527413330104055017213 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class 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_identifier_with_string klass = Class.new(IdentifierSample) assert_nil klass.identifier klass.identifier 'asdf' assert_equal :asdf, klass.identifier end def test_identifiers klass = Class.new(IdentifierSample) assert_empty klass.identifiers klass.identifiers :foo1, :foo2 assert_equal %i[foo1 foo2], klass.identifiers klass.identifiers :bar1, :bar2 assert_equal %i[foo1 foo2 bar1 bar2], klass.identifiers end def test_identifiers_with_string klass = Class.new(IdentifierSample) assert_empty klass.identifiers klass.identifiers 'foo1', 'foo2' assert_equal %i[foo1 foo2], klass.identifiers klass.identifiers 'bar1', 'bar2' assert_equal %i[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 %i[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_named_with_string 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.2/test/helper.rb0000644000004100000410000000020613330104055016123 0ustar www-datawww-data# frozen_string_literal: true require 'coveralls' Coveralls.wear! require 'minitest' require 'minitest/autorun' require 'ddplugin' ddplugin-1.0.2/ddplugin.gemspec0000644000004100000410000000144213330104055016516 0ustar www-datawww-data# frozen_string_literal: true require_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.3' 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.2/README.md0000644000004100000410000001042213330104055014620 0ustar www-datawww-data[![Gem version](http://img.shields.io/gem/v/ddplugin.svg)](http://rubygems.org/gems/ddplugin) [![Gem downloads](https://img.shields.io/gem/dt/ddplugin.svg)](http://rubygems.org/gems/ddplugin) [![Build status](http://img.shields.io/travis/ddfreyne/ddplugin.svg)](https://travis-ci.org/ddfreyne/ddplugin) [![Code Climate](http://img.shields.io/codeclimate/github/ddfreyne/ddplugin.svg)](https://codeclimate.com/github/ddfreyne/ddplugin) [![Code Coverage](https://img.shields.io/coveralls/ddfreyne/ddplugin.svg)](https://coveralls.io/r/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.3 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, either as a symbol or a string: ```ruby class ERBFilter < Filter # Specify the identifier as a symbol… identifier :erb end class HamlFilter < Filter # … or as a string … identifier 'haml' end class FilesystemDataSource < DataSource # … or even provide multiple. identifiers :filesystem, :file_system end class PostgresDataSource < DataSource # … or mix and match (not sure why you would, though) identifier :postgres, 'postgresql' 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 ``` In a real-world situation, the plugin types could be described in the environment: ``` % cat .env DATA_SOURCE_TYPE=postgres ``` ```ruby DataSource.named(ENV.fetch('DATA_SOURCE_TYPE')) # => PostgresDataSource ``` … or in a configuration file: ``` % cat config.yml data_source: 'filesystem' ``` ```ruby config = YAML.load_file('config.yml') DataSource.named(config.fetch('data_source')) # => FilesystemDataSource ``` 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`, which returns a symbol: ```ruby Filter.named(:erb).identifier # => :erb Filter.named('haml').identifier # => :haml 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: ``` % bundle install % bundle exec rake ``` ddplugin-1.0.2/LICENSE0000644000004100000410000000204213330104055014345 0ustar www-datawww-dataCopyright (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. ddplugin-1.0.2/Rakefile0000644000004100000410000000057713330104055015020 0ustar www-datawww-data# frozen_string_literal: true require '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: %i[test rubocop] ddplugin-1.0.2/lib/0000755000004100000410000000000013330104055014110 5ustar www-datawww-dataddplugin-1.0.2/lib/ddplugin.rb0000644000004100000410000000016013330104055016240 0ustar www-datawww-data# frozen_string_literal: true require 'ddplugin/version' require 'ddplugin/plugin' require 'ddplugin/registry' ddplugin-1.0.2/lib/ddplugin/0000755000004100000410000000000013330104055015716 5ustar www-datawww-dataddplugin-1.0.2/lib/ddplugin/version.rb0000644000004100000410000000010713330104055017726 0ustar www-datawww-data# frozen_string_literal: true module DDPlugin VERSION = '1.0.2' end ddplugin-1.0.2/lib/ddplugin/plugin.rb0000644000004100000410000000345313330104055017546 0ustar www-datawww-data# frozen_string_literal: true module 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, String] 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, String] 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.2/lib/ddplugin/registry.rb0000644000004100000410000000455413330104055020123 0ustar www-datawww-data# frozen_string_literal: true module 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.map(&:to_sym).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) identifier = identifier.to_sym @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.2/NEWS.md0000644000004100000410000000035313330104055014441 0ustar www-datawww-data# ddplugin release notes ## 1.0.2 (2018-03-31) Enhancements: * Made it possible to pass strings instead of symbols (#4) ## 1.0.1 (2017-02-28) Fixes: * Made `.all` not return duplicates (#3) ## 1.0 (2016-11-27) Initial release. ddplugin-1.0.2/Gemfile0000644000004100000410000000021713330104055014635 0ustar www-datawww-data# frozen_string_literal: true source 'https://rubygems.org' gemspec gem 'coveralls', require: false gem 'minitest' gem 'rake' gem 'rubocop'