ddplugin-1.0.3/0000755000004100000410000000000014002217423013344 5ustar www-datawww-dataddplugin-1.0.3/Gemfile.lock0000644000004100000410000000251114002217423015565 0ustar www-datawww-dataPATH remote: . specs: ddplugin (1.0.3) GEM remote: https://rubygems.org/ specs: ast (2.4.1) coveralls (0.8.23) json (>= 1.8, < 3) simplecov (~> 0.16.1) term-ansicolor (~> 1.3) thor (>= 0.19.4, < 2.0) tins (~> 1.6) docile (1.3.4) json (2.5.1) minitest (5.14.2) parallel (1.20.1) parser (3.0.0.0) ast (~> 2.4.1) rainbow (3.0.0) rake (13.0.3) regexp_parser (2.0.3) rexml (3.2.4) rubocop (1.7.0) parallel (~> 1.10) parser (>= 2.7.1.5) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml rubocop-ast (>= 1.2.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 2.0) rubocop-ast (1.3.0) parser (>= 2.7.1.5) rubocop-minitest (0.10.2) rubocop (>= 0.87, < 2.0) rubocop-rake (0.5.1) rubocop ruby-progressbar (1.11.0) simplecov (0.16.1) docile (~> 1.1) json (>= 1.8, < 3) simplecov-html (~> 0.10.0) simplecov-html (0.10.2) sync (0.5.0) term-ansicolor (1.7.1) tins (~> 1.0) thor (1.0.1) tins (1.26.0) sync unicode-display_width (1.7.0) PLATFORMS ruby DEPENDENCIES coveralls ddplugin! minitest rake rubocop rubocop-minitest (~> 0.10.2) rubocop-rake (~> 0.5.1) BUNDLED WITH 2.2.4 ddplugin-1.0.3/test/0000755000004100000410000000000014002217423014323 5ustar www-datawww-dataddplugin-1.0.3/test/test_plugin.rb0000644000004100000410000000527414002217423017215 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.3/test/helper.rb0000644000004100000410000000020614002217423016125 0ustar www-datawww-data# frozen_string_literal: true require 'coveralls' Coveralls.wear! require 'minitest' require 'minitest/autorun' require 'ddplugin' ddplugin-1.0.3/ddplugin.gemspec0000644000004100000410000000135414002217423016522 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.5' 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'] end ddplugin-1.0.3/README.md0000644000004100000410000001042214002217423014622 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.3/LICENSE0000644000004100000410000000204214002217423014347 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.3/Rakefile0000644000004100000410000000040514002217423015010 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 task default: %i[test rubocop] ddplugin-1.0.3/lib/0000755000004100000410000000000014002217423014112 5ustar www-datawww-dataddplugin-1.0.3/lib/ddplugin.rb0000644000004100000410000000016014002217423016242 0ustar www-datawww-data# frozen_string_literal: true require 'ddplugin/version' require 'ddplugin/plugin' require 'ddplugin/registry' ddplugin-1.0.3/lib/ddplugin/0000755000004100000410000000000014002217423015720 5ustar www-datawww-dataddplugin-1.0.3/lib/ddplugin/version.rb0000644000004100000410000000010714002217423017730 0ustar www-datawww-data# frozen_string_literal: true module DDPlugin VERSION = '1.0.3' end ddplugin-1.0.3/lib/ddplugin/plugin.rb0000644000004100000410000000345314002217423017550 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.3/lib/ddplugin/registry.rb0000644000004100000410000000455414002217423020125 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.3/NEWS.md0000644000004100000410000000055414002217423014446 0ustar www-datawww-data# ddplugin release notes ## 1.0.3 (2021-01-01) Enhancements: * Added support for Ruby 3.0 Changes: * Dropped support for Ruby 2.3 and 2.4 (both EOL) ## 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.3/Gemfile0000644000004100000410000000032214002217423014634 0ustar www-datawww-data# frozen_string_literal: true source 'https://rubygems.org' gemspec gem 'coveralls', require: false gem 'minitest' gem 'rake' gem 'rubocop' gem 'rubocop-minitest', '~> 0.10.2' gem 'rubocop-rake', '~> 0.5.1'