herb 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +20 -0
- data/README.md +171 -0
- data/lib/herb.rb +79 -0
- data/test/helper.rb +7 -0
- data/test/helpers_test.rb +25 -0
- data/test/parsing_test.rb +110 -0
- metadata +108 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5c394bc2c2281a98fffdbd20598cf1b8fb14c710
|
4
|
+
data.tar.gz: a3179a63d031f7599274f144ef7d846518a0bd96
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 02bd3e967c8d5433d141d4c4fb6c84c6610c2ca280413016def1280a7b4b77520289e582c070d030455f3a66b714cab34f292c46c7de94199e95ab8356d90b40
|
7
|
+
data.tar.gz: d2c7bfbd62d6188b5bbf7741b90113ffef0945ae7724f2e92ae98efc2d099a75496a3b4215836dc09bdd3a688776f547e46962a8457605eb219025bd34511c90
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2016 Francesco Rodriguez
|
2
|
+
Copyright (c) 2011-2016 Michel Martens
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
6
|
+
in the Software without restriction, including without limitation the rights
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
9
|
+
furnished to do so, subject to the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be included in
|
12
|
+
all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
herb [![Build Status](https://travis-ci.org/frodsan/herb.svg)](https://travis-ci.org/frodsan/herb)
|
2
|
+
====
|
3
|
+
|
4
|
+
Minimal template engine with default escaping.
|
5
|
+
|
6
|
+
Description
|
7
|
+
-----------
|
8
|
+
|
9
|
+
Herb is a fork of [Mote] that uses a [ERB]-like style syntax and auto-escape HTML special characters by default.
|
10
|
+
|
11
|
+
Installation
|
12
|
+
------------
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
gem "herb"
|
18
|
+
```
|
19
|
+
|
20
|
+
And then execute:
|
21
|
+
|
22
|
+
```
|
23
|
+
$ bundle
|
24
|
+
```
|
25
|
+
|
26
|
+
Or install it yourself as:
|
27
|
+
|
28
|
+
```
|
29
|
+
$ gem install herb
|
30
|
+
```
|
31
|
+
|
32
|
+
Basic Usage
|
33
|
+
-----------
|
34
|
+
|
35
|
+
This is a basic example:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
require "herb"
|
39
|
+
|
40
|
+
template = Herb.parse("your template goes here!")
|
41
|
+
template.call
|
42
|
+
# => "your template goes here!"
|
43
|
+
```
|
44
|
+
|
45
|
+
Herb recognizes two tags to evaluate Ruby code: `<% %>`, and `<%= %>`. The difference between them is that while the `<% %>` tags only evaluate the code, the `<%= %>` tags also prints the result to the template.
|
46
|
+
|
47
|
+
Imagine that your template looks like this:
|
48
|
+
|
49
|
+
```erb
|
50
|
+
<% # single line code %>
|
51
|
+
<% gems = ["rack", "cuba", "herb"] %>
|
52
|
+
|
53
|
+
<%
|
54
|
+
# multi-line code
|
55
|
+
sorted = gems.sort
|
56
|
+
%>
|
57
|
+
|
58
|
+
<ul>
|
59
|
+
<% sorted.each do |name| %>
|
60
|
+
<li><%= name %></li>
|
61
|
+
<% end %>
|
62
|
+
</ul>
|
63
|
+
```
|
64
|
+
|
65
|
+
The generated result will be like:
|
66
|
+
|
67
|
+
```html
|
68
|
+
<ul>
|
69
|
+
<li>cuba</li>
|
70
|
+
<li>herb</li>
|
71
|
+
<li>rack</li>
|
72
|
+
</ul>
|
73
|
+
```
|
74
|
+
|
75
|
+
Parameters
|
76
|
+
----------
|
77
|
+
|
78
|
+
The values passed to the template are available as local variables:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
template = Herb.parse("Hello {{ name }}", [:name])
|
82
|
+
template.call(name: "Ruby")
|
83
|
+
# => Hello Ruby
|
84
|
+
```
|
85
|
+
|
86
|
+
You can also use the `params` local variable to access the given parameters:
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
template = Herb.parse("Hello {{ params[:name] }}")
|
90
|
+
template.call(name: "Ruby")
|
91
|
+
# => Hello Ruby
|
92
|
+
```
|
93
|
+
|
94
|
+
Auto-escaping
|
95
|
+
-------------
|
96
|
+
|
97
|
+
By default, Herb escapes HTML special characters to prevent [XSS][xss] attacks. You can start the expression with an exclamation mark to disable escaping for that expression:
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
template = Herb.parse("Hello {{ name }}", [:name])
|
101
|
+
template.call(name: "<b>World</b>")
|
102
|
+
# => Hello <b>World<b>
|
103
|
+
|
104
|
+
template = Herb.parse("Hello {{! name }}", [:name])
|
105
|
+
template.call(name: "<b>World</b>")
|
106
|
+
# => Hello <b>World</b>
|
107
|
+
```
|
108
|
+
|
109
|
+
Herb::Helpers
|
110
|
+
--------------
|
111
|
+
|
112
|
+
There's a helper available in the `Herb::Helpers` module, and you are
|
113
|
+
free to include it in your code. To do it, just type:
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
include Herb::Helpers
|
117
|
+
```
|
118
|
+
|
119
|
+
### Using the `herb` helper
|
120
|
+
|
121
|
+
The `herb` helper receives a file name and a hash and returns the rendered version of its content. The compiled template is cached for subsequent calls.
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
herb("test/basic.erb", n: 3)
|
125
|
+
# => "***\n"
|
126
|
+
```
|
127
|
+
|
128
|
+
### Template caching
|
129
|
+
|
130
|
+
When the `herb` helper is first called with a template name, the file is read and parsed, and a proc is created and stored in the current thread. The parameters passed are defined as local variables in the template. If you want to provide more parameters once the template was cached, you won't be able to access the values as local variables, but you can always access the `params` hash.
|
131
|
+
|
132
|
+
For example:
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
# First call
|
136
|
+
herb("foo.erb", a: 1, b: 2)
|
137
|
+
```
|
138
|
+
|
139
|
+
Contributing
|
140
|
+
------------
|
141
|
+
|
142
|
+
Fork the project with:
|
143
|
+
|
144
|
+
```
|
145
|
+
$ git clone git@github.com:frodsan/herb.git
|
146
|
+
```
|
147
|
+
|
148
|
+
To install dependencies, use:
|
149
|
+
|
150
|
+
```
|
151
|
+
$ bundle install
|
152
|
+
```
|
153
|
+
|
154
|
+
To run the test suite, do:
|
155
|
+
|
156
|
+
```
|
157
|
+
$ rake test
|
158
|
+
```
|
159
|
+
|
160
|
+
For bug reports and pull requests use [GitHub][issues].
|
161
|
+
|
162
|
+
License
|
163
|
+
-------
|
164
|
+
|
165
|
+
Herb is released under the [MIT License][mit].
|
166
|
+
|
167
|
+
[erb]: http://ruby-doc.org/stdlib-2.3.1/libdoc/erb/rdoc/ERB.html
|
168
|
+
[issues]: https://github.com/frodsan/herb/issues
|
169
|
+
[mit]: http://www.opensource.org/licenses/MIT
|
170
|
+
[mote]: https://github.com/soveran/mote
|
171
|
+
[xss]: http://en.wikipedia.org/wiki/Cross-Site_Scripting
|
data/lib/herb.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2016 Francesco Rodríguez
|
4
|
+
# Copyright (c) 2011-2016 Michel Martens (https://github.com/soveran/mote)
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
# of this software and associated documentation files (the "Software"), to deal
|
8
|
+
# in the Software without restriction, including without limitation the rights
|
9
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
# copies of the Software, and to permit persons to whom the Software is
|
11
|
+
# furnished to do so, subject to the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
14
|
+
# all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
# THE SOFTWARE.
|
23
|
+
|
24
|
+
module Herb
|
25
|
+
PATTERN = /(<%)\s+(.*?)\s+%>(?:\n)|(<%==?)(.*?)%>/m
|
26
|
+
|
27
|
+
def self.parse(template, vars = [], context = self)
|
28
|
+
terms = template.split(PATTERN)
|
29
|
+
parts = "proc do |params = {}, __o = ''|\n".dup
|
30
|
+
|
31
|
+
vars.each { |var| parts << sprintf("%s = params[%p]\n", var, var) }
|
32
|
+
|
33
|
+
while (term = terms.shift)
|
34
|
+
parts << parse_expression(terms, term)
|
35
|
+
end
|
36
|
+
|
37
|
+
parts << "__o; end"
|
38
|
+
|
39
|
+
compile(context, parts)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.parse_expression(terms, term)
|
43
|
+
case term
|
44
|
+
when "<%" then terms.shift << "\n"
|
45
|
+
when "<%=" then "__o << Herb.h((" + terms.shift << ").to_s)\n"
|
46
|
+
when "<%==" then "__o << (" + terms.shift << ").to_s\n"
|
47
|
+
else "__o << " + term.dump << "\n"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.compile(context, parts)
|
52
|
+
context.instance_eval(parts)
|
53
|
+
end
|
54
|
+
|
55
|
+
HTML_ESCAPE = {
|
56
|
+
"&" => "&",
|
57
|
+
">" => ">",
|
58
|
+
"<" => "<",
|
59
|
+
'"' => "'",
|
60
|
+
"'" => """
|
61
|
+
}.freeze
|
62
|
+
|
63
|
+
UNSAFE = /[&"'><]/
|
64
|
+
|
65
|
+
def self.h(str)
|
66
|
+
str.gsub(UNSAFE, HTML_ESCAPE)
|
67
|
+
end
|
68
|
+
|
69
|
+
module Helpers
|
70
|
+
def herb(file, params = {}, context = self)
|
71
|
+
herb_cache[file] ||= Herb.parse(File.read(file), params.keys, context)
|
72
|
+
herb_cache[file].call(params)
|
73
|
+
end
|
74
|
+
|
75
|
+
def herb_cache
|
76
|
+
Thread.current[:herb_cache] ||= {}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "helper"
|
4
|
+
|
5
|
+
class HelpersTest < Minitest::Test
|
6
|
+
include Herb::Helpers
|
7
|
+
|
8
|
+
setup do
|
9
|
+
herb_cache.clear
|
10
|
+
end
|
11
|
+
|
12
|
+
def foo
|
13
|
+
"foo"
|
14
|
+
end
|
15
|
+
|
16
|
+
test "using functions in the context" do
|
17
|
+
assert_equal("foo\n", herb("test/views/foo.erb"))
|
18
|
+
end
|
19
|
+
|
20
|
+
test "passing in a context" do
|
21
|
+
assert_raises(NameError) do
|
22
|
+
herb("test/views/foo.erb", {}, TOPLEVEL_BINDING)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "helper"
|
4
|
+
|
5
|
+
class ParsingTest < Minitest::Test
|
6
|
+
test "assignment" do
|
7
|
+
template = Herb.parse("<%= 1 + 2 %>")
|
8
|
+
|
9
|
+
assert_equal("3", template.call)
|
10
|
+
end
|
11
|
+
|
12
|
+
test "control flow" do
|
13
|
+
template = <<-EOT.gsub(/ {4}/, "")
|
14
|
+
<% if false %>
|
15
|
+
false
|
16
|
+
<% else %>
|
17
|
+
true
|
18
|
+
<% end %>
|
19
|
+
EOT
|
20
|
+
|
21
|
+
result = Herb.parse(template).call
|
22
|
+
|
23
|
+
assert_equal(" true\n", result)
|
24
|
+
end
|
25
|
+
|
26
|
+
test "parameters" do
|
27
|
+
template = <<-EOT.gsub(/ {4}/, "")
|
28
|
+
<% params[:n].times do %>
|
29
|
+
*
|
30
|
+
<% end %>
|
31
|
+
EOT
|
32
|
+
|
33
|
+
example = Herb.parse(template)
|
34
|
+
|
35
|
+
assert_equal("*\n*\n*\n", example[n: 3])
|
36
|
+
assert_equal("*\n*\n*\n*\n", example[n: 4])
|
37
|
+
end
|
38
|
+
|
39
|
+
test "multiline" do
|
40
|
+
example = Herb.parse("The\nMan\nAnd\n<%=\"The\"%>\nSea")
|
41
|
+
|
42
|
+
assert_equal("The\nMan\nAnd\nThe\nSea", example.call)
|
43
|
+
end
|
44
|
+
|
45
|
+
test "quotes" do
|
46
|
+
example = Herb.parse("'foo' 'bar' 'baz'")
|
47
|
+
|
48
|
+
assert_equal("'foo' 'bar' 'baz'", example.call)
|
49
|
+
end
|
50
|
+
|
51
|
+
test "context" do
|
52
|
+
context = Object.new
|
53
|
+
|
54
|
+
def context.user
|
55
|
+
"Bruno"
|
56
|
+
end
|
57
|
+
|
58
|
+
example = Herb.parse("<%= user %>", [], context)
|
59
|
+
|
60
|
+
assert_equal("Bruno", example.call)
|
61
|
+
end
|
62
|
+
|
63
|
+
test "locals" do
|
64
|
+
example = Herb.parse("<%= user %>", [:user], self)
|
65
|
+
|
66
|
+
assert_equal("Mayn", example.call(user: "Mayn"))
|
67
|
+
end
|
68
|
+
|
69
|
+
test "nil" do
|
70
|
+
example = Herb.parse("<%= params[:user] %>", [], TOPLEVEL_BINDING)
|
71
|
+
|
72
|
+
assert_equal("", example.call(user: nil))
|
73
|
+
end
|
74
|
+
|
75
|
+
test "multi-line" do
|
76
|
+
template = <<-EOT.gsub(/^ /, "")
|
77
|
+
<%
|
78
|
+
# Multiline code evaluation
|
79
|
+
lucky = [1, 3, 7, 9, 13, 15]
|
80
|
+
prime = [2, 3, 5, 7, 11, 13]
|
81
|
+
%>
|
82
|
+
|
83
|
+
<%= lucky & prime %>
|
84
|
+
EOT
|
85
|
+
|
86
|
+
example = Herb.parse(template)
|
87
|
+
|
88
|
+
assert_equal "\n[3, 7, 13]\n", example.call
|
89
|
+
end
|
90
|
+
|
91
|
+
test "escapes by default" do
|
92
|
+
text = %q(<>&"')
|
93
|
+
|
94
|
+
template = Herb.parse("<%= params[:text] %>")
|
95
|
+
|
96
|
+
result = template.call(text: text)
|
97
|
+
|
98
|
+
assert_equal("<>&'"", result)
|
99
|
+
end
|
100
|
+
|
101
|
+
test "no escaping please" do
|
102
|
+
text = %q(<>&"')
|
103
|
+
|
104
|
+
template = Herb.parse("<%== params[:text] %>")
|
105
|
+
|
106
|
+
result = template.call(text: text)
|
107
|
+
|
108
|
+
assert_equal(text, result)
|
109
|
+
end
|
110
|
+
end
|
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: herb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Francesco Rodriguez
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-06-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: minitest
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.8'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.8'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest-sugar
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.1'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '11.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '11.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.39'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.39'
|
69
|
+
description: ERB-like template engine that escapes HTML by default
|
70
|
+
email: frodsan@protonmail.com
|
71
|
+
executables: []
|
72
|
+
extensions: []
|
73
|
+
extra_rdoc_files: []
|
74
|
+
files:
|
75
|
+
- LICENSE
|
76
|
+
- README.md
|
77
|
+
- lib/herb.rb
|
78
|
+
- test/helper.rb
|
79
|
+
- test/helpers_test.rb
|
80
|
+
- test/parsing_test.rb
|
81
|
+
homepage: https://github.com/frodsan/herb
|
82
|
+
licenses:
|
83
|
+
- MIT
|
84
|
+
metadata: {}
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options: []
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
requirements: []
|
100
|
+
rubyforge_project:
|
101
|
+
rubygems_version: 2.5.1
|
102
|
+
signing_key:
|
103
|
+
specification_version: 4
|
104
|
+
summary: ERB-like template engine that escapes HTML by default
|
105
|
+
test_files:
|
106
|
+
- test/helper.rb
|
107
|
+
- test/helpers_test.rb
|
108
|
+
- test/parsing_test.rb
|