advisor 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: dce5c0242a387f50ef334a97a2583cade06759e3
4
+ data.tar.gz: 3eccbc9c38091e8ab6eafd8f4a64f5fa2ecb07e7
5
+ SHA512:
6
+ metadata.gz: 3d749fa9ae030a74fa61ee73b3a96cf739f48c0233ba110575453150d3adb73030a4522fb61d80bc1f1a00b189b6b0cc949f2d5e3120448767c9e9cf84311cc0
7
+ data.tar.gz: 4427ed176fb927dda723ff41fdbc84ad07107dc02f5b255e6363177abd0f6fc207bf435b6591f68799d9ffb59d4c2f15e646a6a0d51845e1dcb4785dcad34958
data/.gitignore ADDED
@@ -0,0 +1,34 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+
12
+ ## Specific to RubyMotion:
13
+ .dat*
14
+ .repl_history
15
+ build/
16
+
17
+ ## Documentation cache and generated files:
18
+ /.yardoc/
19
+ /_yardoc/
20
+ /doc/
21
+ /rdoc/
22
+
23
+ ## Environment normalisation:
24
+ /.bundle/
25
+ /lib/bundler/man/
26
+
27
+ # for a library or gem, you might want to ignore these files since the code is
28
+ # intended to run in multiple environments; otherwise, check them in:
29
+ # Gemfile.lock
30
+ # .ruby-version
31
+ # .ruby-gemset
32
+
33
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
34
+ .rvmrc
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ Documentation:
2
+ Enabled: false
3
+
4
+ Style/Encoding:
5
+ Enabled: false
6
+
7
+ Style/MultilineOperationIndentation:
8
+ Enabled: false
9
+
10
+ AllCops:
11
+ Include:
12
+ - '**/*.gemspec'
13
+ - '**/Rakefile'
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.5
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,55 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ advisor (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ast (2.0.0)
10
+ astrolabe (1.3.0)
11
+ parser (>= 2.2.0.pre.3, < 3.0)
12
+ coderay (1.1.0)
13
+ diff-lcs (1.2.5)
14
+ method_source (0.8.2)
15
+ parser (2.2.0.3)
16
+ ast (>= 1.1, < 3.0)
17
+ powerpack (0.1.0)
18
+ pry (0.10.1)
19
+ coderay (~> 1.1.0)
20
+ method_source (~> 0.8.1)
21
+ slop (~> 3.4)
22
+ rainbow (2.0.0)
23
+ rake (10.4.2)
24
+ rspec (3.2.0)
25
+ rspec-core (~> 3.2.0)
26
+ rspec-expectations (~> 3.2.0)
27
+ rspec-mocks (~> 3.2.0)
28
+ rspec-core (3.2.3)
29
+ rspec-support (~> 3.2.0)
30
+ rspec-expectations (3.2.1)
31
+ diff-lcs (>= 1.2.0, < 2.0)
32
+ rspec-support (~> 3.2.0)
33
+ rspec-mocks (3.2.1)
34
+ diff-lcs (>= 1.2.0, < 2.0)
35
+ rspec-support (~> 3.2.0)
36
+ rspec-support (3.2.2)
37
+ rubocop (0.30.0)
38
+ astrolabe (~> 1.3)
39
+ parser (>= 2.2.0.1, < 3.0)
40
+ powerpack (~> 0.1)
41
+ rainbow (>= 1.99.1, < 3.0)
42
+ ruby-progressbar (~> 1.4)
43
+ ruby-progressbar (1.7.5)
44
+ slop (3.6.0)
45
+
46
+ PLATFORMS
47
+ ruby
48
+
49
+ DEPENDENCIES
50
+ advisor!
51
+ bundler (~> 1.7)
52
+ pry
53
+ rake (~> 10.0)
54
+ rspec (~> 3.0)
55
+ rubocop
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
data/Readme.org ADDED
@@ -0,0 +1,125 @@
1
+ * Advisor: Solve your cross-cutting concerns without mumbo-jumbo.
2
+
3
+ =Advisor= is Ruby gem that enables you to solve cross-cutting concerns without
4
+ the usual =method-renaming= present in most alternatives.
5
+
6
+ =Advisor= intercepts method calls and allows you to mix cross cutting concerns
7
+ and tedious book-keeping tasks. Logging, metric reporting, auditing, timing,
8
+ timeouting can be handled beautifully.
9
+
10
+ =Advisor= works with plain Ruby modules and do not mess your stack trace.
11
+
12
+ Also, the amount of /intrusion/ required to set up is kept to a minimum while
13
+ still keeping it /discoverable/. Every affected class must explicitly extend a
14
+ given module and every affected method call must also be declared.
15
+
16
+ *** Usage
17
+
18
+ =Advisor= is organized between two main concepts only: =Advisor modules= and
19
+ =Advice modules=. =Advisor modules= are extensions applied to your classes
20
+ and =Advice modules= define the actual behavior of the intercepted method
21
+ calls.
22
+
23
+ In order to understand better how =Advisor= works, we are going to use an
24
+ example:
25
+
26
+ ***** Example
27
+
28
+ Suppose you want to log calls to some methods but don't want to keep
29
+ repeating the message formatting or messing with the method body.
30
+ =Advisor= provides a simple built-in module called =Advisor::Loggable=
31
+ that solves this issue.
32
+
33
+ #+begin_src ruby
34
+ class Account
35
+ extend Advisor::Loggable
36
+
37
+ log_calls_to :deposit
38
+
39
+ def deposit(_amount, _origin)
40
+ #...
41
+ :done
42
+ end
43
+ end
44
+ #+end_src
45
+
46
+ In an interactive console:
47
+
48
+ #+begin_src ruby
49
+ $ Account.new.deposit(300, 'Jane Doe')
50
+ # => I, [2015-04-11T21:26:42.405180 #13840] INFO -- : [Time=2015-04-11 21:26:42 -0300][Thread=70183196300040]Called: Account#deposit(300, "Jane Doe")
51
+ # => :done
52
+ #+end_src
53
+
54
+ As you can see, the method call is intercepted and a message is printed to
55
+ =stdout=.
56
+
57
+ =Advisor= achieves this by using Ruby 2.0's =Module#prepend=. If you were
58
+ to check =Account='s ancestors you would get:
59
+
60
+ #+begin_src ruby
61
+ $ Account.ancestors
62
+ # => [Advisor::Advices::CallLogger(deposit), Account, Object, Kernel, BasicObject]
63
+ #+end_src
64
+
65
+ As you can see, the =Advisor::Advices::CallLogger(deposit)= module is
66
+ listed *before* Account itself in the ancestor chain.
67
+
68
+ In the next session we are going to explain how to write your own custom
69
+ advice.
70
+
71
+ ***** Writing an =Advice=
72
+
73
+ An =Advice= defines what to do with the advised method call.
74
+
75
+ The required interface for an advice must be like the example bellow:
76
+
77
+ #+begin_src ruby
78
+ class Advice
79
+ def initialize(receiver, advised_method, call_args, **options)
80
+ # The constructor of an advice must receive 3 arguments and extra options.
81
+ # Those extra options are defined when applying the extension to the advised
82
+ # class.
83
+ end
84
+
85
+ def self.applier_method
86
+ # Must return the name of the method which must be called in the class body
87
+ # to define which methods will be intercepted with the advice.
88
+
89
+ # In the case of `Advisor::Loggable`, this method returns `:log_calls_to`
90
+ end
91
+
92
+ def call
93
+ # This is the body of the advice.
94
+ #
95
+ # This method will always be called with the block `{ super(*call_args,
96
+ # &blk) }` That means the method implementation can decide when to run the
97
+ # advised method call. Check `Advisor::Advices::CallLogger` for an example.
98
+ end
99
+ end
100
+ #+end_src
101
+
102
+ ***** Creating an =Advisor= module
103
+
104
+ Every =Advisor= module must be built from the corresponding =Advice= by
105
+ using the =Advisor::Factory#build= method.
106
+
107
+ =Advisor::Loggable= is built from the =Advisor::Advices::CallLogger=
108
+ module.
109
+
110
+ =Advisor::Loggable= itself is built like this:
111
+
112
+ #+begin_src ruby
113
+ module Advisor
114
+ Loggable = Factory.new(Advices::CallLogger).build
115
+ end
116
+ #+end_src
117
+
118
+ Hence, if your custom =Advice= complies to the required interface,
119
+ =Advisor::Factory= will be able to convert it to an extension module with
120
+ no problems.
121
+
122
+ *** Disclaimer
123
+
124
+ This version of the library is still experimental and probably not
125
+ production ready. Use at your own risk.
data/advisor.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ require 'advisor/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'advisor'
8
+ spec.version = Advisor::VERSION
9
+ spec.required_ruby_version = '~>2.0'
10
+
11
+ spec.summary = 'AOP with anonymous modules'
12
+ spec.authors = ['Renan Ranelli']
13
+ spec.email = ['renanranelli@gmail.com']
14
+ spec.homepage = 'http://github.com/rranelli/advisor'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.executables = spec.files.grep(%r{^bin\/}) { |f| File.basename(f) }
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.7'
22
+ spec.add_development_dependency 'rspec', '~> 3.0'
23
+ spec.add_development_dependency 'rake', '~> 10.0'
24
+ spec.add_development_dependency 'rubocop'
25
+ spec.add_development_dependency 'pry'
26
+ end
@@ -0,0 +1,69 @@
1
+ require 'logger'
2
+
3
+ module Advisor
4
+ module Advices
5
+ class CallLogger
6
+ class << self
7
+ attr_accessor :default_logger
8
+ end
9
+ self.default_logger = Logger.new(STDOUT)
10
+
11
+ def initialize(object, method, call_args, **opts)
12
+ @object = object
13
+ @method = method
14
+ @call_args = call_args
15
+ @logger = opts[:logger] || CallLogger.default_logger
16
+ end
17
+
18
+ attr_reader :object, :method, :call_args, :logger
19
+
20
+ def self.applier_method
21
+ :log_calls_to
22
+ end
23
+
24
+ def call
25
+ logger.info(success_message)
26
+ yield
27
+ rescue => e
28
+ logger.warn(failure_message(e))
29
+ raise
30
+ end
31
+
32
+ private
33
+
34
+ def success_message
35
+ call_message('Called: ')
36
+ end
37
+
38
+ def failure_message(ex)
39
+ call_message('Failed: ', "\n#{ex}")
40
+ end
41
+
42
+ def call_message(prefix, suffix = '')
43
+ "#{time}#{thread}#{id}#{prefix}\
44
+ #{klass}##{method}(#{arguments})\
45
+ #{suffix}"
46
+ end
47
+
48
+ def thread
49
+ "[Thread=#{Thread.current.object_id}]"
50
+ end
51
+
52
+ def time
53
+ "[Time=#{Time.now}]"
54
+ end
55
+
56
+ def klass
57
+ object.class
58
+ end
59
+
60
+ def arguments
61
+ call_args.map(&:inspect).join(', ')
62
+ end
63
+
64
+ def id
65
+ "[id=#{object.id}]" if object.respond_to?(:id)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1 @@
1
+ require_relative 'advices/call_logger'
@@ -0,0 +1,5 @@
1
+ require 'logger'
2
+
3
+ module Advisor
4
+ Loggable = Factory.new(Advices::CallLogger).build
5
+ end
@@ -0,0 +1,45 @@
1
+ module Advisor
2
+ class Factory
3
+ def initialize(advice_klass)
4
+ @advice_klass = advice_klass
5
+ end
6
+
7
+ def build
8
+ advice_klazz = advice_klass
9
+ advisor_module = method(:advisor_module)
10
+
11
+ Module.new do
12
+ define_method(advice_klazz.applier_method) do |*methods, **args|
13
+ methods_str = methods.map(&:to_s).join(', ')
14
+
15
+ mod = advisor_module.call(methods, args)
16
+ mod.module_eval(%(def self.inspect
17
+ "#{advice_klazz}(#{methods_str})"
18
+ end))
19
+ mod.module_eval(%(def self.to_s; inspect; end))
20
+
21
+ prepend mod
22
+ end
23
+ end
24
+ end
25
+
26
+ protected
27
+
28
+ attr_reader :advice_klass
29
+
30
+ private
31
+
32
+ def advisor_module(methods, args)
33
+ advice_klazz = advice_klass
34
+
35
+ Module.new do
36
+ methods.each do |method_name|
37
+ define_method(method_name) do |*call_args, &blk|
38
+ advice = advice_klazz.new(self, method_name, call_args, **args)
39
+ advice.call { super(*call_args, &blk) }
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,3 @@
1
+ module Advisor
2
+ VERSION = '0.1.0'
3
+ end
data/lib/advisor.rb ADDED
@@ -0,0 +1,4 @@
1
+ require_relative 'advisor/version'
2
+ require_relative 'advisor/advices'
3
+ require_relative 'advisor/factory'
4
+ require_relative 'advisor/builtin_advisors'
@@ -0,0 +1,60 @@
1
+ require 'ostruct'
2
+
3
+ module Advisor
4
+ module Advices
5
+ describe CallLogger do
6
+ subject(:advice) do
7
+ described_class.new(object, method, args, logger: logger)
8
+ end
9
+
10
+ let(:object) { OpenStruct.new(id: 42) }
11
+ let(:method) { 'the_meaning_of_life' }
12
+ let(:args) { ['the universe', 'and everything'] }
13
+ let(:logger) { instance_double(Logger) }
14
+
15
+ let(:block) { -> () { :bla } }
16
+
17
+ describe '#call' do
18
+ subject(:call) { advice.call(&block) }
19
+
20
+ let(:log_message) do
21
+ "[Time=#{Time.now}][Thread=#{Thread.current.object_id}][id=42]\
22
+ Called: OpenStruct#the_meaning_of_life(\"the universe\", \"and everything\")"
23
+ end
24
+
25
+ before do
26
+ allow(Time).to receive(:now).and_return(Time.now)
27
+ allow(logger).to receive(:info)
28
+ end
29
+
30
+ it { is_expected.to eq(:bla) }
31
+
32
+ it do
33
+ expect(logger).to receive(:info).with(log_message)
34
+
35
+ call
36
+ end
37
+
38
+ context 'when yielding the block raises an exception' do
39
+ let(:block) { -> () { fail 'deu ruim!' } }
40
+
41
+ let(:log_message) do
42
+ "[Time=#{Time.now}][Thread=#{Thread.current.object_id}][id=42]\
43
+ Failed: OpenStruct#the_meaning_of_life(\"the universe\", \"and everything\")
44
+ deu ruim!"
45
+ end
46
+
47
+ before { allow(logger).to receive(:warn) }
48
+
49
+ it { expect { call }.to raise_error(StandardError, 'deu ruim!') }
50
+
51
+ it do
52
+ expect(logger).to receive(:warn).with(log_message)
53
+
54
+ expect { call }.to raise_error
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,91 @@
1
+ require 'logger'
2
+
3
+ module Advisor
4
+ describe Factory do
5
+ subject(:loggable) do
6
+ Advices::CallLogger.default_logger = default_logger
7
+
8
+ Class.new do
9
+ extend Loggable
10
+ log_calls_to :inc
11
+
12
+ def inc(number)
13
+ number + 1
14
+ end
15
+
16
+ def foo(*)
17
+ end
18
+ end
19
+ end
20
+
21
+ let(:loggable_instance) { loggable.new }
22
+ let(:default_logger) { instance_double(Logger, info: nil, warn: nil) }
23
+ let(:call_logger) do
24
+ Advices::CallLogger.new(
25
+ loggable_instance, :inc, [1], logger: default_logger
26
+ )
27
+ end
28
+
29
+ before do
30
+ allow(Advices::CallLogger).to receive(:new)
31
+ .and_return(call_logger)
32
+ end
33
+
34
+ it 'instantiates a call logger when calling the advised method' do
35
+ expect(Advices::CallLogger)
36
+ .to receive(:new)
37
+ .with(loggable_instance, :inc, [1], {})
38
+ .and_call_original
39
+
40
+ loggable_instance.inc(1)
41
+ end
42
+
43
+ it 'uses the call_logger to log the method call' do
44
+ expect(call_logger)
45
+ .to receive(:call)
46
+ .and_call_original
47
+
48
+ loggable_instance.inc(1)
49
+ end
50
+
51
+ it 'does not change the return value' do
52
+ expect(loggable_instance.inc(1)).to eq(2)
53
+ end
54
+
55
+ it 'does not log when calling a non-advised method' do
56
+ expect(Advices::CallLogger).to_not receive(:new)
57
+
58
+ loggable_instance.foo
59
+ end
60
+
61
+ describe '#log_calls_to' do
62
+ subject(:log_calls_to) { loggable.send(:log_calls_to, :foo, :bar) }
63
+
64
+ it 'prepends an anonymous module in the ancestor chain' do
65
+ expect(loggable).to receive(:prepend)
66
+ .and_call_original
67
+
68
+ expect { log_calls_to }.to change { loggable.ancestors.count }.by(1)
69
+ end
70
+
71
+ context 'when redefining an advised method' do
72
+ let(:child_class) do
73
+ Class.new(loggable) do
74
+ def inc(number)
75
+ number + 2
76
+ end
77
+ end
78
+ end
79
+
80
+ let(:child_class_instance) { child_class.new }
81
+
82
+ it 'the advice is overridden' do
83
+ log_calls_to
84
+
85
+ expect(Advices::CallLogger).not_to receive(:new)
86
+ expect(child_class_instance.inc(1)).to eq(3)
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,56 @@
1
+ describe Advisor::Factory do
2
+ subject(:factory) { described_class.new(advice_klass) }
3
+
4
+ let(:advice_klass) do
5
+ Struct.new(:obj, :method, :call_args, :args) do
6
+ define_method(:call) { 'overridden!' }
7
+ define_singleton_method(:applier_method) { 'apply_advice_to' }
8
+ end
9
+ end
10
+ let(:advice_instance) do
11
+ advice_klass.new(advised_instance, :apply_advice_to, [], arg1: 1, arg2: 2)
12
+ end
13
+
14
+ let(:advised_klass) do
15
+ advisor = build
16
+
17
+ Struct.new(:advised_method) do
18
+ extend advisor
19
+
20
+ apply_advice_to :advised_method, arg1: 1, arg2: 2
21
+ end
22
+ end
23
+ let(:advised_instance) { advised_klass.new(33) }
24
+
25
+ before do
26
+ allow(advised_klass).to receive(:new)
27
+ .and_return(advised_instance)
28
+
29
+ allow(advice_klass).to receive(:new)
30
+ .and_return(advice_instance)
31
+ end
32
+
33
+ describe '#build' do
34
+ subject(:build) { factory.build }
35
+
36
+ it { is_expected.to be_kind_of(Module) }
37
+
38
+ describe 'when applying the advice to methods' do
39
+ subject(:invoke_advised_method) { advised_instance.advised_method }
40
+
41
+ it do
42
+ expect(advice_klass).to receive(:new)
43
+ .with(advised_instance, :advised_method, [], arg1: 1, arg2: 2)
44
+
45
+ invoke_advised_method
46
+ end
47
+
48
+ it do
49
+ expect(advice_instance).to receive(:call)
50
+
51
+ invoke_advised_method
52
+ end
53
+ it { is_expected.to eq('overridden!') }
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,5 @@
1
+ require 'advisor'
2
+
3
+ RSpec.configure do |config|
4
+ config.order = 'random'
5
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: advisor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Renan Ranelli
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description:
84
+ email:
85
+ - renanranelli@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".rspec"
92
+ - ".rubocop.yml"
93
+ - ".ruby-version"
94
+ - Gemfile
95
+ - Gemfile.lock
96
+ - Rakefile
97
+ - Readme.org
98
+ - advisor.gemspec
99
+ - lib/advisor.rb
100
+ - lib/advisor/advices.rb
101
+ - lib/advisor/advices/call_logger.rb
102
+ - lib/advisor/builtin_advisors.rb
103
+ - lib/advisor/factory.rb
104
+ - lib/advisor/version.rb
105
+ - spec/advisor/advices/call_logger_spec.rb
106
+ - spec/advisor/builtin_advisors_spec.rb
107
+ - spec/advisor/factory_spec.rb
108
+ - spec/spec_helper.rb
109
+ homepage: http://github.com/rranelli/advisor
110
+ licenses:
111
+ - MIT
112
+ metadata: {}
113
+ post_install_message:
114
+ rdoc_options: []
115
+ require_paths:
116
+ - lib
117
+ required_ruby_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - "~>"
120
+ - !ruby/object:Gem::Version
121
+ version: '2.0'
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ requirements: []
128
+ rubyforge_project:
129
+ rubygems_version: 2.2.2
130
+ signing_key:
131
+ specification_version: 4
132
+ summary: AOP with anonymous modules
133
+ test_files: []
134
+ has_rdoc: