orb_template 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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/CODE_OF_CONDUCT.md +132 -0
  4. data/LICENSE.txt +21 -0
  5. data/Makefile +45 -0
  6. data/README.md +429 -0
  7. data/Rakefile +15 -0
  8. data/lib/orb/ast/abstract_node.rb +27 -0
  9. data/lib/orb/ast/attribute.rb +51 -0
  10. data/lib/orb/ast/block_node.rb +26 -0
  11. data/lib/orb/ast/control_expression_node.rb +27 -0
  12. data/lib/orb/ast/newline_node.rb +22 -0
  13. data/lib/orb/ast/printing_expression_node.rb +29 -0
  14. data/lib/orb/ast/private_comment_node.rb +22 -0
  15. data/lib/orb/ast/public_comment_node.rb +22 -0
  16. data/lib/orb/ast/root_node.rb +11 -0
  17. data/lib/orb/ast/tag_node.rb +208 -0
  18. data/lib/orb/ast/text_node.rb +22 -0
  19. data/lib/orb/ast.rb +19 -0
  20. data/lib/orb/document.rb +19 -0
  21. data/lib/orb/errors.rb +40 -0
  22. data/lib/orb/parser.rb +182 -0
  23. data/lib/orb/patterns.rb +40 -0
  24. data/lib/orb/rails_derp.rb +138 -0
  25. data/lib/orb/rails_template.rb +101 -0
  26. data/lib/orb/railtie.rb +9 -0
  27. data/lib/orb/render_context.rb +36 -0
  28. data/lib/orb/template.rb +72 -0
  29. data/lib/orb/temple/attributes_compiler.rb +114 -0
  30. data/lib/orb/temple/compiler.rb +204 -0
  31. data/lib/orb/temple/engine.rb +40 -0
  32. data/lib/orb/temple/filters.rb +132 -0
  33. data/lib/orb/temple/generators.rb +108 -0
  34. data/lib/orb/temple/identity.rb +16 -0
  35. data/lib/orb/temple/parser.rb +46 -0
  36. data/lib/orb/temple.rb +16 -0
  37. data/lib/orb/token.rb +47 -0
  38. data/lib/orb/tokenizer.rb +757 -0
  39. data/lib/orb/tokenizer2.rb +591 -0
  40. data/lib/orb/utils/erb.rb +40 -0
  41. data/lib/orb/utils/orb.rb +12 -0
  42. data/lib/orb/version.rb +5 -0
  43. data/lib/orb.rb +50 -0
  44. metadata +89 -0
@@ -0,0 +1,591 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'strscan'
4
+ require_relative 'patterns'
5
+
6
+ module ORB
7
+ # Tokenizer2 is a streaming, non-recursive tokenizer for ORB templates.
8
+ #
9
+ # It scans the source sequentially and emits tokens as it passes over the input.
10
+ # During scanning, it keeps track of the current state and the list of tokens.
11
+ # Any consumption of the source, either by buffering or skipping moves the cursor.
12
+ # The cursor position is used to keep track of the current line and column in the
13
+ # virtual source document. When tokens are generated, they are annotated with the
14
+ # position they were found in the virtual document.
15
+ class Tokenizer2
16
+ include ORB::Patterns
17
+
18
+ # Tags that should be ignored
19
+ IGNORED_BODY_TAGS = %w[script style].freeze
20
+
21
+ # Tags that are self-closing by HTML5 spec
22
+ VOID_ELEMENTS = %w[area base br col command embed hr img input keygen link meta param source track wbr].freeze
23
+
24
+ attr_reader :errors, :tokens
25
+
26
+ def initialize(source, options = {})
27
+ @source = StringScanner.new(source)
28
+ @raise_errors = options.fetch(:raise_errors, true)
29
+
30
+ # Streaming Tokenizer State
31
+ @cursor = 0
32
+ @column = 1
33
+ @line = 1
34
+ @errors = []
35
+ @tokens = []
36
+ @attributes = []
37
+ @braces = []
38
+ @state = :initial
39
+ @buffer = StringIO.new
40
+ end
41
+
42
+ # Main Entry
43
+ def tokenize
44
+ next_token until @source.eos?
45
+
46
+ # Consume remaining buffer
47
+ buffer_to_text_token
48
+
49
+ # Return the tokens
50
+ @tokens
51
+ end
52
+
53
+ alias_method :tokenize!, :tokenize
54
+
55
+ private
56
+
57
+ # Dispatcher based on current state
58
+ def next_token
59
+ # Detect infinite loop
60
+ # if @previous_cursor == @cursor && @previous_state == @state
61
+ # raise "Internal Error: detected infinite loop in :#{@state}"
62
+ # end
63
+
64
+ # Dispatch to state handler
65
+ send(:"next_in_#{@state}")
66
+ end
67
+
68
+ # Read next token in :initial state
69
+ # rubocop:disable Metrics/AbcSize
70
+ def next_in_initial
71
+ if @source.scan(NEWLINE) || @source.scan(CRLF)
72
+ buffer_to_text_token
73
+ add_token(:newline, @source.matched)
74
+ move_by_matched
75
+ elsif @source.scan(PRIVATE_COMMENT_START)
76
+ buffer_to_text_token
77
+ add_token(:private_comment, nil)
78
+ move_by_matched
79
+ transition_to(:private_comment)
80
+ elsif @source.scan(PUBLIC_COMMENT_START)
81
+ buffer_to_text_token
82
+ add_token(:public_comment, nil)
83
+ move_by_matched
84
+ transition_to(:public_comment)
85
+ elsif @source.scan(BLOCK_OPEN)
86
+ buffer_to_text_token
87
+ add_token(:block_open, nil)
88
+ move_by_matched
89
+ transition_to(:block_open)
90
+ elsif @source.scan(BLOCK_CLOSE)
91
+ buffer_to_text_token
92
+ add_token(:block_close, nil)
93
+ move_by_matched
94
+ transition_to(:block_close)
95
+ elsif @source.scan(PRINTING_EXPRESSION_START)
96
+ buffer_to_text_token
97
+ add_token(:printing_expression, nil)
98
+ move_by_matched
99
+ clear_braces
100
+ transition_to(:printing_expression)
101
+ elsif @source.scan(CONTROL_EXPRESSION_START)
102
+ buffer_to_text_token
103
+ add_token(:control_expression, nil)
104
+ move_by_matched
105
+ clear_braces
106
+ transition_to(:control_expression)
107
+ elsif @source.scan(END_TAG_START)
108
+ buffer_to_text_token
109
+ add_token(:tag_close, nil)
110
+ move_by_matched
111
+ transition_to(:tag_close)
112
+ elsif @source.scan(START_TAG_START)
113
+ buffer_to_text_token
114
+ add_token(:tag_open, nil)
115
+ move_by_matched
116
+ transition_to(:tag_open)
117
+ elsif @source.scan(OTHER)
118
+ buffer_matched
119
+ move_by_matched
120
+ else
121
+ syntax_error!("Unexpected '#{@source.peek(1)}'")
122
+ end
123
+ end
124
+ # rubocop:enable Metrics/AbcSize
125
+
126
+ # Read next token in :tag_open state
127
+ def next_in_tag_open
128
+ if @source.scan(NEWLINE) || @source.scan(CRLF)
129
+ move_by_matched
130
+ elsif @source.scan(TAG_NAME)
131
+ tag = @source.matched
132
+ update_current_token(tag)
133
+ move_by_matched
134
+ clear_attributes
135
+ transition_to(:tag_open_content)
136
+ else
137
+ syntax_error!("Unexpected '#{@source.peek(1)}'")
138
+ end
139
+ end
140
+
141
+ # Read next token in :tag_open_content state
142
+ def next_in_tag_open_content
143
+ if @source.scan(NEWLINE) || @source.scan(CRLF) || @source.scan(BLANK)
144
+ move_by_matched
145
+ elsif @source.scan(START_TAG_END_VERBATIM)
146
+ current_token.set_meta(:self_closing, false)
147
+ current_token.set_meta(:verbatim, true)
148
+ current_token.set_meta(:attributes, @attributes) if @attributes.any?
149
+ clear_attributes
150
+ move_by_matched
151
+ transition_to(:verbatim)
152
+ elsif @source.scan(START_TAG_END_SELF_CLOSING)
153
+ current_token.set_meta(:self_closing, true)
154
+ current_token.set_meta(:attributes, @attributes) if @attributes.any?
155
+ clear_attributes
156
+ move_by_matched
157
+ transition_to(:initial)
158
+ elsif @source.scan(START_TAG_END)
159
+ current_token.set_meta(:self_closing, VOID_ELEMENTS.include?(current_token.value))
160
+ current_token.set_meta(:attributes, @attributes) if @attributes.any?
161
+ clear_attributes
162
+ move_by_matched
163
+ transition_to(:initial)
164
+ elsif @source.scan(START_TAG_START)
165
+ syntax_error!("Unexpected start of tag")
166
+ elsif @source.scan(%r{\*[^\s>/=]+})
167
+ splat = @source.matched
168
+ @attributes << [nil, :splat, splat]
169
+ move_by_matched
170
+ elsif @source.check(OTHER)
171
+ transition_to(:attribute_name)
172
+ else
173
+ syntax_error!("Unexpected '#{@source.peek(1)}'")
174
+ end
175
+ end
176
+
177
+ # Read next token in :attribute_name state
178
+ def next_in_attribute_name
179
+ if @source.scan(ATTRIBUTE_NAME)
180
+ @attributes << [@source.matched, :boolean, true]
181
+ move_by_matched
182
+ transition_to(:attribute_value?)
183
+ else
184
+ syntax_error!("Expected a valid attribute name")
185
+ end
186
+ end
187
+
188
+ # Read next token in :attribute_value? state
189
+ def next_in_attribute_value?
190
+ if @source.scan(NEWLINE) || @source.scan(CRLF) || @source.scan(BLANK)
191
+ move_by_matched
192
+ elsif @source.scan(ATTRIBUTE_ASSIGN)
193
+ move_by_matched
194
+ transition_to(:attribute_value!)
195
+ else
196
+ transition_to(:tag_open_content)
197
+ end
198
+ end
199
+
200
+ # Read next token in :attribute_value! state
201
+ def next_in_attribute_value!
202
+ if @source.scan(NEWLINE) || @source.scan(CRLF) || @source.scan(BLANK)
203
+ move_by_matched
204
+ elsif @source.scan(SINGLE_QUOTE)
205
+ move_by_matched
206
+ transition_to(:attribute_value_single_quoted)
207
+ elsif @source.scan(DOUBLE_QUOTE)
208
+ move_by_matched
209
+ transition_to(:attribute_value_double_quoted)
210
+ elsif @source.scan(BRACE_OPEN)
211
+ move_by_matched
212
+ transition_to(:attribute_value_expression)
213
+ elsif @source.check(OTHER)
214
+ transition_to(:attribute_value_unquoted)
215
+ else
216
+ syntax_error!("Unexpected '#{@source.peek(1)}'")
217
+ end
218
+ end
219
+
220
+ # Read next token in :attribute_value_single_quoted state
221
+ def next_in_attribute_value_single_quoted
222
+ if @source.scan(SINGLE_QUOTE)
223
+ attribute_value = consume_buffer
224
+ current_attribute[1] = :string
225
+ current_attribute[2] = attribute_value
226
+ move_by_matched
227
+ transition_to(:tag_open_content)
228
+ elsif @source.scan(NEWLINE) || @source.scan(CRLF) || @source.scan(BLANK) || @source.scan(OTHER)
229
+ buffer_matched
230
+ move_by_matched
231
+ else
232
+ syntax_error!("Unexpected '#{@source.peek(1)}'")
233
+ end
234
+ end
235
+
236
+ # Read next token in :attribute_value_double_quoted state
237
+ def next_in_attribute_value_double_quoted
238
+ if @source.scan(DOUBLE_QUOTE)
239
+ attribute_value = consume_buffer
240
+ current_attribute[1] = :string
241
+ current_attribute[2] = attribute_value
242
+ move_by_matched
243
+ transition_to(:tag_open_content)
244
+ elsif @source.scan(NEWLINE) || @source.scan(CRLF) || @source.scan(BLANK) || @source.scan(OTHER)
245
+ buffer_matched
246
+ move_by_matched
247
+ else
248
+ syntax_error!("Unexpected '#{@source.peek(1)}'")
249
+ end
250
+ end
251
+
252
+ # Read next token in :attribute_value_expression state
253
+ def next_in_attribute_value_expression
254
+ if @source.scan(BRACE_OPEN)
255
+ @braces << "{"
256
+ buffer_matched
257
+ move_by_matched
258
+ elsif @source.scan(BRACE_CLOSE)
259
+ if @braces.any?
260
+ @braces.pop
261
+ buffer_matched
262
+ move_by_matched
263
+ else
264
+ attribute_expression = consume_buffer
265
+ current_attribute[1] = :expression
266
+ current_attribute[2] = attribute_expression.strip
267
+ move_by_matched
268
+ transition_to(:tag_open_content)
269
+ end
270
+ elsif @source.scan(NEWLINE) || @source.scan(CRLF) || @source.scan(BLANK) || @source.scan(OTHER)
271
+ buffer_matched
272
+ move_by_matched
273
+ else
274
+ syntax_error!("Unexpected end of input while reading expression attribute value")
275
+ end
276
+ end
277
+
278
+ # Read next token in :attribute_value_unquoted state
279
+ def next_in_attribute_value_unquoted
280
+ if @source.scan(UNQUOTED_VALUE)
281
+ attribute_value = @source.matched
282
+ current_attribute[1] = :string
283
+ current_attribute[2] = attribute_value
284
+ move_by_matched
285
+ transition_to(:tag_open_content)
286
+ elsif @source.scan(UNQUOTED_VALUE_INVALID_CHARS)
287
+ syntax_error!("Unexpected '#{@source.peek(1)}' in unquoted attribute value")
288
+ else
289
+ syntax_error!("Unexpected end of input while reading unquoted attribute value")
290
+ end
291
+ end
292
+
293
+ # Read next token in :tag_close state
294
+ def next_in_tag_close
295
+ if @source.scan(TAG_NAME)
296
+ buffer_matched
297
+ move_by_matched
298
+ elsif @source.scan(/[$?>]+/)
299
+ tag = consume_buffer
300
+ update_current_token(tag)
301
+ move_by(tag)
302
+ transition_to(:initial)
303
+ else
304
+ syntax_error!("Unexpected '#{@source.peek(1)}'")
305
+ end
306
+ end
307
+
308
+ # Read next token in :public_comment state
309
+ def next_in_public_comment
310
+ if @source.scan(PUBLIC_COMMENT_END)
311
+ text = consume_buffer
312
+ update_current_token(text)
313
+ move_by_matched
314
+ transition_to(:initial)
315
+ elsif @source.scan(NEWLINE) || @source.scan(CRLF) || @source.scan(BLANK) || @source.scan(OTHER)
316
+ buffer_matched
317
+ move_by_matched
318
+ else
319
+ syntax_error!("Unexpected input '#{@source.peek(1)}' while reading public comment")
320
+ end
321
+ end
322
+
323
+ # Read tokens in :private_comment state
324
+ def next_in_private_comment
325
+ if @source.scan(PRIVATE_COMMENT_END)
326
+ text = consume_buffer
327
+ update_current_token(text)
328
+ move_by_matched
329
+ transition_to(:initial)
330
+ elsif @source.scan(NEWLINE) || @source.scan(CRLF) || @source.scan(BLANK) || @source.scan(OTHER)
331
+ buffer_matched
332
+ move_by_matched
333
+ else
334
+ syntax_error!("Unexpected input '#{@source.peek(1)}' while reading public comment")
335
+ end
336
+ end
337
+
338
+ # Read tokens in :block_open state
339
+ def next_in_block_open
340
+ if @source.scan(BLOCK_NAME_CHARS)
341
+ block_name = @source.matched
342
+ update_current_token(block_name)
343
+ move_by_matched
344
+ clear_braces
345
+ transition_to(:block_open_content)
346
+ else
347
+ syntax_error!("Exptected valid block name")
348
+ end
349
+ end
350
+
351
+ # Read block expression until closing brace
352
+ def next_in_block_open_content
353
+ if @source.scan(BRACE_OPEN)
354
+ @braces << "{"
355
+ buffer_matched
356
+ move_by_matched
357
+ elsif @source.scan(BRACE_CLOSE)
358
+ if @braces.empty?
359
+ block_expression = consume_buffer
360
+ update_current_token(nil, expression: block_expression.strip)
361
+ move_by_matched
362
+ transition_to(:initial)
363
+ else
364
+ @braces.pop
365
+ buffer_matched
366
+ move_by_matched
367
+ end
368
+ elsif @source.scan(BLANK) || @source.scan(OTHER)
369
+ buffer_matched
370
+ move_by_matched
371
+ end
372
+ end
373
+
374
+ # Read tokens in :block_close state
375
+ def next_in_block_close
376
+ if @source.scan(BLOCK_NAME_CHARS)
377
+ block_name = @source.matched
378
+ update_current_token(block_name)
379
+ move_by_matched
380
+ transition_to(:block_close_content)
381
+ else
382
+ syntax_error!("Expected valid block name")
383
+ end
384
+ end
385
+
386
+ # Read block close name until closing brace
387
+ def next_in_block_close_content
388
+ if @source.scan(/\s/)
389
+ move_by_matched
390
+ elsif @source.scan(BRACE_CLOSE)
391
+ move_by_matched
392
+ transition_to(:initial)
393
+ else
394
+ syntax_error!("Expected closing brace '}'")
395
+ end
396
+ end
397
+
398
+ # Read tokens in :printing_expression state
399
+ def next_in_printing_expression
400
+ if @source.scan(PRINTING_EXPRESSION_END)
401
+ expression = consume_buffer
402
+ update_current_token(expression)
403
+ move_by_matched
404
+ transition_to(:initial)
405
+ elsif @source.scan(BRACE_OPEN)
406
+ @braces << "{"
407
+ buffer_matched
408
+ move_by_matched
409
+ elsif @source.scan(BRACE_CLOSE)
410
+ if @braces.any?
411
+ @braces.pop
412
+ buffer_matched
413
+ move_by_matched
414
+ else
415
+ syntax_error!("Unexpected closing brace '}'")
416
+ end
417
+ elsif @source.scan(OTHER) || @source.scan(NEWLINE) || @source.scan(CRLF)
418
+ buffer_matched
419
+ move_by_matched
420
+ end
421
+ end
422
+
423
+ # Read tokens in :control_expression state
424
+ def next_in_control_expression
425
+ if @source.scan(CONTROL_EXPRESSION_END)
426
+ expression = consume_buffer
427
+ update_current_token(expression)
428
+ move_by_matched
429
+ transition_to(:initial)
430
+ elsif @source.scan(BRACE_OPEN)
431
+ @braces << "{"
432
+ buffer_matched
433
+ move_by_matched
434
+ elsif @source.scan(BRACE_CLOSE)
435
+ if @braces.any?
436
+ @braces.pop
437
+ buffer_matched
438
+ move_by_matched
439
+ else
440
+ syntax_error!("Unexpected closing brace '}'")
441
+ end
442
+ elsif @source.scan(OTHER) || @source.scan(NEWLINE) || @source.scan(CRLF)
443
+ buffer_matched
444
+ move_by_matched
445
+ end
446
+ end
447
+
448
+ # Read tokens in :verbatim state
449
+ def next_in_verbatim
450
+ if @source.scan(END_TAG_START)
451
+ # store the match up to here in a temporary variable
452
+ tmp = @source.matched
453
+ # then find the next verbatim end tag end and
454
+ # check if the tag name matches the current verbatim tag name
455
+ lookahead = @source.check_until(END_TAG_END_VERBATIM)
456
+
457
+ # if the tag name matches, we have found the end of the verbatim tag
458
+ # and we can add a text token, as well as a tag_close token
459
+ if lookahead[0..-3] == current_token.value
460
+ buffer_to_text_token
461
+ add_token(:tag_close, nil)
462
+ transition_to(:tag_close)
463
+ else
464
+ buffer(tmp)
465
+ move_by(tmp)
466
+ end
467
+ elsif @source.scan(NEWLINE) || @source.scan(CRLF) || @source.scan(BLANK) || @source.scan(OTHER)
468
+ buffer_matched
469
+ move_by_matched
470
+ end
471
+ end
472
+
473
+ # --------------------------------------------------------------
474
+ # Helpers
475
+
476
+ # Terminates the tokenizer.
477
+ def terminate
478
+ @source.terminate
479
+ end
480
+
481
+ # Retrieve the current token
482
+ def current_token
483
+ raise "Invalid tokenizer state: no tokens present" if @tokens.empty?
484
+
485
+ @tokens.last
486
+ end
487
+
488
+ def current_attribute
489
+ raise "Invalid tokenizer state: no attributes present" if @attributes.empty?
490
+
491
+ @attributes.last
492
+ end
493
+
494
+ def add_attribute(name, type, value)
495
+ @attributes << [name, type, value]
496
+ end
497
+
498
+ def clear_attributes
499
+ @attributes = []
500
+ end
501
+
502
+ def clear_braces
503
+ @braces = []
504
+ end
505
+
506
+ # Moves the cursor
507
+ def move(line, column)
508
+ @line = line
509
+ @column = column
510
+ end
511
+
512
+ def move_by(str)
513
+ scan = StringScanner.new(str)
514
+ until scan.eos?
515
+ if scan.scan(NEWLINE) || scan.scan(CRLF)
516
+ move(@line + 1, 1)
517
+ elsif scan.scan(OTHER)
518
+ move(@line, @column + scan.matched.size)
519
+ end
520
+ end
521
+ end
522
+
523
+ def move_by_matched
524
+ move_by(@source.matched)
525
+ end
526
+
527
+ # Changes the state
528
+ def transition_to(state)
529
+ @state = state
530
+ end
531
+
532
+ # Create a new token
533
+ def create_token(type, value, meta = {})
534
+ Token.new(type, value, meta.merge(line: @line, column: @column) { |_k, v1, _v2| v1 })
535
+ end
536
+
537
+ # Create a token and add it to the token list
538
+ def add_token(type, value, meta = {})
539
+ @tokens << create_token(type, value, meta)
540
+ end
541
+
542
+ # Update the current token
543
+ def update_current_token(value = nil, meta = {})
544
+ current_token.value = value if value
545
+ current_token.meta.merge!(meta)
546
+ end
547
+
548
+ # Write given str to buffer
549
+ def buffer(str)
550
+ @buffer << str
551
+ end
552
+
553
+ # Read the buffer to a string
554
+ def read_buffer
555
+ @buffer.string.clone
556
+ end
557
+
558
+ # Clear the buffer
559
+ def clear_buffer
560
+ @buffer = StringIO.new
561
+ end
562
+
563
+ # Read the buffer to a string and clear it
564
+ def consume_buffer
565
+ str = read_buffer
566
+ clear_buffer
567
+ str
568
+ end
569
+
570
+ def buffer_matched
571
+ buffer(@source.matched)
572
+ end
573
+
574
+ # Turn the buffer into a text token
575
+ def buffer_to_text_token
576
+ text = consume_buffer
577
+ add_token(:text, text, line: @line, column: @column - text.size) unless text.empty?
578
+ end
579
+
580
+ # Raise a syntax error
581
+ def syntax_error!(message)
582
+ if @raise_errors
583
+ raise ORB::SyntaxError.new("#{message} at line #{@line} and column #{@column} during :#{@state}", @line)
584
+ end
585
+
586
+ @errors << ORB::SyntaxError.new("#{message} at line #{@line} and column #{@column} during :#{@state}", @line)
587
+
588
+ terminate
589
+ end
590
+ end
591
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ORB
4
+ module Utils
5
+ class ERB
6
+ def self.tokenize(source) # :nodoc:
7
+ require "strscan"
8
+ source = StringScanner.new(source.chomp)
9
+ tokens = []
10
+
11
+ start_re = /<%(?:={1,2}|-|\#|%)?/m
12
+ finish_re = /(?:[-=])?%>/m
13
+
14
+ until source.eos?
15
+ pos = source.pos
16
+ source.scan_until(/(?:#{start_re}|#{finish_re})/)
17
+ len = source.pos - source.matched.bytesize - pos
18
+
19
+ case source.matched
20
+ when start_re
21
+ tokens << [:TEXT, source.string[pos, len]] if len.positive?
22
+ tokens << [:OPEN, source.matched]
23
+ raise NotImplemented unless source.scan(/(.*?)(?=#{finish_re}|\z)/m)
24
+
25
+ tokens << [:CODE, source.matched] unless source.matched.empty?
26
+ tokens << [:CLOSE, source.scan(finish_re)] unless source.eos?
27
+
28
+ when finish_re
29
+ tokens << [:CODE, source.string[pos, len]] if len.positive?
30
+ tokens << [:CLOSE, source.matched]
31
+ else
32
+ raise NotImplemented, source.matched
33
+ end
34
+ end
35
+
36
+ tokens
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ORB
4
+ module Utils
5
+ class ORB
6
+ def self.tokenize(source) # :nodoc:
7
+ tokenizer = ::ORB::Tokenizer2.new(source)
8
+ tokenizer.tokenize
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ORB
4
+ VERSION = "0.1.0"
5
+ end
data/lib/orb.rb ADDED
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'temple'
4
+ require 'cgi/util'
5
+ require "active_support/dependencies/autoload"
6
+
7
+ module ORB
8
+ extend ActiveSupport::Autoload
9
+
10
+ autoload :Error, 'orb/errors'
11
+ autoload :SyntaxError, 'orb/errors'
12
+ autoload :ParserError, 'orb/errors'
13
+ autoload :CompilerError, 'orb/errors'
14
+ autoload :Token
15
+ autoload :Tokenizer
16
+ autoload :RenderContext
17
+ autoload :AST
18
+ autoload :Parser
19
+ autoload :Document
20
+ autoload :Template
21
+ autoload :Temple
22
+ autoload :RailsTemplate
23
+
24
+ # Next-gen tokenizer built on top of strscan
25
+ autoload :Tokenizer2
26
+
27
+ # Configure class caching
28
+ singleton_class.send(:attr_accessor, :cache_classes)
29
+ self.cache_classes = true
30
+
31
+ # Configure order of component namespace lookups
32
+ singleton_class.send(:attr_accessor, :namespaces)
33
+ self.namespaces = []
34
+
35
+ def self.lookup_component(name)
36
+ namespaces.each do |namespace|
37
+ klass = "#{namespace}::#{name}"
38
+ return klass if Object.const_defined?(klass)
39
+ end
40
+
41
+ nil
42
+ end
43
+
44
+ def self.html_escape(str)
45
+ CGI.escapeHTML(str.to_s)
46
+ end
47
+ end
48
+
49
+ # Load the Railtie if we are in a Rails environment
50
+ require 'orb/railtie' if defined?(Rails)