yart 0.0.1 → 0.1.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/.gitignore +1 -0
- data/Gemfile +6 -3
- data/Gemfile.lock +3 -0
- data/README.md +92 -18
- data/bin/console +45 -7
- data/lib/yart.rb +2 -2
- data/lib/yart/parser.rb +125 -0
- data/lib/yart/version.rb +1 -1
- data/load.rb +7 -0
- data/yart.gemspec +6 -2
- metadata +11 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6dd8a4e37aedd9a60366cf53c6d3a270d098fb4739d2a4044b70c9515fe3f323
|
4
|
+
data.tar.gz: ad1b5c369574d17de2025fb1945bbb6a611d2ea88f239dfd08877af78a3e87d1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b5eb0adbd6e5d46267db1c655248122c7e8028700ba315f16200a995d77ba4a815213a6a7b1374def5211fff61c82c3d930940cac5c907e94d4fa169cf4ed2b0
|
7
|
+
data.tar.gz: accd0937be8d188c1d7f5b46bd36739be711901d5eacdb667e7e891da4bc70230e00a313d314d3d46f64b5378f4670c5bef782aa2f3681c4601aedff606ab021
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
@@ -4,6 +4,9 @@ source "https://rubygems.org"
|
|
4
4
|
|
5
5
|
ruby "~> 2.7"
|
6
6
|
|
7
|
-
|
8
|
-
gem "
|
9
|
-
gem "
|
7
|
+
group :development do
|
8
|
+
gem "minitest", "~> 5.0"
|
9
|
+
gem "rake", "~> 13.0"
|
10
|
+
gem "rubocop", "~> 1.7"
|
11
|
+
gem "byebug", "~> 11.1"
|
12
|
+
end
|
data/Gemfile.lock
CHANGED
@@ -2,6 +2,7 @@ GEM
|
|
2
2
|
remote: https://rubygems.org/
|
3
3
|
specs:
|
4
4
|
ast (2.4.2)
|
5
|
+
byebug (11.1.3)
|
5
6
|
minitest (5.14.4)
|
6
7
|
parallel (1.20.1)
|
7
8
|
parser (3.0.2.0)
|
@@ -25,9 +26,11 @@ GEM
|
|
25
26
|
unicode-display_width (2.0.0)
|
26
27
|
|
27
28
|
PLATFORMS
|
29
|
+
ruby
|
28
30
|
x86_64-linux
|
29
31
|
|
30
32
|
DEPENDENCIES
|
33
|
+
byebug (~> 11.1)
|
31
34
|
minitest (~> 5.0)
|
32
35
|
rake (~> 13.0)
|
33
36
|
rubocop (~> 1.7)
|
data/README.md
CHANGED
@@ -1,38 +1,44 @@
|
|
1
1
|
# YART
|
2
2
|
|
3
|
-
*Yet Another Ruby Templater*
|
3
|
+
*Yet Another Ruby Templater* turns plain Ruby into HTML making it fun to write webpages.
|
4
4
|
|
5
|
-
|
5
|
+
- YART provides an intuitive DSL that feels natural to use and removes the boiler plate from writing HTML
|
6
|
+
- YART has zero runtime dependencies and around 100 lines of code
|
7
|
+
- YART is fully unit tested
|
6
8
|
|
7
|
-
## Usage
|
9
|
+
## Example Usage
|
10
|
+
|
11
|
+
> login.html
|
8
12
|
|
9
13
|
```ruby
|
10
|
-
require
|
14
|
+
require "yart"
|
11
15
|
|
12
16
|
YART.parse do
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
17
|
+
form action: "/auth" do
|
18
|
+
input type: :email, placeholder: "Email Address", required: true, close: true
|
19
|
+
input type: :password, placeholder: "Password", required: true, close: true
|
20
|
+
button(type: :submit, id: :login) { "Login" }
|
21
|
+
end
|
19
22
|
end
|
20
23
|
```
|
21
24
|
|
22
|
-
Which
|
25
|
+
Which renders:
|
23
26
|
|
24
27
|
```html
|
25
|
-
<
|
26
|
-
<
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
</div>
|
28
|
+
<form action='/auth'>
|
29
|
+
<input type='email' placeholder='Email Address' required>
|
30
|
+
<input type='password' placeholder='Password' required>
|
31
|
+
<button type='submit' id='login'>Login</button>
|
32
|
+
</form>
|
31
33
|
```
|
32
34
|
|
35
|
+
Note that the above HTML snippet is *prettified* for demonstration. The actual generated HTML will be *minified*.
|
36
|
+
|
33
37
|
## Installation
|
34
38
|
|
35
|
-
|
39
|
+
Requires Ruby `>= 2.7`
|
40
|
+
|
41
|
+
### RubyGems
|
36
42
|
|
37
43
|
$ gem install yart
|
38
44
|
|
@@ -40,6 +46,74 @@ Which produces and returns (from `YART.parse`):
|
|
40
46
|
|
41
47
|
$ bundle add yart
|
42
48
|
|
49
|
+
## API
|
50
|
+
|
51
|
+
The best way to fully demonstrate the YART API is with a more complex example:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
require 'yart'
|
55
|
+
|
56
|
+
YART.parse do
|
57
|
+
element "!DOCTYPE", html: true, close: true # Or just call `doctype`
|
58
|
+
html lang: :en do
|
59
|
+
head do
|
60
|
+
title { "YART API" }
|
61
|
+
end
|
62
|
+
body do
|
63
|
+
h1 { "Use a block to return a String of innerText or more elements" }
|
64
|
+
div data_test_id: "String attribute values will be parsed as is" do
|
65
|
+
h2(data_x: :sub_heading) { "Symbol attribute keys/values will be kebab-cased" }
|
66
|
+
text { "Set the div's innerText, before and/or after its child elements" }
|
67
|
+
p(class: [:content, :italic_text], id: :paragraph) do
|
68
|
+
"You can pass an array of attribute values and they will be space separated"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
footer # Render an empty <footer></footer> element
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
Which renders, minifies and returns the following HTML5 from `YART.parse`:
|
78
|
+
|
79
|
+
```html
|
80
|
+
<!DOCTYPE html>
|
81
|
+
<html lang='en'>
|
82
|
+
<head>
|
83
|
+
<title>YART API</title>
|
84
|
+
</head>
|
85
|
+
<body>
|
86
|
+
<h1>Use a block to return a String of innerText or more elements</h1>
|
87
|
+
<div data-test-id='String attribute values will be parsed as is'>
|
88
|
+
<h2 data-x='sub-heading'>Symbol attribute keys/values will be kebab-cased</h2>
|
89
|
+
Set the div's innerText, before and/or after its child elements
|
90
|
+
<p class='content italic-text' id='paragraph'>
|
91
|
+
You can pass an array of attribute values and they will be space separated
|
92
|
+
</p>
|
93
|
+
</div>
|
94
|
+
<footer></footer>
|
95
|
+
</body>
|
96
|
+
</html>
|
97
|
+
```
|
98
|
+
|
99
|
+
Main points to note:
|
100
|
+
|
101
|
+
- Pass a block to `YART.parse` and it will render and return a HTML `String`.
|
102
|
+
- Create the HTML document hierarchy using element calls and blocks.
|
103
|
+
- Call the element as it's named in HTML, e.g. `h1`, `div`, `p` etc. This works as long as it's lowercase.
|
104
|
+
- Call `element` when you need to render the *raw* element name (case insensitive) e.g. `!DOCTYPE`.
|
105
|
+
- Pass the element's attributes as a `Hash` argument.
|
106
|
+
- Pass a block to return a `String` of `innerText` or more DSL calls (which will eventually return a `String`).
|
107
|
+
- An element doesn't require attributes or even a block. Where a block is absent, an empty element will be rendered.
|
108
|
+
- Use the `text` method to render the `innerText` of the element when it consists of *both* inner text *and* child elements.
|
109
|
+
- An attribute key or value of type `Symbol` will be parsed, converting `snake_case` to `kebab-case`.
|
110
|
+
- An attribute *value* of type `String` will be parsed as is (not modified in any way).
|
111
|
+
- An attribute *value* of `true` renders the attribute key without a value e.g. `input required: true` renders `<input required>`.
|
112
|
+
- Several attibute *values* can be rendered by passing an `Array` e.g. `p class: [:para, :italic]`. The values will be rendered space separated.
|
113
|
+
- Attribute *values* containing illegal characters (like quotes etc.) will be escaped in the rendered HTML.
|
114
|
+
- The attribute `close: true` is special and tells the parser to auto-close the element (because it's empty).
|
115
|
+
- Use the convenience methods `doctype`, `script` and `stylesheet` as needed.
|
116
|
+
|
43
117
|
## Development
|
44
118
|
|
45
119
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/bin/console
CHANGED
@@ -2,14 +2,52 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "bundler/setup"
|
5
|
-
require "
|
5
|
+
require "irb"
|
6
|
+
require "byebug" # Call "byebug" anywhere in the code to debug.
|
6
7
|
|
7
|
-
#
|
8
|
-
|
8
|
+
# Define a method to facilitate the reloading of code changes.
|
9
|
+
def reload
|
10
|
+
original_verbose = $VERBOSE
|
11
|
+
$VERBOSE = nil # Suppress warning messages (from reloading CONSTANTS).
|
12
|
+
load "load.rb" # (Re)load all code changes.
|
13
|
+
$VERBOSE = original_verbose # Activate warning messages again globally.
|
14
|
+
true
|
15
|
+
end
|
9
16
|
|
10
|
-
#
|
11
|
-
|
12
|
-
|
17
|
+
# Load the most recent code into the session and include modules etc.
|
18
|
+
reload
|
19
|
+
|
20
|
+
# Load any fixture data into the session.
|
21
|
+
def login
|
22
|
+
proc do
|
23
|
+
form action: "/auth" do
|
24
|
+
input type: :email, placeholder: "Email Address", required: true, close: true
|
25
|
+
input type: :password, placeholder: "Password", required: true, close: true
|
26
|
+
button(type: :submit, id: :login) { "Login" }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def sample
|
32
|
+
proc do
|
33
|
+
doctype
|
34
|
+
html lang: :en do
|
35
|
+
head do
|
36
|
+
title { "YART API" }
|
37
|
+
end
|
38
|
+
body do
|
39
|
+
h1 { "Use a block to return a String of innerText or more elements" }
|
40
|
+
div data_test_id: "String attribute values will be parsed as is" do
|
41
|
+
h2(data_x: :sub_heading) { "Symbol attribute keys/values will be kebab-cased" }
|
42
|
+
text { "Set the div's innerText, before and/or after its child elements" }
|
43
|
+
p(class: [:content, :italic_text], id: :paragraph) do
|
44
|
+
"You can pass an array of attribute values and they will be space separated"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
footer # Render an empty <footer></footer> element
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
13
52
|
|
14
|
-
require "irb"
|
15
53
|
IRB.start(__FILE__)
|
data/lib/yart.rb
CHANGED
data/lib/yart/parser.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module YART::Parser
|
4
|
+
CUSTOM_ATTRIBUTES = [:close]
|
5
|
+
|
6
|
+
# Parses a block of Ruby, rendering and returning a HTML String.
|
7
|
+
def parse(&block)
|
8
|
+
raise "Must pass a block to parse" unless block_given?
|
9
|
+
|
10
|
+
@@yart_buffer = []
|
11
|
+
instance_eval(&block)
|
12
|
+
@@yart_buffer.join
|
13
|
+
end
|
14
|
+
|
15
|
+
# Allows elements to be called and rendered via a DSL.
|
16
|
+
def method_missing(m, *args, &block)
|
17
|
+
attributes = args.fetch(0, {})
|
18
|
+
|
19
|
+
render(m, attributes, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Renders an element with the raw name (case insensitive).
|
23
|
+
def element(name, **attributes, &block)
|
24
|
+
render(name, attributes, &block)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Renders a <!DOCTYPE html> element, for convenience.
|
28
|
+
def doctype
|
29
|
+
element("!DOCTYPE", html: true, close: true)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Renders a JS <script src="..."> element, for convenience.
|
33
|
+
def script(src = nil, &block)
|
34
|
+
raise "Must pass a String param or a block returning a String" unless src || block_given?
|
35
|
+
|
36
|
+
src ||= block.call
|
37
|
+
element("script", src: src, close: true)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Renders a CSS <link href="..."> element, for convenience.
|
41
|
+
def stylesheet(href = nil, &block)
|
42
|
+
raise "Must pass a String param or a block returning a String" unless href || block_given?
|
43
|
+
|
44
|
+
href ||= block.call
|
45
|
+
element("link", href: href, rel: :stylesheet, type: "text/css", close: true)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Overrides Ruby's `p` method to render the element instead of printing.
|
49
|
+
def p(**attributes, &block)
|
50
|
+
render("p", attributes, &block)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Sets the `innerText` of the element being rendered.
|
54
|
+
def text(str = nil, &block)
|
55
|
+
raise "Must pass a String param or a block returning a String" unless str || block_given?
|
56
|
+
|
57
|
+
str ||= block.call
|
58
|
+
buffer(str)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def buffer(str)
|
64
|
+
@@yart_buffer << str if str.is_a?(String)
|
65
|
+
end
|
66
|
+
|
67
|
+
def render(element, attributes, &block)
|
68
|
+
raise "Must pass attributes as a Hash" unless attributes.is_a?(Hash)
|
69
|
+
|
70
|
+
buffer(build_opening_tag(element, attributes))
|
71
|
+
buffer(instance_eval(&block)) if block_given?
|
72
|
+
buffer(build_closing_tag(element, attributes))
|
73
|
+
end
|
74
|
+
|
75
|
+
def build_opening_tag(element, attributes)
|
76
|
+
attributes_str = sanitise_attributes(attributes)
|
77
|
+
.reject { |k, v| CUSTOM_ATTRIBUTES.include?(k) }
|
78
|
+
.map { |k, v| v == true ? k.to_s : "#{k}='#{v}'" }
|
79
|
+
.join(" ")
|
80
|
+
separator = attributes_str.empty? ? "" : " "
|
81
|
+
|
82
|
+
"<#{element}#{separator}#{attributes_str}>"
|
83
|
+
end
|
84
|
+
|
85
|
+
def build_closing_tag(element, attributes)
|
86
|
+
attributes[:close] ? "" : "</#{element}>"
|
87
|
+
end
|
88
|
+
|
89
|
+
def sanitise_attributes(attributes)
|
90
|
+
attributes.map do |k, v|
|
91
|
+
k = kebab_case(k)
|
92
|
+
v = v.respond_to?(:map) ?
|
93
|
+
v.map { |v2| convert_attribute_value(v2) }.join(" ") :
|
94
|
+
convert_attribute_value(v)
|
95
|
+
|
96
|
+
[k, v]
|
97
|
+
end.to_h
|
98
|
+
end
|
99
|
+
|
100
|
+
def convert_attribute_value(value)
|
101
|
+
value = kebab_case(value)
|
102
|
+
value = replace_illegal_chars(value)
|
103
|
+
|
104
|
+
value
|
105
|
+
end
|
106
|
+
|
107
|
+
def kebab_case(s)
|
108
|
+
return s unless s.is_a?(Symbol)
|
109
|
+
|
110
|
+
s
|
111
|
+
.to_s
|
112
|
+
.gsub("_", "-")
|
113
|
+
.to_sym
|
114
|
+
end
|
115
|
+
|
116
|
+
def replace_illegal_chars(s)
|
117
|
+
return s unless s.is_a?(String)
|
118
|
+
|
119
|
+
s
|
120
|
+
.gsub('"', """)
|
121
|
+
.gsub("'", "'")
|
122
|
+
.gsub("<", "<")
|
123
|
+
.gsub(">", ">")
|
124
|
+
end
|
125
|
+
end
|
data/lib/yart/version.rb
CHANGED
data/load.rb
ADDED
data/yart.gemspec
CHANGED
@@ -8,9 +8,13 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.authors = ["Michael Telford"]
|
9
9
|
spec.email = ["michael.telford@live.com"]
|
10
10
|
|
11
|
-
spec.summary = "Yet Another Ruby Templater
|
11
|
+
spec.summary = "Yet Another Ruby Templater (YART) turns plain Ruby into HTML making it fun to write webpages."
|
12
12
|
spec.description = <<~TEXT
|
13
|
-
|
13
|
+
Yet Another Ruby Templater (YART) turns plain Ruby into HTML making it fun to write webpages.
|
14
|
+
|
15
|
+
- YART provides an intuitive DSL that feels natural to use and removes the boiler plate from writing HTML
|
16
|
+
- YART has zero runtime dependencies and ~120 LOC
|
17
|
+
- YART is fully unit tested
|
14
18
|
TEXT
|
15
19
|
spec.homepage = "https://github.com/michaeltelford/yart"
|
16
20
|
spec.license = "MIT"
|
metadata
CHANGED
@@ -1,20 +1,21 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yart
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Telford
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-08-03 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description:
|
14
|
-
|
15
|
-
bits of a webpage e.g. a form for posting to the server etc.
|
13
|
+
description: |
|
14
|
+
Yet Another Ruby Templater (YART) turns plain Ruby into HTML making it fun to write webpages.
|
16
15
|
|
17
|
-
|
16
|
+
- YART provides an intuitive DSL that feels natural to use and removes the boiler plate from writing HTML
|
17
|
+
- YART has zero runtime dependencies and ~120 LOC
|
18
|
+
- YART is fully unit tested
|
18
19
|
email:
|
19
20
|
- michael.telford@live.com
|
20
21
|
executables: []
|
@@ -34,7 +35,9 @@ files:
|
|
34
35
|
- bin/console
|
35
36
|
- bin/setup
|
36
37
|
- lib/yart.rb
|
38
|
+
- lib/yart/parser.rb
|
37
39
|
- lib/yart/version.rb
|
40
|
+
- load.rb
|
38
41
|
- yart.gemspec
|
39
42
|
homepage: https://github.com/michaeltelford/yart
|
40
43
|
licenses:
|
@@ -61,5 +64,6 @@ requirements: []
|
|
61
64
|
rubygems_version: 3.1.2
|
62
65
|
signing_key:
|
63
66
|
specification_version: 4
|
64
|
-
summary: Yet Another Ruby Templater
|
67
|
+
summary: Yet Another Ruby Templater (YART) turns plain Ruby into HTML making it fun
|
68
|
+
to write webpages.
|
65
69
|
test_files: []
|