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.
- data/lib/acts_as_wrapped_class.rb +155 -0
- data/lib/wrapper_base.rb +38 -0
- metadata +3 -1
|
@@ -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")
|
data/lib/wrapper_base.rb
ADDED
|
@@ -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.
|
|
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:
|