mtrack 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 09f257cc34dc5d1126287a8d5086b567de5bb4be
4
+ data.tar.gz: 0131aac3c34421089a18246398b58ca2d69627dd
5
+ SHA512:
6
+ metadata.gz: 3e477c84966256fa56a36de12e8031b3272e98f1980e6904cd0720fc8206491a38afd40c64e0ff259462e55b741291ab26e0c10477f96195f987e213b39f1641
7
+ data.tar.gz: a6b86548b5075ed830d586c12074e7edf2d1ca7c2f4d9dd12c2dbb993861247578f00002aaffd6033512937adcf97421e85b3eb374878279ec5333a8e1339d9a
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ .buildpath
16
+ .project
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.2
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ rvm:
3
+ - ruby-head
4
+ - 2.1
5
+ - 2.0
6
+ - 1.9.3
7
+ - 1.9.2
8
+ - 1.8.7
9
+ addons:
10
+ code_climate:
11
+ repo_token: cc41eb6449075390a8bdb804fa3ff5865a7fbf22384fcd1757730b702eefdb0a
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "https://rubygems.org"
2
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,5 @@
1
+ guard :rspec, cmd: "rspec" do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) {|m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch("spec/spec_helper.rb") { "spec" }
5
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Gabriel de Oliveira
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,190 @@
1
+ # MTrack
2
+
3
+ [![Gem Version](http://img.shields.io/gem/v/mtrack.svg)][gem]
4
+ [![Build Status](http://img.shields.io/travis/gdeoliveira/mtrack.svg)][travis]
5
+ [![Code Climate](http://img.shields.io/codeclimate/github/gdeoliveira/mtrack.svg)][codeclimate]
6
+ [![Test Coverage](http://img.shields.io/codeclimate/coverage/github/gdeoliveira/mtrack.svg)][codeclimate]
7
+ [![Inline docs](http://inch-ci.org/github/gdeoliveira/mtrack.svg?branch=master)][inch-ci]
8
+
9
+ [gem]: https://rubygems.org/gems/mtrack
10
+ [travis]: http://travis-ci.org/gdeoliveira/mtrack
11
+ [codeclimate]: https://codeclimate.com/github/gdeoliveira/mtrack
12
+ [inch-ci]: http://inch-ci.org/github/gdeoliveira/mtrack
13
+
14
+ MTrack extends the functionality of Modules and Classes and enables them to
15
+ define public methods within groups. These methods can then be queried back even
16
+ through a hierarchy of inclusion and/or inheritance.
17
+
18
+ ## Installation
19
+
20
+ Add this line to your application's Gemfile:
21
+
22
+ ```ruby
23
+ gem "mtrack"
24
+ ```
25
+
26
+ And then execute:
27
+
28
+ $ bundle
29
+
30
+ Or install it yourself as:
31
+
32
+ $ gem install mtrack
33
+
34
+ ## Usage
35
+
36
+ To track a group of methods within a Module (or a Class).
37
+
38
+ ```ruby
39
+ require "mtrack"
40
+
41
+ module Stooges
42
+ def shemp; end
43
+
44
+ track_methods do
45
+ def curly; end
46
+ def larry; end
47
+ def moe; end
48
+ end
49
+ end
50
+
51
+ Stooges.tracked_methods #=> #<Set: {:curly, :larry, :moe}>
52
+ ```
53
+
54
+ Methods can be grouped using an optional name.
55
+
56
+ ```ruby
57
+ require "mtrack"
58
+
59
+ module Numbers
60
+ def zero; end
61
+
62
+ track_methods :integers do
63
+ track_methods :odd do
64
+ def one; end
65
+ def three; end
66
+ end
67
+
68
+ track_methods :even do
69
+ def two; end
70
+ def four; end
71
+ end
72
+ end
73
+ end
74
+
75
+ Numbers.tracked_methods :integers #=> #<Set: {:one, :three, :two, :four}>
76
+ Numbers.tracked_methods :odd #=> #<Set: {:one, :three}>
77
+ Numbers.tracked_methods :even #=> #<Set: {:two, :four}>
78
+ ```
79
+
80
+ Tracked methods can be carried to other Modules and Classes via inclusion and
81
+ inheritance.
82
+
83
+ ```ruby
84
+ # We're using the previously defined Stooges and Numbers modules here.
85
+
86
+ class MyClass
87
+ include Stooges
88
+ include Numbers
89
+ end
90
+
91
+ class MySubClass < MyClass
92
+ end
93
+
94
+ MySubClass.tracked_methods #=> #<Set: {:curly, :larry, :moe}>
95
+ MySubClass.tracked_methods :integers #=> #<Set: {:one, :three, :two, :four}>
96
+ ```
97
+
98
+ ## Example
99
+
100
+ ### Simple State Machine
101
+
102
+ We'll create a simple state machine using MTrack. First, let's create an
103
+ abstraction for the state machine.
104
+
105
+ ```ruby
106
+ require "mtrack"
107
+
108
+ class SimpleStateMachine
109
+ class << self
110
+ private
111
+
112
+ alias_method :allow_while, :track_methods
113
+
114
+ def actions(transitions)
115
+ transitions.each do |action, state|
116
+ define_method action, transition_implementation(action, state)
117
+ end
118
+ end
119
+
120
+ def transition_implementation(action, new_state)
121
+ proc do
122
+ if self.class.tracked_methods(state).include? action
123
+ self.state = new_state
124
+ state_changed action
125
+ else
126
+ state_not_changed action
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ def initialize(state)
133
+ self.state = state
134
+ end
135
+
136
+ private
137
+
138
+ attr_accessor :state
139
+ end
140
+ ```
141
+
142
+ And now we'll implement a box that can be either _locked_, _closed_ or _open_.
143
+
144
+ ```ruby
145
+ class Box < SimpleStateMachine
146
+ def initialize
147
+ super :locked
148
+ end
149
+
150
+ allow_while(:locked) { actions :unlock => :closed }
151
+ allow_while(:closed) { actions :lock => :locked, :open => :open }
152
+ allow_while(:open) { actions :close => :closed }
153
+
154
+ def look
155
+ "The box is #{state}."
156
+ end
157
+
158
+ private
159
+
160
+ def state_changed(action)
161
+ "You #{action} the box."
162
+ end
163
+
164
+ def state_not_changed(action)
165
+ "You can't #{action} the box while it is #{state}!"
166
+ end
167
+ end
168
+ ```
169
+
170
+ A sample output for our box could be:
171
+
172
+ ```ruby
173
+ box = Box.new #=> #<Box:0x007f00f0be30c8 @state=:locked>
174
+ box.look #=> "The box is locked."
175
+ box.open #=> "You can't open the box while it is locked!"
176
+ box.unlock #=> "You unlock the box."
177
+ box.look #=> "The box is closed."
178
+ box.open #=> "You open the box."
179
+ box.lock #=> "You can't lock the box while it is open!"
180
+ box.close #=> "You close the box."
181
+ box.lock #=> "You lock the box."
182
+ ```
183
+
184
+ ## Contributing
185
+
186
+ 1. Fork it ( https://github.com/gdeoliveira/mtrack/fork )
187
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
188
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
189
+ 4. Push to the branch (`git push origin my-new-feature`)
190
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ Dir[File.join(File.dirname(__FILE__), "tasks", "**", "*.rb")].each do |file|
5
+ require file
6
+ end
7
+
8
+ RSpec::Core::RakeTask.new(:spec)
9
+
10
+ task :default => :coverage
data/lib/mtrack.rb ADDED
@@ -0,0 +1,4 @@
1
+ require "mtrack/module_mixin"
2
+ require "mtrack/version"
3
+
4
+ Module.send(:include, MTrack::ModuleMixin)
@@ -0,0 +1,79 @@
1
+ require "mtrack/state"
2
+
3
+ module MTrack
4
+
5
+ ##
6
+ # Implements the core tracking functionality of the gem by extending those
7
+ # Modules and Classes that use MTrack::ModuleMixin#track_methods. Additionally
8
+ # it will extend Modules and Classes that include a Module that is tracking
9
+ # methods and Classes that inherit from a Class that is tracking methods.
10
+ module Core
11
+
12
+ ##
13
+ # call-seq:
14
+ # tracked_methods(group_name = nil) => set
15
+ #
16
+ # Returns a set containing the currently tracked methods. An optional
17
+ # +group_name+ parameter can be passed to get the tracked methods of groups
18
+ # othen than the default +nil+ group.
19
+ #
20
+ # class C
21
+ # track_methods :my_group do
22
+ # def method_1; end
23
+ # def method_2; end
24
+ # end
25
+ # end
26
+ #
27
+ # C.tracked_methods :my_group #=> #<Set: {:method_1, :method_2}>
28
+ def tracked_methods(group_name = nil)
29
+ @__mtrack__.tracked group_name
30
+ end
31
+
32
+ private
33
+
34
+ ##
35
+ # Sets this state as a super-state of the Class or Module that has included
36
+ # the current Module.
37
+ def included(submodule)
38
+ state = @__mtrack__
39
+ submodule.instance_eval do
40
+ if @__mtrack__.nil?
41
+ @__mtrack__ = State.new(state)
42
+ extend Core
43
+ else
44
+ @__mtrack__.add_super_state state
45
+ end
46
+ end
47
+ end
48
+
49
+ ##
50
+ # Sets this state as a super-state of the Class that has inherited from the
51
+ # current Class.
52
+ def inherited(subclass)
53
+ state = @__mtrack__
54
+ subclass.instance_eval { @__mtrack__ = State.new(state) }
55
+ end
56
+
57
+ ##
58
+ # Allows method +name+ to be displayed on #tracked_methods once again after
59
+ # being disabled by a call to #method_undefined.
60
+ def method_added(name)
61
+ @__mtrack__.delete_undefined name
62
+ end
63
+
64
+ ##
65
+ # Stops tracking method +name+ in the current Class or Module.
66
+ def method_removed(name)
67
+ @__mtrack__.delete_tracked name
68
+ end
69
+
70
+ ##
71
+ # Stops tracking method +name+ in the current Class or Module and prevents
72
+ # homonymous methods tracked in super-states from being displayed as
73
+ # #tracked_methods.
74
+ def method_undefined(name)
75
+ @__mtrack__.delete_tracked name
76
+ @__mtrack__.add_undefined name
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,52 @@
1
+ require "set"
2
+
3
+ require "mtrack/core"
4
+ require "mtrack/state"
5
+
6
+ module MTrack
7
+
8
+ ##
9
+ # Provides the #track_methods method to all Classes and Modules by being mixed
10
+ # into the +Module+ class.
11
+ module ModuleMixin
12
+ private
13
+
14
+ ##
15
+ # call-seq:
16
+ # track_methods(group_name = nil) => set
17
+ # track_methods(group_name = nil) {|| ... } => set
18
+ #
19
+ # Sets up an MTrack::State instance for this Class or Module and extends it
20
+ # using MTrack::Core.
21
+ #
22
+ # If a block is provided all the methods defined within the block will be
23
+ # tracked under the optional +group_name+ parameter.
24
+ #
25
+ # Returns a set containing the methods that were defined within the block or
26
+ # an empty set otherwise.
27
+ #
28
+ # class C
29
+ # track_methods do
30
+ # def method_1; end
31
+ # track_methods(:inner_group_1) { def method_2; end }
32
+ # def method_3; end
33
+ # end
34
+ # end #=> #<Set: {:method_1, :method_2, :method_3}>
35
+ def track_methods(group_name = nil, &b)
36
+ @__mtrack__ ||= State.new
37
+ extend Core
38
+
39
+ if block_given?
40
+ old_methods = public_instance_methods(false)
41
+ begin
42
+ module_eval(&b)
43
+ ensure
44
+ tracked = (public_instance_methods(false) - old_methods).map(&:to_sym).to_set
45
+ @__mtrack__[group_name].merge_tracked tracked unless tracked.empty?
46
+ end
47
+ end
48
+
49
+ tracked || Set.new
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,113 @@
1
+ require "set"
2
+
3
+ require "mtrack/state/group"
4
+
5
+ module MTrack
6
+
7
+ ##
8
+ # Holds the internal state of tracked methods on Modules and Classes.
9
+ class State
10
+
11
+ ##
12
+ # call-seq:
13
+ # new(super_state = nil) => new_state
14
+ #
15
+ # Creates a new State instance. An optional +super_state+ parameter can be
16
+ # passed that will be added to #super_states.
17
+ def initialize(super_state = nil)
18
+ self.groups = {}
19
+ self.super_states = super_state ? Set[super_state] : Set.new
20
+ self.undefined = Set.new
21
+ end
22
+
23
+ ##
24
+ # call-seq:
25
+ # state[group_name] => group
26
+ #
27
+ # Returns a particular group in #groups. If the group doesn't exist it will
28
+ # be created.
29
+ def [](group_name)
30
+ groups[group_name] ||= Group.new
31
+ end
32
+
33
+ ##
34
+ # call-seq:
35
+ # add_super_state(state) => state
36
+ #
37
+ # Adds a new state to #super_states.
38
+ def add_super_state(state)
39
+ super_states.add state
40
+ state
41
+ end
42
+
43
+ ##
44
+ # call-seq:
45
+ # add_undefined(name) => name
46
+ #
47
+ # Adds +name+ to the #undefined methods set.
48
+ def add_undefined(name)
49
+ undefined.add name
50
+ name
51
+ end
52
+
53
+ ##
54
+ # call-seq:
55
+ # delete_tracked(name) => name
56
+ #
57
+ # Removes method +name+ from all #groups.
58
+ def delete_tracked(name)
59
+ groups.each {|k, v| v.delete_tracked name }
60
+ name
61
+ end
62
+
63
+ ##
64
+ # call-seq:
65
+ # delete_undefined(name) => name
66
+ #
67
+ # Removes +name+ from the #undefined methods set.
68
+ def delete_undefined(name)
69
+ undefined.delete name
70
+ name
71
+ end
72
+
73
+ ##
74
+ # call-seq:
75
+ # tracked(group_name = nil) => set
76
+ #
77
+ # Returns a set containing the currently tracked methods. An optional
78
+ # +group_name+ parameter can be passed to get the tracked methods of groups
79
+ # othen than the default +nil+ group.
80
+ def tracked(group_name = nil)
81
+ ret_val = merge_super_states group_name
82
+ ret_val.merge groups[group_name].tracked unless groups[group_name].nil?
83
+ ret_val.subtract undefined
84
+ end
85
+
86
+ private
87
+
88
+ ##
89
+ # A Hash containing the groups defined in the current Class or Module.
90
+ attr_accessor :groups
91
+
92
+ ##
93
+ # A set of references to the states of inherited Classes and included
94
+ # Modules.
95
+ attr_accessor :super_states
96
+
97
+ ##
98
+ # A set of methods that are currently undefined on this Class or Module.
99
+ attr_accessor :undefined
100
+
101
+ ##
102
+ # call-seq:
103
+ # merge_super_states(group_name) => set
104
+ #
105
+ # Returns a set containing all the methods being tracked for +group_name+
106
+ # by the #super_states.
107
+ def merge_super_states(group_name)
108
+ super_states.inject(Set.new) do |set, state|
109
+ set.merge state.tracked(group_name)
110
+ end
111
+ end
112
+ end
113
+ end