rns 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,3 +1,5 @@
1
+ /Gemfile.lock
2
+
1
3
  # rcov generated
2
4
  coverage
3
5
 
data/README.md CHANGED
@@ -12,43 +12,33 @@ functional programming in Ruby. It is inspired by
12
12
  ```ruby
13
13
  require 'rns'
14
14
 
15
- module Arithmetic
16
- class << self
17
- def dec(n) n - 1 end
18
- def inc(n) n + 1 end
19
- end
15
+ Arithmetic = Rns do
16
+ def dec(n) n - 1 end
17
+ def inc(n) n + 1 end
20
18
  end
21
19
 
22
- module Statistics
23
- def self.avg(arr); arr.reduce(:+).to_f / arr.count end
20
+ Statistics = Rns do
21
+ def avg(arr) arr.reduce(:+).to_f / arr.count end
24
22
  end
25
23
 
26
24
  class Main
27
- extend Rns.use(Arithmetic => [:inc])
28
- include Rns.use(Statistics => [:avg])
29
-
30
- def self.incremented(n)
31
- "#{n} incremented is #{inc n}"
25
+ Funcs = Rns(Statistics, Arithmetic => [:inc]) do
26
+ def incremented_avg(nums)
27
+ avg nums.map(&method(:inc))
28
+ end
32
29
  end
33
30
 
34
31
  def main
35
- puts "#{self.class.incremented 1} and the average of [1,2,3] is #{avg [1,2,3]}"
32
+ nums = [1, 2, 3]
33
+ puts "The average of #{nums.inspect} incremented is: #{Funcs.incremented_avg nums}"
36
34
  end
37
35
  end
38
36
 
39
37
  Main.new.main
40
38
  ```
41
39
 
42
- ## Importing Methods into Blocks
43
-
44
- ```ruby
45
- Rns::using(Arithmetic => [:inc], Statistics => [:avg]) do
46
- puts avg((1..10).to_a.map(&method(:inc)))
47
- end
48
- ```
49
-
50
40
  Please see the
51
- [tests](https://github.com/alandipert/rns/tree/master/spec/rns) for more
41
+ [tests](https://github.com/alandipert/rns/tree/master/test) for more
52
42
  usage examples.
53
43
 
54
44
  # Rationale
data/Rakefile CHANGED
@@ -1,13 +1,9 @@
1
1
  require 'rubygems'
2
2
  require 'bundler'
3
- require "rspec/core/rake_task"
4
- require 'rake'
3
+ require 'rake/testtask'
5
4
 
6
- desc "Run all specs"
7
- RSpec::Core::RakeTask.new(:spec) do |t|
8
- t.pattern = "spec/**/*_spec.rb"
9
- t.ruby_opts = '-Ilib -Ispec -I.'
10
- t.rspec_opts = '--color'
5
+ Rake::TestTask.new do |t|
6
+ t.pattern = "test/*_test.rb"
11
7
  end
12
8
 
13
- task :default => :spec
9
+ task :default => :test
data/lib/rns.rb CHANGED
@@ -1,45 +1,101 @@
1
+ require 'rns/version'
2
+
3
+ module Kernel
4
+ # Returns an immutable namespace of functions
5
+ #
6
+ # * `imports` can be any number of other namespaces from which to import functions or hashes
7
+ # specifying which functions to import.
8
+ # * The block contains the functions to be `def`ed for the namespace. Since namespaces are
9
+ # immutable, instance variable and class variable cannot be set in functions. In other
10
+ # words, `@var = :something` will raise an error.
11
+ #
12
+ # Example:
13
+ #
14
+ # StringManipulation = Rns(SomeOtherNs, AnotherNs => [:alternate_case]) do
15
+ # def crazify(str)
16
+ # "#{alternate_case str}!"
17
+ # end
18
+ # end
19
+ #
20
+ # StringManipulation.crazify("whoa") #=> "WhOa!"
21
+ #
22
+ def Rns(*imports, &block)
23
+ klass = Class.new(Rns::Namespace, &block)
24
+ klass.import(imports)
25
+ klass.freeze.send(:new).freeze
26
+ end
27
+ end
28
+
1
29
  module Rns
2
- class << self
3
- def constant_for(module_names)
4
- (m, *more) = module_names.map{|n| n.split("::")}.flatten
5
- more.reduce(Kernel.const_get(m)){|m, s| m.const_get(s)}
6
- end
30
+ # Error raised while importing methods
31
+ class ImportError < StandardError
32
+ end
7
33
 
8
- def use(use_spec)
9
- Module.new.tap {|m| add_methods(m, use_spec) }
10
- end
34
+ # An internal class used to support Kernel#Rns in returning namespaces
35
+ class Namespace
36
+ class << self
37
+ # Use Kernel#Rns instead
38
+ private :new
39
+
40
+ # Imports methods from objects into this namespace class as private instance methods.
41
+ def import(imports)
42
+ ns_methods = instance_methods()
43
+ @_import_hash = array_to_key_value_tuples(imports).reduce({}) do |h, (obj, methods)|
44
+ if !obj.frozen?
45
+ raise ImportError, "#{obj} cannot be imported into Namespace because it is not frozen"
46
+ elsif !obj.class.frozen?
47
+ raise ImportError, "#{obj} cannot be imported into Namespace because its class is not frozen"
48
+ end
49
+
50
+ (methods || obj.public_methods(false)).each do |method|
51
+ if ns_methods.include? method
52
+ raise ImportError, "cannot override #{method} with an import"
53
+ end
54
+ h[method.to_sym] = obj.method(method)
55
+ end
11
56
 
12
- def process_spec_entry(entry)
13
- (k,v) = entry
14
- if (v.is_a? Array)
15
- v.map{|x| process_spec_entry([k,x])}
16
- elsif (v.is_a? Hash)
17
- v.map do |x,y|
18
- process_spec_entry([constant_for([k, x].map(&:to_s)), y])
57
+ h
58
+ end
59
+
60
+ file, line = import_call_site(caller)
61
+ @_import_hash.each do |method, _|
62
+ # eval is needed because:
63
+ # * Module#define_method can't delegate to methods that accept blocks
64
+ # * method_missing can, but then imported methods are available publicly
65
+ module_eval(delegate_to_hash_source(method, :@_import_hash), file, line - 1)
66
+ private method
19
67
  end
20
- else
21
- [k,v]
22
68
  end
23
- end
24
69
 
25
- def process_spec(use_spec)
26
- use_spec.map(&method(:process_spec_entry)).
27
- flatten.
28
- each_slice(2).
29
- to_a
30
- end
70
+ private
71
+ # array_to_key_value_tuples([:a, {:b => 1, :c => 2}, :d])
72
+ # #=> [[:a, nil], [:b, 1], [:c, 2], [:d, nil]]
73
+ def array_to_key_value_tuples(array)
74
+ array.reduce([]) do |tuples, elem|
75
+ if elem.is_a? Hash
76
+ tuples + Array(elem)
77
+ else
78
+ tuples << [elem, nil]
79
+ end
80
+ end
81
+ end
31
82
 
32
- def add_methods(to, use_spec)
33
- process_spec(use_spec).each do |from, method|
34
- to.send(:define_method, method, &from.method(method))
35
- to.send(:private, method)
83
+ # Delegates all calls to a callable stored in an ivar of the class'es
84
+ def delegate_to_hash_source(method_name, hash_name)
85
+ <<-EOS
86
+ def #{method_name}(*args, &block)
87
+ self.class.instance_variable_get(:#{hash_name}).fetch(:#{method_name}).call(*args, &block)
88
+ end
89
+ EOS
36
90
  end
37
- end
38
91
 
39
- def using(use_spec, &blk)
40
- klass = Class.new
41
- add_methods(klass, use_spec)
42
- klass.new.instance_eval(&blk)
92
+ # Given a backtrace, return the file/line where the user imported a method. Skip frames in
93
+ # Kernel#Rns since the user's import code will be in the frame afterwards.
94
+ def import_call_site(backtrace)
95
+ frame = backtrace.detect {|f| f !~ /in `Rns'$/ }
96
+ file, line = frame.split(':', 2)
97
+ [file, line.to_i]
98
+ end
43
99
  end
44
100
  end
45
- end
101
+ end
@@ -1,3 +1,3 @@
1
1
  module Rns
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.6"
3
3
  end
@@ -18,7 +18,6 @@ Gem::Specification.new do |s|
18
18
  s.require_paths = ["lib"]
19
19
 
20
20
  # specify any dependencies here; for example:
21
- s.add_development_dependency 'rspec'
22
21
  s.add_development_dependency 'guard'
23
22
  s.add_development_dependency 'guard-rspec'
24
23
  s.add_development_dependency 'simplecov'
@@ -0,0 +1,174 @@
1
+ require 'minitest/autorun'
2
+ require 'rns'
3
+
4
+ class RnsTest < MiniTest::Unit::TestCase
5
+ Math = Rns do
6
+ def inc(n)
7
+ n + 1
8
+ end
9
+
10
+ def dec(n)
11
+ n - 1
12
+ end
13
+ end
14
+
15
+ def test_functions_function
16
+ m = Rns do
17
+ def add(a, b)
18
+ a + b
19
+ end
20
+
21
+ def inc(a)
22
+ add a, 1
23
+ end
24
+
25
+ def dec_block
26
+ add yield(), -1
27
+ end
28
+ end
29
+
30
+ assert_equal 42, m.add(20, 22)
31
+ assert_equal 2, m.inc(1)
32
+ assert_equal 11, m.dec_block { 12 }
33
+ end
34
+
35
+ def test_private_functions
36
+ m = Rns do
37
+ def whoa_dave
38
+ whoa "Dave"
39
+ end
40
+ private
41
+ def whoa(dude)
42
+ "Whoa, #{dude}, totally awesome wave"
43
+ end
44
+ end
45
+
46
+ assert_equal "Whoa, Dave, totally awesome wave", m.whoa_dave
47
+ assert_raises NoMethodError do
48
+ m.whoa "Man"
49
+ end
50
+ end
51
+
52
+ def test_ivars_cant_be_set
53
+ m = Rns do
54
+ def set_ivar
55
+ @ivar = "something"
56
+ end
57
+ end
58
+
59
+ assert_raises_frozen_error do
60
+ m.set_ivar
61
+ end
62
+ assert_raises_frozen_error do
63
+ m.instance_variable_set :@ivar, "something"
64
+ end
65
+ end
66
+
67
+ def test_cvars_cant_be_set
68
+ m = Rns do
69
+ def set_cvar
70
+ @@cvar = "something"
71
+ end
72
+ end
73
+
74
+ # this fails on 1.8.7
75
+ assert_raises_frozen_error do
76
+ m.set_cvar
77
+ end
78
+ end
79
+
80
+ def test_importing_namespaces
81
+ ns = Rns(Math) do
82
+ def double_inc(n)
83
+ inc inc(n)
84
+ end
85
+ end
86
+
87
+ assert_equal 3, ns.double_inc(1)
88
+ end
89
+
90
+ def test_importing_namespace_functions
91
+ ns = Rns(Math => [:dec]) do
92
+ def double_inc(n)
93
+ inc inc(n)
94
+ end
95
+
96
+ def double_dec(n)
97
+ dec dec(n)
98
+ end
99
+ end
100
+
101
+ assert_equal -1, ns.double_dec(1)
102
+ assert_raises NoMethodError do
103
+ ns.double_inc(1)
104
+ end
105
+ end
106
+
107
+ def test_imported_methods_arent_public
108
+ ns = Rns(Math => [:dec]) do
109
+ end
110
+
111
+ assert_raises NoMethodError do
112
+ ns.dec(1)
113
+ end
114
+ end
115
+
116
+ def test_mutable_objects_cant_be_imported_from
117
+ assert_raises Rns::ImportError do
118
+ Rns(IO) {}
119
+ end
120
+ end
121
+
122
+ def test_objects_of_mutable_classes_cant_be_imported_from
123
+ assert_raises Rns::ImportError do
124
+ Rns(Object.new.freeze) {}
125
+ end
126
+ end
127
+
128
+ def test_imports_cannot_override_functions
129
+ assert_raises Rns::ImportError do
130
+ Rns(Math => [:inc]) do
131
+ def inc
132
+ "Can I be overridden?"
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ def test_error_backtraces_include_point_of_import
139
+ ns = Rns do
140
+ def kablammo!
141
+ raise "kablammo!"
142
+ end
143
+ end
144
+
145
+ import_line = __LINE__ + 1
146
+ ns2 = Rns(ns => [:kablammo!]) do
147
+ def totally_safe
148
+ kablammo!
149
+ end
150
+ end
151
+
152
+ begin
153
+ ns2.totally_safe
154
+ rescue => e
155
+ import_frame = e.backtrace[1]
156
+ file, line = import_frame.split(':', 2)
157
+ assert_equal __FILE__, file
158
+ assert_equal import_line, line.to_i
159
+ else
160
+ flunk "Expected an error on import"
161
+ end
162
+ end
163
+
164
+ private
165
+
166
+ def assert_raises_frozen_error
167
+ yield
168
+ rescue RuntimeError, TypeError => e
169
+ assert_match /can't modify/i, e.message
170
+ else
171
+ flunk "Frozen object exception expected"
172
+ end
173
+
174
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rns
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,24 +9,8 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-10 00:00:00.000000000 Z
12
+ date: 2012-07-23 00:00:00.000000000 Z
13
13
  dependencies:
14
- - !ruby/object:Gem::Dependency
15
- name: rspec
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
14
  - !ruby/object:Gem::Dependency
31
15
  name: guard
32
16
  requirement: !ruby/object:Gem::Requirement
@@ -88,7 +72,6 @@ files:
88
72
  - .rvmrc
89
73
  - .travis.yml
90
74
  - Gemfile
91
- - Gemfile.lock
92
75
  - Guardfile
93
76
  - LICENSE.txt
94
77
  - README.md
@@ -96,8 +79,7 @@ files:
96
79
  - lib/rns.rb
97
80
  - lib/rns/version.rb
98
81
  - rns.gemspec
99
- - spec/rns/rns_spec.rb
100
- - spec/spec_helper.rb
82
+ - test/rns_test.rb
101
83
  homepage: http://github.com/alandipert/rns
102
84
  licenses:
103
85
  - BSD-new
@@ -1,41 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- rns (0.0.5)
5
-
6
- GEM
7
- remote: http://rubygems.org/
8
- specs:
9
- diff-lcs (1.1.3)
10
- ffi (1.0.11)
11
- guard (1.0.2)
12
- ffi (>= 0.5.0)
13
- thor (~> 0.14.6)
14
- guard-rspec (0.7.0)
15
- guard (>= 0.10.0)
16
- multi_json (1.3.4)
17
- rake (0.9.2.2)
18
- rspec (2.10.0)
19
- rspec-core (~> 2.10.0)
20
- rspec-expectations (~> 2.10.0)
21
- rspec-mocks (~> 2.10.0)
22
- rspec-core (2.10.0)
23
- rspec-expectations (2.10.0)
24
- diff-lcs (~> 1.1.3)
25
- rspec-mocks (2.10.1)
26
- simplecov (0.6.2)
27
- multi_json (~> 1.3)
28
- simplecov-html (~> 0.5.3)
29
- simplecov-html (0.5.3)
30
- thor (0.14.6)
31
-
32
- PLATFORMS
33
- ruby
34
-
35
- DEPENDENCIES
36
- guard
37
- guard-rspec
38
- rake
39
- rns!
40
- rspec
41
- simplecov
@@ -1,163 +0,0 @@
1
- require File.join(File.dirname(__FILE__), *%w[.. spec_helper.rb])
2
- require 'rns'
3
-
4
- module Math
5
- def self.identity(x); x end
6
-
7
- module Arithmetic
8
- class << self
9
- def dec(n) n - one end
10
- def inc(n) n + one end
11
- def add(x,y) x + y end
12
- private
13
- def one() 1 end
14
- end
15
- end
16
-
17
- module Statistics
18
- def self.avg(arr); arr.reduce(:+).to_f / arr.count end
19
- module Foo
20
- def self.blah; :quux end
21
- end
22
- end
23
- end
24
-
25
- module BlockTakers
26
- def self.takes_block(&blk); yield; end
27
- end
28
-
29
- module Util
30
- class << self
31
- def assoc!(h, k, v)
32
- h.tap{|o| o[k] = v}
33
- end
34
-
35
- def merge_with(f, *hshs)
36
- merge_entry = lambda do |h, (k,v)|
37
- if (h.has_key?(k))
38
- assoc!(h, k, f[h[k], v])
39
- else
40
- assoc!(h, k, v)
41
- end
42
- end
43
- merge2 = lambda do |h1,h2|
44
- h2.to_a.reduce(h1, &merge_entry)
45
- end
46
- ([{}] + hshs).reduce(&merge2)
47
- end
48
- end
49
- end
50
-
51
- class Thing
52
- extend Rns.use(Math::Arithmetic => [:inc])
53
- include Rns.use(Math::Statistics => [:avg])
54
-
55
- def self.inced_one
56
- inc 1
57
- end
58
-
59
- def average
60
- avg [11, 42, 7]
61
- end
62
- end
63
-
64
- describe Rns do
65
- context 'adding methods to classes' do
66
- it "works" do
67
- Thing.new.average.should == 20
68
- end
69
-
70
- it "works with private module methods" do
71
- Thing.inced_one.should == 2
72
- end
73
-
74
- it "should add methods privately" do
75
- lambda { Thing.new.avg [11, 42, 7] }.should raise_error(NoMethodError)
76
- end
77
- end
78
-
79
- context 'adding methods to blocks' do
80
-
81
- it 'computes' do
82
- Rns::using(Util => [:merge_with],
83
- Math::Arithmetic => [:inc, :add]) do
84
-
85
- sum = lambda{|*xs| xs.reduce(:+)}
86
-
87
- merge_with(sum, *(1..10).map{|n| {x: n}})[:x].should == 55
88
- merge_with(sum,
89
- {x: 10, y: 20},
90
- {x: 3, z: 30}).should == {x: 13, y: 20, z: 30}
91
-
92
- merge_with(lambda{|l,r| l.send(:+, r)},
93
- {:x => [:something]},
94
- {:x => [:else]}).should == {:x => [:something, :else]}
95
- end
96
- end
97
-
98
- it "works with individual modules" do
99
- Rns::using(Math::Arithmetic => [:inc],
100
- Math::Statistics => [:avg]) do
101
- avg((1..10).to_a.map(&method(:inc))).should == 6.5
102
- end
103
- end
104
-
105
- it "works with nested modules" do
106
- Rns::using(Math => {:Arithmetic => [:inc],
107
- :Statistics => [:avg]}) do
108
- avg((1..10).to_a.map(&method(:inc))).should == 6.5
109
- end
110
- end
111
-
112
- it "works with mix of module declaration styles" do
113
- Rns::using(Math::Arithmetic => [:inc],
114
- Math => [:identity,
115
- {:Statistics => [:avg]}]) do
116
- identity(1).should == 1
117
- inc(10).should == 11
118
- avg((1..10).to_a.map(&method(:inc))).should == 6.5
119
- end
120
- end
121
-
122
- it "does not modify Object" do
123
- Rns::using(Math::Arithmetic => [:inc]) do
124
- # do nothing
125
- end
126
- lambda { Object.new.inc 1 }.should raise_error(NoMethodError)
127
- end
128
-
129
- it "does not modify Class" do
130
- Rns::using(Math::Arithmetic => [:inc]) do
131
- # do nothing
132
- end
133
- lambda { Class.new.inc 1 }.should raise_error(NoMethodError)
134
- end
135
-
136
- it "imports a method that takes a block" do
137
- Rns::using(BlockTakers => [:takes_block]) do
138
- takes_block do
139
- :returned_from_block
140
- end.should == :returned_from_block
141
- end
142
- end
143
-
144
- it 'processes specs correctly' do
145
- Rns::using(Rns => [:process_spec]) do
146
- process_spec({Math => [:inc, :dec]}).
147
- should == [[Math, :inc], [Math, :dec]]
148
- end
149
-
150
- Rns::using(Rns => [:process_spec]) do
151
- spec = {Math::Arithmetic => [:inc],
152
- Math => [:identity,
153
- {:Statistics => [:avg,
154
- {:Foo => [:blah, :quux]}]}]}
155
- process_spec(spec).should == [[Math::Arithmetic, :inc],
156
- [Math, :identity],
157
- [Math::Statistics, :avg],
158
- [Math::Statistics::Foo, :blah],
159
- [Math::Statistics::Foo, :quux]]
160
- end
161
- end
162
- end
163
- end
@@ -1,7 +0,0 @@
1
- require File.join(File.dirname(__FILE__), *%w[.. lib rns])
2
-
3
- RSpec.configure do |config|
4
- config.filter_run :focused => true
5
- config.alias_example_to :fit, :focused => true
6
- config.run_all_when_everything_filtered = true
7
- end