rns 0.0.5 → 0.0.6

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.
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