xrb 0.1 → 0.2.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 (54) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/bake/xrb/entities.rb +60 -0
  4. data/bake/xrb/parsers.rb +69 -0
  5. data/ext/extconf.rb +21 -0
  6. data/ext/xrb/escape.c +152 -0
  7. data/ext/xrb/escape.h +15 -0
  8. data/ext/xrb/markup.c +1949 -0
  9. data/ext/xrb/markup.h +6 -0
  10. data/ext/xrb/markup.rl +226 -0
  11. data/ext/xrb/query.c +619 -0
  12. data/ext/xrb/query.h +6 -0
  13. data/ext/xrb/query.rl +82 -0
  14. data/ext/xrb/tag.c +204 -0
  15. data/ext/xrb/tag.h +21 -0
  16. data/ext/xrb/template.c +1114 -0
  17. data/ext/xrb/template.h +6 -0
  18. data/ext/xrb/template.rl +77 -0
  19. data/ext/xrb/xrb.c +72 -0
  20. data/ext/xrb/xrb.h +132 -0
  21. data/lib/xrb/buffer.rb +103 -0
  22. data/lib/xrb/builder.rb +222 -0
  23. data/lib/xrb/entities.rb +2137 -0
  24. data/lib/xrb/entities.xrb +30 -0
  25. data/lib/xrb/error.rb +81 -0
  26. data/lib/xrb/fallback/markup.rb +1658 -0
  27. data/lib/xrb/fallback/markup.rl +228 -0
  28. data/lib/xrb/fallback/query.rb +548 -0
  29. data/lib/xrb/fallback/query.rl +88 -0
  30. data/lib/xrb/fallback/template.rb +829 -0
  31. data/lib/xrb/fallback/template.rl +80 -0
  32. data/lib/xrb/markup.rb +56 -0
  33. data/lib/xrb/native.rb +15 -0
  34. data/lib/xrb/parse_delegate.rb +19 -0
  35. data/lib/xrb/parsers.rb +17 -0
  36. data/lib/xrb/query.rb +80 -0
  37. data/lib/xrb/reference.rb +108 -0
  38. data/lib/xrb/strings.rb +47 -0
  39. data/lib/xrb/tag.rb +115 -0
  40. data/lib/xrb/template.rb +164 -0
  41. data/lib/xrb/uri.rb +100 -0
  42. data/lib/xrb/version.rb +8 -0
  43. data/lib/xrb.rb +11 -0
  44. data/license.md +23 -0
  45. data/readme.md +29 -0
  46. data.tar.gz.sig +0 -0
  47. metadata +109 -58
  48. metadata.gz.sig +0 -0
  49. data/README +0 -60
  50. data/app/helpers/ui_helper.rb +0 -80
  51. data/app/models/xrb/element.rb +0 -9
  52. data/lib/xrb/engine.rb +0 -4
  53. data/rails/init.rb +0 -1
  54. 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/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,222 @@
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
+ end
41
+
42
+ # A helper to generate fragments of markup.
43
+ def self.fragment(output = nil, &block)
44
+ if output.is_a?(Binding)
45
+ output = Template.buffer(output)
46
+ end
47
+
48
+ if output.nil?
49
+ return Fragment.new(block)
50
+ end
51
+
52
+ if output.is_a?(Builder)
53
+ block.call(output)
54
+ else
55
+ block.call(Builder.new(output))
56
+ end
57
+
58
+ return nil
59
+ end
60
+
61
+ def self.tag(name, content, **attributes)
62
+ self.fragment do |builder|
63
+ builder.inline(name, attributes) do
64
+ builder.text(content)
65
+ end
66
+ end
67
+ end
68
+
69
+ def initialize(output = nil, indent: true, encoding: Encoding::UTF_8)
70
+ # This field gets togged in #inline so we keep track of it separately from @indentation.
71
+ @indent = indent
72
+
73
+ # We don't need to use MarkupString here as Builder itself is considered markup and should be inserted directly into the output stream.
74
+ @output = output || MarkupString.new.force_encoding(encoding)
75
+
76
+ @level = [0]
77
+ @children = [0]
78
+ end
79
+
80
+ attr :output
81
+
82
+ def encoding
83
+ @output.encoding
84
+ end
85
+
86
+ # Required for output to buffer.
87
+ def to_str
88
+ @output
89
+ end
90
+
91
+ alias to_s to_str
92
+
93
+ def == other
94
+ @output == String(other)
95
+ end
96
+
97
+ def indentation
98
+ if @indent
99
+ INDENT * (@level.size - 1)
100
+ else
101
+ ''
102
+ end
103
+ end
104
+
105
+ def doctype(attributes = 'html')
106
+ @output << "<!DOCTYPE #{attributes}>\n"
107
+ end
108
+
109
+ # Begin a block tag.
110
+ def tag(name, attributes = {}, &block)
111
+ full_tag(name, attributes, @indent, @indent, &block)
112
+ end
113
+
114
+ # Begin an inline tag.
115
+ def inline_tag(name, attributes = {}, &block)
116
+ original_indent = @indent
117
+
118
+ full_tag(name, attributes, @indent, false) do
119
+ @indent = false
120
+ yield if block_given?
121
+ end
122
+ ensure
123
+ @indent = original_indent
124
+ end
125
+
126
+ alias inline inline_tag
127
+
128
+ def inline!
129
+ original_indent = @indent
130
+ @indent = false
131
+
132
+ yield
133
+ ensure
134
+ @indent = original_indent
135
+ end
136
+
137
+ def text(content)
138
+ return unless content
139
+
140
+ if @indent
141
+ @output << "\n" if @level.last > 0
142
+ @output << indentation
143
+ end
144
+
145
+ Markup.append(@output, content)
146
+
147
+ if @indent
148
+ @output << "\n"
149
+ end
150
+ end
151
+
152
+ def raw(content)
153
+ @output << content
154
+ end
155
+
156
+ def <<(content)
157
+ return unless content
158
+
159
+ if content.is_a?(Fragment)
160
+ inline! do
161
+ content.call(self)
162
+ end
163
+ else
164
+ Markup.append(@output, content)
165
+ end
166
+ end
167
+
168
+ # Append pre-existing markup:
169
+ def append(value)
170
+ return unless value
171
+
172
+ # The parent has one more child:
173
+ @level[-1] += 1
174
+
175
+ if @indent
176
+ value.each_line.with_index do |line, i|
177
+ @output << indentation << line
178
+ end
179
+ else
180
+ @output << value
181
+ end
182
+ end
183
+
184
+ protected
185
+
186
+ # A normal block level/container tag.
187
+ def full_tag(name, attributes, indent_outer, indent_inner, &block)
188
+ if block_given?
189
+ if indent_outer
190
+ @output << "\n" if @level.last > 0
191
+ @output << indentation
192
+ end
193
+
194
+ tag = XRB::Tag.opened(name.to_s, attributes)
195
+ tag.write_opening_tag(@output)
196
+ @output << "\n" if indent_inner
197
+
198
+ # The parent has one more child:
199
+ @level[-1] += 1
200
+
201
+ @level << 0
202
+
203
+ yield
204
+
205
+ children = @level.pop
206
+
207
+ if indent_inner
208
+ @output << "\n" if children > 0
209
+ @output << indentation
210
+ end
211
+
212
+ tag.write_closing_tag(@output)
213
+ else
214
+ # The parent has one more child:
215
+ @level[-1] += 1
216
+
217
+ @output << indentation
218
+ XRB::Tag.append_tag(@output, name.to_s, attributes, nil)
219
+ end
220
+ end
221
+ end
222
+ end