xrb 0.1 → 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.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +1 -0
  3. data/bake/xrb/entities.rb +60 -0
  4. data/bake/xrb/parsers.rb +66 -0
  5. data/ext/Makefile +270 -0
  6. data/ext/XRB_Extension.bundle +0 -0
  7. data/ext/escape.o +0 -0
  8. data/ext/extconf.h +5 -0
  9. data/ext/extconf.rb +21 -0
  10. data/ext/markup.o +0 -0
  11. data/ext/mkmf.log +122 -0
  12. data/ext/query.o +0 -0
  13. data/ext/tag.o +0 -0
  14. data/ext/template.o +0 -0
  15. data/ext/xrb/escape.c +152 -0
  16. data/ext/xrb/escape.h +15 -0
  17. data/ext/xrb/markup.c +1949 -0
  18. data/ext/xrb/markup.h +6 -0
  19. data/ext/xrb/markup.rl +226 -0
  20. data/ext/xrb/query.c +619 -0
  21. data/ext/xrb/query.h +6 -0
  22. data/ext/xrb/query.rl +82 -0
  23. data/ext/xrb/tag.c +204 -0
  24. data/ext/xrb/tag.h +21 -0
  25. data/ext/xrb/template.c +1114 -0
  26. data/ext/xrb/template.h +6 -0
  27. data/ext/xrb/template.rl +77 -0
  28. data/ext/xrb/xrb.c +72 -0
  29. data/ext/xrb/xrb.h +132 -0
  30. data/ext/xrb.o +0 -0
  31. data/lib/xrb/buffer.rb +103 -0
  32. data/lib/xrb/builder.rb +229 -0
  33. data/lib/xrb/entities.rb +2137 -0
  34. data/lib/xrb/entities.xrb +15 -0
  35. data/lib/xrb/error.rb +81 -0
  36. data/lib/xrb/fallback/markup.rb +1657 -0
  37. data/lib/xrb/fallback/markup.rl +227 -0
  38. data/lib/xrb/fallback/query.rb +548 -0
  39. data/lib/xrb/fallback/query.rl +88 -0
  40. data/lib/xrb/fallback/template.rb +829 -0
  41. data/lib/xrb/fallback/template.rl +80 -0
  42. data/lib/xrb/markup.rb +56 -0
  43. data/lib/xrb/native.rb +15 -0
  44. data/lib/xrb/parsers.rb +16 -0
  45. data/lib/xrb/query.rb +80 -0
  46. data/lib/xrb/reference.rb +108 -0
  47. data/lib/xrb/strings.rb +47 -0
  48. data/lib/xrb/tag.rb +115 -0
  49. data/lib/xrb/template.rb +128 -0
  50. data/lib/xrb/uri.rb +100 -0
  51. data/lib/xrb/version.rb +8 -0
  52. data/lib/xrb.rb +11 -0
  53. data/license.md +23 -0
  54. data/readme.md +34 -0
  55. data.tar.gz.sig +0 -0
  56. metadata +118 -58
  57. metadata.gz.sig +2 -0
  58. data/README +0 -60
  59. data/app/helpers/ui_helper.rb +0 -80
  60. data/app/models/xrb/element.rb +0 -9
  61. data/lib/xrb/engine.rb +0 -4
  62. data/rails/init.rb +0 -1
  63. data/xrb.gemspec +0 -12
@@ -0,0 +1,6 @@
1
+
2
+ #pragma once
3
+
4
+ #include "xrb.h"
5
+
6
+ VALUE XRB_Native_parse_template(VALUE self, VALUE buffer, VALUE delegate);
@@ -0,0 +1,77 @@
1
+
2
+ #include "template.h"
3
+
4
+ %%{
5
+ machine XRB_template_parser;
6
+
7
+ action instruction_begin {
8
+ instruction.begin = p;
9
+ }
10
+
11
+ action instruction_end {
12
+ instruction.end = p;
13
+ }
14
+
15
+ action emit_instruction {
16
+ rb_funcall(delegate, id_instruction, 1, XRB_Token_string(instruction, encoding));
17
+ }
18
+
19
+ action emit_instruction_line {
20
+ rb_funcall(delegate, id_instruction, 2, XRB_Token_string(instruction, encoding), newline);
21
+ }
22
+
23
+ action instruction_error {
24
+ XRB_raise_error("failed to parse instruction", buffer, p-s);
25
+ }
26
+
27
+ action expression_begin {
28
+ expression.begin = p;
29
+ }
30
+
31
+ action expression_end {
32
+ expression.end = p;
33
+ }
34
+
35
+ action emit_expression {
36
+ rb_funcall(delegate, id_expression, 1, XRB_Token_string(expression, encoding));
37
+ }
38
+
39
+ action expression_error {
40
+ XRB_raise_error("failed to parse expression", buffer, p-s);
41
+ }
42
+
43
+ action emit_text {
44
+ rb_funcall(delegate, id_text, 1, XRB_string(ts, te, encoding));
45
+ }
46
+
47
+ include template "xrb/template.rl";
48
+
49
+ write data;
50
+ }%%
51
+
52
+ VALUE XRB_Native_parse_template(VALUE self, VALUE buffer, VALUE delegate) {
53
+ VALUE string = rb_funcall(buffer, id_read, 0);
54
+
55
+ rb_encoding *encoding = rb_enc_get(string);
56
+
57
+ VALUE newline = rb_obj_freeze(rb_enc_str_new("\n", 1, encoding));
58
+
59
+ const char *s, *p, *pe, *eof, *ts, *te;
60
+ unsigned long cs, act, top = 0, stack[32] = {0};
61
+
62
+ XRB_Token expression = {0}, instruction = {0};
63
+
64
+ s = p = RSTRING_PTR(string);
65
+ eof = pe = p + RSTRING_LEN(string);
66
+
67
+ %%{
68
+ write init;
69
+ write exec;
70
+ }%%
71
+
72
+ if (p != eof) {
73
+ XRB_raise_error("could not parse all input", buffer, p-s);
74
+ }
75
+
76
+ return Qnil;
77
+ }
data/ext/xrb/xrb.c ADDED
@@ -0,0 +1,72 @@
1
+
2
+ #include "xrb.h"
3
+
4
+ #include "markup.h"
5
+ #include "template.h"
6
+ #include "query.h"
7
+ #include "tag.h"
8
+ #include "escape.h"
9
+
10
+ VALUE rb_XRB = Qnil, rb_XRB_Native = Qnil, rb_XRB_Tag = Qnil, rb_XRB_Markup = Qnil, rb_XRB_MarkupString = Qnil, rb_XRB_ParseError = Qnil;
11
+ ID id_cdata, id_open_tag_begin, id_open_tag_end, id_attribute, id_close_tag, id_text, id_doctype, id_comment, id_instruction, id_read, id_expression, id_key_get, id_string, id_integer, id_append, id_assign, id_pair, id_new, id_name, id_attributes, id_closed, id_to_s, id_is_a;
12
+
13
+ void XRB_raise_error(const char * message, VALUE buffer, size_t offset) {
14
+ VALUE exception = rb_funcall(rb_XRB_ParseError, id_new, 3, rb_str_new_cstr(message), buffer, ULONG2NUM(offset));
15
+
16
+ rb_exc_raise(exception);
17
+ }
18
+
19
+ void Init_XRB_Extension() {
20
+ id_open_tag_begin = rb_intern("open_tag_begin");
21
+ id_open_tag_end = rb_intern("open_tag_end");
22
+ id_close_tag = rb_intern("close_tag");
23
+
24
+ id_cdata = rb_intern("cdata");
25
+ id_attribute = rb_intern("attribute");
26
+ id_comment = rb_intern("comment");
27
+ id_text = rb_intern("text");
28
+ id_doctype = rb_intern("doctype");
29
+ id_instruction = rb_intern("instruction");
30
+ id_expression = rb_intern("expression");
31
+
32
+ id_read = rb_intern("read");
33
+ id_new = rb_intern("new");
34
+
35
+ id_name = rb_intern("name");
36
+ id_attributes = rb_intern("attributes");
37
+ id_closed = rb_intern("closed");
38
+
39
+ id_key_get = rb_intern("[]");
40
+
41
+ id_string = rb_intern("string");
42
+ id_integer = rb_intern("integer");
43
+ id_append = rb_intern("append");
44
+ id_assign = rb_intern("assign");
45
+ id_pair = rb_intern("pair");
46
+
47
+ id_to_s = rb_intern("to_s");
48
+ id_is_a = rb_intern("is_a?");
49
+
50
+ rb_XRB = rb_define_module("XRB");
51
+ rb_gc_register_mark_object(rb_XRB);
52
+
53
+ rb_XRB_Markup = rb_define_module_under(rb_XRB, "Markup");
54
+ rb_gc_register_mark_object(rb_XRB_Markup);
55
+
56
+ rb_XRB_Native = rb_define_module_under(rb_XRB, "Native");
57
+ rb_gc_register_mark_object(rb_XRB_Native);
58
+
59
+ Init_xrb_escape();
60
+
61
+ rb_XRB_ParseError = rb_const_get_at(rb_XRB, rb_intern("ParseError"));
62
+ rb_gc_register_mark_object(rb_XRB_ParseError);
63
+
64
+ rb_define_module_function(rb_XRB_Native, "parse_markup", XRB_Native_parse_markup, 3);
65
+ rb_define_module_function(rb_XRB_Native, "parse_template", XRB_Native_parse_template, 2);
66
+ rb_define_module_function(rb_XRB_Native, "parse_query", XRB_Native_parse_query, 2);
67
+
68
+ rb_XRB_Tag = rb_const_get_at(rb_XRB, rb_intern("Tag"));
69
+ rb_gc_register_mark_object(rb_XRB_Tag);
70
+
71
+ Init_xrb_tag();
72
+ }
data/ext/xrb/xrb.h ADDED
@@ -0,0 +1,132 @@
1
+
2
+ #pragma once
3
+
4
+ #include "ruby.h"
5
+ #include <ruby/encoding.h>
6
+
7
+ // Used to efficiently convert symbols to strings (e.g. tag attribute keys).
8
+ #ifndef HAVE_RB_SYM2STR
9
+ #define rb_sym2str(sym) rb_id2str(SYM2ID(sym))
10
+ #endif
11
+
12
+ // Consistent and meaningful append cstring to ruby string/buffer.
13
+ #ifndef HAVE_RB_STR_CAT_CSTR
14
+ #define rb_str_cat_cstr rb_str_cat2
15
+ #endif
16
+
17
+ // Prefer non-generic macro names where possible.
18
+ #ifndef RB_IMMEDIATE_P
19
+ #define RB_IMMEDIATE_P IMMEDIATE_P
20
+ #endif
21
+
22
+ // A helper to reserve a specific capacity of data for a buffer.
23
+ #ifndef HAVE_RB_STR_RESERVE
24
+ inline VALUE rb_str_reserve(VALUE string, long extra) {
25
+ long actual = RSTRING_LEN(string);
26
+ rb_str_resize(string, actual + extra);
27
+ rb_str_set_len(string, actual);
28
+ return string;
29
+ }
30
+ #endif
31
+
32
+ // Modules and classes exposed by XRB.
33
+ extern VALUE
34
+ rb_XRB,
35
+ rb_XRB_Markup,
36
+ rb_XRB_Tag,
37
+ rb_XRB_MarkupString,
38
+ rb_XRB_Native,
39
+ rb_XRB_ParseError;
40
+
41
+ // Symbols used for delegate callbacks and general function calls.
42
+ extern ID
43
+ id_cdata,
44
+ id_open_tag_begin,
45
+ id_open_tag_end,
46
+ id_attribute,
47
+ id_close_tag,
48
+ id_text,
49
+ id_doctype,
50
+ id_comment,
51
+ id_instruction,
52
+ id_read,
53
+ id_expression,
54
+ id_key_get,
55
+ id_new,
56
+ id_name,
57
+ id_integer,
58
+ id_string,
59
+ id_append,
60
+ id_assign,
61
+ id_pair,
62
+ id_attributes,
63
+ id_closed,
64
+ id_to_s,
65
+ id_is_a;
66
+
67
+ // A convenient C string token class.
68
+ typedef struct {
69
+ const char * begin;
70
+ const char * end;
71
+ } XRB_Token;
72
+
73
+ // Convert a token to a Ruby string.
74
+ static inline VALUE XRB_Token_string(XRB_Token token, rb_encoding * encoding) {
75
+ return rb_enc_str_new(token.begin, token.end - token.begin, encoding);
76
+ }
77
+
78
+ // Convert a C string to a Ruby string.
79
+ static inline VALUE XRB_string(const char * begin, const char * end, rb_encoding * encoding) {
80
+ return rb_enc_str_new(begin, end - begin, encoding);
81
+ }
82
+
83
+ // Create an empty buffer for the given input string.
84
+ static inline VALUE XRB_buffer_for(VALUE string) {
85
+ VALUE buffer = rb_enc_str_new(0, 0, rb_enc_get(string));
86
+
87
+ rb_str_reserve(buffer, RSTRING_LEN(string) + 128);
88
+
89
+ return buffer;
90
+ }
91
+
92
+ // Raise a parse error for the given input buffer at a specific offset.
93
+ NORETURN(void XRB_raise_error(const char * message, VALUE buffer, size_t offset));
94
+
95
+ // Append a string to a buffer. The buffer may or may not be initialized.
96
+ static inline void XRB_append(VALUE * buffer, rb_encoding * encoding, VALUE string) {
97
+ if (*buffer == Qnil) {
98
+ *buffer = rb_enc_str_new(0, 0, encoding);
99
+ }
100
+
101
+ rb_str_concat(*buffer, string);
102
+ }
103
+
104
+ // Append a token to a buffer. The buffer may or may not be initialized.
105
+ static inline void XRB_append_token(VALUE * buffer, rb_encoding * encoding, XRB_Token token) {
106
+ if (*buffer == Qnil) {
107
+ // Allocate a buffer exactly the right size:
108
+ *buffer = rb_enc_str_new(token.begin, token.end - token.begin, encoding);
109
+ } else {
110
+ // Append the characters to the existing buffer:
111
+ rb_str_buf_cat(*buffer, token.begin, token.end - token.begin);
112
+ }
113
+ }
114
+
115
+ // Append a (unicode) codepoint to a buffer. The buffer may or may not be initialized.
116
+ static inline void XRB_append_codepoint(VALUE * buffer, rb_encoding * encoding, unsigned long codepoint) {
117
+ if (*buffer == Qnil) {
118
+ *buffer = rb_enc_str_new(0, 0, encoding);
119
+ }
120
+
121
+ rb_str_concat(*buffer, ULONG2NUM(codepoint));
122
+ }
123
+
124
+ // Convert the class of a string if there were no entities detected.
125
+ static inline VALUE XRB_markup_safe(VALUE string, unsigned has_entities) {
126
+ if (!has_entities) {
127
+ // Apparently should not use this to change klass, but it's exactly what we need here to make things lightning fast.
128
+ rb_obj_reveal(string, rb_XRB_MarkupString);
129
+ }
130
+
131
+ return string;
132
+ }
data/ext/xrb.o ADDED
Binary file
data/lib/xrb/buffer.rb ADDED
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2016-2024, by Samuel Williams.
5
+
6
+ module XRB
7
+ class Buffer
8
+ def initialize(string, path: '<string>')
9
+ @string = string
10
+ @path = path
11
+ end
12
+
13
+ attr :path
14
+
15
+ def encoding
16
+ @string.encoding
17
+ end
18
+
19
+ def read
20
+ @string
21
+ end
22
+
23
+ def self.load_file(path)
24
+ FileBuffer.new(path).freeze
25
+ end
26
+
27
+ def self.load(string)
28
+ Buffer.new(string).freeze
29
+ end
30
+
31
+ def to_buffer
32
+ self
33
+ end
34
+ end
35
+
36
+ class FileBuffer
37
+ def initialize(path)
38
+ @path = path
39
+ end
40
+
41
+ def freeze
42
+ return self if frozen?
43
+
44
+ read
45
+
46
+ super
47
+ end
48
+
49
+ attr :path
50
+
51
+ def encoding
52
+ read.encoding
53
+ end
54
+
55
+ def read
56
+ @cache ||= File.read(@path).freeze
57
+ end
58
+
59
+ def to_buffer
60
+ Buffer.new(self.read, @path)
61
+ end
62
+ end
63
+
64
+ class IOBuffer
65
+ def initialize(io, path: io.inspect)
66
+ @io = io
67
+ @path = path
68
+ end
69
+
70
+ def freeze
71
+ return self if frozen?
72
+
73
+ read
74
+
75
+ super
76
+ end
77
+
78
+ attr :path
79
+
80
+ def encoding
81
+ read.encoding
82
+ end
83
+
84
+ def read
85
+ @cache ||= @io.read.freeze
86
+ end
87
+
88
+ def to_buffer
89
+ Buffer.new(self.read, path: @path)
90
+ end
91
+ end
92
+
93
+ def self.Buffer(value)
94
+ case value
95
+ when String
96
+ Buffer.new(value)
97
+ when Buffer, FileBuffer, IOBuffer
98
+ value
99
+ else
100
+ value.to_buffer
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,229 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2012-2024, by Samuel Williams.
5
+
6
+ require_relative 'markup'
7
+ require_relative 'tag'
8
+
9
+ module XRB
10
+ # Build markup quickly and efficiently.
11
+ class Builder
12
+ include Markup
13
+
14
+ INDENT = "\t"
15
+
16
+ class Fragment
17
+ def initialize(block)
18
+ @block = block
19
+ @builder = nil
20
+ end
21
+
22
+ def call(builder)
23
+ @block.call(builder)
24
+ end
25
+
26
+ def to_s
27
+ unless @builder
28
+ @builder = Builder.new
29
+
30
+ self.call(@builder)
31
+ end
32
+
33
+ return @builder.to_s
34
+ end
35
+
36
+ def == other
37
+ # This is a bit of a hack... but is required for existing specs to pass:
38
+ self.to_s == other.to_s
39
+ end
40
+
41
+ def >> block
42
+ if block
43
+ Template.buffer(block.binding) << self
44
+
45
+ return nil
46
+ else
47
+ return self
48
+ end
49
+ end
50
+ end
51
+
52
+ # A helper to generate fragments of markup.
53
+ def self.fragment(output = nil, &block)
54
+ if output.is_a?(Binding)
55
+ output = Template.buffer(output)
56
+ end
57
+
58
+ if output.nil?
59
+ return Fragment.new(block)
60
+ end
61
+
62
+ block.call(output)
63
+
64
+ # We explicitly return nil here as we don't want to append the output twice.
65
+ return nil
66
+ end
67
+
68
+ def self.tag(name, content, **attributes)
69
+ self.fragment do |builder|
70
+ builder.inline(name, attributes) do
71
+ builder.text(content)
72
+ end
73
+ end
74
+ end
75
+
76
+ def initialize(output = nil, indent: true, encoding: Encoding::UTF_8)
77
+ # This field gets togged in #inline so we keep track of it separately from @indentation.
78
+ @indent = indent
79
+
80
+ # We don't need to use MarkupString here as Builder itself is considered markup and should be inserted directly into the output stream.
81
+ @output = output || MarkupString.new.force_encoding(encoding)
82
+
83
+ @level = [0]
84
+ @children = [0]
85
+ end
86
+
87
+ attr :output
88
+
89
+ def encoding
90
+ @output.encoding
91
+ end
92
+
93
+ # Required for output to buffer.
94
+ def to_str
95
+ @output
96
+ end
97
+
98
+ alias to_s to_str
99
+
100
+ def == other
101
+ @output == String(other)
102
+ end
103
+
104
+ def indentation
105
+ if @indent
106
+ INDENT * (@level.size - 1)
107
+ else
108
+ ''
109
+ end
110
+ end
111
+
112
+ def doctype(attributes = 'html')
113
+ @output << "<!doctype #{attributes}>\n"
114
+ end
115
+
116
+ # Begin a block tag.
117
+ def tag(name, attributes = {}, &block)
118
+ full_tag(name, attributes, @indent, @indent, &block)
119
+ end
120
+
121
+ # Begin an inline tag.
122
+ def inline_tag(name, attributes = {}, &block)
123
+ original_indent = @indent
124
+
125
+ full_tag(name, attributes, @indent, false) do
126
+ @indent = false
127
+ yield if block_given?
128
+ end
129
+ ensure
130
+ @indent = original_indent
131
+ end
132
+
133
+ alias inline inline_tag
134
+
135
+ def inline!
136
+ original_indent = @indent
137
+ @indent = false
138
+
139
+ yield
140
+ ensure
141
+ @indent = original_indent
142
+ end
143
+
144
+ def text(content)
145
+ return unless content
146
+
147
+ if @indent
148
+ @output << "\n" if @level.last > 0
149
+ @output << indentation
150
+ end
151
+
152
+ Markup.append(@output, content)
153
+
154
+ if @indent
155
+ @output << "\n"
156
+ end
157
+ end
158
+
159
+ def raw(content)
160
+ @output << content
161
+ end
162
+
163
+ def <<(content)
164
+ return unless content
165
+
166
+ if content.is_a?(Fragment)
167
+ inline! do
168
+ content.call(self)
169
+ end
170
+ else
171
+ Markup.append(@output, content)
172
+ end
173
+ end
174
+
175
+ # Append pre-existing markup:
176
+ def append(value)
177
+ return unless value
178
+
179
+ # The parent has one more child:
180
+ @level[-1] += 1
181
+
182
+ if @indent
183
+ value.each_line.with_index do |line, i|
184
+ @output << indentation << line
185
+ end
186
+ else
187
+ @output << value
188
+ end
189
+ end
190
+
191
+ protected
192
+
193
+ # A normal block level/container tag.
194
+ def full_tag(name, attributes, indent_outer, indent_inner, &block)
195
+ if block_given?
196
+ if indent_outer
197
+ @output << "\n" if @level.last > 0
198
+ @output << indentation
199
+ end
200
+
201
+ tag = XRB::Tag.opened(name.to_s, attributes)
202
+ tag.write_opening_tag(@output)
203
+ @output << "\n" if indent_inner
204
+
205
+ # The parent has one more child:
206
+ @level[-1] += 1
207
+
208
+ @level << 0
209
+
210
+ yield
211
+
212
+ children = @level.pop
213
+
214
+ if indent_inner
215
+ @output << "\n" if children > 0
216
+ @output << indentation
217
+ end
218
+
219
+ tag.write_closing_tag(@output)
220
+ else
221
+ # The parent has one more child:
222
+ @level[-1] += 1
223
+
224
+ @output << indentation
225
+ XRB::Tag.append_tag(@output, name.to_s, attributes, nil)
226
+ end
227
+ end
228
+ end
229
+ end