matthewtodd-doily 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.
- data/README.rdoc +30 -0
- data/Rakefile +47 -0
- data/features/chef_nodes.feature +55 -0
- data/features/chef_openid_registrations.feature +46 -0
- data/features/chef_roles.feature +32 -0
- data/features/steps/chef.rb +20 -0
- data/features/support/environment.rb +9 -0
- data/features/support/helpers.rb +27 -0
- data/lib/doily.rb +7 -0
- data/lib/doily/errors.rb +13 -0
- data/lib/doily/parser.rb +534 -0
- data/lib/doily/parser.y +157 -0
- data/lib/doily/types.rb +5 -0
- data/lib/doily/types/access.rb +35 -0
- data/lib/doily/types/assignment.rb +12 -0
- data/lib/doily/types/binding.rb +41 -0
- data/lib/doily/types/block.rb +17 -0
- data/lib/doily/types/call.rb +12 -0
- data/lib/doily/types/conditional.rb +17 -0
- data/lib/doily/types/declaration.rb +15 -0
- data/lib/doily/types/function.rb +32 -0
- data/lib/doily/types/literal.rb +11 -0
- data/lib/doily/types/loop.rb +20 -0
- data/lib/doily/types/object.rb +15 -0
- data/lib/doily/types/reference.rb +15 -0
- data/test/doily_test.rb +140 -0
- data/test/test_helper.rb +20 -0
- metadata +118 -0
data/lib/doily/parser.y
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
class Doily::Parser
|
2
|
+
|
3
|
+
token FUNCTION IF ELSE FOR VAR BOOLEAN_LITERAL IDENTIFIER STRING_LITERAL INTEGER_LITERAL BINARY_OPERATOR
|
4
|
+
|
5
|
+
rule
|
6
|
+
target
|
7
|
+
: function_definition
|
8
|
+
;
|
9
|
+
|
10
|
+
function_definition
|
11
|
+
: FUNCTION '(' argument_name_list ')' block { result = Function.new(val[2], val[4]) }
|
12
|
+
;
|
13
|
+
|
14
|
+
argument_name_list
|
15
|
+
: { result = [] }
|
16
|
+
| IDENTIFIER { result = [val[0]] }
|
17
|
+
| IDENTIFIER ',' argument_name_list { result = [val[0]] + val[2] }
|
18
|
+
;
|
19
|
+
|
20
|
+
block
|
21
|
+
: '{' '}' { result = Block.new([]) }
|
22
|
+
| '{' statement_list '}' { result = Block.new(val[1]) }
|
23
|
+
;
|
24
|
+
|
25
|
+
statement_list
|
26
|
+
: statement { result = [val[0]] }
|
27
|
+
| statement statement_list { result = [val[0]] + val[1] }
|
28
|
+
;
|
29
|
+
|
30
|
+
statement
|
31
|
+
: if_statement
|
32
|
+
| for_loop
|
33
|
+
| expression ';'
|
34
|
+
;
|
35
|
+
|
36
|
+
if_statement
|
37
|
+
: IF '(' expression ')' block { result = Conditional.new(val[2], val[4]) }
|
38
|
+
| IF '(' expression ')' block ELSE block { result = Conditional.new(val[2], val[4], val[6]) }
|
39
|
+
;
|
40
|
+
|
41
|
+
for_loop
|
42
|
+
: FOR '(' expression ';' expression ';' expression ')' block { result = Loop.new(val[2], val[4], val[6], val[8]) }
|
43
|
+
;
|
44
|
+
|
45
|
+
expression
|
46
|
+
: reference
|
47
|
+
| declaration
|
48
|
+
| assignment
|
49
|
+
| binary_expression
|
50
|
+
| increment
|
51
|
+
;
|
52
|
+
|
53
|
+
reference
|
54
|
+
: variable
|
55
|
+
| INTEGER_LITERAL { result = Literal.new(val[0].to_i) }
|
56
|
+
| BOOLEAN_LITERAL { result = Literal.new(eval(val[0])) }
|
57
|
+
| string_literal
|
58
|
+
| '{' key_value_list '}' { result = Object.new(val[1]) }
|
59
|
+
| reference '.' IDENTIFIER { result = Access.new(val[0], Literal.new(val[2])) }
|
60
|
+
| reference '[' reference ']' { result = Access.new(val[0], val[2]) }
|
61
|
+
| reference '(' argument_list ')' { result = Call.new(val[0], val[2]) }
|
62
|
+
;
|
63
|
+
|
64
|
+
variable
|
65
|
+
: IDENTIFIER { result = Reference.new(val[0]) }
|
66
|
+
;
|
67
|
+
|
68
|
+
string_literal
|
69
|
+
: STRING_LITERAL { result = Literal.new(eval(val[0])) }
|
70
|
+
;
|
71
|
+
|
72
|
+
key_value_list
|
73
|
+
: { result = {} }
|
74
|
+
| key_value { result = val[0] }
|
75
|
+
| key_value ',' key_value_list { result = val[0].merge(val[2]) }
|
76
|
+
;
|
77
|
+
|
78
|
+
key_value
|
79
|
+
: string_literal ':' reference { result = { val[0] => val[2] }}
|
80
|
+
;
|
81
|
+
|
82
|
+
argument_list
|
83
|
+
: { result = [] }
|
84
|
+
| expression { result = [val[0]] }
|
85
|
+
| expression ',' argument_list { result = [val[0]] + val[2] }
|
86
|
+
;
|
87
|
+
|
88
|
+
assignment
|
89
|
+
: expression '=' reference { result = Assignment.new(val[0], val[2]) }
|
90
|
+
;
|
91
|
+
|
92
|
+
declaration
|
93
|
+
: VAR IDENTIFIER { result = Declaration.new(val[1]) }
|
94
|
+
;
|
95
|
+
|
96
|
+
binary_expression
|
97
|
+
: reference BINARY_OPERATOR reference { result = Call.new(Access.new(val[0], Literal.new(val[1])), [val[2]]) }
|
98
|
+
;
|
99
|
+
|
100
|
+
increment
|
101
|
+
: variable '++' { result = Assignment.new(val[0], Call.new(Access.new(val[0], Literal.new('+')), [Literal.new(1)])) }
|
102
|
+
;
|
103
|
+
|
104
|
+
---- header ----
|
105
|
+
require 'strscan'
|
106
|
+
---- inner ----
|
107
|
+
|
108
|
+
def self.function(string)
|
109
|
+
new.parse(string)
|
110
|
+
end
|
111
|
+
|
112
|
+
def parse(string)
|
113
|
+
@tokens = []
|
114
|
+
scanner = StringScanner.new(string)
|
115
|
+
|
116
|
+
until scanner.empty?
|
117
|
+
case
|
118
|
+
when scanner.scan(/\s+/)
|
119
|
+
# ignore space
|
120
|
+
when m = scanner.scan(/function/)
|
121
|
+
@tokens.push [:FUNCTION, m]
|
122
|
+
when m = scanner.scan(/if/)
|
123
|
+
@tokens.push [:IF, m]
|
124
|
+
when m = scanner.scan(/else/)
|
125
|
+
@tokens.push [:ELSE, m]
|
126
|
+
when m = scanner.scan(/for/)
|
127
|
+
@tokens.push [:FOR, m]
|
128
|
+
when m = scanner.scan(/var/)
|
129
|
+
@tokens.push [:VAR, m]
|
130
|
+
when m = scanner.scan(/true|false/)
|
131
|
+
@tokens.push [:BOOLEAN_LITERAL, m]
|
132
|
+
when m = scanner.scan(/==|</)
|
133
|
+
@tokens.push [:BINARY_OPERATOR, m]
|
134
|
+
when m = scanner.scan(/\+\+/)
|
135
|
+
@tokens.push [m, m]
|
136
|
+
when m = scanner.scan(/[(){}\[\],\.:;=]/)
|
137
|
+
@tokens.push [m, m]
|
138
|
+
when m = scanner.scan(/[a-zA-Z_]+/)
|
139
|
+
@tokens.push [:IDENTIFIER, m]
|
140
|
+
when m = scanner.scan(/"([^"])*"/)
|
141
|
+
@tokens.push [:STRING_LITERAL, m]
|
142
|
+
when m = scanner.scan(/'([^'])*'/)
|
143
|
+
@tokens.push [:STRING_LITERAL, m]
|
144
|
+
when m = scanner.scan(/\d+/)
|
145
|
+
@tokens.push [:INTEGER_LITERAL, m]
|
146
|
+
else
|
147
|
+
raise ParseError.new(scanner)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
@tokens.push [false, false]
|
152
|
+
do_parse
|
153
|
+
end
|
154
|
+
|
155
|
+
def next_token
|
156
|
+
@tokens.shift
|
157
|
+
end
|
data/lib/doily/types.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Doily
|
2
|
+
class Access
|
3
|
+
def initialize(target, name)
|
4
|
+
@target = target
|
5
|
+
@name = name
|
6
|
+
end
|
7
|
+
|
8
|
+
def assign(reference, binding)
|
9
|
+
ruby_target = @target.to_ruby(binding)
|
10
|
+
ruby_name = @name.to_ruby(binding)
|
11
|
+
ruby_reference = reference.to_ruby(binding)
|
12
|
+
|
13
|
+
if ruby_target.respond_to?(:has_key?)
|
14
|
+
ruby_target.store(ruby_name, ruby_reference)
|
15
|
+
else
|
16
|
+
ruby_target.send("#{ruby_name}=", ruby_reference)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_ruby(binding)
|
21
|
+
ruby_target = @target.to_ruby(binding)
|
22
|
+
ruby_name = @name.to_ruby(binding)
|
23
|
+
|
24
|
+
if ruby_target.respond_to?(ruby_name.to_s)
|
25
|
+
if ruby_name == 'length'
|
26
|
+
ruby_target.length
|
27
|
+
else
|
28
|
+
ruby_target.method(ruby_name)
|
29
|
+
end
|
30
|
+
else
|
31
|
+
ruby_target[ruby_name]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Doily
|
2
|
+
class DelegateBinding
|
3
|
+
def initialize(delegate)
|
4
|
+
@delegate = delegate
|
5
|
+
end
|
6
|
+
|
7
|
+
def fetch(name)
|
8
|
+
@delegate.method(name)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Binding
|
13
|
+
def initialize(parent, contents={})
|
14
|
+
@parent = parent
|
15
|
+
@contents = contents
|
16
|
+
end
|
17
|
+
|
18
|
+
def fetch(name)
|
19
|
+
if @contents.has_key?(name)
|
20
|
+
@contents[name]
|
21
|
+
else
|
22
|
+
@parent.fetch(name)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class ArgumentBinding < Binding
|
28
|
+
def initialize(parent, names, values)
|
29
|
+
raise ArgumentError.new(names, values) unless names.length == values.length
|
30
|
+
contents = {}
|
31
|
+
names.zip(values).each { |key, value| contents[key] = value }
|
32
|
+
super parent, contents
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class LocalBinding < Binding
|
37
|
+
def store(name, value)
|
38
|
+
@contents[name] = value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Doily
|
2
|
+
class Block
|
3
|
+
def initialize(statements = [])
|
4
|
+
@statements = statements
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_ruby(binding)
|
8
|
+
binding = LocalBinding.new(binding)
|
9
|
+
|
10
|
+
result = nil
|
11
|
+
@statements.each do |statement|
|
12
|
+
result = statement.to_ruby(binding)
|
13
|
+
end
|
14
|
+
result
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Doily
|
2
|
+
class Conditional
|
3
|
+
def initialize(conditional, true_block, false_block = Block.new)
|
4
|
+
@conditional = conditional
|
5
|
+
@true_block = true_block
|
6
|
+
@false_block = false_block
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_ruby(binding)
|
10
|
+
if @conditional.to_ruby(binding)
|
11
|
+
@true_block.to_ruby(binding)
|
12
|
+
else
|
13
|
+
@false_block.to_ruby(binding)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Doily
|
2
|
+
class Function
|
3
|
+
def initialize(parameters, block)
|
4
|
+
@parameters = parameters
|
5
|
+
@block = block
|
6
|
+
end
|
7
|
+
|
8
|
+
def bind(binding)
|
9
|
+
BoundFunction.new(@parameters, @block, binding)
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(*args)
|
13
|
+
bind(nil).call(*args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def delegate(delegate)
|
17
|
+
bind(DelegateBinding.new(delegate))
|
18
|
+
end
|
19
|
+
|
20
|
+
class BoundFunction
|
21
|
+
def initialize(parameters, block, binding)
|
22
|
+
@parameters = parameters
|
23
|
+
@block = block
|
24
|
+
@binding = binding
|
25
|
+
end
|
26
|
+
|
27
|
+
def call(*args)
|
28
|
+
@block.to_ruby(ArgumentBinding.new(@binding, @parameters, args))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Doily
|
2
|
+
class Loop
|
3
|
+
def initialize(before_all, before_each, after_each, block)
|
4
|
+
@before_all = before_all
|
5
|
+
@before_each = before_each
|
6
|
+
@after_each = after_each
|
7
|
+
@block = block
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_ruby(binding)
|
11
|
+
binding = LocalBinding.new(binding)
|
12
|
+
@before_all.to_ruby(binding)
|
13
|
+
loop do
|
14
|
+
break unless @before_each.to_ruby(binding)
|
15
|
+
@block.to_ruby(binding)
|
16
|
+
@after_each.to_ruby(binding)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Doily
|
2
|
+
class Object
|
3
|
+
def initialize(properties)
|
4
|
+
@properties = properties
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_ruby(binding)
|
8
|
+
result = {}
|
9
|
+
@properties.each do |key, value|
|
10
|
+
result[key.to_ruby(binding)] = value.to_ruby(binding)
|
11
|
+
end
|
12
|
+
result
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/test/doily_test.rb
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class DoilyTest < Test::Unit::TestCase
|
4
|
+
should 'handle nothing' do
|
5
|
+
Doily('function() {}').call.should == nil
|
6
|
+
end
|
7
|
+
|
8
|
+
context 'delegation to a ruby object' do
|
9
|
+
setup do
|
10
|
+
@delegate = Class.new do
|
11
|
+
def echo(*args); args; end
|
12
|
+
end.new
|
13
|
+
end
|
14
|
+
|
15
|
+
should 'handle calling a function' do
|
16
|
+
Doily('function() { echo(); }').delegate(@delegate).call.should == []
|
17
|
+
end
|
18
|
+
|
19
|
+
should 'handle calling a function with one literal argument' do
|
20
|
+
Doily('function() { echo("foo"); }').delegate(@delegate).call.should == ['foo']
|
21
|
+
end
|
22
|
+
|
23
|
+
should 'handle calling a function with many literal arguments' do
|
24
|
+
Doily('function() { echo("foo", 42); }').delegate(@delegate).call.should == ['foo', 42]
|
25
|
+
end
|
26
|
+
|
27
|
+
should 'handle calling a function with a value from the binding' do
|
28
|
+
Doily('function(document) { echo(document); }').delegate(@delegate).call('key' => 'value').should == [{'key' => 'value'}]
|
29
|
+
end
|
30
|
+
|
31
|
+
should 'handle calling a function with a property access on a variable in the binding' do
|
32
|
+
Doily('function(document) { echo(document.key); }').delegate(@delegate).call('key' => 'value').should == ['value']
|
33
|
+
end
|
34
|
+
|
35
|
+
should 'handle calling a function with an invocation on a variable in the binding' do
|
36
|
+
Doily('function(document) { echo(document.keys()); }').delegate(@delegate).call('key' => 'value').should == [['key']]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
should 'handle a boolean comparison of integers' do
|
41
|
+
Doily('function() { 1 == 1; }').call.should == true
|
42
|
+
end
|
43
|
+
|
44
|
+
should 'handle a boolean comparison of strings' do
|
45
|
+
Doily('function() { "foo" == "foo"; }').call.should == true
|
46
|
+
end
|
47
|
+
|
48
|
+
should 'handle a boolean comparison with calls' do
|
49
|
+
Doily('function() { "foo".reverse() == "oof"; }').call.should == true
|
50
|
+
end
|
51
|
+
|
52
|
+
should 'handle a false boolean comparison' do
|
53
|
+
Doily('function() { "foo" == "bar"; }').call.should == false
|
54
|
+
end
|
55
|
+
|
56
|
+
should 'handle a negative boolean comparison' do
|
57
|
+
Doily('function() { 1 < 2; }').call.should == true
|
58
|
+
end
|
59
|
+
|
60
|
+
should 'handle an if statement' do
|
61
|
+
Doily('function() { if (1 == 1) { 42; } }').call.should == 42
|
62
|
+
end
|
63
|
+
|
64
|
+
should 'handle a negative if statement' do
|
65
|
+
Doily('function() { if (1 == 2) { 42; } }').call.should == nil
|
66
|
+
end
|
67
|
+
|
68
|
+
should 'handle an else statement' do
|
69
|
+
Doily('function() { if (1 == 2) { 42; } else { "foo"; } }').call.should == 'foo'
|
70
|
+
end
|
71
|
+
|
72
|
+
should 'handle variable declaration' do
|
73
|
+
Doily('function() { var foo = "42"; }').call.should == '42'
|
74
|
+
end
|
75
|
+
|
76
|
+
should 'handle variable declaration with continuing statements' do
|
77
|
+
Doily('function() { var foo = "42"; foo; }').call.should == '42'
|
78
|
+
end
|
79
|
+
|
80
|
+
should 'handle hash literals' do
|
81
|
+
Doily('function() { {"name":"Bob", "age":42 }; }').call.should == { 'name' => 'Bob', 'age' => 42 }
|
82
|
+
end
|
83
|
+
|
84
|
+
should 'handle square-bracket hash access with literal' do
|
85
|
+
Doily('function(document) { document["key"]; }').call('key' => 'value').should == 'value'
|
86
|
+
end
|
87
|
+
|
88
|
+
should 'handle square-bracket hash access with expression' do
|
89
|
+
Doily('function(document) { var key = "key"; document[key]; }').call('key' => 'value').should == 'value'
|
90
|
+
end
|
91
|
+
|
92
|
+
should 'handle hash assignment' do
|
93
|
+
Doily('function() { var doc = {}; doc["key"] = "value"; doc; }').call.should == { 'key' => 'value' }
|
94
|
+
end
|
95
|
+
|
96
|
+
should 'handle literal true' do
|
97
|
+
Doily('function() { true; }').call.should == true
|
98
|
+
end
|
99
|
+
|
100
|
+
should 'handle literal false' do
|
101
|
+
Doily('function() { false; }').call.should == false
|
102
|
+
end
|
103
|
+
|
104
|
+
should 'handle single-quoted strings' do
|
105
|
+
Doily("function() { 'foo'; }").call.should == 'foo'
|
106
|
+
end
|
107
|
+
|
108
|
+
should 'handle increment operator' do
|
109
|
+
Doily('function() { var i = 0; i++; i; }').call.should == 1
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'for loop' do
|
113
|
+
setup do
|
114
|
+
@counter = Class.new do
|
115
|
+
attr_reader :count
|
116
|
+
|
117
|
+
def initialize
|
118
|
+
@count = 0
|
119
|
+
end
|
120
|
+
|
121
|
+
def tick
|
122
|
+
@count = @count + 1
|
123
|
+
end
|
124
|
+
end.new
|
125
|
+
end
|
126
|
+
|
127
|
+
should 'work' do
|
128
|
+
Doily('function() { for (var i=0; i < 3; i++) { tick(); } }').delegate(@counter).call
|
129
|
+
@counter.count.should == 3
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
should 'special-case property access for length (no parens necessary to call)' do
|
134
|
+
Doily('function() { "foo".length; }').call.should == 3
|
135
|
+
end
|
136
|
+
|
137
|
+
should 'index arrays' do
|
138
|
+
Doily('function(array) { array[0]; }').call(['a', 'b']).should == 'a'
|
139
|
+
end
|
140
|
+
end
|