philiprehberger-html_builder 0.5.0 → 0.7.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 +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +51 -0
- data/lib/philiprehberger/html_builder/builder.rb +67 -0
- data/lib/philiprehberger/html_builder/version.rb +1 -1
- data/lib/philiprehberger/html_builder.rb +21 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9a1ed5d63b82c40002240f74e0f808a5e62926efd8b55a0c52b482d804a23bab
|
|
4
|
+
data.tar.gz: 814fd3cf20cf2cf358f25e38d14dcf9f02e84e32827a584c0c94a947d4404f48
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0e0214af812ff0754349ee79c494f544f34b6b5a16852387d3c51c30b1580c7e88f75d0392f4761dacf754c91722cc83015f95131f065c60eaa073df64453217
|
|
7
|
+
data.tar.gz: eb761b219b3a40319752ba84628ed2a7a88f0ea423581619a641a98932a3c39f8deabb83edb077be8c0319436d60f7ccac2f53f5acaa6e63cb5d469e4d04b271
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.7.0] - 2026-04-26
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `merge_attrs(*hashes)` helper for merging attribute hashes; concatenates `:class` (space-separated) and `:style` (semicolon-separated) values rather than overwriting
|
|
14
|
+
- `aria(**pairs)` helper for building ARIA attribute hashes from snake_case keyword pairs (converted to `aria-kebab-case` string keys; nil values omitted)
|
|
15
|
+
|
|
16
|
+
## [0.6.0] - 2026-04-16
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- HTML5 `<!DOCTYPE html>` helper via `Builder#doctype` and `HtmlBuilder.document`
|
|
20
|
+
|
|
10
21
|
## [0.5.0] - 2026-04-15
|
|
11
22
|
|
|
12
23
|
### Added
|
data/README.md
CHANGED
|
@@ -163,6 +163,28 @@ end
|
|
|
163
163
|
# => '<ul><li><strong>Alice</strong></li><li><strong>Bob</strong></li></ul>'
|
|
164
164
|
```
|
|
165
165
|
|
|
166
|
+
### Attribute Helpers
|
|
167
|
+
|
|
168
|
+
Merge multiple attribute hashes — concatenating `:class` (single space) and `:style` (`'; '`) values rather than overwriting — and build ARIA attribute hashes from snake_case keyword pairs:
|
|
169
|
+
|
|
170
|
+
```ruby
|
|
171
|
+
Philiprehberger::HtmlBuilder.build do
|
|
172
|
+
base = { class: 'btn', style: 'color: red' }
|
|
173
|
+
variant = { class: 'btn-primary', style: 'font-weight: bold' }
|
|
174
|
+
attrs = merge_attrs(base, variant)
|
|
175
|
+
|
|
176
|
+
button('Save', **attrs)
|
|
177
|
+
end
|
|
178
|
+
# => '<button class="btn btn-primary" style="color: red; font-weight: bold">Save</button>'
|
|
179
|
+
|
|
180
|
+
Philiprehberger::HtmlBuilder.build do
|
|
181
|
+
aria(label: 'Save', expanded: false, describedby: nil)
|
|
182
|
+
end
|
|
183
|
+
# => { 'aria-label' => 'Save', 'aria-expanded' => 'false' }
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`merge_attrs` joins `:class` values with a single space and `:style` values with `'; '`. Other keys follow last-write-wins, and input hashes are not mutated. `aria` converts snake_case keys to `aria-kebab-case` string keys, stringifies values, and omits keys whose value is `nil`.
|
|
187
|
+
|
|
166
188
|
### CSS Class Helpers
|
|
167
189
|
|
|
168
190
|
Build conditional CSS class strings from mixed arguments. Strings are included as-is, hash keys are included when their value is truthy:
|
|
@@ -227,6 +249,31 @@ end
|
|
|
227
249
|
|
|
228
250
|
Components without parameters use a simple block with no arguments. Components with parameters receive a hash of locals.
|
|
229
251
|
|
|
252
|
+
### HTML5 Documents
|
|
253
|
+
|
|
254
|
+
Emit a standards-compliant `<!DOCTYPE html>` declaration via the `doctype` DSL helper, or use `HtmlBuilder.document` for a full HTML5 document shortcut that prefixes the doctype automatically. The block decides the root element, so no hardcoded `<html>` wrapper is added:
|
|
255
|
+
|
|
256
|
+
```ruby
|
|
257
|
+
Philiprehberger::HtmlBuilder.build do
|
|
258
|
+
doctype
|
|
259
|
+
html { head { title 'Home' } }
|
|
260
|
+
end
|
|
261
|
+
# => '<!DOCTYPE html><html><head><title>Home</title></head></html>'
|
|
262
|
+
|
|
263
|
+
Philiprehberger::HtmlBuilder.document do
|
|
264
|
+
html do
|
|
265
|
+
head { title 'Home' }
|
|
266
|
+
body { h1 'Welcome' }
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
# => "<!DOCTYPE html>\n<html><head><title>Home</title></head><body><h1>Welcome</h1></body></html>"
|
|
270
|
+
|
|
271
|
+
Philiprehberger::HtmlBuilder.document(pretty: true) do
|
|
272
|
+
html { head { title 'Home' } }
|
|
273
|
+
end
|
|
274
|
+
# pretty-printed with the doctype on its own line
|
|
275
|
+
```
|
|
276
|
+
|
|
230
277
|
### Output Modes
|
|
231
278
|
|
|
232
279
|
Choose between minified and pretty-printed output:
|
|
@@ -279,12 +326,14 @@ Philiprehberger::HtmlBuilder.merge(header, body, footer)
|
|
|
279
326
|
| `HtmlBuilder.build { ... }` | Build minified HTML using the tag DSL, returns a string |
|
|
280
327
|
| `HtmlBuilder.build_pretty { ... }` | Build pretty-printed HTML with indentation |
|
|
281
328
|
| `HtmlBuilder.build_minified { ... }` | Alias for `build`, explicitly produces minified output |
|
|
329
|
+
| `HtmlBuilder.document(pretty:, indent_size:) { ... }` | Build an HTML5 document; prefixes `<!DOCTYPE html>` before the block output |
|
|
282
330
|
| `HtmlBuilder.merge(*fragments)` | Merge multiple HTML fragment strings into one |
|
|
283
331
|
| `HtmlBuilder.escape(value)` | Escape HTML special characters in a string using the DSL's escaper |
|
|
284
332
|
| `Builder#to_html` | Render builder contents to a minified HTML string |
|
|
285
333
|
| `Builder#to_pretty_html` | Render builder contents to a pretty-printed HTML string |
|
|
286
334
|
| `Builder#text(content)` | Add escaped text content to the current element |
|
|
287
335
|
| `Builder#raw(html)` | Add raw HTML without escaping |
|
|
336
|
+
| `Builder#doctype` | Emit an HTML5 `<!DOCTYPE html>` declaration |
|
|
288
337
|
| `Builder#render_if(condition) { ... }` | Conditionally render a block if condition is truthy |
|
|
289
338
|
| `Builder#render_unless(condition) { ... }` | Conditionally render a block if condition is falsy |
|
|
290
339
|
| `Builder#define_component(name) { ... }` | Define a reusable named block |
|
|
@@ -297,6 +346,8 @@ Philiprehberger::HtmlBuilder.merge(header, body, footer)
|
|
|
297
346
|
| `Builder#submit(text, **attrs)` | Generate a submit button (default text "Submit") |
|
|
298
347
|
| `Builder#list(items, ordered:, **attrs, &block)` | Build a `<ul>` or `<ol>` from an array of items |
|
|
299
348
|
| `Builder#class_names(*args)` | Build a conditional CSS class string from strings and hashes |
|
|
349
|
+
| `Builder#merge_attrs(*hashes)` | Merge attribute hashes, concatenating `:class` (space) and `:style` (`'; '`) values |
|
|
350
|
+
| `Builder#aria(**pairs)` | Build an ARIA attribute hash from snake_case keys (rendered as `aria-kebab-case`); omits nil values |
|
|
300
351
|
| `Builder#cache(key) { ... }` | Cache rendered block output by key; return cached HTML on repeat calls |
|
|
301
352
|
| `Escape.html(value)` | Escape HTML special characters in a string |
|
|
302
353
|
|
|
@@ -80,6 +80,16 @@ module Philiprehberger
|
|
|
80
80
|
current_children << node
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
+
# Emit an HTML5 doctype declaration (`<!DOCTYPE html>`)
|
|
84
|
+
#
|
|
85
|
+
# Has no children and no attributes. In pretty mode the declaration is
|
|
86
|
+
# rendered on its own line at the current indentation.
|
|
87
|
+
#
|
|
88
|
+
# @return [void]
|
|
89
|
+
def doctype
|
|
90
|
+
current_children << DoctypeNode.new
|
|
91
|
+
end
|
|
92
|
+
|
|
83
93
|
# Conditionally render a block if the condition is truthy
|
|
84
94
|
#
|
|
85
95
|
# @param condition [Object] the condition to evaluate
|
|
@@ -255,6 +265,49 @@ module Philiprehberger
|
|
|
255
265
|
result.join(' ')
|
|
256
266
|
end
|
|
257
267
|
|
|
268
|
+
# Merge multiple attribute hashes into one
|
|
269
|
+
#
|
|
270
|
+
# `:class` values are concatenated with a single space, and `:style` values
|
|
271
|
+
# are concatenated with a semicolon and space. All other keys follow last-write-wins
|
|
272
|
+
# semantics. Input hashes are not mutated.
|
|
273
|
+
#
|
|
274
|
+
# @param hashes [Array<Hash>] one or more attribute hashes to merge
|
|
275
|
+
# @return [Hash] the merged attribute hash
|
|
276
|
+
def merge_attrs(*hashes)
|
|
277
|
+
result = {}
|
|
278
|
+
hashes.each do |h|
|
|
279
|
+
next if h.nil?
|
|
280
|
+
|
|
281
|
+
h.each do |key, value|
|
|
282
|
+
if key == :class && result.key?(:class)
|
|
283
|
+
result[:class] = "#{result[:class]} #{value}"
|
|
284
|
+
elsif key == :style && result.key?(:style)
|
|
285
|
+
result[:style] = "#{result[:style]}; #{value}"
|
|
286
|
+
else
|
|
287
|
+
result[key] = value
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
result
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Build an ARIA attribute hash from keyword pairs
|
|
295
|
+
#
|
|
296
|
+
# snake_case keys are converted to `aria-kebab-case` string keys. Values are
|
|
297
|
+
# converted to strings. Keys whose value is `nil` are omitted from the result.
|
|
298
|
+
#
|
|
299
|
+
# @param pairs [Hash] keyword pairs to convert into ARIA attributes
|
|
300
|
+
# @return [Hash<String, String>] hash with `"aria-*"` string keys and string values
|
|
301
|
+
def aria(**pairs)
|
|
302
|
+
result = {}
|
|
303
|
+
pairs.each do |key, value|
|
|
304
|
+
next if value.nil?
|
|
305
|
+
|
|
306
|
+
result["aria-#{key.to_s.tr('_', '-')}"] = value.to_s
|
|
307
|
+
end
|
|
308
|
+
result
|
|
309
|
+
end
|
|
310
|
+
|
|
258
311
|
# Cache a rendered block result by key
|
|
259
312
|
#
|
|
260
313
|
# On the first call with a given key, the block is executed, its rendered
|
|
@@ -306,5 +359,19 @@ module Philiprehberger
|
|
|
306
359
|
end
|
|
307
360
|
end
|
|
308
361
|
end
|
|
362
|
+
|
|
363
|
+
# A node that renders the HTML5 doctype declaration
|
|
364
|
+
class DoctypeNode
|
|
365
|
+
DECLARATION = '<!DOCTYPE html>'
|
|
366
|
+
|
|
367
|
+
# @return [String] the doctype declaration
|
|
368
|
+
def to_html(indent: nil, indent_size: 2)
|
|
369
|
+
if indent
|
|
370
|
+
"#{' ' * (indent * indent_size)}#{DECLARATION}"
|
|
371
|
+
else
|
|
372
|
+
DECLARATION
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
end
|
|
309
376
|
end
|
|
310
377
|
end
|
|
@@ -45,6 +45,27 @@ module Philiprehberger
|
|
|
45
45
|
build(&)
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
+
# Build a full HTML5 document: emits `<!DOCTYPE html>` followed by the
|
|
49
|
+
# rendered block, separated by a newline.
|
|
50
|
+
#
|
|
51
|
+
# The block is evaluated at the root level exactly like `.build` / `.build_pretty`,
|
|
52
|
+
# so the caller decides whether to add an `<html>` wrapper. When `pretty: true`,
|
|
53
|
+
# output is pretty-printed with the given indent size.
|
|
54
|
+
#
|
|
55
|
+
# @param pretty [Boolean] whether to pretty-print the block output (default false)
|
|
56
|
+
# @param indent_size [Integer] number of spaces per indent level when pretty (default 2)
|
|
57
|
+
# @yield [Builder] the builder instance for DSL evaluation
|
|
58
|
+
# @return [String] the rendered HTML document string
|
|
59
|
+
# @raise [Error] if no block is given
|
|
60
|
+
def self.document(pretty: false, indent_size: 2, &block)
|
|
61
|
+
raise Error, 'a block is required' unless block
|
|
62
|
+
|
|
63
|
+
builder = Builder.new
|
|
64
|
+
builder.instance_eval(&block)
|
|
65
|
+
body = pretty ? builder.to_pretty_html(indent_size: indent_size) : builder.to_html
|
|
66
|
+
"#{DoctypeNode::DECLARATION}\n#{body}"
|
|
67
|
+
end
|
|
68
|
+
|
|
48
69
|
# Merge multiple HTML fragment strings into one
|
|
49
70
|
#
|
|
50
71
|
# @param fragments [Array<String>] HTML fragments to merge
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: philiprehberger-html_builder
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Philip Rehberger
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-04-
|
|
11
|
+
date: 2026-04-27 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Build HTML programmatically using a clean tag DSL with nested blocks,
|
|
14
14
|
automatic content escaping, void element support, and attribute hashes.
|