rubyless 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +15 -1
- data/{README.rdoc → README.txt} +14 -11
- data/Rakefile +30 -106
- data/lib/basic_types.rb +12 -36
- data/lib/processor.rb +35 -18
- data/lib/rubyless.rb +2 -2
- data/lib/safe_class.rb +18 -4
- data/rubyless.gemspec +23 -9
- data/tasks/ann.rake +80 -0
- data/tasks/bones.rake +20 -0
- data/tasks/gem.rake +201 -0
- data/tasks/git.rake +40 -0
- data/tasks/notes.rake +27 -0
- data/tasks/post_load.rake +34 -0
- data/tasks/rdoc.rake +51 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +292 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- data/tasks/zentest.rake +36 -0
- data/test/RubyLess/basic.yml +13 -5
- data/test/RubyLess/errors.yml +13 -1
- data/test/RubyLess_test.rb +17 -9
- data/test/mock/dummy_class.rb +9 -8
- metadata +77 -22
data/History.txt
CHANGED
@@ -1,9 +1,23 @@
|
|
1
|
+
== 0.3.0 2009-10-03
|
2
|
+
|
3
|
+
* 1 major enhancement
|
4
|
+
* Moved from ParseTree to RubyParser
|
5
|
+
|
6
|
+
* 1 minor enhancement:
|
7
|
+
* Using Mr Bones to generate gems
|
8
|
+
|
9
|
+
== 0.2.1 2009-07-01
|
10
|
+
|
11
|
+
* 2 minor enhancements:
|
12
|
+
* :[] method is now treated as in conventional ruby (foo[:bar], not foo.[](:bar))
|
13
|
+
* Better error reporting in case of signature mismatch
|
14
|
+
|
1
15
|
== 0.2.0 2009-06-02
|
2
16
|
|
3
17
|
* 1 major enhancement:
|
4
18
|
* Added support for ActiveRecord attributes
|
5
19
|
|
6
|
-
* 2 minor
|
20
|
+
* 2 minor enhancements:
|
7
21
|
* Better documentation
|
8
22
|
* Removed eval (this means safe methods are globally declared)
|
9
23
|
|
data/{README.rdoc → README.txt}
RENAMED
@@ -5,16 +5,19 @@
|
|
5
5
|
== DESCRIPTION:
|
6
6
|
|
7
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.
|
8
|
+
ruby, eventually rewriting some variables or methods.
|
9
|
+
|
10
|
+
|
11
|
+
== GOALS:
|
9
12
|
|
10
13
|
1. give ruby scripting access to users without any security risk
|
11
14
|
2. rewrite variable names depending on compilation context
|
12
|
-
3. never raise runtime errors through compile time type checking and powerful nil handling
|
15
|
+
3. never raise runtime errors through compile time type checking and powerful nil handling
|
13
16
|
|
14
17
|
This library is based on Ruby2Ruby by Ryan Davis, thanks to him for sharing his work.
|
15
18
|
|
16
19
|
== SYNOPSIS:
|
17
|
-
|
20
|
+
|
18
21
|
For every class that will be involved in your RubyLess scripts, you need to declare safe methods with the 'safe_method' macro if
|
19
22
|
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
23
|
return 'nil' instead of the declared output, you need to wrap your final ruby 'eval' with a rescue clause.
|
@@ -22,17 +25,17 @@ return 'nil' instead of the declared output, you need to wrap your final ruby 'e
|
|
22
25
|
# signature is made of [method, arg_class, arg_class, ...]
|
23
26
|
class Node
|
24
27
|
include RubyLess::SafeClass
|
25
|
-
safe_method [:ancestor?, Node] =>
|
28
|
+
safe_method [:ancestor?, Node] => Boolean
|
26
29
|
end
|
27
|
-
|
30
|
+
|
28
31
|
# methods defined in helper
|
29
|
-
|
32
|
+
|
30
33
|
# global methods
|
31
34
|
include RubyLess::SafeClass
|
32
35
|
safe_method :prev => {:class => Dummy, :method => 'previous', :nil => true}
|
33
36
|
safe_method :node => lambda {|h| {:class => h.context[:node_class], :method => h.context[:node]}}
|
34
37
|
safe_method [:strftime, Time, String] => String
|
35
|
-
safe_method_for String, [:==, String] =>
|
38
|
+
safe_method_for String, [:==, String] => Boolean
|
36
39
|
safe_method_for String, [:to_s] => String
|
37
40
|
|
38
41
|
You can also redefine 'safe_method_type' for any class or for the main helper in order to do some more complicated renaming. Note
|
@@ -48,20 +51,20 @@ You can now parse some ruby code:
|
|
48
51
|
|
49
52
|
RubyLess.translate("!prev.ancestor?(main) && !node.ancestor?(main)", self)
|
50
53
|
=> "(not previous.ancestor?(@node) and not var1.ancestor?(@node))"
|
51
|
-
|
54
|
+
|
52
55
|
RubyLess.translate("id > 45 and (3 > -id or 3+3)", self)
|
53
56
|
=> "(var1.zip>45 and ((3>-var1.zip) or (3+3)))"
|
54
|
-
|
57
|
+
|
55
58
|
RubyLess.translate("strftime(now, '%Y')", self)
|
56
59
|
=> "strftime(Time.now, \"%Y\")"
|
57
|
-
|
60
|
+
|
58
61
|
RubyLess.translate("log_info(spouse, spouse.name)", self)
|
59
62
|
=> "(var1.spouse ? log_info(var1.spouse, var1.spouse.name) : nil)"
|
60
63
|
|
61
64
|
You can look at the tests for an idea of how to declare things. If you have more questions, ask on zena's mailing list:
|
62
65
|
|
63
66
|
http://zenadmin.org/community
|
64
|
-
|
67
|
+
|
65
68
|
== REQUIREMENTS:
|
66
69
|
|
67
70
|
* parse_tree
|
data/Rakefile
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
require "rubygems"
|
2
|
-
require "rake/gempackagetask"
|
3
2
|
require "rake/rdoctask"
|
4
|
-
require "lib/rubyless"
|
5
3
|
|
6
4
|
task :default => :test
|
7
5
|
|
@@ -12,113 +10,39 @@ Rake::TestTask.new do |t|
|
|
12
10
|
t.verbose = true
|
13
11
|
end
|
14
12
|
|
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
13
|
|
31
|
-
|
32
|
-
s.extra_rdoc_files = %w(README.rdoc)
|
33
|
-
s.rdoc_options = %w(--main README.rdoc)
|
14
|
+
# BONES gem management
|
34
15
|
|
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
16
|
begin
|
82
|
-
require
|
83
|
-
|
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
|
17
|
+
require 'bones'
|
18
|
+
Bones.setup
|
115
19
|
rescue LoadError
|
116
|
-
|
20
|
+
begin
|
21
|
+
load 'tasks/setup.rb'
|
22
|
+
rescue LoadError
|
23
|
+
raise RuntimeError, '### please install the "bones" gem ###'
|
24
|
+
end
|
117
25
|
end
|
118
26
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
27
|
+
ensure_in_path 'lib'
|
28
|
+
require 'rubyless'
|
29
|
+
|
30
|
+
PROJ.name = 'rubyless'
|
31
|
+
PROJ.authors = 'Gaspard Bucher'
|
32
|
+
PROJ.email = 'gaspard@teti.ch'
|
33
|
+
PROJ.url = 'http://zenadmin.org/546'
|
34
|
+
PROJ.version = RubyLess::VERSION
|
35
|
+
PROJ.rubyforge.name = 'rubyless'
|
36
|
+
|
37
|
+
PROJ.spec.opts << '--color'
|
38
|
+
PROJ.gem.files = (
|
39
|
+
['History.txt', 'README.txt', 'Rakefile', 'rubyless.gemspec'] +
|
40
|
+
['lib', 'test'].map do |d|
|
41
|
+
Dir.glob("#{d}/**/*").reject {|path| File.basename(path) =~ /^\./ }
|
42
|
+
end
|
43
|
+
).flatten
|
44
|
+
|
45
|
+
PROJ.gem.dependencies << ['ruby_parser', '>= 2.0.4']
|
46
|
+
PROJ.gem.dependencies << ['sexp_processor', '>= 3.0.1']
|
47
|
+
PROJ.gem.development_dependencies << ['yamltest', '>= 0.5.3']
|
48
|
+
|
data/lib/basic_types.rb
CHANGED
@@ -1,39 +1,15 @@
|
|
1
1
|
require 'safe_class'
|
2
2
|
|
3
|
-
module RubyLess
|
4
|
-
|
5
|
-
class Boolean
|
6
|
-
end
|
7
|
-
|
8
|
-
class Number
|
9
|
-
include SafeClass
|
10
|
-
safe_method( [:==, Number] => Boolean, [:< , Number] => Boolean, [:> , Number] => Boolean, [:<=, Number] => Boolean, [:>=, Number] => Boolean,
|
11
|
-
[:- , Number] => Number, [:+ , Number] => Number, [:* , Number] => Number, [:/ , Number] => Number,
|
12
|
-
[:% , Number] => Number, [:"-@"] => Number )
|
13
|
-
end
|
14
|
-
|
15
|
-
|
16
|
-
class Missing
|
17
|
-
[:==, :< , :> , :<=, :>=, :"?"].each do |sym|
|
18
|
-
define_method(sym) do |arg|
|
19
|
-
false
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def to_s
|
24
|
-
''
|
25
|
-
end
|
26
|
-
|
27
|
-
def nil?
|
28
|
-
true
|
29
|
-
end
|
30
|
-
|
31
|
-
def method_missing(*meth)
|
32
|
-
self
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
Nil = Missing.new
|
37
3
|
|
38
|
-
|
39
|
-
end
|
4
|
+
class Boolean
|
5
|
+
end
|
6
|
+
|
7
|
+
class Number
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
RubyLess::SafeClass.safe_method_for( Number,
|
12
|
+
[:==, Number] => Boolean, [:< , Number] => Boolean, [:> , Number] => Boolean, [:<=, Number] => Boolean, [:>=, Number] => Boolean,
|
13
|
+
[:- , Number] => Number, [:+ , Number] => Number, [:* , Number] => Number, [:/ , Number] => Number,
|
14
|
+
[:% , Number] => Number, [:"-@"] => Number )
|
15
|
+
|
data/lib/processor.rb
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require '
|
2
|
+
require 'ruby_parser'
|
3
|
+
require 'sexp_processor'
|
3
4
|
|
4
5
|
require 'basic_types'
|
5
6
|
require 'typed_string'
|
6
7
|
require 'safe_class'
|
8
|
+
require 'ruby-debug'
|
9
|
+
Debugger.start
|
7
10
|
|
8
|
-
module RubyLess
|
11
|
+
module RubyLess
|
9
12
|
class RubyLessProcessor < SexpProcessor
|
10
13
|
attr_reader :ruby
|
11
14
|
|
@@ -13,19 +16,25 @@ module RubyLess
|
|
13
16
|
PREFIX_OPERATOR = [:"-@"]
|
14
17
|
|
15
18
|
def self.translate(string, helper)
|
16
|
-
sexp =
|
19
|
+
sexp = RubyParser.new.parse(string)
|
17
20
|
self.new(helper).process(sexp)
|
18
21
|
end
|
19
22
|
|
20
23
|
def initialize(helper)
|
21
24
|
super()
|
22
|
-
@helper
|
23
|
-
@indent
|
25
|
+
@helper = helper
|
26
|
+
@indent = " "
|
24
27
|
self.auto_shift_type = true
|
25
28
|
self.strict = true
|
26
29
|
self.expected = TypedString
|
27
30
|
end
|
28
31
|
|
32
|
+
#def process(exp)
|
33
|
+
# return nil if exp.nil?
|
34
|
+
# method = exp.shift
|
35
|
+
# send("process_#{method}", exp)
|
36
|
+
#end
|
37
|
+
|
29
38
|
def process_and(exp)
|
30
39
|
t "(#{process(exp.shift)} and #{process(exp.shift)})", Boolean
|
31
40
|
end
|
@@ -37,12 +46,12 @@ module RubyLess
|
|
37
46
|
def process_not(exp)
|
38
47
|
t "not #{process(exp.shift)}", Boolean
|
39
48
|
end
|
40
|
-
|
49
|
+
|
41
50
|
def process_if(exp)
|
42
51
|
cond = process(exp.shift)
|
43
52
|
true_res = process(exp.shift)
|
44
53
|
false_res = process(exp.shift)
|
45
|
-
|
54
|
+
|
46
55
|
if true_res && false_res && true_res.klass != false_res.klass
|
47
56
|
raise "Error in conditional expression: '#{true_res}' and '#{false_res}' do not return results of same type (#{true_res.klass} != #{false_res.klass})."
|
48
57
|
end
|
@@ -90,7 +99,8 @@ module RubyLess
|
|
90
99
|
end
|
91
100
|
|
92
101
|
def process_lit(exp)
|
93
|
-
|
102
|
+
lit = exp.shift
|
103
|
+
t lit.inspect, lit.class == Symbol ? Symbol : Number
|
94
104
|
end
|
95
105
|
|
96
106
|
def process_str(exp)
|
@@ -114,7 +124,7 @@ module RubyLess
|
|
114
124
|
end
|
115
125
|
TypedString.new(content, opts)
|
116
126
|
end
|
117
|
-
|
127
|
+
|
118
128
|
def t_if(cond, true_res, opts)
|
119
129
|
if cond != []
|
120
130
|
if cond.size > 1
|
@@ -122,7 +132,7 @@ module RubyLess
|
|
122
132
|
else
|
123
133
|
condition = cond.join('')
|
124
134
|
end
|
125
|
-
|
135
|
+
|
126
136
|
# we can append to 'raw'
|
127
137
|
if opts[:nil]
|
128
138
|
# applied method could produce a nil value (so we cannot concat method on top of 'raw' and only check previous condition)
|
@@ -138,9 +148,14 @@ module RubyLess
|
|
138
148
|
|
139
149
|
def method_call(receiver, exp)
|
140
150
|
method = exp.shift
|
141
|
-
|
142
|
-
|
143
|
-
|
151
|
+
arg_sexp = args = exp.shift # rescue nil
|
152
|
+
if arg_sexp
|
153
|
+
args = process(arg_sexp)
|
154
|
+
if args == ''
|
155
|
+
signature = [method]
|
156
|
+
else
|
157
|
+
signature = [method] + [args.klass].flatten
|
158
|
+
end
|
144
159
|
# execution conditional
|
145
160
|
cond = args.cond || []
|
146
161
|
else
|
@@ -148,12 +163,12 @@ module RubyLess
|
|
148
163
|
signature = [method]
|
149
164
|
cond = []
|
150
165
|
end
|
151
|
-
|
166
|
+
|
152
167
|
if receiver
|
153
168
|
if receiver.could_be_nil?
|
154
169
|
cond += receiver.cond
|
155
170
|
end
|
156
|
-
raise "'#{receiver}' does not respond to '#{method}(#{
|
171
|
+
raise "'#{receiver}' does not respond to '#{method}(#{signature[1..-1].join(', ')})'." unless opts = get_method(signature, receiver.klass)
|
157
172
|
method = opts[:method] if opts[:method]
|
158
173
|
if method == :/
|
159
174
|
t_if cond, "(#{receiver.raw}#{method}#{args.raw} rescue nil)", opts.merge(:nil => true)
|
@@ -161,14 +176,16 @@ module RubyLess
|
|
161
176
|
t_if cond, "(#{receiver.raw}#{method}#{args.raw})", opts
|
162
177
|
elsif PREFIX_OPERATOR.include?(method)
|
163
178
|
t_if cond, "#{method.to_s[0..0]}#{receiver.raw}", opts
|
179
|
+
elsif method == :[]
|
180
|
+
t_if cond, "#{receiver.raw}[#{args.raw}]", opts
|
164
181
|
else
|
165
|
-
args = "(#{args.raw})" if args !=
|
182
|
+
args = "(#{args.raw})" if args != ''
|
166
183
|
t_if cond, "#{receiver.raw}.#{method}#{args}", opts
|
167
184
|
end
|
168
185
|
else
|
169
186
|
raise "Unknown method '#{method}(#{args.raw})'." unless opts = get_method(signature, @helper, false)
|
170
187
|
method = opts[:method] if opts[:method]
|
171
|
-
args = "(#{args.raw})" if args !=
|
188
|
+
args = "(#{args.raw})" if args != ''
|
172
189
|
t_if cond, "#{method}#{args}", opts
|
173
190
|
end
|
174
191
|
end
|
@@ -192,7 +209,7 @@ module RubyLess
|
|
192
209
|
res.gsub!(/\//, '\/') if in_regex
|
193
210
|
res
|
194
211
|
end
|
195
|
-
|
212
|
+
|
196
213
|
def get_method(signature, receiver, is_method = true)
|
197
214
|
res = receiver.respond_to?(:safe_method_type) ? receiver.safe_method_type(signature) : SafeClass.safe_method_type_for(receiver, signature)
|
198
215
|
res = res.call(@helper) if res.kind_of?(Proc)
|