hmote 1.0.0 → 1.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/.gems +3 -1
- data/LICENSE +1 -2
- data/README.md +188 -15
- data/benchmarks/.gems +2 -0
- data/benchmarks/escaping.rb +17 -0
- data/benchmarks/helper.rb +8 -0
- data/benchmarks/hmote_helper.rb +7 -0
- data/benchmarks/rails_helper.rb +28 -0
- data/benchmarks/safe.rb +17 -0
- data/benchmarks/templates/erb +1 -0
- data/benchmarks/templates/mote +1 -0
- data/hmote.gemspec +4 -4
- data/lib/hmote/render.rb +31 -0
- data/lib/hmote.rb +12 -9
- data/makefile +3 -0
- data/test/custom_views/custom.mote +1 -0
- data/test/custom_views/layout.mote +2 -0
- data/test/helper.rb +2 -0
- data/test/helpers.rb +6 -10
- data/test/parsing.rb +108 -0
- data/test/render.rb +78 -0
- data/test/views/context.mote +1 -0
- data/test/views/home.mote +1 -0
- data/test/views/layout.mote +2 -0
- metadata +23 -6
- data/test/fixtures/basic.mote +0 -3
- data/test/hmote.rb +0 -151
- /data/test/{fixtures/foo.mote → foo.mote} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2bd46a8c3c5b7fdccdbc4bc125330763de517667
|
4
|
+
data.tar.gz: a049d0e48e684f6471b1fec0a4f1354c40263e30
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 99f43f5f506bdbbf8e9613c1e1bd8f5239ff90fae229c9976b7d08d9365109bc0f76d89aaa09e34e0d984420c6f040087f5c5520b06411cd007579affb2f9903
|
7
|
+
data.tar.gz: 262abfe709226712b17c044a1b5567b3a5d66b9a5c51773a4a9ece75e43fe745a94830ee27cf7e94766cb00fec1be5ccc20f79489c4831fda93806fafb0f70d0
|
data/.gems
CHANGED
data/LICENSE
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
Copyright (c) 2014 Francesco Rodríguez
|
2
|
-
Copyright (c) 2011-2014 Michel Martens
|
1
|
+
Copyright (c) 2014-Present Francesco Rodríguez, Mayn Kjær
|
3
2
|
|
4
3
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
4
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
@@ -1,29 +1,202 @@
|
|
1
1
|
hmote
|
2
2
|
=====
|
3
3
|
|
4
|
-
|
5
|
-
tag characters by default. It uses [hache][hache] to do the escaping.
|
4
|
+
Minimal template engine with default escaping.
|
6
5
|
|
7
|
-
Usage
|
8
|
-
|
6
|
+
Basic Usage
|
7
|
+
-----------
|
9
8
|
|
10
|
-
|
9
|
+
This is a basic example:
|
11
10
|
|
12
11
|
```ruby
|
13
|
-
|
14
|
-
|
12
|
+
require "hmote"
|
13
|
+
|
14
|
+
template = HMote.parse("your template goes here!")
|
15
|
+
template.call
|
16
|
+
# => "your template goes here!"
|
17
|
+
```
|
18
|
+
|
19
|
+
HMote recognizes two tags to evaluate Ruby code: `%` and `{{}}`.
|
20
|
+
The difference between them is that while the `%` tag only evaluates
|
21
|
+
the code, the `{{}}` tag also prints the result to the template.
|
22
|
+
|
23
|
+
Imagine that your template looks like this:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
% gems = ["rack", "cuba", "hmote"]
|
27
|
+
|
28
|
+
<ul>
|
29
|
+
% # this is a comment.
|
30
|
+
% gems.sort.each do |gem|
|
31
|
+
<li>{{ gem }}</li>
|
32
|
+
% end
|
33
|
+
</ul>
|
34
|
+
```
|
35
|
+
|
36
|
+
The generated result will be:
|
37
|
+
|
38
|
+
```html
|
39
|
+
<ul>
|
40
|
+
<li>cuba</li>
|
41
|
+
<li>hmote</li>
|
42
|
+
<li>rack</li>
|
43
|
+
</ul>
|
44
|
+
```
|
45
|
+
|
46
|
+
Parameters
|
47
|
+
----------
|
48
|
+
|
49
|
+
The values passed to the template are available as local variables:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
template = HMote.parse("Hello {{ name }}", self, [:name])
|
53
|
+
template.call(name: "Ruby")
|
54
|
+
# => Hello Ruby
|
55
|
+
```
|
56
|
+
|
57
|
+
You can also use the `params` local variable to access the given
|
58
|
+
parameters:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
template = HMote.parse("Hello {{ params[:name] }}", self)
|
62
|
+
template.call(name: "Ruby")
|
63
|
+
# => Hello Ruby
|
64
|
+
```
|
65
|
+
|
66
|
+
Auto-escaping
|
67
|
+
-------------
|
68
|
+
|
69
|
+
By default, HMote escapes HTML special characters to prevent [XSS][xss]
|
70
|
+
attacks. You can start the expression with an exclamation mark to disable
|
71
|
+
escaping for that expression:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
template = HMote.parse("Hello {{ name }}", self, [:name])
|
75
|
+
template.call(name: "<b>World</b>")
|
76
|
+
# => Hello <b>World<b>
|
77
|
+
|
78
|
+
template = HMote.parse("Hello {{! name }}", self, [:name])
|
79
|
+
template.call(name: "<b>World</b>")
|
80
|
+
# => Hello <b>World</b>
|
81
|
+
```
|
82
|
+
|
83
|
+
HMote::Helpers
|
84
|
+
--------------
|
85
|
+
|
86
|
+
There's a helper available in the `HMote::Helpers` module, and you are
|
87
|
+
free to include it in your code. To do it, just type:
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
include HMote::Helpers
|
91
|
+
```
|
92
|
+
|
93
|
+
### Using the hmote helper
|
94
|
+
|
95
|
+
The `hmote` helper receives a file name and a hash and returns the rendered
|
96
|
+
version of its content. The compiled template is cached for subsequent calls.
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
hmote("test/basic.mote", n: 3)
|
100
|
+
# => "***\n"
|
15
101
|
```
|
16
102
|
|
17
|
-
|
18
|
-
|
103
|
+
### Template caching
|
104
|
+
|
105
|
+
When the `hmote` helper is first called with a template name, the
|
106
|
+
file is read and parsed, and a proc is created and stored in the
|
107
|
+
current thread. The parameters passed are defined as local variables
|
108
|
+
in the template. If you want to provide more parameters once the template
|
109
|
+
was cached, you won't be able to access the values as local variables,
|
110
|
+
but you can always access the `params` hash.
|
111
|
+
|
112
|
+
For example:
|
19
113
|
|
20
114
|
```ruby
|
21
|
-
|
22
|
-
|
115
|
+
# First call
|
116
|
+
hmote("foo.mote", a: 1, b: 2)
|
23
117
|
```
|
24
118
|
|
25
|
-
|
26
|
-
|
119
|
+
HMote::Render
|
120
|
+
-------------
|
121
|
+
|
122
|
+
To use HMote in [Cuba][cuba], you need to load the `HMote::Render`
|
123
|
+
plugin as shown below:
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
require "hmote"
|
127
|
+
require "hmote/render"
|
128
|
+
|
129
|
+
Cuba.plugin(HMote::Render)
|
130
|
+
```
|
131
|
+
|
132
|
+
`HMote::Render` provides three helper methods for rendering templates:
|
133
|
+
`partial`, `view` and `render`.
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
Cuba.define do
|
137
|
+
on "about" do
|
138
|
+
# `partial` renders a template without a layout.
|
139
|
+
res.write partial("about")
|
140
|
+
end
|
141
|
+
|
142
|
+
on "home" do
|
143
|
+
# `view` renders a template within a layout.
|
144
|
+
res.write view("about")
|
145
|
+
end
|
146
|
+
|
147
|
+
on "contact" do
|
148
|
+
# `render` is a shortcut to `res.write view(...)`
|
149
|
+
render("contact")
|
150
|
+
end
|
151
|
+
end
|
152
|
+
```
|
153
|
+
|
154
|
+
By default, `HMote::Render` assumes that all view templates are placed
|
155
|
+
in a folder named `views` and that they use the `.mote` extension. Also
|
156
|
+
for `view` and `render` methods, it assumes that the layout template is
|
157
|
+
called `layout.mote`.
|
158
|
+
|
159
|
+
The defaults can be changed through the `Cuba.settings` method:
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
Cuba.settings[:hmote][:views] = "./views/admin/"
|
163
|
+
Cuba.settings[:hmote][:layout] = "admin"
|
164
|
+
```
|
165
|
+
|
166
|
+
### Layouts
|
167
|
+
|
168
|
+
To render inner content into a layout, use the `{{! content }}` tag.
|
169
|
+
|
170
|
+
```html
|
171
|
+
<html>
|
172
|
+
<head>
|
173
|
+
<title>Mote Layout</title>
|
174
|
+
</head>
|
175
|
+
<body>
|
176
|
+
<h1>Hello, world!</h1>
|
177
|
+
|
178
|
+
{{! content }}
|
179
|
+
</body>
|
180
|
+
</html>
|
181
|
+
```
|
182
|
+
|
183
|
+
### Helpers
|
184
|
+
|
185
|
+
You can use the `app` variable to access the application helpers.
|
186
|
+
|
187
|
+
```ruby
|
188
|
+
Cuba.define do
|
189
|
+
def h(unsafe)
|
190
|
+
...
|
191
|
+
end
|
192
|
+
end
|
193
|
+
```
|
194
|
+
|
195
|
+
```html
|
196
|
+
<h1>{{! app.h("unsafe") }}</h1>
|
197
|
+
|
198
|
+
{{ app.partial("list") }}
|
199
|
+
```
|
27
200
|
|
28
201
|
Installation
|
29
202
|
------------
|
@@ -32,5 +205,5 @@ Installation
|
|
32
205
|
$ gem install hmote
|
33
206
|
```
|
34
207
|
|
35
|
-
[
|
36
|
-
[
|
208
|
+
[cuba]: http://cuba.is
|
209
|
+
[xss]: http://en.wikipedia.org/wiki/Cross-Site_Scripting
|
data/benchmarks/.gems
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
require_relative "hmote_helper"
|
3
|
+
require_relative "rails_helper"
|
4
|
+
|
5
|
+
text = %q(some < text > inside & these " escapable' characters/1234)
|
6
|
+
|
7
|
+
Benchmark.ips do |x|
|
8
|
+
x.report("hmote") { hmote(text: text) }
|
9
|
+
x.report("rails") { rails(text: text) }
|
10
|
+
end
|
11
|
+
|
12
|
+
# Calculating -------------------------------------
|
13
|
+
# hmote 13.122k i/100ms
|
14
|
+
# rails 7.907k i/100ms
|
15
|
+
# -------------------------------------------------
|
16
|
+
# hmote 148.020k (± 3.6%) i/s - 747.954k
|
17
|
+
# rails 88.623k (± 2.7%) i/s - 442.792k
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "action_view"
|
2
|
+
|
3
|
+
class Context
|
4
|
+
class LookupContext
|
5
|
+
def disable_cache
|
6
|
+
yield
|
7
|
+
end
|
8
|
+
|
9
|
+
def find_template(*args)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def lookup_context
|
14
|
+
@lookup_context ||= LookupContext.new
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
ACTIONVIEW_CONTEXT = Context.new
|
19
|
+
ACTIONVIEW_TEMPLATE = ActionView::Template.new(
|
20
|
+
File.read(ERB_TEMPLATE), "template",
|
21
|
+
ActionView::Template::Handlers::ERB.new,
|
22
|
+
format: :html, virtual_path: "template"
|
23
|
+
)
|
24
|
+
ACTIONVIEW_TEMPLATE.locals = [:text]
|
25
|
+
|
26
|
+
def rails(params)
|
27
|
+
ACTIONVIEW_TEMPLATE.render(ACTIONVIEW_CONTEXT, params)
|
28
|
+
end
|
data/benchmarks/safe.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
require_relative "hmote_helper"
|
3
|
+
require_relative "rails_helper"
|
4
|
+
|
5
|
+
text = %q(some text without escapable characters).html_safe
|
6
|
+
|
7
|
+
Benchmark.ips do |x|
|
8
|
+
x.report("hmote") { hmote(text: text) }
|
9
|
+
x.report("rails") { rails(text: text) }
|
10
|
+
end
|
11
|
+
|
12
|
+
# Calculating -------------------------------------
|
13
|
+
# hmote 29.035k i/100ms
|
14
|
+
# rails 14.607k i/100ms
|
15
|
+
# -------------------------------------------------
|
16
|
+
# hmote 367.044k (± 5.9%) i/s - 1.829M
|
17
|
+
# rails 157.936k (± 5.9%) i/s - 788.778k
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= text %>
|
@@ -0,0 +1 @@
|
|
1
|
+
{{ text }}
|
data/hmote.gemspec
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "hmote"
|
3
|
-
s.version = "1.
|
3
|
+
s.version = "1.1.0"
|
4
4
|
s.summary = "A minimum operational template that escapes HTML tags by default."
|
5
5
|
s.description = s.summary
|
6
|
-
s.authors
|
7
|
-
s.email
|
8
|
-
s.homepage = "
|
6
|
+
s.authors = ["Francesco Rodríguez", "Mayn Kjær"]
|
7
|
+
s.email = ["frodsan@me.com", "mayn.kjaer@gmail.com"]
|
8
|
+
s.homepage = "https://github.com/harmoni-io/hmote"
|
9
9
|
s.license = "MIT"
|
10
10
|
|
11
11
|
s.files = `git ls-files`.split("\n")
|
data/lib/hmote/render.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative "../hmote"
|
2
|
+
|
3
|
+
module HMote::Render
|
4
|
+
include HMote::Helpers
|
5
|
+
|
6
|
+
def self.setup(app)
|
7
|
+
app.settings[:hmote] ||= {}
|
8
|
+
app.settings[:hmote][:views] ||= File.expand_path("views", Dir.pwd)
|
9
|
+
app.settings[:hmote][:layout] ||= "layout"
|
10
|
+
end
|
11
|
+
|
12
|
+
def render(template, params = {}, layout = settings[:hmote][:layout])
|
13
|
+
res.write(view(template, params, layout))
|
14
|
+
end
|
15
|
+
|
16
|
+
def view(template, params = {}, layout = settings[:hmote][:layout])
|
17
|
+
return partial(layout, params.merge(content: partial(template, params)))
|
18
|
+
end
|
19
|
+
|
20
|
+
def partial(template, params = {})
|
21
|
+
return hmote(template_path(template), params.merge(app: self), TOPLEVEL_BINDING)
|
22
|
+
end
|
23
|
+
|
24
|
+
def template_path(template)
|
25
|
+
if template.end_with?(".mote")
|
26
|
+
return template
|
27
|
+
else
|
28
|
+
return File.join(settings[:hmote][:views], "#{template}.mote")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/hmote.rb
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
require "hache"
|
2
2
|
|
3
|
-
class
|
4
|
-
PATTERN = /^(\n)
|
3
|
+
class HMote
|
4
|
+
PATTERN = /^(\n)| # new lines.
|
5
|
+
^\s*(%)\s*(.*?)(?:\n|\Z)| # % code
|
6
|
+
(<\?)\s+(.*?)\s+\?>| # <? multi-line code ?>
|
7
|
+
(\{\{!?)(.*?)\}\} # {{ escaped }} or {{! unescaped }}
|
8
|
+
/mx
|
5
9
|
|
6
10
|
def self.parse(template, context = self, vars = [])
|
7
11
|
terms = template.split(PATTERN)
|
8
|
-
|
9
12
|
parts = "Proc.new do |params, __o|\n params ||= {}; __o ||= ''\n"
|
10
13
|
|
11
14
|
vars.each do |var|
|
@@ -17,7 +20,7 @@ class Mote
|
|
17
20
|
when "<?" then parts << "#{terms.shift}\n"
|
18
21
|
when "%" then parts << "#{terms.shift}\n"
|
19
22
|
when "{{" then parts << "__o << Hache.h((#{terms.shift}).to_s)\n"
|
20
|
-
when "{{
|
23
|
+
when "{{!" then parts << "__o << (#{terms.shift}).to_s\n"
|
21
24
|
else parts << "__o << #{term.dump}\n"
|
22
25
|
end
|
23
26
|
end
|
@@ -32,13 +35,13 @@ class Mote
|
|
32
35
|
end
|
33
36
|
|
34
37
|
module Helpers
|
35
|
-
def
|
36
|
-
|
37
|
-
|
38
|
+
def hmote(file, params = {}, context = self)
|
39
|
+
hmote_cache[file] ||= HMote.parse(File.read(file), context, params.keys)
|
40
|
+
hmote_cache[file].call(params)
|
38
41
|
end
|
39
42
|
|
40
|
-
def
|
41
|
-
Thread.current[:
|
43
|
+
def hmote_cache
|
44
|
+
Thread.current[:_hmote_cache] ||= {}
|
42
45
|
end
|
43
46
|
end
|
44
47
|
end
|
data/makefile
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
<h1>Custom</h1>
|
data/test/helper.rb
ADDED
data/test/helpers.rb
CHANGED
@@ -1,29 +1,25 @@
|
|
1
|
-
require_relative "
|
1
|
+
require_relative "helper"
|
2
2
|
|
3
3
|
class Cutest::Scope
|
4
|
-
include
|
4
|
+
include HMote::Helpers
|
5
5
|
|
6
6
|
def foo
|
7
7
|
"foo"
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
scope do
|
11
|
+
scope("helpers") do
|
12
12
|
prepare do
|
13
|
-
|
14
|
-
end
|
15
|
-
|
16
|
-
test "helpers" do
|
17
|
-
assert_equal " *\n *\n *\n", mote("test/fixtures/basic.mote", n: 3)
|
13
|
+
hmote_cache.clear
|
18
14
|
end
|
19
15
|
|
20
16
|
test "using functions in the context" do
|
21
|
-
assert_equal
|
17
|
+
assert_equal("foo\n", hmote("test/foo.mote"))
|
22
18
|
end
|
23
19
|
|
24
20
|
test "passing in a context" do
|
25
21
|
assert_raise(NameError) do
|
26
|
-
|
22
|
+
hmote("test/foo.mote", {}, TOPLEVEL_BINDING)
|
27
23
|
end
|
28
24
|
end
|
29
25
|
end
|
data/test/parsing.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
|
3
|
+
scope("parsing") do
|
4
|
+
test "assignment" do
|
5
|
+
template = HMote.parse("{{ 1 + 2 }}")
|
6
|
+
|
7
|
+
assert_equal("3", template.call)
|
8
|
+
end
|
9
|
+
|
10
|
+
test "control flow" do
|
11
|
+
template = (<<-EOT).gsub(/ {4}/, "")
|
12
|
+
% if false
|
13
|
+
false
|
14
|
+
% else
|
15
|
+
true
|
16
|
+
% end
|
17
|
+
EOT
|
18
|
+
|
19
|
+
result = HMote.parse(template).call
|
20
|
+
|
21
|
+
assert_equal(" true\n", result)
|
22
|
+
end
|
23
|
+
|
24
|
+
test "parameters" do
|
25
|
+
template = (<<-EOT).gsub(/ {4}/, "")
|
26
|
+
% params[:n].times do
|
27
|
+
*
|
28
|
+
% end
|
29
|
+
EOT
|
30
|
+
|
31
|
+
example = HMote.parse(template)
|
32
|
+
|
33
|
+
assert_equal("*\n*\n*\n", example[n: 3])
|
34
|
+
assert_equal("*\n*\n*\n*\n", example[n: 4])
|
35
|
+
end
|
36
|
+
|
37
|
+
test "multiline" do
|
38
|
+
example = HMote.parse("The\nMan\nAnd\n{{\"The\"}}\nSea")
|
39
|
+
assert_equal("The\nMan\nAnd\nThe\nSea", example[n: 3])
|
40
|
+
end
|
41
|
+
|
42
|
+
test "quotes" do
|
43
|
+
example = HMote.parse("'foo' 'bar' 'baz'")
|
44
|
+
assert_equal("'foo' 'bar' 'baz'", example.call)
|
45
|
+
end
|
46
|
+
|
47
|
+
test "context" do
|
48
|
+
context = Object.new
|
49
|
+
def context.user; "Bruno"; end
|
50
|
+
|
51
|
+
example = HMote.parse("{{ user }}", context)
|
52
|
+
|
53
|
+
assert_equal("Bruno", example.call)
|
54
|
+
end
|
55
|
+
|
56
|
+
test "locals" do
|
57
|
+
example = HMote.parse("{{ user }}", TOPLEVEL_BINDING, [:user])
|
58
|
+
|
59
|
+
assert_equal("Mayn", example.call(user: "Mayn"))
|
60
|
+
end
|
61
|
+
|
62
|
+
test "nil" do
|
63
|
+
example = HMote.parse("{{ params[:user] }}", TOPLEVEL_BINDING)
|
64
|
+
|
65
|
+
assert_equal("", example.call(user: nil))
|
66
|
+
end
|
67
|
+
|
68
|
+
test "multi-line XML-style directives" do
|
69
|
+
template = (<<-EOT).gsub(/^ /, "")
|
70
|
+
<? res = ""
|
71
|
+
[1, 2, 3].each_with_index do |item, idx|
|
72
|
+
res << "%d. %d\n" % [idx + 1, item * item]
|
73
|
+
end
|
74
|
+
?>
|
75
|
+
{{ res }}
|
76
|
+
EOT
|
77
|
+
|
78
|
+
example = HMote.parse(template)
|
79
|
+
|
80
|
+
assert_equal("\n1. 1\n2. 4\n3. 9\n\n", example.call)
|
81
|
+
end
|
82
|
+
|
83
|
+
test "preserve XML directives" do
|
84
|
+
template = (<<-EOT).gsub(/^ /, "")
|
85
|
+
<?xml "hello" ?>
|
86
|
+
EOT
|
87
|
+
|
88
|
+
example = HMote.parse(template)
|
89
|
+
|
90
|
+
assert_equal("<?xml \"hello\" ?>\n", example.call)
|
91
|
+
end
|
92
|
+
|
93
|
+
test "escapes by default" do
|
94
|
+
text = %q(<>&"'/)
|
95
|
+
template = HMote.parse("{{ params[:text] }}")
|
96
|
+
result = template.call(text: text)
|
97
|
+
|
98
|
+
assert_equal("<>&"'/", result)
|
99
|
+
end
|
100
|
+
|
101
|
+
test "no escaping please" do
|
102
|
+
text = %q(<>&"'/)
|
103
|
+
template = HMote.parse("{{! params[:text] }}")
|
104
|
+
result = template.call(text: text)
|
105
|
+
|
106
|
+
assert_equal(text, result)
|
107
|
+
end
|
108
|
+
end
|
data/test/render.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
require "cuba/test"
|
3
|
+
require_relative "../lib/hmote/render"
|
4
|
+
|
5
|
+
Cuba.plugin(HMote::Render)
|
6
|
+
Cuba.settings[:hmote][:views] = "./test/views"
|
7
|
+
|
8
|
+
Cuba.define do
|
9
|
+
def name
|
10
|
+
"App"
|
11
|
+
end
|
12
|
+
|
13
|
+
on "partial" do
|
14
|
+
res.write partial("home")
|
15
|
+
end
|
16
|
+
|
17
|
+
on "view" do
|
18
|
+
res.write view("home", title: "Hello")
|
19
|
+
end
|
20
|
+
|
21
|
+
on "render" do
|
22
|
+
render("home", title: "Hola")
|
23
|
+
end
|
24
|
+
|
25
|
+
on "context" do
|
26
|
+
res.write partial("context")
|
27
|
+
end
|
28
|
+
|
29
|
+
on "absolute" do
|
30
|
+
render("./test/custom_views/custom.mote", title: "Custom")
|
31
|
+
end
|
32
|
+
|
33
|
+
on "absolute_layout" do
|
34
|
+
render("./test/custom_views/custom.mote", title: "Custom Layout")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
scope do
|
39
|
+
test "view renders view with layout" do
|
40
|
+
expected = "<title>Hello</title>\n<h1>Home</h1>"
|
41
|
+
|
42
|
+
get "/view"
|
43
|
+
|
44
|
+
assert last_response.body[expected]
|
45
|
+
end
|
46
|
+
|
47
|
+
test "partial renders view without layout" do
|
48
|
+
get "/partial"
|
49
|
+
|
50
|
+
assert last_response.body["<h1>Home</h1>"]
|
51
|
+
end
|
52
|
+
|
53
|
+
test "render renders view with layout" do
|
54
|
+
get "/render"
|
55
|
+
|
56
|
+
assert last_response.body["<title>Hola</title>\n<h1>Home</h1>"]
|
57
|
+
end
|
58
|
+
|
59
|
+
test "access to application context" do
|
60
|
+
get "/context"
|
61
|
+
|
62
|
+
assert last_response.body["App"]
|
63
|
+
end
|
64
|
+
|
65
|
+
test "use of absolute path for template" do
|
66
|
+
get "/absolute"
|
67
|
+
|
68
|
+
assert last_response.body["<title>Custom</title>\n<h1>Custom</h1>"]
|
69
|
+
end
|
70
|
+
|
71
|
+
test "use of absolute path for layout" do
|
72
|
+
Cuba.settings[:hmote][:layout] = "./test/custom_views/layout.mote"
|
73
|
+
|
74
|
+
get "/absolute_layout"
|
75
|
+
|
76
|
+
assert last_response.body["<title>Custom Layout</title>\n<h1>Custom</h1>"]
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
{{ app.name }}
|
@@ -0,0 +1 @@
|
|
1
|
+
<h1>Home</h1>
|
metadata
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hmote
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Francesco Rodríguez
|
8
|
+
- Mayn Kjær
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2014-
|
12
|
+
date: 2014-12-14 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: hache
|
@@ -41,6 +42,7 @@ dependencies:
|
|
41
42
|
description: A minimum operational template that escapes HTML tags by default.
|
42
43
|
email:
|
43
44
|
- frodsan@me.com
|
45
|
+
- mayn.kjaer@gmail.com
|
44
46
|
executables: []
|
45
47
|
extensions: []
|
46
48
|
extra_rdoc_files: []
|
@@ -48,14 +50,29 @@ files:
|
|
48
50
|
- ".gems"
|
49
51
|
- LICENSE
|
50
52
|
- README.md
|
53
|
+
- benchmarks/.gems
|
54
|
+
- benchmarks/escaping.rb
|
55
|
+
- benchmarks/helper.rb
|
56
|
+
- benchmarks/hmote_helper.rb
|
57
|
+
- benchmarks/rails_helper.rb
|
58
|
+
- benchmarks/safe.rb
|
59
|
+
- benchmarks/templates/erb
|
60
|
+
- benchmarks/templates/mote
|
51
61
|
- hmote.gemspec
|
52
62
|
- lib/hmote.rb
|
63
|
+
- lib/hmote/render.rb
|
53
64
|
- makefile
|
54
|
-
- test/
|
55
|
-
- test/
|
65
|
+
- test/custom_views/custom.mote
|
66
|
+
- test/custom_views/layout.mote
|
67
|
+
- test/foo.mote
|
68
|
+
- test/helper.rb
|
56
69
|
- test/helpers.rb
|
57
|
-
- test/
|
58
|
-
|
70
|
+
- test/parsing.rb
|
71
|
+
- test/render.rb
|
72
|
+
- test/views/context.mote
|
73
|
+
- test/views/home.mote
|
74
|
+
- test/views/layout.mote
|
75
|
+
homepage: https://github.com/harmoni-io/hmote
|
59
76
|
licenses:
|
60
77
|
- MIT
|
61
78
|
metadata: {}
|
data/test/fixtures/basic.mote
DELETED
data/test/hmote.rb
DELETED
@@ -1,151 +0,0 @@
|
|
1
|
-
require_relative "../lib/hmote"
|
2
|
-
|
3
|
-
scope do
|
4
|
-
test "empty lines" do
|
5
|
-
example = Mote.parse("\n\n \n")
|
6
|
-
assert_equal "\n\n \n", example.call
|
7
|
-
end
|
8
|
-
|
9
|
-
test "empty lines with mixed code" do
|
10
|
-
example = Mote.parse("\n% true\n\n% false\n\n")
|
11
|
-
assert_equal "\n\n\n", example.call
|
12
|
-
end
|
13
|
-
|
14
|
-
test "empty lines with control flow" do
|
15
|
-
example = Mote.parse("\n% if true\n\n\n% else\n\n% end\n")
|
16
|
-
assert_equal "\n\n\n", example.call
|
17
|
-
end
|
18
|
-
|
19
|
-
test "control flow without final newline" do
|
20
|
-
example = Mote.parse("\n% if true\n\n\n% else\n\n% end")
|
21
|
-
assert_equal "\n\n\n", example.call
|
22
|
-
end
|
23
|
-
|
24
|
-
test "assignment" do
|
25
|
-
example = Mote.parse("{{ \"***\" }}")
|
26
|
-
assert_equal "***", example.call
|
27
|
-
end
|
28
|
-
|
29
|
-
test "comment" do
|
30
|
-
template = (<<-EOT).gsub(/ {4}/, "")
|
31
|
-
*
|
32
|
-
% # "*"
|
33
|
-
*
|
34
|
-
EOT
|
35
|
-
|
36
|
-
example = Mote.parse(template)
|
37
|
-
assert_equal "*\n*\n", example.call.squeeze("\n")
|
38
|
-
end
|
39
|
-
|
40
|
-
test "control flow" do
|
41
|
-
template = (<<-EOT).gsub(/ {4}/, "")
|
42
|
-
% if false
|
43
|
-
*
|
44
|
-
% else
|
45
|
-
***
|
46
|
-
% end
|
47
|
-
EOT
|
48
|
-
|
49
|
-
example = Mote.parse(template)
|
50
|
-
assert_equal " ***\n", example.call
|
51
|
-
end
|
52
|
-
|
53
|
-
test "block evaluation" do
|
54
|
-
template = (<<-EOT).gsub(/ {4}/, "")
|
55
|
-
% 3.times {
|
56
|
-
*
|
57
|
-
% }
|
58
|
-
EOT
|
59
|
-
|
60
|
-
example = Mote.parse(template)
|
61
|
-
assert_equal "*\n*\n*\n", example.call
|
62
|
-
end
|
63
|
-
|
64
|
-
test "parameters" do
|
65
|
-
template = (<<-EOT).gsub(/ {4}/, "")
|
66
|
-
% params[:n].times {
|
67
|
-
*
|
68
|
-
% }
|
69
|
-
EOT
|
70
|
-
|
71
|
-
example = Mote.parse(template)
|
72
|
-
assert_equal "*\n*\n*\n", example[:n => 3]
|
73
|
-
assert_equal "*\n*\n*\n*\n", example[:n => 4]
|
74
|
-
end
|
75
|
-
|
76
|
-
test "multiline" do
|
77
|
-
example = Mote.parse("The\nMan\nAnd\n{{\"The\"}}\nSea")
|
78
|
-
assert_equal "The\nMan\nAnd\nThe\nSea", example[:n => 3]
|
79
|
-
end
|
80
|
-
|
81
|
-
test "quotes" do
|
82
|
-
example = Mote.parse("'foo' 'bar' 'baz'")
|
83
|
-
assert_equal "'foo' 'bar' 'baz'", example.call
|
84
|
-
end
|
85
|
-
|
86
|
-
test "context" do
|
87
|
-
context = Object.new
|
88
|
-
def context.user; "Bruno"; end
|
89
|
-
|
90
|
-
example = Mote.parse("{{ context.user }}", context, [:context])
|
91
|
-
assert_equal "Bruno", example.call(context: context)
|
92
|
-
end
|
93
|
-
|
94
|
-
test "locals" do
|
95
|
-
example = Mote.parse("{{ user }}", TOPLEVEL_BINDING, [:user])
|
96
|
-
assert_equal "Bruno", example.call(user: "Bruno")
|
97
|
-
end
|
98
|
-
|
99
|
-
test "nil" do
|
100
|
-
example = Mote.parse("{{ params[:user] }}", TOPLEVEL_BINDING, [:user])
|
101
|
-
assert_equal "", example.call(user: nil)
|
102
|
-
end
|
103
|
-
|
104
|
-
test "curly bug" do
|
105
|
-
example = Mote.parse("{{ [1, 2, 3].map { |i| i * i }.join(',') }}")
|
106
|
-
assert_equal "1,4,9", example.call
|
107
|
-
end
|
108
|
-
|
109
|
-
test "multi-line XML-style directives" do
|
110
|
-
template = (<<-EOT).gsub(/^ /, "")
|
111
|
-
<? res = ""
|
112
|
-
[1, 2, 3].each_with_index do |item, idx|
|
113
|
-
res << "%d. %d\n" % [idx + 1, item * item]
|
114
|
-
end
|
115
|
-
?>
|
116
|
-
{{ res }}
|
117
|
-
EOT
|
118
|
-
|
119
|
-
example = Mote.parse(template)
|
120
|
-
assert_equal "\n1. 1\n2. 4\n3. 9\n\n", example.call
|
121
|
-
end
|
122
|
-
|
123
|
-
test "preserve XML directives" do
|
124
|
-
template = (<<-EOT).gsub(/^ /, "")
|
125
|
-
<?xml "hello" ?>
|
126
|
-
EOT
|
127
|
-
|
128
|
-
example = Mote.parse(template)
|
129
|
-
assert_equal "<?xml \"hello\" ?>\n", example.call
|
130
|
-
end
|
131
|
-
|
132
|
-
test "escapes html" do
|
133
|
-
template = (<<-EOT).gsub(/^ /, "")
|
134
|
-
% res = '<img src="img.jpeg">'
|
135
|
-
{{ res }}
|
136
|
-
EOT
|
137
|
-
|
138
|
-
example = Mote.parse(template)
|
139
|
-
assert_equal "<img src="img.jpeg">\n", example.call
|
140
|
-
end
|
141
|
-
|
142
|
-
test "outputs raw html" do
|
143
|
-
template = (<<-EOT).gsub(/^ /, "")
|
144
|
-
% res = '<img src="img.jpeg">'
|
145
|
-
{{> res }}
|
146
|
-
EOT
|
147
|
-
|
148
|
-
example = Mote.parse(template)
|
149
|
-
assert_equal "<img src=\"img.jpeg\">\n", example.call
|
150
|
-
end
|
151
|
-
end
|
File without changes
|