ruby-features 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +21 -0
- data/README.md +4 -0
- data/lib/generators/ruby_features/install_generator.rb +13 -0
- data/lib/generators/ruby_features/templates/ruby-features.rb +1 -0
- data/lib/ruby-features.rb +45 -0
- data/lib/ruby-features/concern.rb +50 -0
- data/lib/ruby-features/container.rb +13 -0
- data/lib/ruby-features/lazy.rb +38 -0
- data/lib/ruby-features/mixins.rb +27 -0
- data/lib/ruby-features/single.rb +36 -0
- data/lib/ruby-features/utils.rb +51 -0
- data/lib/ruby-features/utils/const_accessor_19.rb +32 -0
- data/lib/ruby-features/utils/const_accessor_20.rb +13 -0
- data/lib/ruby-features/version.rb +3 -0
- data/spec/define_spec.rb +69 -0
- data/spec/find_and_apply_spec.rb +44 -0
- data/spec/lazy_apply_spec.rb +19 -0
- data/spec/ruby_features/nested/nested_feature.rb +9 -0
- data/spec/ruby_features/root_feature.rb +9 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/support/helpers.rb +25 -0
- metadata +89 -0
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Sergey Tokarenko
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
module RubyFeatures
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
|
+
desc 'Copy RubyFeatures initializer'
|
5
|
+
source_root File.expand_path('../templates', __FILE__)
|
6
|
+
|
7
|
+
def copy_initializer
|
8
|
+
template 'ruby-features.rb', 'config/initializers/ruby-features.rb'
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
RubyFeatures.find_in_path(Rails.root.join('app/models/concerns')).apply_all
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'ruby-features/version'
|
2
|
+
|
3
|
+
module RubyFeatures
|
4
|
+
autoload :Container, 'ruby-features/container'
|
5
|
+
autoload :Single, 'ruby-features/single'
|
6
|
+
autoload :Mixins, 'ruby-features/mixins'
|
7
|
+
autoload :Concern, 'ruby-features/concern'
|
8
|
+
autoload :Utils, 'ruby-features/utils'
|
9
|
+
autoload :Lazy, 'ruby-features/lazy'
|
10
|
+
|
11
|
+
module Generators
|
12
|
+
autoload :InstallGenerator, 'generators/ruby-features/install_generator'
|
13
|
+
end
|
14
|
+
|
15
|
+
@@features = {}
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def find_in_path(*folders)
|
19
|
+
old_feature_names = @@features.keys
|
20
|
+
|
21
|
+
Dir[*folders.map{|folder| File.join(folder, '**', '*_feature.rb') }].each do |file|
|
22
|
+
require file
|
23
|
+
end
|
24
|
+
|
25
|
+
Container.new(@@features.keys - old_feature_names)
|
26
|
+
end
|
27
|
+
|
28
|
+
def define(feature_name, &feature_body)
|
29
|
+
feature = Single.new(feature_name, feature_body)
|
30
|
+
feature_name = feature.name
|
31
|
+
raise NameError.new("Such feature is already registered: #{feature_name}") if @@features.has_key?(feature_name)
|
32
|
+
|
33
|
+
@@features[feature_name] = feature
|
34
|
+
end
|
35
|
+
|
36
|
+
def apply(*feature_names)
|
37
|
+
feature_names.each do |feature_name|
|
38
|
+
raise NameError.new("Such feature is not registered: #{feature_name}") unless @@features.has_key?(feature_name)
|
39
|
+
|
40
|
+
@@features[feature_name].apply
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module RubyFeatures
|
2
|
+
module Concern
|
3
|
+
|
4
|
+
def _apply(target, feature_name)
|
5
|
+
target_class = RubyFeatures::Utils.ruby_const_get(self, "::#{target}")
|
6
|
+
|
7
|
+
_apply_methods(target_class, feature_name, :class)
|
8
|
+
_apply_methods(target_class, feature_name, :instance)
|
9
|
+
_apply_applied_blocks(target_class)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def self.extended(base)
|
15
|
+
base.instance_variable_set(:@_applied_blocks, [])
|
16
|
+
end
|
17
|
+
|
18
|
+
def applied(&block)
|
19
|
+
instance_variable_get(:@_applied_blocks) << block
|
20
|
+
end
|
21
|
+
|
22
|
+
def class_methods(&block)
|
23
|
+
RubyFeatures::Utils.prepare_module(self, 'ClassMethods').class_eval(&block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def instance_methods(&block)
|
27
|
+
RubyFeatures::Utils.prepare_module(self, 'InstanceMethods').class_eval(&block)
|
28
|
+
end
|
29
|
+
|
30
|
+
def _apply_methods(target_class, feature_name, methods_type)
|
31
|
+
methods_module_name = "#{methods_type.capitalize}Methods"
|
32
|
+
|
33
|
+
if RubyFeatures::Utils.module_defined?(self, methods_module_name)
|
34
|
+
methods_module = const_get(methods_module_name)
|
35
|
+
common_methods = target_class.public_send(:"#{'instance_' if methods_type == :instance}methods") & methods_module.instance_methods
|
36
|
+
raise NameError.new("Feature #{feature_name} tried to define already existing #{methods_type} methods: #{common_methods.inspect}") unless common_methods.empty?
|
37
|
+
|
38
|
+
target_class.send((methods_type == :instance ? :include : :extend), methods_module)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
def _apply_applied_blocks(target_class)
|
44
|
+
instance_variable_get(:@_applied_blocks).each do |applied_block|
|
45
|
+
target_class.class_eval(&applied_block)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module RubyFeatures
|
2
|
+
module Lazy
|
3
|
+
|
4
|
+
ACTIVE_SUPPORT_LAZY_TARGETS = %w(
|
5
|
+
action_view action_controller action_mailer
|
6
|
+
active_record active_job
|
7
|
+
i18n
|
8
|
+
).map(&:to_sym).freeze
|
9
|
+
|
10
|
+
@active_support_available = begin
|
11
|
+
require 'active_support'
|
12
|
+
true
|
13
|
+
rescue LoadError
|
14
|
+
false
|
15
|
+
end
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def active_support_available?
|
19
|
+
@active_support_available
|
20
|
+
end
|
21
|
+
|
22
|
+
def apply(target, &block)
|
23
|
+
if active_support_available?
|
24
|
+
target_namespace = RubyFeatures::Utils.underscore(target.split('::').first).to_sym
|
25
|
+
|
26
|
+
if ACTIVE_SUPPORT_LAZY_TARGETS.include?(target_namespace)
|
27
|
+
ActiveSupport.on_load target_namespace, yield: true, &block
|
28
|
+
|
29
|
+
return
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
yield
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module RubyFeatures
|
2
|
+
module Mixins
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def build_and_apply!(feature)
|
6
|
+
feature_module = RubyFeatures::Utils.prepare_module!(
|
7
|
+
self,
|
8
|
+
RubyFeatures::Utils.modulize(feature.name)
|
9
|
+
)
|
10
|
+
|
11
|
+
feature.apply_to_blocks.each do |target, blocks|
|
12
|
+
RubyFeatures::Lazy.apply(target) do
|
13
|
+
_lazy_build_and_apply!(feature, feature_module, target, blocks)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def _lazy_build_and_apply!(feature, feature_module, target, blocks)
|
19
|
+
lazy_module = RubyFeatures::Utils.prepare_module!(feature_module, target)
|
20
|
+
lazy_module.extend RubyFeatures::Concern
|
21
|
+
blocks.each { |block| lazy_module.class_eval(&block) }
|
22
|
+
lazy_module._apply(target, feature.name)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module RubyFeatures
|
2
|
+
class Single
|
3
|
+
|
4
|
+
attr_reader :name, :applied, :apply_to_blocks
|
5
|
+
alias applied? applied
|
6
|
+
|
7
|
+
def initialize(name, feature_body)
|
8
|
+
@name = name = name.to_s
|
9
|
+
raise NameError.new("Wrong feature name: #{name}") unless name.match(/^[\/_a-z\d]+$/)
|
10
|
+
|
11
|
+
@apply_to_blocks = {}
|
12
|
+
@applied = false
|
13
|
+
|
14
|
+
instance_eval(&feature_body) if feature_body
|
15
|
+
end
|
16
|
+
|
17
|
+
def apply
|
18
|
+
unless applied?
|
19
|
+
Mixins.build_and_apply!(self)
|
20
|
+
|
21
|
+
@apply_to_blocks = nil
|
22
|
+
@applied = true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def apply_to(target, &block)
|
29
|
+
target = target.to_s
|
30
|
+
|
31
|
+
@apply_to_blocks[target] ||= []
|
32
|
+
@apply_to_blocks[target] << block
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module RubyFeatures
|
2
|
+
module Utils
|
3
|
+
|
4
|
+
autoload :ConstAccessor19, 'ruby-features/utils/const_accessor_19'
|
5
|
+
autoload :ConstAccessor20, 'ruby-features/utils/const_accessor_20'
|
6
|
+
|
7
|
+
begin
|
8
|
+
const_defined?('Some::Const')
|
9
|
+
extend ConstAccessor20
|
10
|
+
rescue NameError
|
11
|
+
extend ConstAccessor19
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
|
16
|
+
def module_defined?(target, module_name)
|
17
|
+
ruby_const_defined?(target, module_name) && ruby_const_get(target, module_name).name.start_with?(target.name)
|
18
|
+
end
|
19
|
+
|
20
|
+
def prepare_module!(target, module_name)
|
21
|
+
module_defined?(target, module_name) ?
|
22
|
+
raise(NameError.new("Module already initiated: #{target.name}::#{module_name}")) :
|
23
|
+
prepare_module(target, module_name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def prepare_module(target, modules)
|
27
|
+
modules = modules.split('::') unless modules.kind_of?(Array)
|
28
|
+
|
29
|
+
first_submodule = modules.shift
|
30
|
+
new_target = module_defined?(target, first_submodule) ?
|
31
|
+
target.const_get(first_submodule) :
|
32
|
+
target.const_set(first_submodule, Module.new)
|
33
|
+
|
34
|
+
modules.empty? ?
|
35
|
+
new_target :
|
36
|
+
prepare_module(new_target, modules)
|
37
|
+
end
|
38
|
+
|
39
|
+
def modulize(string)
|
40
|
+
string.gsub(/([a-z\d]+)/i) { $1.capitalize }.gsub(/[-_]/, '').gsub('/', '::')
|
41
|
+
end
|
42
|
+
|
43
|
+
def underscore(string)
|
44
|
+
string.gsub(/([A-Z][a-z\d]*)/){ "_#{$1.downcase}" }.
|
45
|
+
gsub(/^_/, '').
|
46
|
+
gsub('::', '/')
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module RubyFeatures
|
2
|
+
module Utils
|
3
|
+
module ConstAccessor19
|
4
|
+
def ruby_const_defined?(target, const_parts)
|
5
|
+
const_parts = const_parts.split('::') unless const_parts.kind_of?(Array)
|
6
|
+
|
7
|
+
first_const_part = const_parts.shift
|
8
|
+
first_const_defined = target.const_defined?(first_const_part)
|
9
|
+
|
10
|
+
!first_const_defined || const_parts.empty? ?
|
11
|
+
first_const_defined :
|
12
|
+
ruby_const_defined?(target.const_get(first_const_part), const_parts)
|
13
|
+
end
|
14
|
+
|
15
|
+
def ruby_const_get(target, const_parts)
|
16
|
+
const_parts = const_parts.split('::') unless const_parts.kind_of?(Array)
|
17
|
+
|
18
|
+
first_const_part = const_parts.shift
|
19
|
+
if first_const_part == ''
|
20
|
+
target = ::Object
|
21
|
+
first_const_part = const_parts.shift
|
22
|
+
end
|
23
|
+
|
24
|
+
first_const = target.const_get(first_const_part)
|
25
|
+
|
26
|
+
const_parts.empty? ?
|
27
|
+
first_const :
|
28
|
+
ruby_const_get(first_const, const_parts)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/spec/define_spec.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
describe RubyFeatures::Single do
|
2
|
+
|
3
|
+
prepare_test_class 'DefineTestModule::DefineTestClass'
|
4
|
+
|
5
|
+
it 'should apply class methods' do
|
6
|
+
expect{
|
7
|
+
define_test_feature('class_methods') do
|
8
|
+
class_methods do
|
9
|
+
def test_class_method; end
|
10
|
+
end
|
11
|
+
end.apply
|
12
|
+
}.to change{test_class.respond_to?(:test_class_method)}.from(false).to(true)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should apply instance methods' do
|
16
|
+
expect{
|
17
|
+
define_test_feature('instance_methods') do
|
18
|
+
instance_methods do
|
19
|
+
def test_instance_method; end
|
20
|
+
end
|
21
|
+
end.apply
|
22
|
+
}.to change{test_class.new.respond_to?(:test_instance_method)}.from(false).to(true)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should process applied block' do
|
26
|
+
expect{
|
27
|
+
define_test_feature('applied_block') do
|
28
|
+
applied do
|
29
|
+
attr_accessor :applied_block_accessor
|
30
|
+
end
|
31
|
+
end.apply
|
32
|
+
}.to change{test_class.new.respond_to?(:applied_block_accessor)}.from(false).to(true)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should check that feature name is correct' do
|
36
|
+
expect{
|
37
|
+
define_test_feature('wrong feature name')
|
38
|
+
}.to raise_error(/Wrong feature name/)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should raise error if target already has feature class method' do
|
42
|
+
test_class.class_eval do
|
43
|
+
def self.existing_class_method; end
|
44
|
+
end
|
45
|
+
|
46
|
+
expect{
|
47
|
+
define_test_feature('existing_class_method') do
|
48
|
+
class_methods do
|
49
|
+
def existing_class_method; end
|
50
|
+
end
|
51
|
+
end.apply
|
52
|
+
}.to raise_error(/tried to define already existing class methods: \[:existing_class_method\]/)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should raise error if target already has feature instance method' do
|
56
|
+
test_class.class_eval do
|
57
|
+
def existing_instance_method; end
|
58
|
+
end
|
59
|
+
|
60
|
+
expect{
|
61
|
+
define_test_feature('existing_instance_method') do
|
62
|
+
instance_methods do
|
63
|
+
def existing_instance_method; end
|
64
|
+
end
|
65
|
+
end.apply
|
66
|
+
}.to raise_error(/tried to define already existing instance methods: \[:existing_instance_method\]/)
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
describe RubyFeatures do
|
2
|
+
|
3
|
+
prepare_test_class('FindAndApplyTestClass')
|
4
|
+
|
5
|
+
it 'should find features in path and apply all on demand' do
|
6
|
+
features_container = subject.find_in_path(File.expand_path('../ruby_features', __FILE__))
|
7
|
+
|
8
|
+
expect(test_class).to_not respond_to(:root_method)
|
9
|
+
expect(test_class).to_not respond_to(:nested_method)
|
10
|
+
|
11
|
+
features_container.apply_all
|
12
|
+
|
13
|
+
expect(test_class).to respond_to(:root_method)
|
14
|
+
expect(test_class).to respond_to(:nested_method)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should apply features by name' do
|
18
|
+
|
19
|
+
define_test_feature('manual') do
|
20
|
+
class_methods do
|
21
|
+
def manual_method; end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
expect(test_class).to_not respond_to(:manual_method)
|
26
|
+
RubyFeatures.apply('find_and_apply_test_class/manual')
|
27
|
+
expect(test_class).to respond_to(:manual_method)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should raise error if trying to apply not existing feature' do
|
31
|
+
expect{
|
32
|
+
RubyFeatures.apply('find_and_apply_test_class/not_existing_feature')
|
33
|
+
}.to raise_error(/Such feature is not registered/)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should raise error if trying to define already registered feature' do
|
37
|
+
RubyFeatures.define('find_and_apply_test_class/duplicate_feature')
|
38
|
+
|
39
|
+
expect{
|
40
|
+
RubyFeatures.define('find_and_apply_test_class/duplicate_feature')
|
41
|
+
}.to raise_error(/Such feature is already registered/)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
if RubyFeatures::Lazy.active_support_available?
|
2
|
+
describe RubyFeatures::Lazy do
|
3
|
+
|
4
|
+
it 'should use ActiveSupport lazy load' do
|
5
|
+
RubyFeatures.define 'lazy_load/active_support' do
|
6
|
+
apply_to 'ActiveRecord::Base' do
|
7
|
+
class_methods do
|
8
|
+
def lazy_active_support; end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end.apply
|
12
|
+
expect(defined?(ActiveRecord)).to_not be true
|
13
|
+
|
14
|
+
require 'active_record'
|
15
|
+
expect(ActiveRecord::Base).to respond_to(:lazy_active_support)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
RSpec.configure do
|
2
|
+
|
3
|
+
def prepare_test_class(class_parts)
|
4
|
+
class_parts = class_parts.split('::') unless class_parts.kind_of?(Array)
|
5
|
+
|
6
|
+
test_class = Object
|
7
|
+
|
8
|
+
until class_parts.empty?
|
9
|
+
test_class = class_parts.size > 1 ?
|
10
|
+
test_class.const_set(class_parts.shift, Module.new) :
|
11
|
+
test_class.const_set(class_parts.shift, Class.new)
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:test_class){ test_class }
|
15
|
+
end
|
16
|
+
|
17
|
+
def define_test_feature(feature_name_postfix, &block)
|
18
|
+
test_class_name = test_class.name
|
19
|
+
feature_name = "#{RubyFeatures::Utils.underscore(test_class_name)}/#{feature_name_postfix}"
|
20
|
+
|
21
|
+
RubyFeatures.define feature_name do
|
22
|
+
apply_to test_class_name, &block
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-features
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Sergey Tokarenko
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2015-05-17 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
description: Makes extending of Ruby classes and modules to be easy, safe and controlled.
|
31
|
+
email: private.tokarenko.sergey@gmail.com
|
32
|
+
executables: []
|
33
|
+
extensions: []
|
34
|
+
extra_rdoc_files: []
|
35
|
+
files:
|
36
|
+
- lib/generators/ruby_features/install_generator.rb
|
37
|
+
- lib/generators/ruby_features/templates/ruby-features.rb
|
38
|
+
- lib/ruby-features/concern.rb
|
39
|
+
- lib/ruby-features/container.rb
|
40
|
+
- lib/ruby-features/lazy.rb
|
41
|
+
- lib/ruby-features/mixins.rb
|
42
|
+
- lib/ruby-features/single.rb
|
43
|
+
- lib/ruby-features/utils/const_accessor_19.rb
|
44
|
+
- lib/ruby-features/utils/const_accessor_20.rb
|
45
|
+
- lib/ruby-features/utils.rb
|
46
|
+
- lib/ruby-features/version.rb
|
47
|
+
- lib/ruby-features.rb
|
48
|
+
- LICENSE
|
49
|
+
- README.md
|
50
|
+
- spec/define_spec.rb
|
51
|
+
- spec/find_and_apply_spec.rb
|
52
|
+
- spec/lazy_apply_spec.rb
|
53
|
+
- spec/ruby_features/nested/nested_feature.rb
|
54
|
+
- spec/ruby_features/root_feature.rb
|
55
|
+
- spec/spec_helper.rb
|
56
|
+
- spec/support/helpers.rb
|
57
|
+
homepage: https://github.com/stokarenko/ruby-features
|
58
|
+
licenses:
|
59
|
+
- MIT
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.9.3
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
requirements: []
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 1.8.23.2
|
79
|
+
signing_key:
|
80
|
+
specification_version: 3
|
81
|
+
summary: Makes extending of Ruby classes and modules to be easy, safe and controlled.
|
82
|
+
test_files:
|
83
|
+
- spec/define_spec.rb
|
84
|
+
- spec/find_and_apply_spec.rb
|
85
|
+
- spec/lazy_apply_spec.rb
|
86
|
+
- spec/ruby_features/nested/nested_feature.rb
|
87
|
+
- spec/ruby_features/root_feature.rb
|
88
|
+
- spec/spec_helper.rb
|
89
|
+
- spec/support/helpers.rb
|