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.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data/bake/xrb/entities.rb +60 -0
- data/bake/xrb/parsers.rb +69 -0
- data/ext/extconf.rb +21 -0
- data/ext/xrb/escape.c +152 -0
- data/ext/xrb/escape.h +15 -0
- data/ext/xrb/markup.c +1949 -0
- data/ext/xrb/markup.h +6 -0
- data/ext/xrb/markup.rl +226 -0
- data/ext/xrb/query.c +619 -0
- data/ext/xrb/query.h +6 -0
- data/ext/xrb/query.rl +82 -0
- data/ext/xrb/tag.c +204 -0
- data/ext/xrb/tag.h +21 -0
- data/ext/xrb/template.c +1114 -0
- data/ext/xrb/template.h +6 -0
- data/ext/xrb/template.rl +77 -0
- data/ext/xrb/xrb.c +72 -0
- data/ext/xrb/xrb.h +132 -0
- data/lib/xrb/buffer.rb +103 -0
- data/lib/xrb/builder.rb +222 -0
- data/lib/xrb/entities.rb +2137 -0
- data/lib/xrb/entities.xrb +30 -0
- data/lib/xrb/error.rb +81 -0
- data/lib/xrb/fallback/markup.rb +1658 -0
- data/lib/xrb/fallback/markup.rl +228 -0
- data/lib/xrb/fallback/query.rb +548 -0
- data/lib/xrb/fallback/query.rl +88 -0
- data/lib/xrb/fallback/template.rb +829 -0
- data/lib/xrb/fallback/template.rl +80 -0
- data/lib/xrb/markup.rb +56 -0
- data/lib/xrb/native.rb +15 -0
- data/lib/xrb/parse_delegate.rb +19 -0
- data/lib/xrb/parsers.rb +17 -0
- data/lib/xrb/query.rb +80 -0
- data/lib/xrb/reference.rb +108 -0
- data/lib/xrb/strings.rb +47 -0
- data/lib/xrb/tag.rb +115 -0
- data/lib/xrb/template.rb +164 -0
- data/lib/xrb/uri.rb +100 -0
- data/lib/xrb/version.rb +8 -0
- data/lib/xrb.rb +11 -0
- data/license.md +23 -0
- data/readme.md +29 -0
- data.tar.gz.sig +0 -0
- metadata +109 -58
- metadata.gz.sig +0 -0
- data/README +0 -60
- data/app/helpers/ui_helper.rb +0 -80
- data/app/models/xrb/element.rb +0 -9
- data/lib/xrb/engine.rb +0 -4
- data/rails/init.rb +0 -1
- data/xrb.gemspec +0 -12
    
        data/ext/xrb/template.h
    ADDED
    
    
    
        data/ext/xrb/template.rl
    ADDED
    
    | @@ -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
         | 
    
        data/lib/xrb/builder.rb
    ADDED
    
    | @@ -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
         |