dsboulder-acts_as_wrapped_class 1.0.3 → 1.0.4

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.
@@ -0,0 +1,155 @@
1
+ require "erb"
2
+
3
+ module ActsAsWrappedClass
4
+ VERSION = "1.0.1"
5
+ WRAPPED_CLASSES = []
6
+
7
+ module InstanceMethods
8
+ def to_wrapper
9
+ eval("#{self.class.name}Wrapper").new(self)
10
+ end
11
+ end
12
+
13
+ module ClassMethods
14
+ def wrapped_class?
15
+ true
16
+ end
17
+ end
18
+
19
+ class WrapperFinder
20
+ SANDBOX_BASE_IMPORTS = ["Object", "Module", "Class", "Kernel", "Main", "Array", "Bignum", "Binding", "Comparable", "Cont", "Data", "Dir", "Enumerable", "Exception", "FalseClass", "FConst", "File", "FileTest", "Fixnum", "Float", "GC", "Hash", "Integer", "IO", "Marshal", "Math", "Match", "Method", "NilClass", "Numeric", "ObSpace", "Precision", "Proc", "Process", "ProcStatus", "ProcUID", "ProcGID", "ProcID_Syscall", "Range", "Regexp", "Stat", "String", "Struct", "Symbol", "Thread", "ThGroup", "Time", "Tms", "TrueClass", "UnboundMethod", "StandardError", "SystemExit", "Interrupt", "Signal", "Fatal", "ArgError", "EOFError", "IndexError", "RangeError", "RegexpError", "IOError", "RuntimeError", "SecurityError", "SystemCallError", "SysStackError", "ThreadError", "TypeError", "ZeroDivError", "NotImpError", "NoMemError", "NoMethodError", "FloatDomainError", "ScriptError", "NameError", "NameErrorMesg", "SyntaxError", "LoadError", "LocalJumpError", "Errno", "BoxedClass"]
21
+ @@special_handlers = {Array => Proc.new{ |object| object.collect{|val| find_wrapper_for(val)} },
22
+ Hash => Proc.new{ |object| object.inject({}){|h, key_val| h[find_wrapper_for(key_val[0])] = find_wrapper_for(key_val[1]); h } }}
23
+
24
+ # Returns a wrapper for an instance of an object, if one exsits, or the original object if it's a core datatype.
25
+ # * This will first attempt to find a special handler for the type of object being wrapped and invoke it's block
26
+ # * Then it will look for a wrapper classes that fits the object's type (Something looks for SomethingWrapper)
27
+ # * Finally, it checks a list of "safe" classes that don't need wrapping
28
+ # If no match is found, an exception is raised
29
+ def self.find_wrapper_for(object)
30
+ wrapper_name = "#{object.class.name}Wrapper"
31
+ return nil if nil
32
+
33
+ @@special_handlers.each do |key, value|
34
+ return value.call(object) if object.is_a?(key)
35
+ end
36
+
37
+ return object if object.kind_of?(WrapperBase)
38
+ return eval(wrapper_name).new(object) if eval("defined?(#{wrapper_name})")
39
+ return object if SANDBOX_BASE_IMPORTS.include?(object.class.name)
40
+
41
+ raise "Can't find wrapper for class: #{object.class.name}"
42
+ end
43
+
44
+ # Add a special handler for how to wrap certain types of classes.
45
+ # For example, if you wanted to wrap Arrays by wrapping each of their elements
46
+ def self.add_special_handler(klass, prc)
47
+ raise "1st arg must be a Class" unless klass.is_a?(Class)
48
+ raise "2st arg must be a Proc" unless prc.is_a?(Proc)
49
+ @@special_handlers[klass] = prc
50
+ end
51
+ end
52
+
53
+ def wrapped_class?
54
+ false
55
+ end
56
+
57
+ # Mark a class as wrapped, creating a wrapper class which allows access to certain methods specified by EITHER the :methods safe list of the :except_methods blacklist.
58
+ # You cannot use both :methods and :except_methods at once.
59
+ # * options[:methods] contains a list of method names (symbols) to allow access to
60
+ # * options[:except_methods] contains a list of method names (symbols) to not allow access to
61
+ # * options[:constants] contains a list of constant names (symbols) to allow access to
62
+ # * options[:except_constants] contains a list of constant names (symbols) to not allow access to
63
+ def acts_as_wrapped_class(options = {})
64
+
65
+ raise "Can't specify methods to allow and to deny." if options[:methods] && options[:except_methods]
66
+ raise "Can't specify constants to allow and to deny." if options[:constants] && options[:except_constants]
67
+ options[:methods] ||= :all unless options[:except_methods]
68
+ options[:constants] ||= :all unless options[:except_constants]
69
+
70
+ WRAPPED_CLASSES << self
71
+
72
+ if options[:methods] == :all
73
+ options.delete(:methods)
74
+ options[:except_methods] = []
75
+ end
76
+
77
+ if options[:constants] == :all
78
+ options.delete(:constants)
79
+ options[:except_constants] = []
80
+ end
81
+
82
+ meths = options[:methods] || options[:except_methods]
83
+ consts = options[:constants] || options[:except_constants]
84
+
85
+ allowed_method_missing = options[:methods] ? options[:methods].include?(:method_missing) : !options[:except_methods].include?(:method_missing)
86
+ allowed_const_missing = options[:constants] ? options[:constants].include?(:cont_missing) : !options[:except_constants].include?(:const_missing)
87
+
88
+ self.send(:include, ActsAsWrappedClass::InstanceMethods)
89
+ self.send(:extend, ActsAsWrappedClass::ClassMethods)
90
+
91
+ if allowed_method_missing
92
+ method_defs_erb = <<-EOF
93
+ def method_missing(meth, *args);
94
+ <% if options[:except_methods] %>
95
+ raise NameError.new("Method `"+meth.to_s+"' now allowed") if [<%= meths.collect {|m| ":\#{m}"}.join(", ") %>].include?(meth)
96
+ <% else %>
97
+ raise NameError.new("Method `"+meth.to_s+"' now allowed") unless [<%= meths.collect {|m| ":\#{m}"}.join(", ") %>].include?(meth) || !@wrapped_object.class.method_defined?(meth)
98
+ <% end %>
99
+ ActsAsWrappedClass::WrapperFinder.find_wrapper_for(@wrapped_object.send(meth, *args));
100
+ end
101
+ EOF
102
+ else
103
+ method_defs_erb = <<-EOF
104
+ def method_missing(meth, *args);
105
+ raise NameError.new("Method `"+meth.to_s+"' now allowed") <%= options[:methods] ? "unless" : "if"%> [<%=meths.collect {|m| ":\#{m}"}.join(", ")%>].include?(meth)
106
+ ActsAsWrappedClass::WrapperFinder.find_wrapper_for(@wrapped_object.method(meth).call(*args));
107
+ end
108
+ EOF
109
+ end
110
+
111
+ # if allowed_const_missing
112
+ # const_defs_erb = <<-EOF
113
+ # def const_missing(const, *args);
114
+ # <% if options[:except_constants] %>
115
+ # raise NameError.new("Constant `"+const.to_s+"' now allowed") if [<%= consts.collect {|c| ":\#{c}"}.join(", ") %>].include?(const)
116
+ # <% else %>
117
+ # raise NameError.new("Method `"+meth.to_s+"' now allowed") unless [<%= meths.collect {|c| ":\#{c}"}.join(", ") %>].include?(const) || !@wrapped_object.class.const_defined?(const)
118
+ # <% end %>
119
+ # @wrapped_object.const_get(const)
120
+ # end
121
+ # EOF
122
+ # else
123
+ # const_defs_erb = <<-EOF
124
+ # def const_missing(const, *args);
125
+ # raise NameError.new("Constant `"+const.to_s+"' now allowed") <%= options[:constants] ? "unless" : "if"%> [<%=consts.collect {|m| ":\#{m}"}.join(", ")%>].include?(const)
126
+ # @wrapped_object.const_get(const)
127
+ # end
128
+ # EOF
129
+ # end
130
+
131
+ method_defs = ERB.new(method_defs_erb).result(binding)
132
+
133
+ allowed_methods = <<-EOF
134
+ def self.allowed_methods
135
+ #{ options[:except_methods] ?
136
+ ("#{self.name}.public_instance_methods - [" + (meths + WrapperBase::ALLOWED_METHODS + ["to_wrapper"]).collect{|m| "\"#{m}\"" }.join(", ") + "]") :
137
+ ("[" + meths.collect{|m| "\"#{m}\""}.join(", ") + "]")
138
+ }
139
+ end
140
+ EOF
141
+
142
+ wrapper_class_code = <<-EOF
143
+ class #{self.name}Wrapper < WrapperBase
144
+ #{method_defs}
145
+ #{allowed_methods}
146
+ end
147
+ EOF
148
+
149
+ eval wrapper_class_code, TOPLEVEL_BINDING
150
+ end
151
+ end
152
+
153
+ Object.send(:include, ActsAsWrappedClass)
154
+
155
+ require File.join(File.dirname(__FILE__), "wrapper_base")
@@ -0,0 +1,38 @@
1
+ class WrapperBase
2
+ ALLOWED_METHODS = ["__id__","__send__", "is_a?", "kind_of?", "hash", "class", "inspect"]
3
+
4
+ eval((public_instance_methods - ALLOWED_METHODS).collect{|meth| "undef "+meth}.join("; "))
5
+
6
+ # Create a wrapper, passing in an object to wrap
7
+ def initialize(wrapped_object)
8
+ @wrapped_object = wrapped_object
9
+ end
10
+
11
+ def hash
12
+ @wrapped_object.hash
13
+ end
14
+
15
+ def ==(other)
16
+ return false if self.class != other.class
17
+ @wrapped_object == other._wrapped_object
18
+ end
19
+
20
+ def <=>(other)
21
+ raise "Can't compare objects of different types" if self.class != other.class
22
+ @wrapped_object <=> other._wrapped_object
23
+ end
24
+
25
+ def ===(other)
26
+ return false if self.class != other.class
27
+ @wrapped_object === other._wrapped_object
28
+ end
29
+
30
+ # Provide access to the wrapped object
31
+ def _wrapped_object
32
+ @wrapped_object
33
+ end
34
+
35
+ def self.wrapper_class?
36
+ true
37
+ end
38
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dsboulder-acts_as_wrapped_class
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Stevenson
@@ -24,6 +24,8 @@ extra_rdoc_files: []
24
24
  files:
25
25
  - README.txt
26
26
  - History.txt
27
+ - lib/acts_as_wrapped_class.rb
28
+ - lib/wrapper_base.rb
27
29
  has_rdoc: true
28
30
  homepage: http://github.com/dsboulder/acts_as_wrapped_cass
29
31
  post_install_message: