rubyless 0.2.0 → 0.3.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.
- 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)
|