hmote 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|