hotcell 0.0.1

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.
Files changed (50) hide show
  1. data/.gitignore +19 -0
  2. data/.rspec +2 -0
  3. data/.rvmrc +1 -0
  4. data/Gemfile +15 -0
  5. data/Guardfile +24 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +29 -0
  8. data/Rakefile +17 -0
  9. data/hotcell.gemspec +22 -0
  10. data/lib/hotcell/.DS_Store +0 -0
  11. data/lib/hotcell/config.rb +31 -0
  12. data/lib/hotcell/context.rb +36 -0
  13. data/lib/hotcell/errors.rb +43 -0
  14. data/lib/hotcell/extensions.rb +42 -0
  15. data/lib/hotcell/lexer.rb +783 -0
  16. data/lib/hotcell/lexer.rl +299 -0
  17. data/lib/hotcell/manipulator.rb +31 -0
  18. data/lib/hotcell/node/arrayer.rb +7 -0
  19. data/lib/hotcell/node/assigner.rb +11 -0
  20. data/lib/hotcell/node/block.rb +58 -0
  21. data/lib/hotcell/node/calculator.rb +35 -0
  22. data/lib/hotcell/node/command.rb +41 -0
  23. data/lib/hotcell/node/hasher.rb +7 -0
  24. data/lib/hotcell/node/joiner.rb +7 -0
  25. data/lib/hotcell/node/sequencer.rb +7 -0
  26. data/lib/hotcell/node/summoner.rb +11 -0
  27. data/lib/hotcell/node/tag.rb +26 -0
  28. data/lib/hotcell/node.rb +55 -0
  29. data/lib/hotcell/parser.rb +1186 -0
  30. data/lib/hotcell/parser.y +231 -0
  31. data/lib/hotcell/scope.rb +57 -0
  32. data/lib/hotcell/template.rb +29 -0
  33. data/lib/hotcell/version.rb +3 -0
  34. data/lib/hotcell.rb +19 -0
  35. data/misc/rage.rl +1999 -0
  36. data/misc/unicode2ragel.rb +305 -0
  37. data/spec/data/dstrings +8 -0
  38. data/spec/data/sstrings +6 -0
  39. data/spec/lib/hotcell/config_spec.rb +57 -0
  40. data/spec/lib/hotcell/context_spec.rb +53 -0
  41. data/spec/lib/hotcell/lexer_spec.rb +340 -0
  42. data/spec/lib/hotcell/manipulator_spec.rb +64 -0
  43. data/spec/lib/hotcell/node/block_spec.rb +188 -0
  44. data/spec/lib/hotcell/node/command_spec.rb +71 -0
  45. data/spec/lib/hotcell/parser_spec.rb +382 -0
  46. data/spec/lib/hotcell/scope_spec.rb +160 -0
  47. data/spec/lib/hotcell/template_spec.rb +41 -0
  48. data/spec/lib/hotcell_spec.rb +8 -0
  49. data/spec/spec_helper.rb +44 -0
  50. metadata +139 -0
@@ -0,0 +1,305 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This script uses the unicode spec to generate a Ragel state machine
4
+ # that recognizes unicode alphanumeric characters. It generates 5
5
+ # character classes: uupper, ulower, ualpha, udigit, and ualnum.
6
+ # Currently supported encodings are UTF-8 [default] and UCS-4.
7
+ #
8
+ # Usage: unicode2ragel.rb [options]
9
+ # -e, --encoding [ucs4 | utf8] Data encoding
10
+ # -h, --help Show this message
11
+ #
12
+ # This script was originally written as part of the Ferret search
13
+ # engine library.
14
+ #
15
+ # Author: Rakan El-Khalil <rakan@well.com>
16
+
17
+ require 'optparse'
18
+ require 'open-uri'
19
+
20
+ ENCODINGS = [ :utf8, :ucs4 ]
21
+ ALPHTYPES = { :utf8 => "unsigned char", :ucs4 => "unsigned int" }
22
+ CHART_URL = "http://www.unicode.org/Public/5.1.0/ucd/DerivedCoreProperties.txt"
23
+
24
+ ###
25
+ # Display vars & default option
26
+
27
+ TOTAL_WIDTH = 80
28
+ RANGE_WIDTH = 23
29
+ @encoding = :utf8
30
+
31
+ ###
32
+ # Option parsing
33
+
34
+ cli_opts = OptionParser.new do |opts|
35
+ opts.on("-e", "--encoding [ucs4 | utf8]", "Data encoding") do |o|
36
+ @encoding = o.downcase.to_sym
37
+ end
38
+ opts.on("-h", "--help", "Show this message") do
39
+ puts opts
40
+ exit
41
+ end
42
+ end
43
+
44
+ cli_opts.parse(ARGV)
45
+ unless ENCODINGS.member? @encoding
46
+ puts "Invalid encoding: #{@encoding}"
47
+ puts cli_opts
48
+ exit
49
+ end
50
+
51
+ ##
52
+ # Downloads the document at url and yields every alpha line's hex
53
+ # range and description.
54
+
55
+ def each_alpha( url, property )
56
+ open( url ) do |file|
57
+ file.each_line do |line|
58
+ next if line =~ /^#/;
59
+ next if line !~ /; #{property} #/;
60
+
61
+ range, description = line.split(/;/)
62
+ range.strip!
63
+ description.gsub!(/.*#/, '').strip!
64
+
65
+ if range =~ /\.\./
66
+ start, stop = range.split '..'
67
+ else start = stop = range
68
+ end
69
+
70
+ yield start.hex .. stop.hex, description
71
+ end
72
+ end
73
+ end
74
+
75
+ ###
76
+ # Formats to hex at minimum width
77
+
78
+ def to_hex( n )
79
+ r = "%0X" % n
80
+ r = "0#{r}" unless (r.length % 2).zero?
81
+ r
82
+ end
83
+
84
+ ###
85
+ # UCS4 is just a straight hex conversion of the unicode codepoint.
86
+
87
+ def to_ucs4( range )
88
+ rangestr = "0x" + to_hex(range.begin)
89
+ rangestr << "..0x" + to_hex(range.end) if range.begin != range.end
90
+ [ rangestr ]
91
+ end
92
+
93
+ ##
94
+ # 0x00 - 0x7f -> 0zzzzzzz[7]
95
+ # 0x80 - 0x7ff -> 110yyyyy[5] 10zzzzzz[6]
96
+ # 0x800 - 0xffff -> 1110xxxx[4] 10yyyyyy[6] 10zzzzzz[6]
97
+ # 0x010000 - 0x10ffff -> 11110www[3] 10xxxxxx[6] 10yyyyyy[6] 10zzzzzz[6]
98
+
99
+ UTF8_BOUNDARIES = [0x7f, 0x7ff, 0xffff, 0x10ffff]
100
+
101
+ def to_utf8_enc( n )
102
+ r = 0
103
+ if n <= 0x7f
104
+ r = n
105
+ elsif n <= 0x7ff
106
+ y = 0xc0 | (n >> 6)
107
+ z = 0x80 | (n & 0x3f)
108
+ r = y << 8 | z
109
+ elsif n <= 0xffff
110
+ x = 0xe0 | (n >> 12)
111
+ y = 0x80 | (n >> 6) & 0x3f
112
+ z = 0x80 | n & 0x3f
113
+ r = x << 16 | y << 8 | z
114
+ elsif n <= 0x10ffff
115
+ w = 0xf0 | (n >> 18)
116
+ x = 0x80 | (n >> 12) & 0x3f
117
+ y = 0x80 | (n >> 6) & 0x3f
118
+ z = 0x80 | n & 0x3f
119
+ r = w << 24 | x << 16 | y << 8 | z
120
+ end
121
+
122
+ to_hex(r)
123
+ end
124
+
125
+ def from_utf8_enc( n )
126
+ n = n.hex
127
+ r = 0
128
+ if n <= 0x7f
129
+ r = n
130
+ elsif n <= 0xdfff
131
+ y = (n >> 8) & 0x1f
132
+ z = n & 0x3f
133
+ r = y << 6 | z
134
+ elsif n <= 0xefffff
135
+ x = (n >> 16) & 0x0f
136
+ y = (n >> 8) & 0x3f
137
+ z = n & 0x3f
138
+ r = x << 10 | y << 6 | z
139
+ elsif n <= 0xf7ffffff
140
+ w = (n >> 24) & 0x07
141
+ x = (n >> 16) & 0x3f
142
+ y = (n >> 8) & 0x3f
143
+ z = n & 0x3f
144
+ r = w << 18 | x << 12 | y << 6 | z
145
+ end
146
+ r
147
+ end
148
+
149
+ ###
150
+ # Given a range, splits it up into ranges that can be continuously
151
+ # encoded into utf8. Eg: 0x00 .. 0xff => [0x00..0x7f, 0x80..0xff]
152
+ # This is not strictly needed since the current [5.1] unicode standard
153
+ # doesn't have ranges that straddle utf8 boundaries. This is included
154
+ # for completeness as there is no telling if that will ever change.
155
+
156
+ def utf8_ranges( range )
157
+ ranges = []
158
+ UTF8_BOUNDARIES.each do |max|
159
+ if range.begin <= max
160
+ return ranges << range if range.end <= max
161
+
162
+ ranges << range.begin .. max
163
+ range = (max + 1) .. range.end
164
+ end
165
+ end
166
+ ranges
167
+ end
168
+
169
+ def build_range( start, stop )
170
+ size = start.size/2
171
+ left = size - 1
172
+ return [""] if size < 1
173
+
174
+ a = start[0..1]
175
+ b = stop[0..1]
176
+
177
+ ###
178
+ # Shared prefix
179
+
180
+ if a == b
181
+ return build_range(start[2..-1], stop[2..-1]).map do |elt|
182
+ "0x#{a} " + elt
183
+ end
184
+ end
185
+
186
+ ###
187
+ # Unshared prefix, end of run
188
+
189
+ return ["0x#{a}..0x#{b} "] if left.zero?
190
+
191
+ ###
192
+ # Unshared prefix, not end of run
193
+ # Range can be 0x123456..0x56789A
194
+ # Which is equivalent to:
195
+ # 0x123456 .. 0x12FFFF
196
+ # 0x130000 .. 0x55FFFF
197
+ # 0x560000 .. 0x56789A
198
+
199
+ ret = []
200
+ ret << build_range(start, a + "FF" * left)
201
+
202
+ ###
203
+ # Only generate middle range if need be.
204
+
205
+ if a.hex+1 != b.hex
206
+ max = to_hex(b.hex - 1)
207
+ max = "FF" if b == "FF"
208
+ ret << "0x#{to_hex(a.hex+1)}..0x#{max} " + "0x00..0xFF " * left
209
+ end
210
+
211
+ ###
212
+ # Don't generate last range if it is covered by first range
213
+
214
+ ret << build_range(b + "00" * left, stop) unless b == "FF"
215
+ ret.flatten!
216
+ end
217
+
218
+ def to_utf8( range )
219
+ utf8_ranges( range ).map do |r|
220
+ build_range to_utf8_enc(r.begin), to_utf8_enc(r.end)
221
+ end.flatten!
222
+ end
223
+
224
+ ##
225
+ # Perform a 3-way comparison of the number of codepoints advertised by
226
+ # the unicode spec for the given range, the originally parsed range,
227
+ # and the resulting utf8 encoded range.
228
+
229
+ def count_codepoints( code )
230
+ code.split(' ').inject(1) do |acc, elt|
231
+ if elt =~ /0x(.+)\.\.0x(.+)/
232
+ if @encoding == :utf8
233
+ acc * (from_utf8_enc($2) - from_utf8_enc($1) + 1)
234
+ else
235
+ acc * ($2.hex - $1.hex + 1)
236
+ end
237
+ else
238
+ acc
239
+ end
240
+ end
241
+ end
242
+
243
+ def is_valid?( range, desc, codes )
244
+ spec_count = 1
245
+ spec_count = $1.to_i if desc =~ /\[(\d+)\]/
246
+ range_count = range.end - range.begin + 1
247
+
248
+ sum = codes.inject(0) { |acc, elt| acc + count_codepoints(elt) }
249
+ sum == spec_count and sum == range_count
250
+ end
251
+
252
+ ##
253
+ # Generate the state maching to stdout
254
+
255
+ def generate_machine( name, property )
256
+ pipe = " "
257
+ puts " #{name} = "
258
+ each_alpha( CHART_URL, property ) do |range, desc|
259
+
260
+ codes = (@encoding == :ucs4) ? to_ucs4(range) : to_utf8(range)
261
+
262
+ raise "Invalid encoding of range #{range}: #{codes.inspect}" unless
263
+ is_valid? range, desc, codes
264
+
265
+ range_width = codes.map { |a| a.size }.max
266
+ range_width = RANGE_WIDTH if range_width < RANGE_WIDTH
267
+
268
+ desc_width = TOTAL_WIDTH - RANGE_WIDTH - 11
269
+ desc_width -= (range_width - RANGE_WIDTH) if range_width > RANGE_WIDTH
270
+
271
+ if desc.size > desc_width
272
+ desc = desc[0..desc_width - 4] + "..."
273
+ end
274
+
275
+ codes.each_with_index do |r, idx|
276
+ desc = "" unless idx.zero?
277
+ code = "%-#{range_width}s" % r
278
+ puts " #{pipe} #{code} ##{desc}"
279
+ pipe = "|"
280
+ end
281
+ end
282
+ puts " ;"
283
+ puts ""
284
+ end
285
+
286
+ puts <<EOF
287
+ # The following Ragel file was autogenerated with #{$0}
288
+ # from: #{CHART_URL}
289
+ #
290
+ # It defines ualpha, udigit, ualnum.
291
+ #
292
+ # To use this, make sure that your alphtype is set to #{ALPHTYPES[@encoding]},
293
+ # and that your input is in #{@encoding}.
294
+
295
+ %%{
296
+ machine WChar;
297
+ EOF
298
+ generate_machine( :ualpha, "Alphabetic" )
299
+ generate_machine( :ulower, "Lowercase" )
300
+ generate_machine( :uupper, "Uppercase" )
301
+ puts <<EOF
302
+ udigit = '0'..'9';
303
+ ualnum = ualpha | udigit;
304
+ }%%
305
+ EOF
@@ -0,0 +1,8 @@
1
+ "fo\"o"
2
+ "fo\o"
3
+ "fo\\o"
4
+ "fo\no"
5
+ "fo\mo"
6
+ "fo\to"
7
+ "foo
8
+ bar"
@@ -0,0 +1,6 @@
1
+ 'fo\'o'
2
+ 'fo\o'
3
+ 'fo\\o'
4
+ 'fo\no'
5
+ 'foo
6
+ bar'
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hotcell::Config do
4
+ subject { Hotcell::Config.send(:new) }
5
+
6
+ let(:command_class) { Class.new(Hotcell::Command) }
7
+ let(:block_class) do
8
+ Class.new(Hotcell::Block) do
9
+ subcommands :else, :elsif
10
+ end
11
+ end
12
+ let(:misc_class) { Class.new }
13
+
14
+ specify { subject.blocks.should == {} }
15
+ specify { subject.subcommands.should == {} }
16
+ specify { subject.commands.should == {} }
17
+
18
+ describe '#register_command' do
19
+ context do
20
+ before { subject.register_command :for, command_class }
21
+ specify { subject.blocks.should == {} }
22
+ specify { subject.subcommands.should == {} }
23
+ specify { subject.commands.should == { 'for' => command_class } }
24
+ end
25
+
26
+ context do
27
+ before { subject.register_command 'for', block_class }
28
+ specify { subject.blocks.should == { 'for' => block_class } }
29
+ specify { subject.subcommands.should == { 'else' => block_class, 'elsif' => block_class } }
30
+ specify { subject.commands.should == {} }
31
+ end
32
+
33
+ context do
34
+ before { subject.register_command 'for', block_class }
35
+ before { subject.register_command :forloop, block_class }
36
+ before { subject.register_command :include, command_class }
37
+ specify { subject.blocks.should == { 'for' => block_class, 'forloop' => block_class } }
38
+ specify { subject.commands.should == { 'include' => command_class } }
39
+ end
40
+
41
+ context 'errors' do
42
+ context do
43
+ specify { expect { subject.register_command :for, misc_class }.to raise_error }
44
+ end
45
+
46
+ context do
47
+ before { subject.register_command 'for', block_class }
48
+ specify { expect { subject.register_command :for, command_class }.to raise_error }
49
+ end
50
+
51
+ context do
52
+ before { subject.register_command :for, command_class }
53
+ specify { expect { subject.register_command 'for', block_class }.to raise_error }
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hotcell::Context do
4
+ describe '#initialize' do
5
+ its('scope.scope') { should == [{}] }
6
+
7
+ context do
8
+ subject { described_class.new(
9
+ scope: { foo: 42, 'bar' => 'baz' },
10
+ variables: { baz: 'moo' },
11
+ boo: 'goo',
12
+ 'taz' => 'man',
13
+ rescuer: ->{},
14
+ reraise: true
15
+ ) }
16
+ its('scope.scope') { should == [{foo: 42, 'bar' => 'baz', 'baz' => 'moo', 'boo' => 'goo', 'taz' => 'man'}] }
17
+ end
18
+
19
+ context do
20
+ subject { described_class.new(variables: { foo: 42, 'bar' => 'baz' }, environment: { 'baz' => 'moo' }) }
21
+ its('scope.scope') { should == [{'foo' => 42, 'bar' => 'baz', baz: 'moo'}] }
22
+ end
23
+ end
24
+
25
+ describe '#safe' do
26
+ specify { subject.safe { 3 }.should == 3 }
27
+ specify { subject.safe(5) { 3 }.should == 3 }
28
+ specify { subject.safe(nil) { 3 }.should == 3 }
29
+ specify { subject.safe { 3 * 'foo' }.should =~ /TypeError/ }
30
+ specify { subject.safe(nil) { 3 * 'foo' }.should == nil }
31
+ specify { subject.safe(5) { 3 * 'foo' }.should == 5 }
32
+
33
+ context 'reraise' do
34
+ subject { described_class.new(reraise: true) }
35
+
36
+ specify { subject.safe { 'foo' }.should == 'foo' }
37
+ specify { subject.safe('bar') { 'foo' }.should == 'foo' }
38
+ specify { expect { subject.safe { 3 * 'foo' } }.to raise_error TypeError }
39
+ specify { expect { subject.safe('bar') { 3 * 'foo' } }.to raise_error TypeError }
40
+ end
41
+
42
+ context 'custom rescuer' do
43
+ subject { described_class.new(rescuer: ->(e){ "Rescued from: #{e.class}" }) }
44
+ specify { subject.safe { 3 * 'foo' }.should =~ /Rescued from: TypeError/ }
45
+ end
46
+ end
47
+
48
+ describe '#manipulator_invoke' do
49
+ subject { described_class.new(variables: { foo: 42, 'bar' => 'baz' }, environment: { 'baz' => 'moo' }) }
50
+ specify { subject.manipulator_invoke('foo').should == 42 }
51
+ specify { subject.manipulator_invoke('moo').should be_nil }
52
+ end
53
+ end