rubyunderscore 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,60 @@
1
+ RubyUnderscore
2
+ ==============
3
+
4
+ [Closures](http://metaphysicaldeveloper.wordpress.com/2009/05/02/closures-collections-and-some-functional-programming/)
5
+ are very useful tools, and ruby
6
+ [Enumerable](http://ruby-doc.org/core-1.8.7/classes/Enumerable.html) mixin makes them even
7
+ more useful.
8
+
9
+ However, as you decompose more and more your iterations into a sequence of
10
+ [maps](http://ruby-doc.org/core-1.8.7/classes/Enumerable.html#M001146),
11
+ [selects](http://ruby-doc.org/core-1.8.7/classes/Enumerable.html#M001143),
12
+ [rejects](http://ruby-doc.org/core-1.8.7/classes/Enumerable.html#M001144),
13
+ [group_bys](http://ruby-doc.org/core-1.8.7/classes/Enumerable.html#M001150) and
14
+ [reduces](http://ruby-doc.org/core-1.8.7/classes/Enumerable.html#M001148), more commonly
15
+ you see simple blocks such as:
16
+
17
+ collection.map { |x| x.invoke }
18
+ dates.select { |d| d.greater_than(old_date) }
19
+ classes.reject { |c| c.subclasses.include?(Array) }
20
+
21
+ RubyUnderscore modify classes so that you can also use a short notation for simple closures. With such, the above examples can be written as:
22
+
23
+ collection.map _.invoke
24
+ dates.select _.greater_than old_date
25
+ classes.reject _.subclasses.include? Array
26
+
27
+ Just replace the iterating argument with the underscore symbol (*_*), and ditch the
28
+ parenthesis. [More info](http://metaphysicaldeveloper.wordpress.com/2010/10/31/rubyunderscore-a-bit-of-arc-and-scala-in-ruby/)
29
+
30
+ Quick Example
31
+ ----
32
+ The example consists of getting all instance methods of String, Array, Class that end with 'd?'
33
+
34
+ require 'ruby_underscore'
35
+
36
+ class MethodFinder
37
+ include RubyUnderscore::Base
38
+
39
+ def find_interrogation_methods
40
+ [String, Array, Class].map(_.public_instance_methods.grep /d\?$/).flatten.sort.uniq
41
+ end
42
+ end
43
+ p MethodFinder.new.find_interrogation_methods
44
+
45
+
46
+ Using Ruby Underscore
47
+ ----
48
+ As in the example above, simply by including the module include RubyUnderscore::Base on the
49
+ class, all methods (class methods as well) will allow you to use the underscore symbol to
50
+ write simple blocks.
51
+
52
+
53
+ Meta
54
+ ----
55
+
56
+ Created by Daniel Ribeiro
57
+
58
+ Released under the MIT License: http://www.opensource.org/licenses/mit-license.php
59
+
60
+ http://github.com/danielribeiro/RubyUnderscore
data/Rakefile ADDED
@@ -0,0 +1,44 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/gempackagetask'
5
+ require 'rake/rdoctask'
6
+ require 'rake/testtask'
7
+ require 'spec/rake/spectask'
8
+
9
+ task :default => [:spec]
10
+
11
+ begin
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ gem.name = "rubyunderscore"
15
+ gem.summary = %Q{Simple way to create simple blocks}
16
+ gem.description = %Q{It allows you to create simple blocks by using underscore symbol}
17
+ gem.email = "danrbr+rubyunderscore@gmail.com"
18
+ gem.homepage = "http://github.com/danielribeiro/RubyUnderscore"
19
+ gem.authors = ["Daniel Ribeiro"]
20
+ gem.add_dependency 'ParseTree', '=3.0.5'
21
+ gem.add_dependency 'ruby2ruby'
22
+ gem.files = FileList["[A-Z]*", "{bin,lib}/**/*"]
23
+ # gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
24
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
25
+ end
26
+ Jeweler::GemcutterTasks.new
27
+ rescue LoadError
28
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
29
+ end
30
+
31
+ require 'rake/rdoctask'
32
+ Rake::RDocTask.new do |rdoc|
33
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
34
+
35
+ rdoc.rdoc_dir = 'rdoc'
36
+ rdoc.title = "rubyunderscore #{version}"
37
+ rdoc.rdoc_files.include('README*')
38
+ rdoc.rdoc_files.include('lib/**/*.rb')
39
+ end
40
+
41
+ Spec::Rake::SpecTask.new do |t|
42
+ t.spec_files = FileList['spec/**/*.rb']
43
+ t.libs << Dir["lib"]
44
+ end
data/TODO ADDED
@@ -0,0 +1,7 @@
1
+ - Better readme
2
+ - gem and publish
3
+
4
+ - enhance class
5
+ - changer of ruby files (source)
6
+ - debug support
7
+ - rdoc
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,21 @@
1
+ require 'tree_converters'
2
+
3
+ module RubyUnderscore
4
+ module Base
5
+ module ClassMethods
6
+ def method_added(method_name)
7
+ super
8
+ UnderscoreEnhancer.new.enhance self, method_name
9
+ end
10
+
11
+ def singleton_method_added(method_name)
12
+ super
13
+ metaclass = class << self; self; end
14
+ UnderscoreEnhancer.new.enhance metaclass, method_name
15
+ end
16
+ end
17
+ def self.included(receiver)
18
+ receiver.extend ClassMethods
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,233 @@
1
+ #gem 'ParseTree', '=3.0.5'
2
+ require 'pp'
3
+ require 'sexp_processor'
4
+ require 'ruby2ruby'
5
+ require 'unified_ruby'
6
+ require 'parse_tree'
7
+ module RubyUnderscore
8
+ class AbstractProcessor < SexpProcessor
9
+ def initialize
10
+ super
11
+ @alternate = SexpProcessor.new
12
+ end
13
+
14
+ def proceed(exp)
15
+ @alternate.process exp
16
+ end
17
+
18
+ def assert_empty(meth, exp, exp_orig)
19
+
20
+ end
21
+ end
22
+
23
+ module EnhancerHelper
24
+ # Deep Clone or arrays. Focused on arrays to be converted into sexp.
25
+ def clone(array)
26
+ a = []
27
+ array.each do | x |
28
+ a << if x.is_a? Array
29
+ clone(x)
30
+ elsif x.is_a? Symbol or x.is_a? Fixnum or x.is_a? Regexp
31
+ x
32
+ else
33
+ x.clone
34
+ end
35
+ end
36
+ a
37
+ end
38
+
39
+ def chain(sexp, *processorClasses)
40
+ processorClasses.inject(sexp) do |memo, clas|
41
+ clas.new.process memo
42
+ end
43
+ end
44
+
45
+ def needsEnhancing(clas, method)
46
+ sexpNeedsEnhancing sexpOf clas, method
47
+ end
48
+
49
+
50
+ def sexpOf(clas, method)
51
+ ParseTree.translate clas, method
52
+ end
53
+
54
+ def sexpNeedsEnhancing(sexp)
55
+ sexpEnhancingCount(sexp) > 0
56
+ end
57
+
58
+ def sexpEnhancingCount(sexp)
59
+ return 0 if sexp.nil?
60
+ parser = EnhancerDetector.new
61
+ parser.process clone sexp
62
+ return parser.enhanceCount
63
+ end
64
+
65
+ def assertSexpIs(sexp, type)
66
+ raise "Wrong sexp type: #{sexp.first}" unless sexp.first == type
67
+ end
68
+
69
+ class EnhancerDetector < AbstractProcessor
70
+ attr_reader :enhanceCount
71
+
72
+ def initialize
73
+ super
74
+ @enhanceCount = 0
75
+ end
76
+
77
+ def process_vcall(sexp)
78
+ @enhanceCount += 1 if sexp[1] == :_
79
+ return s *sexp
80
+ end
81
+ end
82
+ end
83
+
84
+ class AbstractSexp
85
+ attr_reader :sexp, :enhancer
86
+
87
+ include EnhancerHelper
88
+ def initialize(sexp, enhancer)
89
+ assertSexpIs sexp, type
90
+ @sexp = sexp
91
+ @enhancer = enhancer
92
+ deconstruct sexp[1..-1]
93
+ end
94
+
95
+ def s(*args)
96
+ enhancer.s *args
97
+ end
98
+
99
+ def process(arg)
100
+ enhancer.process arg
101
+ end
102
+
103
+ def regularEnhance
104
+ params = asArray
105
+ params.push(process(args)) if args
106
+ s *params
107
+ end
108
+
109
+ def subtreeNeedsEnhance?
110
+ enhancer.vcallCount && enhancer.vcallCount > 0
111
+ end
112
+
113
+ def decVcallCount
114
+ enhancer.vcallCount -= 1
115
+ end
116
+
117
+ def enhance
118
+ count = sexpEnhancingCount args
119
+ return regularEnhance unless count > 0
120
+ enhancer.vcallCount = count if enhancer.vcallCount.nil?
121
+ self.args = process args
122
+ if enhancer.vcallCount == 0
123
+ enhancer.vcallCount = nil
124
+ end
125
+ return regularEnhance unless subtreeNeedsEnhance?
126
+ decVcallCount
127
+ s :iter, s(*asArray), s(:dasgn_curr, enhancer.variableName), argumentList
128
+ end
129
+
130
+ def argumentList
131
+ args[1]
132
+ end
133
+
134
+
135
+ def deconstruct(sexp)
136
+ raise "subclass responsibility"
137
+ end
138
+
139
+ def type
140
+ raise "subclass responsibility"
141
+ end
142
+
143
+ def asArray
144
+ raise "subclass responsibility"
145
+ end
146
+
147
+ end
148
+
149
+ class Fcall < AbstractSexp
150
+ attr_accessor :method, :args
151
+ def type
152
+ :fcall
153
+ end
154
+
155
+ def deconstruct(sexp)
156
+ @method, @args = sexp
157
+ end
158
+
159
+
160
+ def asArray
161
+ [type, method]
162
+ end
163
+
164
+
165
+ end
166
+
167
+ class Call < AbstractSexp
168
+ attr_accessor :method, :args, :target
169
+ def type
170
+ :call
171
+ end
172
+
173
+ def deconstruct(sexp)
174
+ @target, @method, @args = sexp
175
+ end
176
+
177
+ def asArray
178
+ [type, process(target), method]
179
+ end
180
+ end
181
+
182
+
183
+ class VcallEnhancer < AbstractProcessor
184
+ attr_accessor :vcallCount
185
+ include EnhancerHelper
186
+ public :s
187
+
188
+ def initialize
189
+ super
190
+ self.vcallCount = nil
191
+ end
192
+
193
+ def variableName
194
+ :"__vcall_enhancer_i"
195
+ end
196
+
197
+
198
+ def process_vcall(sexp)
199
+ return s *sexp unless sexp[1] == :_
200
+ s(:dvar, variableName)
201
+ end
202
+
203
+ def process_call(sexp)
204
+ changeGenericCall sexp
205
+ end
206
+
207
+ def process_fcall(sexp)
208
+ changeGenericCall sexp
209
+ end
210
+
211
+ def changeGenericCall(sexp)
212
+ sexpClass(sexp.first).new(sexp, self).enhance
213
+ end
214
+ protected
215
+
216
+ def sexpClass(type)
217
+ return Call if type == :call
218
+ return Fcall if type == :fcall
219
+ raise "Unknown sexp: #{type}"
220
+ end
221
+ end
222
+
223
+
224
+ class UnderscoreEnhancer
225
+ include EnhancerHelper
226
+
227
+ def enhance(clas, method)
228
+ sexp = sexpOf clas, method
229
+ return unless sexpNeedsEnhancing sexp
230
+ clas.class_eval chain sexp, VcallEnhancer, Unifier, Ruby2Ruby
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,76 @@
1
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__),'..','lib')
2
+ require 'ruby_underscore'
3
+
4
+
5
+ class Enhanced
6
+ include RubyUnderscore::Base
7
+ def self.aPublicClassMethod
8
+ [__method__].map _.to_s.concat('Enhanced')
9
+ end
10
+
11
+ def aPublic
12
+ [__method__].map _.to_s.concat('Enhanced')
13
+ end
14
+
15
+ def aPrivate
16
+ [__method__].map _.to_s.concat('Enhanced')
17
+ end
18
+ end
19
+
20
+ describe RubyUnderscore do
21
+ it "should enhance all methods of a class that includes RubyUnderscore::Base" do
22
+ Enhanced.new.aPublic.should == ["aPublicEnhanced"]
23
+ end
24
+
25
+ it "should enhance private methods as well" do
26
+ Enhanced.new.aPrivate.should == ["aPrivateEnhanced"]
27
+ end
28
+
29
+ it "should enhance class methods" do
30
+ Enhanced.aPublicClassMethod.should == ["aPublicClassMethodEnhanced"]
31
+ end
32
+
33
+ it "the original method_added of the class must call super" do
34
+ cl = Class.new do
35
+ @@original_method_added_invoked = false
36
+ def self.method_added(method_name)
37
+ super
38
+ @@original_method_added_invoked = true
39
+ end
40
+ include RubyUnderscore::Base
41
+
42
+ def dummy
43
+ 'dum'
44
+ end
45
+
46
+ def self.original_method_added_invoked
47
+ @@original_method_added_invoked
48
+ end
49
+ end
50
+ cl.original_method_added_invoked.should be_true
51
+ end
52
+
53
+
54
+ it "will work even if the class defines singleton_method_added, but invokes super" do
55
+ cl = Class.new do
56
+ @@original_singleton_method_added_invoked = false
57
+ def self.singleton_method_added(method_name)
58
+ super
59
+ @@original_singleton_method_added_invoked = true
60
+ end
61
+ include RubyUnderscore::Base
62
+
63
+ def self.dummy
64
+ [__method__].map _.to_s.concat('Enhanced')
65
+ end
66
+
67
+ def self.original_singleton_method_added_invoked
68
+ @@original_singleton_method_added_invoked
69
+ end
70
+ end
71
+ cl.original_singleton_method_added_invoked.should be_true
72
+ cl.dummy.should == ["dummyEnhanced"]
73
+ end
74
+
75
+ end
76
+
@@ -0,0 +1,179 @@
1
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__),'..','lib')
2
+ require 'tree_converters'
3
+
4
+ # Making it simple to test.
5
+ class RubyUnderscore::VcallEnhancer
6
+ def variableName
7
+ :x
8
+ end
9
+ end
10
+
11
+
12
+ class Array
13
+ def mapProc(proc)
14
+ map &proc
15
+ end
16
+ end
17
+
18
+ class Checks
19
+ def doesntNeedEnhancing
20
+ (1..20).map do |i|
21
+ i + 1
22
+ end
23
+ end
24
+
25
+ def stillNoEnhancing
26
+ _(9)
27
+ end
28
+
29
+ def needsEnhacing
30
+ [0].map _
31
+ end
32
+
33
+ def alsoNeedsEnhacing
34
+ [0].map _.to_s
35
+ end
36
+ end
37
+
38
+ module OutputHelper
39
+ def map(&block)
40
+ [0].map &block
41
+ end
42
+
43
+ def idblock(&block)
44
+ block
45
+ end
46
+
47
+ def identity_of(arg)
48
+ arg
49
+ end
50
+ end
51
+
52
+ class Input
53
+ include OutputHelper
54
+ def simple
55
+ [0].map _
56
+ end
57
+
58
+ def methodCall
59
+ [0].map _.to_s
60
+ end
61
+
62
+ def longComplexMethodChain
63
+ [0].map _.to_i.to_s.center(40, '-').to_s
64
+ end
65
+
66
+ def nested
67
+ ["1"].concat([0].map _.to_s)
68
+ end
69
+
70
+ def fcall
71
+ map _.to_s
72
+ end
73
+
74
+ def multiple_fcall
75
+ identity_of map _.to_s
76
+ end
77
+
78
+ def fcall_call_mix
79
+ identity_of [0].map _.to_s
80
+ end
81
+
82
+ def call_fcall_mix
83
+ [0].mapProc idblock _.to_s
84
+ end
85
+
86
+ def nested_enhancement_on_matrix
87
+ [[[:a00], [:a01]], [[:a10], [:a11]]].each(_.each(_.push(:last)))
88
+ end
89
+
90
+ def hasItAll
91
+ identity_of [[[:a00], [:a01]], [[:a10], [:a11]]].each(_.each(_.push(:last)))
92
+ identity_of [0].map _.to_s
93
+ end
94
+
95
+ end
96
+
97
+ class Expected
98
+ include OutputHelper
99
+ def simple
100
+ [0].map { |x| x }
101
+ end
102
+
103
+ def methodCall
104
+ [0].map { |x| x.to_s }
105
+ end
106
+
107
+ def longComplexMethodChain
108
+ [0].map { |x| x.to_i.to_s.center(40, '-').to_s }
109
+ end
110
+
111
+ def nested
112
+ ["1"].concat([0].map { |x| x.to_s } )
113
+ end
114
+
115
+ def fcall
116
+ map { |x| x.to_s }
117
+ end
118
+
119
+ def multiple_fcall
120
+ identity_of map { |x| x.to_s }
121
+ end
122
+
123
+ def fcall_call_mix
124
+ identity_of [0].map { |x| x.to_s }
125
+ end
126
+
127
+ def call_fcall_mix
128
+ [0].mapProc idblock { |x| x.to_s }
129
+ end
130
+
131
+ def nested_enhancement_on_matrix
132
+ [[[:a00], [:a01]], [[:a10], [:a11]]].each { |x| x.each { |x| x.push(:last)} }
133
+ end
134
+
135
+ def hasItAll
136
+ identity_of [[[:a00], [:a01]], [[:a10], [:a11]]].each { |x| x.each { |x| x.push(:last)} }
137
+ identity_of [0].map { |x| x.to_s }
138
+ end
139
+ end
140
+
141
+ describe 'TreeConverters' do
142
+ attr_reader :un
143
+
144
+ before(:each) do
145
+ @un = RubyUnderscore::UnderscoreEnhancer.new
146
+ end
147
+
148
+ def assert_same_after_enhancing(method)
149
+ un.enhance Input, method
150
+ input = un.sexpOf Input, method
151
+ output = un.sexpOf Expected, method
152
+ input.should == output
153
+ Input.new.send(method).should == Expected.new.send(method)
154
+ end
155
+
156
+ it "detects correctly what needs and what doesn't need enhancing" do
157
+ un.needsEnhancing(Checks, :doesntNeedEnhancing).should be_false
158
+ un.needsEnhancing(Checks, :stillNoEnhancing).should be_false
159
+ un.needsEnhancing(Checks, :needsEnhacing).should be_true
160
+ un.needsEnhancing(Checks, :alsoNeedsEnhacing).should be_true
161
+ end
162
+
163
+
164
+ it "can count correctly how many vcalls need to be enhanced" do
165
+ sexp = un.sexpOf Input, :nested_enhancement_on_matrix
166
+ un.sexpEnhancingCount(sexp).should == 2
167
+ end
168
+
169
+
170
+ [:simple, :methodCall, :longComplexMethodChain,
171
+ :nested, :fcall, :multiple_fcall, :fcall_call_mix,
172
+ :call_fcall_mix, :nested_enhancement_on_matrix, :hasItAll].
173
+ each do |m|
174
+ it m.to_s do
175
+ assert_same_after_enhancing m
176
+ end
177
+ end
178
+ end
179
+
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubyunderscore
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Daniel Ribeiro
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-11-06 00:00:00 -02:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: ParseTree
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - "="
28
+ - !ruby/object:Gem::Version
29
+ hash: 13
30
+ segments:
31
+ - 3
32
+ - 0
33
+ - 5
34
+ version: 3.0.5
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: ruby2ruby
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 3
46
+ segments:
47
+ - 0
48
+ version: "0"
49
+ type: :runtime
50
+ version_requirements: *id002
51
+ description: It allows you to create simple blocks by using underscore symbol
52
+ email: danrbr+rubyunderscore@gmail.com
53
+ executables: []
54
+
55
+ extensions: []
56
+
57
+ extra_rdoc_files:
58
+ - README.md
59
+ - TODO
60
+ files:
61
+ - README.md
62
+ - Rakefile
63
+ - TODO
64
+ - VERSION
65
+ - lib/ruby_underscore.rb
66
+ - lib/tree_converters.rb
67
+ - spec/tree_converters_spec.rb
68
+ - spec/ruby_underscore_spec.rb
69
+ has_rdoc: true
70
+ homepage: http://github.com/danielribeiro/RubyUnderscore
71
+ licenses: []
72
+
73
+ post_install_message:
74
+ rdoc_options:
75
+ - --charset=UTF-8
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ hash: 3
84
+ segments:
85
+ - 0
86
+ version: "0"
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ hash: 3
93
+ segments:
94
+ - 0
95
+ version: "0"
96
+ requirements: []
97
+
98
+ rubyforge_project:
99
+ rubygems_version: 1.3.7
100
+ signing_key:
101
+ specification_version: 3
102
+ summary: Simple way to create simple blocks
103
+ test_files:
104
+ - spec/tree_converters_spec.rb
105
+ - spec/ruby_underscore_spec.rb