berns 3.2.1 → 3.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 74e861ab5e3bfb8359f063dc7637b4f9c8f30ed60722b3f6cfec7aaf432915af
4
- data.tar.gz: 17e884c022d55c44ad4ff080ec485d82688664529aee4d656b95d1d93a129e31
3
+ metadata.gz: bcded7fea247763fc7b27807c2c945943e0f08895b117ae086e4041bf1b08371
4
+ data.tar.gz: 965518819feb3eef689f19b7cc5470a973198b891e00f9d7f8ac7fd5b4620119
5
5
  SHA512:
6
- metadata.gz: 17166b6a903fba75f3cf3984710735e92206892eec6310bb2144ff5e088694089326426baf2c8e7c1246e681d4887559f01f7364635bfc29006878a908b9912f
7
- data.tar.gz: 9472135003033d1fdc6db413e44cf7ab69be6fa947b9e2a452d2bd24f316f0708055d03d6157adf292c5b9644d73dd08cad261c1b4c45588be18b8069e5366e2
6
+ metadata.gz: f023bfcde5d5b272477d2dd7e7c2d6de430c4ec544d6a10da75f7ff72b5980349d282710796a2ac5c4bede5e72df5456dd733828c7869f3f1231b26314f450e4
7
+ data.tar.gz: d606bdeed6b199fa467c4f80236d9dab40ef0db13e3105397bbe411caffa71c00274ea5902e1ce3f4764ab915cada0a9574d133905f07e28a348e2e28f9b7cfa
data/LICENSE.txt CHANGED
@@ -1,21 +1,20 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2018 Taylor Beck
3
+ Copyright © 2021 Taylor Beck and Evan Lecklider
4
4
 
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the Software), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
11
 
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
14
 
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- THE SOFTWARE.
15
+ THE SOFTWARE IS PROVIDED AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.org CHANGED
@@ -1,6 +1,7 @@
1
1
  * Berns
2
2
 
3
3
  [[https://badge.fury.io/rb/berns][https://badge.fury.io/rb/berns.svg]]
4
+ [[https://github.com/evanleck/berns/actions/workflows/main.yml][https://github.com/evanleck/berns/actions/workflows/main.yml/badge.svg]]
4
5
 
5
6
  A utility library for generating HTML strings.
6
7
 
@@ -95,10 +96,84 @@ Note that this is an extremely naive implementation of HTML sanitization that
95
96
  literally just looks for "<" and ">" characters and removes the contents between
96
97
  them. This should probably only be used on trusted strings.
97
98
 
99
+ *** =build { content }=
100
+
101
+ The =build= method uses =Berns::Builder= to let you create HTML strings using a DSL.
102
+
103
+ #+begin_src ruby
104
+ Berns.build { h1 { 'Heading' } } # => '<h1>Heading</h1>'
105
+ #+end_src
106
+
107
+ See below for more on =Berns::Builder=.
108
+
109
+ *** =Berns::Builder= HTML DSL
110
+
111
+ Added in version 3.4.0 and heavily inspired by the likes of [[https://github.com/digital-fabric/papercraft][Papercraft]], [[https://github.com/markaby/markaby][Markaby]],
112
+ and [[https://github.com/activeadmin/arbre][Arbre]], the =Berns::Builder= class lets you create HTML strings using a DSL.
113
+
114
+ #+begin_src ruby
115
+ template = Berns::Builder.new do
116
+ h1 { 'Heading' }
117
+ p(class: 'paragraph') do
118
+ text 'Bare text here.'
119
+
120
+ b { 'Bold text here' }
121
+ end
122
+ end
123
+ #+end_src
124
+
125
+ Within the block provided to =Berns::Builder.new= every standard element method,
126
+ void element method, =#element=, and =#void= are available as methods and each
127
+ time you use one of those methods the result is appended to an internal buffer.
128
+ In addition, the =#text= method can be used to append a plain text string to the
129
+ buffer and that text will be HTML escaped, so it's good for untrusted content.
130
+
131
+ Once initialized, rendering the template to a string can be done with the
132
+ =#call= method and any arguments, positional or keyword, will be passed through
133
+ as-is to the block provided to =#new=.
134
+
135
+ #+begin_src ruby
136
+ string = template.call # =>
137
+ # <h1>
138
+ # Heading
139
+ # </h1>
140
+ # <p class='paragraph'>
141
+ # Bare text here.
142
+ # <b>
143
+ # Bold text here.
144
+ # </b>
145
+ # </p>
146
+ #+end_src
147
+
148
+ In addition to initializing a new instance of =Berns::Builder=, you can
149
+ construct and render a template to a string all at once with =Berns.build=.
150
+
151
+ #+begin_src ruby
152
+ Berns.build do
153
+ h1 { 'Heading' }
154
+ p(class: 'paragraph') do
155
+ text 'Bare text here.'
156
+
157
+ b { 'Bold text here' }
158
+ end
159
+ end # =>
160
+ # <h1>
161
+ # Heading
162
+ # </h1>
163
+ # <p class='paragraph'>
164
+ # Bare text here.
165
+ # <b>
166
+ # Bold text here.
167
+ # </b>
168
+ # </p>
169
+ #+end_src
170
+
98
171
  *** Standard and void elements
99
172
 
100
173
  All standard and void HTML elements are defined as methods on Berns, so you can
101
- create e.g. a link with =Berns.a=. Below is the full list of standard elements.
174
+ create e.g. a link with =Berns.a=. Below is the full list of standard elements
175
+ which are also available in the constant =Berns::STANDARD= as an array of
176
+ symbols.
102
177
 
103
178
  #+begin_example
104
179
  a abbr address article aside audio b bdi bdo blockquote body button
@@ -111,10 +186,33 @@ table tbody td template textarea tfoot th thead time title tr u ul var
111
186
  video
112
187
  #+end_example
113
188
 
114
-
115
189
  Below is the full list of void elements that are defined as singleton methods on
116
- Berns.
190
+ Berns which are also available in the constant =Berns::VOID= as an array of
191
+ symbols.
117
192
 
118
193
  #+begin_example
119
194
  area base br col embed hr img input link menuitem meta param source track wbr
120
195
  #+end_example
196
+
197
+ ** Performance
198
+
199
+ Berns 3 is about three times faster than the pure Ruby implementation used in
200
+ version 2. See the file [[file:benchmarks/performance.rb][benchmarks/performance.rb]] for the benchmark code.
201
+
202
+ #+begin_example
203
+ Warming up --------------------------------------
204
+ element 27.373k i/100ms
205
+ berns 94.118k i/100ms
206
+ Calculating -------------------------------------
207
+ element 314.078k (± 4.5%) i/s - 1.588M in 5.065539s
208
+ berns 935.528k (± 6.1%) i/s - 4.706M in 5.049718s
209
+
210
+ Comparison:
211
+ berns: 935527.9 i/s
212
+ element: 314078.4 i/s - 2.98x (± 0.00) slower
213
+ #+end_example
214
+
215
+ ** Trivia
216
+
217
+ The name "Berns" is taken from the name of [[https://en.wikipedia.org/wiki/HTML#Development][the inventor of HTML]],
218
+ [[https://en.wikipedia.org/wiki/Tim_Berners-Lee][Sir Tim Berners-Lee]].
data/ext/berns/berns.c CHANGED
@@ -64,7 +64,7 @@ static const size_t sllen = 1;
64
64
  static VALUE external_##element_name##_element(int argc, VALUE *argv, RB_UNUSED_VAR(VALUE self)) { \
65
65
  rb_check_arity(argc, 0, 1); \
66
66
  \
67
- CONTENT_FROM_BLOCK; \
67
+ CONTENT_FROM_BLOCK \
68
68
  const char *tag = #element_name; \
69
69
  char *string = element(tag, strlen(tag), RSTRING_PTR(content), RSTRING_LEN(content), argv[0]); \
70
70
  VALUE rstring = rb_utf8_str_new_cstr(string); \
@@ -110,17 +110,24 @@ static VALUE external_sanitize(RB_UNUSED_VAR(VALUE self), VALUE string) {
110
110
  char *str = RSTRING_PTR(string);
111
111
 
112
112
  char dest[slen + 1];
113
- int index = 0;
114
- int open = 0;
115
- int opened = 0;
113
+
114
+ unsigned int index = 0;
115
+ unsigned int open = 0;
116
+ unsigned int modified = 0;
117
+ unsigned int entity = 0;
116
118
 
117
119
  for (unsigned int i = 0; i < slen; i++) {
118
120
  if (str[i] == '<') {
119
121
  open = 1;
120
- opened = 1;
122
+ modified = 1;
121
123
  } else if (str[i] == '>') {
122
124
  open = 0;
123
- } else if (!open) {
125
+ } else if (str[i] == '&') {
126
+ entity = 1;
127
+ modified = 1;
128
+ } else if (str[i] == ';') {
129
+ entity = 0;
130
+ } else if (!open && !entity) {
124
131
  dest[index++] = str[i];
125
132
  }
126
133
  }
@@ -128,10 +135,10 @@ static VALUE external_sanitize(RB_UNUSED_VAR(VALUE self), VALUE string) {
128
135
  dest[index] = '\0';
129
136
 
130
137
  /*
131
- * If a tag was never opened, return the original string, otherwise create a new
132
- * string from our destination buffer.
138
+ * If the string was never modified, return the original string, otherwise
139
+ * create a new string from our destination buffer.
133
140
  */
134
- if (opened) {
141
+ if (modified) {
135
142
  return rb_utf8_str_new_cstr(dest);
136
143
  } else {
137
144
  return string;
@@ -221,7 +228,7 @@ static char * hash_value_to_attribute(const char *attr, const size_t attrlen, VA
221
228
 
222
229
  Check_Type(value, T_HASH);
223
230
 
224
- if (rb_hash_size(value) == 1) {
231
+ if (RHASH_SIZE(value) == 0) {
225
232
  return strdup("");
226
233
  }
227
234
 
@@ -432,7 +439,7 @@ static VALUE external_to_attribute(RB_UNUSED_VAR(VALUE self), VALUE attr, VALUE
432
439
  static VALUE external_to_attributes(RB_UNUSED_VAR(VALUE self), VALUE attributes) {
433
440
  Check_Type(attributes, T_HASH);
434
441
 
435
- if (rb_hash_size(attributes) == 1) {
442
+ if (RHASH_SIZE(attributes) == 0) {
436
443
  return rb_utf8_str_new_cstr("");
437
444
  }
438
445
 
@@ -446,37 +453,32 @@ static VALUE external_to_attributes(RB_UNUSED_VAR(VALUE self), VALUE attributes)
446
453
  }
447
454
 
448
455
  static char * void_element(const char *tag, size_t tlen, VALUE attributes) {
449
- /* T_IMEMO is what we get if an optional argument was not passed. */
450
- if (TYPE(attributes) == T_IMEMO) {
451
- size_t total = tag_olen + tlen + tag_clen + 1;
452
- char *string = malloc(total);
453
- char *ptr;
454
- char *end = string + total;
456
+ const char *empty = "";
457
+ char *attrs = hash_value_to_attribute(empty, 0, attributes);
458
+ size_t alen = strlen(attrs);
455
459
 
456
- ptr = stecpy(string, tag_open, end);
457
- ptr = stecpy(ptr, tag, end);
458
- ptr = stecpy(ptr, tag_close, end);
460
+ size_t total = tag_olen + tlen + tag_clen + 1;
459
461
 
460
- return string;
461
- } else {
462
- const char *empty = "";
463
- char *attrs = hash_value_to_attribute(empty, 0, attributes);
462
+ /* If we have some attributes, add a space and the attributes' length. */
463
+ if (alen > 0) {
464
+ total += splen + alen;
465
+ }
466
+
467
+ char *dest = malloc(total);
468
+ char *ptr = NULL;
469
+ char *end = dest + total;
464
470
 
465
- size_t total = tag_olen + tlen + splen + strlen(attrs) + tag_clen + 1;
466
- char *string = malloc(total);
467
- char *ptr;
468
- char *end = string + total;
471
+ ptr = stecpy(dest, tag_open, end);
472
+ ptr = stecpy(ptr, tag, end);
469
473
 
470
- ptr = stecpy(string, tag_open, end);
471
- ptr = stecpy(ptr, tag, end);
474
+ if (alen > 0) {
472
475
  ptr = stecpy(ptr, space, end);
473
476
  ptr = stecpy(ptr, attrs, end);
474
- ptr = stecpy(ptr, tag_close, end);
477
+ }
475
478
 
476
- free(attrs);
479
+ ptr = stecpy(ptr, tag_close, end);
477
480
 
478
- return string;
479
- }
481
+ return dest;
480
482
  }
481
483
 
482
484
  /*
@@ -571,7 +573,7 @@ static VALUE external_element(int argc, VALUE *arguments, RB_UNUSED_VAR(VALUE se
571
573
 
572
574
  StringValue(tag);
573
575
 
574
- CONTENT_FROM_BLOCK;
576
+ CONTENT_FROM_BLOCK
575
577
 
576
578
  char *string = element(RSTRING_PTR(tag), RSTRING_LEN(tag), RSTRING_PTR(content), RSTRING_LEN(content), attributes);
577
579
  VALUE rstring = rb_utf8_str_new_cstr(string);
Binary file
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+ require 'berns'
3
+
4
+ module Berns
5
+ # An HTML builder DSL using Berns' HTML methods.
6
+ class Builder
7
+ def initialize(&block)
8
+ @block = block
9
+ @buffer = +''
10
+ end
11
+
12
+ # @return [String]
13
+ def call(*args, **opts)
14
+ instance_exec(*args, **opts, &@block)
15
+ to_s
16
+ end
17
+
18
+ # @return [String]
19
+ def to_s
20
+ @buffer.freeze
21
+ end
22
+
23
+ # Append text to the buffer.
24
+ #
25
+ # @param string [String]
26
+ # @return [String]
27
+ def text(string)
28
+ @buffer << Berns.escape_html(string.to_s)
29
+ end
30
+
31
+ # Append an arbitrary standard element to the buffer.
32
+ #
33
+ # @return [String]
34
+ def element(*args, **opts, &block)
35
+ content = Builder.new.instance_exec(*args, **opts, &block) if block
36
+ @buffer << Berns.element(*args, **opts) { content }
37
+ end
38
+
39
+ # Append an arbitrary void element to the buffer.
40
+ #
41
+ # @return [String]
42
+ def void(*args, **opts)
43
+ @buffer << Berns.void(*args, **opts)
44
+ end
45
+
46
+ Berns::STANDARD.each do |meth|
47
+ define_method(meth) do |*args, **opts, &block|
48
+ content = Builder.new.instance_exec(*args, **opts, &block) if block
49
+ @buffer << Berns.send(meth, *args, **opts) { content }
50
+ end
51
+ end
52
+
53
+ Berns::VOID.each do |meth|
54
+ define_method(meth) do |*args, **opts|
55
+ @buffer << Berns.send(meth, *args, **opts)
56
+ end
57
+ end
58
+ end
59
+ end
data/lib/berns/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Berns
3
- VERSION = '3.2.1'
3
+ VERSION = '3.5.0'
4
4
  end
data/lib/berns.rb CHANGED
@@ -1,3 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
  require 'berns/berns'
3
3
  require 'berns/version'
4
+
5
+ module Berns # :nodoc:
6
+ autoload :Builder, 'berns/builder'
7
+
8
+ STANDARD = %i[
9
+ a abbr address article aside audio b bdi bdo blockquote body button canvas
10
+ caption cite code colgroup datalist dd del details dfn dialog div dl dt em
11
+ fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 head header html i
12
+ iframe ins kbd label legend li main map mark menu meter nav noscript object
13
+ ol optgroup option output p picture pre progress q rp rt ruby s samp script
14
+ section select small span strong style sub summary table tbody td template
15
+ textarea tfoot th thead time title tr u ul var video
16
+ ].freeze
17
+
18
+ VOID = %i[
19
+ area base br col embed hr img input link menuitem meta param source track wbr
20
+ ].freeze
21
+
22
+ # @return [String]
23
+ def self.build(*args, **opts, &block)
24
+ Builder.new(&block).call(*args, **opts)
25
+ end
26
+ end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: berns
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.1
4
+ version: 3.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Taylor Beck
8
8
  - Evan Lecklider
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-06-15 00:00:00.000000000 Z
12
+ date: 2022-02-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: benchmark-ips
@@ -167,15 +167,17 @@ files:
167
167
  - ext/berns/hescape.c
168
168
  - ext/berns/hescape.h
169
169
  - lib/berns.rb
170
- - lib/berns/berns.so
170
+ - lib/berns/berns.bundle
171
+ - lib/berns/builder.rb
171
172
  - lib/berns/version.rb
172
173
  homepage: https://github.com/evanleck/berns
173
174
  licenses:
174
175
  - MIT
175
176
  metadata:
176
177
  bug_tracker_uri: https://github.com/evanleck/berns/issues
178
+ rubygems_mfa_required: 'true'
177
179
  source_code_uri: https://github.com/evanleck/berns
178
- post_install_message:
180
+ post_install_message:
179
181
  rdoc_options: []
180
182
  require_paths:
181
183
  - lib
@@ -188,10 +190,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
188
190
  requirements:
189
191
  - - ">="
190
192
  - !ruby/object:Gem::Version
191
- version: '0'
193
+ version: '2.0'
192
194
  requirements: []
193
- rubygems_version: 3.2.15
194
- signing_key:
195
+ rubygems_version: 3.3.3
196
+ signing_key:
195
197
  specification_version: 4
196
198
  summary: A utility library for generating HTML strings.
197
199
  test_files: []
data/lib/berns/berns.so DELETED
Binary file