rubyless 0.1.0

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,4 @@
1
+ == 0.1.0 2009-06-02
2
+
3
+ * 1 major enhancement:
4
+ * Initial alpha release
@@ -0,0 +1,92 @@
1
+ = RubyLess
2
+
3
+ * http://zenadmin.org/546
4
+
5
+ == DESCRIPTION:
6
+
7
+ RubyLess is an interpreter for "safe ruby". The idea is to transform some "unsafe" ruby code into safe, type checked
8
+ ruby, eventually rewriting some variables or methods. The goals:
9
+
10
+ 1. give ruby scripting access to users without any security risk
11
+ 2. rewrite variable names depending on compilation context
12
+ 3. never raise runtime errors through compile time type checking and powerful nil handling
13
+
14
+ This library is based on Ruby2Ruby by Ryan Davis, thanks to him for sharing his work.
15
+
16
+ == SYNOPSIS:
17
+
18
+ For every class that will be involved in your RubyLess scripts, you need to declare safe methods with the 'safe_method' macro if
19
+ you want to enable methods from this class. You have to specify the return type of the method. If you have some methods that
20
+ return 'nil' instead of the declared output, you need to wrap your final ruby 'eval' with a rescue clause.
21
+
22
+ # signature is made of [method, arg_class, arg_class, ...]
23
+ class Node
24
+ include RubyLess::SafeClass
25
+ safe_method [:ancestor?, Node] => RubyLess::Boolean
26
+ end
27
+
28
+ # methods defined in helper
29
+
30
+ # global methods
31
+ include RubyLess::SafeClass
32
+ safe_method :prev => {:class => Dummy, :method => 'previous', :nil => true}
33
+ safe_method :node => lambda {|h| {:class => h.context[:node_class], :method => h.context[:node]}}
34
+ safe_method [:strftime, Time, String] => String
35
+ safe_method_for String, [:==, String] => RubyLess::Boolean
36
+ safe_method_for String, [:to_s] => String
37
+
38
+ You can also redefine 'safe_method?' for any class or for the main helper in order to do some more complicated renaming. Note
39
+ also that you should add ':nil => true' declaration to any method that could return a nil value so that RubyLess can render
40
+ code that will not break during runtime (adding nil checking in the form of "foo ? foo.name : nil").
41
+
42
+ You can now parse some ruby code:
43
+
44
+ RubyLess.translate("!prev.ancestor?(main) && !node.ancestor?(main)", self)
45
+ => "(not previous.ancestor?(@node) and not var1.ancestor?(@node))"
46
+
47
+ RubyLess.translate("id > 45 and (3 > -id or 3+3)", self)
48
+ => "(var1.zip>45 and ((3>-var1.zip) or (3+3)))"
49
+
50
+ RubyLess.translate("strftime(now, '%Y')", self)
51
+ => "strftime(Time.now, \"%Y\")"
52
+
53
+ RubyLess.translate("log_info(spouse, spouse.name)", self)
54
+ => "(var1.spouse ? log_info(var1.spouse, var1.spouse.name) : nil)"
55
+
56
+ Since most of the code in SafeClass is string evaluated (to scope class variables), there is not much to parse for rdoc. You
57
+ can look at the tests for an idea of how to declare things. If you have more questions, ask on zena's mailing list:
58
+
59
+ http://zenadmin.org/community
60
+
61
+ == REQUIREMENTS:
62
+
63
+ * parse_tree
64
+
65
+ == INSTALL:
66
+
67
+ sudo gem install rubyless
68
+
69
+ == LICENSE:
70
+
71
+ (The MIT License)
72
+
73
+ Copyright (c) 2009 Gaspard Bucher
74
+
75
+ Permission is hereby granted, free of charge, to any person obtaining
76
+ a copy of this software and associated documentation files (the
77
+ 'Software'), to deal in the Software without restriction, including
78
+ without limitation the rights to use, copy, modify, merge, publish,
79
+ distribute, sublicense, and/or sell copies of the Software, and to
80
+ permit persons to whom the Software is furnished to do so, subject to
81
+ the following conditions:
82
+
83
+ The above copyright notice and this permission notice shall be
84
+ included in all copies or substantial portions of the Software.
85
+
86
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
87
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
88
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
89
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
90
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
91
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
92
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,124 @@
1
+ require "rubygems"
2
+ require "rake/gempackagetask"
3
+ require "rake/rdoctask"
4
+ require "lib/RubyLess"
5
+
6
+ task :default => :test
7
+
8
+ require "rake/testtask"
9
+ Rake::TestTask.new do |t|
10
+ t.libs << "test"
11
+ t.test_files = FileList["test/**/*_test.rb"]
12
+ t.verbose = true
13
+ end
14
+
15
+ # This builds the actual gem. For details of what all these options
16
+ # mean, and other ones you can add, check the documentation here:
17
+ #
18
+ # http://rubygems.org/read/chapter/20
19
+ #
20
+ spec = Gem::Specification.new do |s|
21
+
22
+ # Change these as appropriate
23
+ s.name = "rubyless"
24
+ s.version = RubyLess::VERSION
25
+ s.summary = %q{RubyLess is an interpreter for "safe ruby". The idea is to transform some "unsafe" ruby code into safe, type checked
26
+ ruby, eventually rewriting some variables or methods}
27
+ s.author = "Gaspard Bucher"
28
+ s.email = "gaspard@teti.ch"
29
+ s.homepage = "http://zenadmin.org/546"
30
+
31
+ s.has_rdoc = true
32
+ s.extra_rdoc_files = %w(README.rdoc)
33
+ s.rdoc_options = %w(--main README.rdoc)
34
+
35
+ # Add any extra files to include in the gem
36
+ s.files = %w(History.txt Rakefile README.rdoc rubyless.gemspec) + Dir.glob("{test,lib}/**/*")
37
+
38
+ s.require_paths = ["lib"]
39
+
40
+ # If you want to depend on other gems, add them here, along with any
41
+ # relevant versions
42
+ # s.add_dependency("some_other_gem", "~> 0.1.0")
43
+
44
+ # If your tests use any gems, include them here
45
+ # s.add_development_dependency("mocha")
46
+
47
+ # If you want to publish automatically to rubyforge, you'll may need
48
+ # to tweak this, and the publishing task below too.
49
+ s.rubyforge_project = "rubyless"
50
+ end
51
+
52
+ # This task actually builds the gem. We also regenerate a static
53
+ # .gemspec file, which is useful if something (i.e. GitHub) will
54
+ # be automatically building a gem for this project. If you're not
55
+ # using GitHub, edit as appropriate.
56
+ Rake::GemPackageTask.new(spec) do |pkg|
57
+ pkg.gem_spec = spec
58
+
59
+ # Generate the gemspec file for github.
60
+ file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
61
+ File.open(file, "w") {|f| f << spec.to_ruby }
62
+ end
63
+
64
+ # Generate documentation
65
+ Rake::RDocTask.new do |rd|
66
+ rd.main = "README.rdoc"
67
+ rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
68
+ rd.rdoc_dir = "rdoc"
69
+ end
70
+
71
+ desc 'Clear out RDoc and generated packages'
72
+ task :clean => [:clobber_rdoc, :clobber_package] do
73
+ rm "#{spec.name}.gemspec"
74
+ end
75
+
76
+ # If you want to publish to RubyForge automatically, here's a simple
77
+ # task to help do that. If you don't, just get rid of this.
78
+ # Be sure to set up your Rubyforge account details with the Rubyforge
79
+ # gem; you'll need to run `rubyforge setup` and `rubyforge config` at
80
+ # the very least.
81
+ begin
82
+ require "rake/contrib/sshpublisher"
83
+ namespace :rubyforge do
84
+
85
+ desc "Release gem and RDoc documentation to RubyForge"
86
+ task :release => ["rubyforge:release:gem", "rubyforge:release:docs"]
87
+
88
+ namespace :release do
89
+ desc "Release a new version of this gem"
90
+ task :gem => [:package] do
91
+ require 'rubyforge'
92
+ rubyforge = RubyForge.new
93
+ rubyforge.configure
94
+ rubyforge.login
95
+ rubyforge.userconfig['release_notes'] = spec.summary
96
+ path_to_gem = File.join(File.dirname(__FILE__), "pkg", "#{spec.name}-#{spec.version}.gem")
97
+ puts "Publishing #{spec.name}-#{spec.version.to_s} to Rubyforge..."
98
+ rubyforge.add_release(spec.rubyforge_project, spec.name, spec.version.to_s, path_to_gem)
99
+ end
100
+
101
+ desc "Publish RDoc to RubyForge."
102
+ task :docs => [:rdoc] do
103
+ config = YAML.load(
104
+ File.read(File.expand_path('~/.rubyforge/user-config.yml'))
105
+ )
106
+
107
+ host = "#{config['username']}@rubyforge.org"
108
+ remote_dir = "/var/www/gforge-projects/rubyless/" # Should be the same as the rubyforge project name
109
+ local_dir = 'rdoc'
110
+
111
+ Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
112
+ end
113
+ end
114
+ end
115
+ rescue LoadError
116
+ puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
117
+ end
118
+
119
+ desc 'Generate the gemspec to serve this Gem from Github'
120
+ task :github do
121
+ file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
122
+ File.open(file, 'w') {|f| f << spec.to_ruby }
123
+ puts "Created gemspec: #{file}"
124
+ end
@@ -0,0 +1,304 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'rubygems'
5
+ require 'parse_tree'
6
+ require 'SafeClass'
7
+ =begin rdoc
8
+ =end
9
+ module RubyLess
10
+ VERSION = '0.1.0'
11
+
12
+ def self.translate(string, helper)
13
+ RubyLessProcessor.translate(string, helper)
14
+ end
15
+
16
+ class Boolean
17
+ end
18
+
19
+ class Number
20
+ include SafeClass
21
+ safe_method( [:==, Number] => Boolean, [:< , Number] => Boolean, [:> , Number] => Boolean, [:<=, Number] => Boolean, [:>=, Number] => Boolean,
22
+ [:- , Number] => Number, [:+ , Number] => Number, [:* , Number] => Number, [:/ , Number] => Number,
23
+ [:% , Number] => Number, [:"-@"] => Number )
24
+ end
25
+
26
+
27
+ class Missing
28
+ [:==, :< , :> , :<=, :>=, :"?"].each do |sym|
29
+ define_method(sym) do |arg|
30
+ false
31
+ end
32
+ end
33
+
34
+ def to_s
35
+ ''
36
+ end
37
+
38
+ def nil?
39
+ true
40
+ end
41
+
42
+ def method_missing(*meth)
43
+ self
44
+ end
45
+ end
46
+
47
+ Nil = Missing.new
48
+
49
+ class TypedString < String
50
+ attr_reader :klass, :opts
51
+
52
+ def initialize(content = "", opts = nil)
53
+ opts ||= {:class => String}
54
+ replace(content)
55
+ @opts = opts.dup
56
+ if could_be_nil? && !@opts[:cond]
57
+ @opts[:cond] = [self.to_s]
58
+ end
59
+ end
60
+
61
+ def klass
62
+ @opts[:class]
63
+ end
64
+
65
+ def could_be_nil?
66
+ @opts[:nil]
67
+ end
68
+
69
+ # condition when 'could_be_nil' comes from a different method then the last one:
70
+ # var1.spouse.name == ''
71
+ # "var1.spouse" would be the condition that inserted 'could_be_nil?'.
72
+ def cond
73
+ @opts[:cond]
74
+ end
75
+
76
+ # raw result without nil checking:
77
+ # "var1.spouse.name" instead of "(var1.spouse ? var1.spouse.name : nil)"
78
+ def raw
79
+ @opts[:raw] || self.to_s
80
+ end
81
+
82
+ def <<(typed_string)
83
+ append_opts(typed_string)
84
+ if self.empty?
85
+ replace(typed_string.raw)
86
+ else
87
+ replace("#{self.raw}, #{typed_string.raw}")
88
+ end
89
+ end
90
+
91
+ def append_opts(typed_string)
92
+ if self.empty?
93
+ @opts = typed_string.opts.dup
94
+ else
95
+ if klass.kind_of?(Array)
96
+ klass << typed_string.klass
97
+ else
98
+ @opts[:class] = [klass, typed_string.klass]
99
+ end
100
+ append_cond(typed_string.cond) if typed_string.could_be_nil?
101
+ end
102
+ end
103
+
104
+ def append_cond(condition)
105
+ @opts[:cond] ||= []
106
+ @opts[:cond] += [condition].flatten
107
+ @opts[:cond].uniq!
108
+ end
109
+ end
110
+
111
+ class RubyLessProcessor < SexpProcessor
112
+ attr_reader :ruby
113
+
114
+ INFIX_OPERATOR = [:"<=>", :==, :<, :>, :<=, :>=, :-, :+, :*, :/, :%]
115
+ PREFIX_OPERATOR = [:"-@"]
116
+
117
+ def self.translate(string, helper)
118
+ sexp = ParseTree.translate(string)
119
+ self.new(helper).process(sexp)
120
+ end
121
+
122
+ def initialize(helper)
123
+ super()
124
+ @helper = helper
125
+ @indent = " "
126
+ self.auto_shift_type = true
127
+ self.strict = true
128
+ self.expected = TypedString
129
+ end
130
+
131
+ def process_and(exp)
132
+ t "(#{process(exp.shift)} and #{process(exp.shift)})", Boolean
133
+ end
134
+
135
+ def process_or(exp)
136
+ t "(#{process(exp.shift)} or #{process(exp.shift)})", Boolean
137
+ end
138
+
139
+ def process_not(exp)
140
+ t "not #{process(exp.shift)}", Boolean
141
+ end
142
+
143
+ def process_if(exp)
144
+ cond = process(exp.shift)
145
+ true_res = process(exp.shift)
146
+ false_res = process(exp.shift)
147
+
148
+ if true_res && false_res && true_res.klass != false_res.klass
149
+ raise "Error in conditional expression: '#{true_res}' and '#{false_res}' do not return results of same type (#{true_res.klass} != #{false_res.klass})."
150
+ end
151
+ raise "Error in conditional expression." unless true_res || false_res
152
+ opts = {}
153
+ opts[:nil] = true_res.nil? || true_res.could_be_nil? || false_res.nil? || false_res.could_be_nil?
154
+ opts[:class] = true_res ? true_res.klass : false_res.klass
155
+ t "#{cond} ? #{true_res || 'nil'} : #{false_res || 'nil'}", opts
156
+ end
157
+
158
+ def process_call(exp)
159
+ receiver_node_type = exp.first.nil? ? nil : exp.first.first
160
+ receiver = process exp.shift
161
+
162
+ # receiver = t("(#{receiver})", receiver.klass) if
163
+ # Ruby2Ruby::ASSIGN_NODES.include? receiver_node_type
164
+
165
+ method_call(receiver, exp)
166
+ end
167
+
168
+ def process_fcall(exp)
169
+ method_call(nil, exp)
170
+ end
171
+
172
+ def process_arglist(exp)
173
+ code = t("")
174
+ until exp.empty? do
175
+ code << process(exp.shift)
176
+ end
177
+ code
178
+ end
179
+
180
+ def process_array(exp)
181
+ res = process_arglist(exp)
182
+ exp.size > 1 ? t("[#{res}]", res.opts) : res
183
+ end
184
+
185
+ def process_vcall(exp)
186
+ var_name = exp.shift
187
+ unless opts = get_method([var_name], @helper, false)
188
+ raise "Unknown variable or method '#{var_name}'."
189
+ end
190
+ method = opts[:method] || var_name.to_s
191
+ t method, opts
192
+ end
193
+
194
+ def process_lit(exp)
195
+ t exp.shift.to_s, Number
196
+ end
197
+
198
+ def process_str(exp)
199
+ t exp.shift.inspect, String
200
+ end
201
+
202
+ def process_dstr(exp)
203
+ t "\"#{parse_dstr(exp)}\"", String
204
+ end
205
+
206
+ def process_evstr(exp)
207
+ exp.empty? ? t('', String) : process(exp.shift)
208
+ end
209
+
210
+ private
211
+ def t(content, opts = nil)
212
+ if opts.nil?
213
+ opts = {:class => String}
214
+ elsif !opts.kind_of?(Hash)
215
+ opts = {:class => opts}
216
+ end
217
+ TypedString.new(content, opts)
218
+ end
219
+
220
+ def t_if(cond, true_res, opts)
221
+ if cond != []
222
+ if cond.size > 1
223
+ condition = "(#{cond.join(' && ')})"
224
+ else
225
+ condition = cond.join('')
226
+ end
227
+
228
+ # we can append to 'raw'
229
+ if opts[:nil]
230
+ # applied method could produce a nil value (so we cannot concat method on top of 'raw' and only check previous condition)
231
+ t "(#{condition} ? #{true_res} : nil)", opts
232
+ else
233
+ # we can keep on checking only 'condition' and appending methods to 'raw'
234
+ t "(#{condition} ? #{true_res} : nil)", opts.merge(:nil => true, :cond => cond, :raw => true_res)
235
+ end
236
+ else
237
+ t true_res, opts
238
+ end
239
+ end
240
+
241
+ def method_call(receiver, exp)
242
+ method = exp.shift
243
+ if args = exp.shift rescue nil
244
+ args = process args || []
245
+ signature = [method] + [args.klass].flatten ## FIXME: error prone !
246
+ # execution conditional
247
+ cond = args.cond || []
248
+ else
249
+ args = []
250
+ signature = [method]
251
+ cond = []
252
+ end
253
+
254
+ if receiver
255
+ if receiver.could_be_nil?
256
+ cond += receiver.cond
257
+ end
258
+ raise "'#{receiver}' does not respond to '#{method}(#{args.raw})'." unless opts = get_method(signature, receiver.klass)
259
+ method = opts[:method] if opts[:method]
260
+ if method == :/
261
+ t_if cond, "(#{receiver.raw}#{method}#{args.raw} rescue nil)", opts.merge(:nil => true)
262
+ elsif INFIX_OPERATOR.include?(method)
263
+ t_if cond, "(#{receiver.raw}#{method}#{args.raw})", opts
264
+ elsif PREFIX_OPERATOR.include?(method)
265
+ t_if cond, "#{method.to_s[0..0]}#{receiver.raw}", opts
266
+ else
267
+ args = "(#{args.raw})" if args != []
268
+ t_if cond, "#{receiver.raw}.#{method}#{args}", opts
269
+ end
270
+ else
271
+ raise "Unknown method '#{method}(#{args.raw})'." unless opts = get_method(signature, @helper, false)
272
+ method = opts[:method] if opts[:method]
273
+ args = "(#{args.raw})" if args != []
274
+ t_if cond, "#{method}#{args}", opts
275
+ end
276
+ end
277
+
278
+ def parse_dstr(exp, in_regex = false)
279
+ res = escape_str(exp.shift, in_regex)
280
+
281
+ while part = exp.shift
282
+ case part.first
283
+ when :str then
284
+ res << escape_str(part.last, in_regex)
285
+ else
286
+ res << '#{' << process(part) << '}'
287
+ end
288
+ end
289
+ res
290
+ end
291
+
292
+ def escape_str(str, in_regex = false)
293
+ res = str.gsub(/"/, '\"').gsub(/\n/, '\n')
294
+ res.gsub!(/\//, '\/') if in_regex
295
+ res
296
+ end
297
+
298
+ def get_method(signature, receiver, is_method = true)
299
+ res = receiver.respond_to?(:safe_method?) ? receiver.safe_method?(signature) : @helper.class.safe_method_for?(receiver, signature)
300
+ res = res.call(@helper) if res.kind_of?(Proc)
301
+ res
302
+ end
303
+ end
304
+ end
@@ -0,0 +1,98 @@
1
+ module RubyLess
2
+ module SafeClass
3
+ def self.included(base)
4
+ # add all methods from the module "AddActsAsMethod" to the 'base' module
5
+ base.class_eval <<-END
6
+ @@_safe_methods ||= {} # defined for each class
7
+ @@_safe_methods_all ||= {} # full list with inherited attributes
8
+
9
+ def self.safe_method(hash)
10
+ list = (@@_safe_methods[self] ||= {})
11
+ hash.each do |k,v|
12
+ k = [k] unless k.kind_of?(Array)
13
+ v = {:class => v} unless v.kind_of?(Hash) || v.kind_of?(Proc)
14
+ list[k] = v
15
+ end
16
+ end
17
+
18
+ def self.safe_methods
19
+ safe_methods_for(self)
20
+ end
21
+
22
+ def self.safe_methods_for(klass)
23
+ @@_safe_methods_all[klass] ||= build_safe_methods_list(klass)
24
+ end
25
+
26
+ def self.build_safe_methods_list(klass)
27
+ list = klass.superclass.respond_to?(:safe_methods) ? klass.superclass.safe_methods : {}
28
+ (@@_safe_methods[klass] || {}).map do |signature, return_value|
29
+ if return_value.kind_of?(Hash)
30
+ return_value[:class] = parse_class(return_value[:class])
31
+ elsif !return_value.kind_of?(Proc)
32
+ return_value = {:class => return_value}
33
+ end
34
+ signature.map! {|e| parse_class(e)}
35
+ list[signature] = return_value
36
+ end
37
+ list
38
+ end
39
+
40
+ def self.safe_method?(signature)
41
+ if res = safe_methods[signature]
42
+ res.dup
43
+ else
44
+ nil
45
+ end
46
+ end
47
+
48
+ def self.safe_method_for?(klass, signature)
49
+ if res = safe_methods_for(klass)[signature]
50
+ res.dup
51
+ else
52
+ nil
53
+ end
54
+ end
55
+
56
+ def safe_method?(signature)
57
+ self.class.safe_methods[signature]
58
+ end
59
+
60
+ def self.safe_method_for(klass, hash)
61
+ list = (@@_safe_methods[klass] ||= {})
62
+ hash.each do |k,v|
63
+ k = [k] unless k.kind_of?(Array)
64
+ v = {:class => v} unless v.kind_of?(Hash) || v.kind_of?(Proc)
65
+ list[k] = v
66
+ end
67
+ end
68
+
69
+ def self.parse_class(klass)
70
+ if klass.kind_of?(Array)
71
+ if klass[0].kind_of?(String)
72
+ [Module::const_get(klass[0])]
73
+ else
74
+ klass
75
+ end
76
+ else
77
+ if klass.kind_of?(String)
78
+ Module::const_get(klass)
79
+ else
80
+ klass
81
+ end
82
+ end
83
+ end
84
+
85
+ def self.safe_attribute?(sym)
86
+ column_names.include?(sym) || zafu_readable?(sym) || safe_attribute_list.include?(sym.to_s)
87
+ end
88
+
89
+ def self.zafu_readable?(sym)
90
+ if sym.to_s =~ /(.*)_zips?$/
91
+ return true if self.ancestors.include?(Node) && RelationProxy.find_by_role($1.singularize)
92
+ end
93
+ self.zafu_readable_attributes.include?(sym.to_s)
94
+ end
95
+ END
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{rubyless}
5
+ s.version = "0.1.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Gaspard Bucher"]
9
+ s.date = %q{2009-06-02}
10
+ s.email = %q{gaspard@teti.ch}
11
+ s.extra_rdoc_files = ["README.rdoc"]
12
+ s.files = ["History.txt", "Rakefile", "README.rdoc", "rubyless.gemspec", "test/mock", "test/mock/dummy_class.rb", "test/RubyLess", "test/RubyLess/basic.yml", "test/RubyLess/errors.yml", "test/RubyLess_test.rb", "test/test_helper.rb", "lib/RubyLess.rb", "lib/SafeClass.rb"]
13
+ s.has_rdoc = true
14
+ s.homepage = %q{http://zenadmin.org/546}
15
+ s.rdoc_options = ["--main", "README.rdoc"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = %q{rubyless}
18
+ s.rubygems_version = %q{1.3.1}
19
+ s.summary = %q{RubyLess is an interpreter for "safe ruby". The idea is to transform some "unsafe" ruby code into safe, type checked ruby, eventually rewriting some variables or methods}
20
+
21
+ if s.respond_to? :specification_version then
22
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
23
+ s.specification_version = 2
24
+
25
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
26
+ else
27
+ end
28
+ else
29
+ end
30
+ end
@@ -0,0 +1,81 @@
1
+ empty:
2
+ src: ""
3
+ tem: null
4
+
5
+ numbers:
6
+ src: "id > 45 and (3 > -id or 3+3)"
7
+ tem: "((var1.zip>45) and ((3>-var1.zip) or (3+3)))"
8
+
9
+ global_method:
10
+ src: "strftime(now, '%Y')"
11
+ tem: "strftime(Time.now, \"%Y\")"
12
+
13
+ dynamic_string:
14
+ src: "strftime(now, \"#{name}\")"
15
+ tem: "strftime(Time.now, \"#{var1.name}\")"
16
+
17
+ dynamic_string_again:
18
+ src: "strftime(now, \"#{name}\")"
19
+ tem: "strftime(Time.now, \"#{var1.name}\")"
20
+
21
+ rewrite_variables:
22
+ src: "!prev.ancestor?(main) && !node.ancestor?(main)"
23
+ tem: "(not previous.ancestor?(@node) and not var1.ancestor?(@node))"
24
+
25
+ method_can_return_nil:
26
+ src: "spouse.name"
27
+ tem: "(var1.spouse ? var1.spouse.name : nil)"
28
+
29
+ method_on_method_can_return_nil:
30
+ src: "spouse.name == 'yo'"
31
+ tem: "(var1.spouse ? (var1.spouse.name==\"yo\") : nil)"
32
+ res: ""
33
+
34
+ nil_greater_then:
35
+ src: "spouse.id > 1"
36
+ tem: "(var1.spouse ? (var1.spouse.zip>1) : nil)"
37
+
38
+ nil_ternary_op:
39
+ src: "spouse ? 'foo' : 'bar'"
40
+ tem: "var1.spouse ? \"foo\" : \"bar\""
41
+ res: 'bar'
42
+
43
+ nested_ternary_op:
44
+ src: "spouse.name == 'Adam' ? 'man' : 'not a man'"
45
+ tem: "(var1.spouse ? (var1.spouse.name==\"Adam\") : nil) ? \"man\" : \"not a man\""
46
+ res: "not a man"
47
+
48
+ method_on_method:
49
+ src: "project.name.to_s"
50
+ tem: "var1.project.name.to_s"
51
+ res: 'project'
52
+
53
+ comp_ternary_op:
54
+ src: "1 > 2 ? 'foo' : 'bar'"
55
+ tem: "(1>2) ? \"foo\" : \"bar\""
56
+ res: "bar"
57
+
58
+ method_ternary_op:
59
+ src: "id > 2 ? 'foo' : 'bar'"
60
+ tem: "(var1.zip>2) ? \"foo\" : \"bar\""
61
+ res: "foo"
62
+
63
+ method_argument_can_be_nil:
64
+ src: "vowel_count(spouse.name)"
65
+ tem: "(var1.spouse ? vowel_count(var1.spouse.name) : nil)"
66
+
67
+ multi_arg_method_argument_can_be_nil:
68
+ src: "log_info(spouse, 'foobar')"
69
+ tem: "(var1.spouse ? log_info(var1.spouse, \"foobar\") : nil)"
70
+
71
+ multi_arg_method_arguments_can_be_nil:
72
+ src: "log_info(husband, spouse.name)"
73
+ tem: "((var1.husband && var1.spouse) ? log_info(var1.husband, var1.spouse.name) : nil)"
74
+
75
+ multi_arg_method_arguments_can_be_nil_same_condition:
76
+ src: "log_info(spouse, spouse.name)"
77
+ tem: "(var1.spouse ? log_info(var1.spouse, var1.spouse.name) : nil)"
78
+
79
+ literal_argument_for_method:
80
+ src: "vowel_count('ruby')"
81
+ res: "2"
@@ -0,0 +1,16 @@
1
+ unknown_global_method:
2
+ src: "system('echo date')"
3
+ res: "Unknown method 'system(\"echo date\")'."
4
+
5
+ bad_argument_types:
6
+ src: "strftime(34,'ffoo')"
7
+ res: "Unknown method 'strftime(34, \"ffoo\")'."
8
+
9
+ zero_div:
10
+ src: "1/(id-10)"
11
+ tem: "(1/(var1.zip-10) rescue nil)"
12
+ res: ""
13
+
14
+ no_looping:
15
+ src: "while(true) do puts 'flood' end"
16
+ res: 'Bug! Unknown node-type :while to RubyLess::RubyLessProcessor'
@@ -0,0 +1,65 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class SimpleHelper < Test::Unit::TestCase
4
+ attr_reader :context
5
+ yamltest :src_from_title => false
6
+ include RubyLess::SafeClass
7
+ safe_method :prev => {:class => Dummy, :method => 'previous'}
8
+ safe_method :main => {:class => Dummy, :method => '@node'}
9
+ safe_method :node => lambda {|h| {:class => h.context[:node_class], :method => h.context[:node]}}
10
+ safe_method :now => {:class => Time, :method => 'Time.now'}
11
+ safe_method [:strftime, Time, String] => String
12
+ safe_method [:vowel_count, String] => RubyLess::Number
13
+ safe_method [:log_info, Dummy, String] => String
14
+ safe_method_for String, [:==, String] => RubyLess::Boolean
15
+ safe_method_for String, [:to_s] => String
16
+
17
+ def safe_method?(signature)
18
+ unless res = self.class.safe_method?(signature)
19
+ # try to execute method in the current var "var.method"
20
+ if res = context[:node_class].safe_method?(signature)
21
+ res = res.call(self) if res.kind_of?(Proc)
22
+ res[:method] = "#{context[:node]}.#{res[:method] || signature[0]}"
23
+ end
24
+ end
25
+ res
26
+ end
27
+
28
+ def var1
29
+ Dummy.new
30
+ end
31
+
32
+ def vowel_count(str)
33
+ str.tr('^aeiouy', '').size
34
+ end
35
+
36
+ def log_info(obj, msg)
37
+ "[#{obj.name}] #{msg}"
38
+ end
39
+
40
+ def yt_do_test(file, test, context = yt_get('context',file,test))
41
+ @@test_strings[file][test].keys.each do |key|
42
+ next if ['src', 'context'].include?(key)
43
+ yt_assert yt_get(key,file,test), parse(key, file, test, context)
44
+ end
45
+ end
46
+
47
+ def parse(key, file, test, opts)
48
+ @context = {:node => 'var1', :node_class => Dummy}
49
+ source = yt_get('src', file, test)
50
+ case key
51
+ when 'tem'
52
+ source ? RubyLess.translate(source, self) : yt_get('tem', file, test)
53
+ when 'res'
54
+ eval(source ? RubyLess.translate(source, self) : yt_get('tem', file, test)).to_s
55
+ else
56
+ "Unknown key '#{key}'. Should be 'tem' or 'res'."
57
+ end
58
+ rescue => err
59
+ # puts "\n\n#{err.message}"
60
+ # puts err.backtrace
61
+ err.message
62
+ end
63
+
64
+ yt_make
65
+ end
@@ -0,0 +1,35 @@
1
+ class Dummy
2
+ attr_reader :name
3
+ include RubyLess::SafeClass
4
+
5
+ safe_method [:ancestor?, Dummy] => RubyLess::Boolean
6
+ safe_method :parent => {:class => 'Dummy', :special_option => 'foobar'}
7
+ safe_method :children => ['Dummy']
8
+ safe_method :project => 'Dummy'
9
+ safe_method :spouse => {:class => 'Dummy', :nil => true}
10
+ safe_method :husband => {:class => 'Dummy', :nil => true}
11
+ safe_method :id => {:class => RubyLess::Number, :method => :zip}
12
+ safe_method :name => String
13
+
14
+ def initialize(name = 'dummy')
15
+ @name = name
16
+ end
17
+
18
+ # This method returns pseudo-nil and does not need to be declared with :nil => true
19
+ def project
20
+ Dummy.new('project')
21
+ end
22
+
23
+ # This method can return nil and must be declared with :nil => true
24
+ def spouse
25
+ nil
26
+ end
27
+
28
+ def husband
29
+ nil
30
+ end
31
+
32
+ def zip
33
+ 10
34
+ end
35
+ end
@@ -0,0 +1,6 @@
1
+ require 'stringio'
2
+ require 'test/unit'
3
+ require File.dirname(__FILE__) + '/../lib/RubyLess'
4
+ require File.dirname(__FILE__) + '/mock/dummy_class'
5
+ require 'rubygems'
6
+ require 'yamltest'
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubyless
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Gaspard Bucher
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-02 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: gaspard@teti.ch
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ files:
25
+ - History.txt
26
+ - Rakefile
27
+ - README.rdoc
28
+ - rubyless.gemspec
29
+ - test/mock
30
+ - test/mock/dummy_class.rb
31
+ - test/RubyLess
32
+ - test/RubyLess/basic.yml
33
+ - test/RubyLess/errors.yml
34
+ - test/RubyLess_test.rb
35
+ - test/test_helper.rb
36
+ - lib/RubyLess.rb
37
+ - lib/SafeClass.rb
38
+ has_rdoc: true
39
+ homepage: http://zenadmin.org/546
40
+ post_install_message:
41
+ rdoc_options:
42
+ - --main
43
+ - README.rdoc
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: "0"
51
+ version:
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ requirements: []
59
+
60
+ rubyforge_project: rubyless
61
+ rubygems_version: 1.3.1
62
+ signing_key:
63
+ specification_version: 2
64
+ summary: RubyLess is an interpreter for "safe ruby". The idea is to transform some "unsafe" ruby code into safe, type checked ruby, eventually rewriting some variables or methods
65
+ test_files: []
66
+