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 +7 -0
- data/.gitignore +16 -0
- data/.ruby-version +1 -0
- data/.travis.yml +11 -0
- data/Gemfile +2 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +190 -0
- data/Rakefile +10 -0
- data/lib/mtrack.rb +4 -0
- data/lib/mtrack/core.rb +79 -0
- data/lib/mtrack/module_mixin.rb +52 -0
- data/lib/mtrack/state.rb +113 -0
- data/lib/mtrack/state/group.rb +55 -0
- data/lib/mtrack/version.rb +24 -0
- data/mtrack.gemspec +32 -0
- data/spec/lib/mtrack/core_spec.rb +377 -0
- data/spec/lib/mtrack/state/group_spec.rb +38 -0
- data/spec/lib/mtrack/state_spec.rb +135 -0
- data/spec/lib/mtrack/version_spec.rb +5 -0
- data/spec/spec_helper.rb +21 -0
- data/tasks/console.rb +13 -0
- data/tasks/coverage.rb +9 -0
- metadata +157 -0
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
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.1.2
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
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]
|
4
|
+
[][travis]
|
5
|
+
[][codeclimate]
|
6
|
+
[][codeclimate]
|
7
|
+
[][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
data/lib/mtrack.rb
ADDED
data/lib/mtrack/core.rb
ADDED
@@ -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
|
data/lib/mtrack/state.rb
ADDED
@@ -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
|