renshi 0.0.8 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/README +35 -11
- data/lib/renshi/frameworks/generic.rb +7 -0
- data/lib/renshi/frameworks/rails.rb +2 -0
- data/lib/renshi/parser.rb +80 -24
- data/lib/renshi.rb +1 -1
- data/spec/data/attribute_values_parsed.ren +1 -1
- data/spec/data/inner_braces.ren +7 -0
- data/spec/data/multiple_statements.ren +11 -0
- data/spec/data/quots.ren +7 -0
- data/spec/data/quots2.ren +10 -0
- data/spec/parser_spec.rb +39 -0
- data/spec/spec_helper.rb +1 -2
- metadata +11 -8
data/README
CHANGED
@@ -21,8 +21,7 @@ $[end]
|
|
21
21
|
|
22
22
|
Attribute Expressions
|
23
23
|
===================
|
24
|
-
The $ syntax will give you everything ERB does, with less typing. But Renshi also has 'attribute expressions',
|
25
|
-
which can be inserted into a HTML element as an attribute - e.g. <span r:if="user.is_administrator">...</span>.
|
24
|
+
The $ syntax will give you everything ERB does, with less typing. But Renshi also has 'attribute expressions', which can be inserted into a HTML element as an attribute - e.g. <span r:if="user.is_administrator">...</span>. After Renshi::Parser interprets an attribute expression it removes it from the HTML output; the code is unobtrusive.
|
26
25
|
|
27
26
|
r:expression_name="expression" - the expression value is treated as a Ruby expression .
|
28
27
|
|
@@ -103,7 +102,8 @@ hello99
|
|
103
102
|
* Each
|
104
103
|
The expression takes the object and then any arguments for the each block after a comma; so you can do anything with it that each supports.
|
105
104
|
|
106
|
-
r:each="foos, |k,v|"
|
105
|
+
r:each="foos, |k,v|" to iterate over a hash
|
106
|
+
r:each="foos, |foo|" to iterate over an array
|
107
107
|
|
108
108
|
<div id="content$foo" r:each="foos, |foo|">
|
109
109
|
hello$foo
|
@@ -117,7 +117,11 @@ hello0
|
|
117
117
|
hello99
|
118
118
|
</div>
|
119
119
|
|
120
|
-
Further elements will appear as I have time to make them or others want to contribute them.
|
120
|
+
Further elements will appear as I have time to make them or others want to contribute them.
|
121
|
+
There isn't much to making them. If there's one you'd especially like, let me know on lighthouse and I'll see about adding it.
|
122
|
+
|
123
|
+
The one restriction is that I want to limit attribute expressions to one key and value combination on the element (everything fits into r:foo="expression").
|
124
|
+
I want to avoid complexity like "<span r:foo='..' r:params="k,v" />" for example - see r:each for the workaround.
|
121
125
|
|
122
126
|
See renshi/lib/renshi/attribute_expressions for how they work.
|
123
127
|
|
@@ -133,6 +137,10 @@ Renshi documents are appended with the suffix of .ren
|
|
133
137
|
|
134
138
|
e.g. index.html.ren or index.ren
|
135
139
|
|
140
|
+
Installation
|
141
|
+
============
|
142
|
+
Renshi is hosted on Rubyforge so 'sudo gem install renshi'. Alternatively, use github to do what you want with it.
|
143
|
+
|
136
144
|
|
137
145
|
Framework Integration
|
138
146
|
=====================
|
@@ -142,13 +150,30 @@ Simply require the renshi gem in your code, and Renshi will make itself availabl
|
|
142
150
|
|
143
151
|
There is a Rails 2.3.2 example app in the examples directory. Go there, start up the application, and visit http://localhost:3000/hello
|
144
152
|
|
145
|
-
I'd welcome integrations for Sinatra
|
153
|
+
I'd welcome integrations for Sinatra or Merb or XYZ framework. See renshi/lib/renshi/frameworks (see below - How Fast Is It?)
|
146
154
|
|
147
155
|
|
148
|
-
|
149
|
-
|
150
|
-
|
156
|
+
How Fast Is It? - Compilable Templates
|
157
|
+
======================================
|
158
|
+
I don't know - I've not benchmarked it yet. It's built solely on Nokogiri, which is very fast. To a certain extent it's an irrelevant question, given that Renshi compiles .ren documents into chunks of ruby code, which are kept in memory and reused. This was written specifically for the Rails notion of compilable templates (see ActionView::TemplateHandlers::Compilable and Renshi::Frameworks::Rails).
|
159
|
+
|
160
|
+
ERB and HAML also do this (in fact I worked from the source code of both). It means, essentially, that a foo.html.ren document is compiled once into ruby, and each presentation of the template is then a matter of using the generated code (not reading the template).
|
161
|
+
|
162
|
+
Thus, n requests for the template mean 1 call to Renshi to generate the necessary code, which is then reused. At some stage I might benchmark this raw ruby - but I've written it with an eye on efficiency as it's generated and so far it seems very fast. If someone enjoys benchmarking then please jump in.
|
163
|
+
|
164
|
+
When integrating this feature with other frameworks, simply use a delegate object to hold the compiled code and evaluate it against the binding you want to use (see the spec.s for this).
|
165
|
+
|
166
|
+
E.g. some helper methods in the spec_helper.rb might get you started. For the integration you'd keep memory of which files had been compiled (only call compile_file once).
|
167
|
+
|
168
|
+
def compile_file(file)
|
169
|
+
compiled = Renshi::Parser.parse(read_file(file))
|
170
|
+
end
|
171
|
+
|
172
|
+
def interpret(file, context)
|
173
|
+
eval(compile_file(file), context)
|
174
|
+
end
|
151
175
|
|
176
|
+
rendered_template = interpret("index.html.ren", binding)
|
152
177
|
|
153
178
|
Development
|
154
179
|
===========
|
@@ -164,10 +189,9 @@ But ...
|
|
164
189
|
|
165
190
|
I've always found ERB to be a bit cumbersome - <%= is quite tiring to type out when you realise it could be much shorter. I used to think that Velocity, in Java, was the most fun templating language I'd used, and I had wanted something equally as concise for Ruby.
|
166
191
|
|
167
|
-
A real need for it emerged in a project which relied upon an external design house handing us HTML. Converting it incessantly into HAML was
|
192
|
+
A real need for it emerged in a project which relied upon an external design house handing us HTML. Converting it incessantly into HAML was a headache. A colleague mentioned Genshi in Python as ideal, which was when the idea of Renshi was conceived.
|
168
193
|
|
169
|
-
It should give the developer power to do what they want, without being restrictive. You can throw $ statements around the place in a document
|
170
|
-
or use attribute expressions to preserve a strict xhtml layout.
|
194
|
+
It should give the developer power to do what they want, without being restrictive. You can throw $ statements around the place in a document or use attribute expressions to preserve a strict xhtml layout.
|
171
195
|
|
172
196
|
English to Japanese
|
173
197
|
===================
|
@@ -2,6 +2,8 @@ module Renshi
|
|
2
2
|
module Frameworks
|
3
3
|
module Rails
|
4
4
|
if defined? ActionView::TemplateHandler
|
5
|
+
#this is an old, first try of integrating with Rails, without compilable templates
|
6
|
+
#use it for older Rails versions (pre 2.2.0, I think).
|
5
7
|
class Plugin < ActionView::TemplateHandler
|
6
8
|
def self.call(template)
|
7
9
|
"#{name}.new(self).render(template, local_assigns)"
|
data/lib/renshi/parser.rb
CHANGED
@@ -5,11 +5,13 @@ module Renshi
|
|
5
5
|
# the document, which are finally compiled into Ruby.
|
6
6
|
|
7
7
|
class Parser
|
8
|
-
STRING_END = "^R_END
|
9
|
-
STRING_START = "^R_START
|
8
|
+
STRING_END = "^R_END" #maybe replace this with a funky unicode char
|
9
|
+
STRING_START = "^R_START" #maybe replace this with a funky unicode char
|
10
10
|
BUFFER_CONCAT_OPEN = "@output_buffer.concat(\""
|
11
11
|
BUFFER_CONCAT_CLOSE = "\");"
|
12
12
|
NEW_LINE = "@output_buffer.concat('\n');"
|
13
|
+
INSTRUCTION_START = "^R_INSTR_IDX_START^"
|
14
|
+
INSTRUCTION_END = "^R_INSTR_IDX_END^"
|
13
15
|
|
14
16
|
#these symbols cannot be normally escaped, as we need to differentiate between < as an
|
15
17
|
#escaped string, to be left in the document, and < as a boolean operator
|
@@ -19,18 +21,31 @@ module Renshi
|
|
19
21
|
|
20
22
|
def self.parse(xhtml)
|
21
23
|
doc = Nokogiri::HTML.fragment(xhtml)
|
22
|
-
|
23
|
-
|
24
|
+
|
25
|
+
parser = self.new(doc)
|
26
|
+
out = parser.parse
|
27
|
+
|
28
|
+
return out
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(nokogiri_node)
|
32
|
+
@doc = nokogiri_node
|
33
|
+
@instructions = []
|
34
|
+
@instr_idx = 0
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse
|
38
|
+
@doc.children.each do |node|
|
24
39
|
transform_node(node)
|
25
40
|
end
|
26
|
-
|
27
|
-
inner_html = doc.inner_html
|
41
|
+
|
42
|
+
inner_html = @doc.inner_html
|
28
43
|
compiled = compile_to_buffer(inner_html) if inner_html
|
29
44
|
# puts "\n", compiled, "\n"
|
30
45
|
return compiled
|
31
46
|
end
|
32
47
|
|
33
|
-
def
|
48
|
+
def transform_node(node)
|
34
49
|
if node.attributes
|
35
50
|
expressions = node.commands
|
36
51
|
for expression in expressions
|
@@ -51,13 +66,20 @@ module Renshi
|
|
51
66
|
#compile text in nodes, e.g. <p>*</p>
|
52
67
|
if node.text?
|
53
68
|
compiled = compile(node.text)
|
54
|
-
|
69
|
+
if compiled
|
70
|
+
@instructions << compiled
|
71
|
+
key = "#{INSTRUCTION_START}#{@instr_idx}#{INSTRUCTION_END}"
|
72
|
+
@instr_idx = @instr_idx + 1
|
73
|
+
# node.content = compiled if compiled
|
74
|
+
node.content = key
|
75
|
+
end
|
55
76
|
end
|
56
77
|
|
57
78
|
node.children.each {|child| transform_node(child)}
|
58
79
|
end
|
59
|
-
|
60
|
-
|
80
|
+
|
81
|
+
|
82
|
+
def compile(text)
|
61
83
|
idx = text.index("$")
|
62
84
|
return text if idx.nil?
|
63
85
|
|
@@ -66,8 +88,10 @@ module Renshi
|
|
66
88
|
|
67
89
|
while idx != nil do
|
68
90
|
next_char = text[(idx + 1)..(idx + 1)]
|
69
|
-
|
70
|
-
if next_char == "
|
91
|
+
|
92
|
+
if next_char == " "
|
93
|
+
raise SyntaxError, "Floating $ - use $$ to output '$': #{text[(idx +1)..-1]}", caller
|
94
|
+
elsif next_char == "("
|
71
95
|
#this may be jquery, etc. $(...)
|
72
96
|
end_statement_idx = (idx + 2)
|
73
97
|
bits << text[idx..(idx + 1)]
|
@@ -79,25 +103,28 @@ module Renshi
|
|
79
103
|
#${...}
|
80
104
|
elsif next_char == "{"
|
81
105
|
begin
|
82
|
-
|
106
|
+
#scan for the next $ in this block
|
107
|
+
closing_brace_idx = close_of_phrase_ending_with("}", text, idx)
|
83
108
|
statement_str = text[(idx + 2)..(closing_brace_idx -1)]
|
84
109
|
statement = Renshi::Statement.new(statement_str)
|
85
110
|
bits << statement.compile_to_print!
|
86
111
|
end_statement_idx = closing_brace_idx + 1
|
87
|
-
rescue
|
88
|
-
raise SyntaxError, "No closing brace: #{text
|
112
|
+
rescue StandardError
|
113
|
+
raise SyntaxError, "No closing brace: #{text}", caller
|
89
114
|
end
|
90
115
|
|
91
116
|
#$[...]
|
92
117
|
elsif next_char == "["
|
93
118
|
begin
|
94
|
-
|
119
|
+
|
120
|
+
# closing_brace_idx = text.rindex("]")
|
121
|
+
closing_brace_idx = close_of_phrase_ending_with("]", text, idx)
|
95
122
|
statement_str = text[(idx + 2)..(closing_brace_idx -1)]
|
96
123
|
statement = Renshi::Statement.new(statement_str)
|
97
124
|
bits << statement.compile_to_expression!
|
98
125
|
end_statement_idx = closing_brace_idx + 1
|
99
|
-
rescue
|
100
|
-
raise SyntaxError, "No closing bracket: #{text
|
126
|
+
rescue StandardError
|
127
|
+
raise SyntaxError, "No closing bracket: #{text}", caller
|
101
128
|
end
|
102
129
|
else #$foo
|
103
130
|
words = text[(idx +1)..-1].split(/\s/)
|
@@ -107,7 +134,7 @@ module Renshi
|
|
107
134
|
bits << statement.compile_to_print!
|
108
135
|
end_statement_idx = (words.first.length) + 1 + idx
|
109
136
|
end
|
110
|
-
|
137
|
+
|
111
138
|
next_statement_idx = text.index("$", end_statement_idx)
|
112
139
|
|
113
140
|
if next_statement_idx
|
@@ -118,26 +145,55 @@ module Renshi
|
|
118
145
|
end
|
119
146
|
idx = next_statement_idx
|
120
147
|
end
|
121
|
-
|
148
|
+
|
122
149
|
return bits.join
|
123
150
|
end
|
151
|
+
|
124
152
|
|
125
|
-
def
|
153
|
+
def close_of_phrase_ending_with(char, text, idx)
|
154
|
+
phrase_end = (text[(idx + 1)..-1].index("$"))
|
155
|
+
|
156
|
+
if phrase_end.nil?
|
157
|
+
phrase_end = text.length
|
158
|
+
else
|
159
|
+
phrase_end = phrase_end + idx
|
160
|
+
end
|
161
|
+
closing_brace_idx = text[idx...phrase_end].rindex(char) + idx
|
162
|
+
return closing_brace_idx
|
163
|
+
end
|
164
|
+
|
165
|
+
def compile_to_buffer(str)
|
126
166
|
compiled = "@output_buffer ='';" << BUFFER_CONCAT_OPEN
|
127
167
|
str = compile_print_flags(str)
|
128
168
|
compiled = "#{compiled}#{str}" << BUFFER_CONCAT_CLOSE
|
129
169
|
end
|
130
170
|
|
131
|
-
def
|
171
|
+
def compile_print_flags(str)
|
132
172
|
#now we parse for RENSHI::STRING_END and RENSHI::STRING_START
|
133
173
|
#and ensure natural strings are buffered
|
134
174
|
str.gsub!("\"", "\\\"")
|
135
|
-
str.gsub!(STRING_END, BUFFER_CONCAT_CLOSE)
|
136
|
-
str.gsub!(STRING_START, BUFFER_CONCAT_OPEN)
|
137
175
|
str.gsub!(XML_GT, ">")
|
138
176
|
str.gsub!(XML_LT, "<")
|
139
177
|
str.gsub!(XML_AMP, "&")
|
140
178
|
|
179
|
+
#restore instructions in the string
|
180
|
+
bits = []
|
181
|
+
str.split(INSTRUCTION_START).each do |bit|
|
182
|
+
if bit.index(INSTRUCTION_END)
|
183
|
+
index = bit[0..0]
|
184
|
+
instruction = @instructions[index.to_i]
|
185
|
+
|
186
|
+
bit.gsub!("#{index}#{INSTRUCTION_END}", instruction)
|
187
|
+
end
|
188
|
+
|
189
|
+
bits << bit
|
190
|
+
end
|
191
|
+
|
192
|
+
str = bits.join
|
193
|
+
|
194
|
+
str.gsub!(STRING_END, BUFFER_CONCAT_CLOSE)
|
195
|
+
str.gsub!(STRING_START, BUFFER_CONCAT_OPEN)
|
196
|
+
|
141
197
|
return str
|
142
198
|
end
|
143
199
|
end
|
data/lib/renshi.rb
CHANGED
data/spec/data/quots.ren
ADDED
data/spec/parser_spec.rb
CHANGED
@@ -33,4 +33,43 @@ describe Renshi::Parser do
|
|
33
33
|
html = N(html)
|
34
34
|
(html/"div[@id='content1']").text.strip.should =~ /hello/
|
35
35
|
end
|
36
|
+
|
37
|
+
def foo(one, two, three = {})
|
38
|
+
"#{one}, #{two}"
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should understand double quotations marks within ruby code!" do
|
42
|
+
#${link_to "alter this template", edit_cms_page_template_path(PageTemplate.find_by_file_name("default.html.erb"))}
|
43
|
+
raw = compile_file("data/quots.ren")
|
44
|
+
html = eval(raw, binding)
|
45
|
+
html = N(html)
|
46
|
+
(html/"div[@id='content']").text.strip.should =~ /1, 2/
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should understand double quotations marks within ruby code!" do
|
50
|
+
#${link_to "alter this template", edit_cms_page_template_path(PageTemplate.find_by_file_name("default.html.erb"))}
|
51
|
+
raw = compile_file("data/quots2.ren")
|
52
|
+
html = eval(raw, binding)
|
53
|
+
html = N(html)
|
54
|
+
(html/"div[@id='content']").text.strip.should =~ /1, 2/
|
55
|
+
(html/"div[@id='content2']").text.strip.should =~ /a, 2/
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should understand double quotations marks within ruby code! 2" do
|
59
|
+
doc = Nokogiri::HTML(%Q!<p>${foo "1", foo("3", "4")}</p>!)
|
60
|
+
|
61
|
+
puts doc.root
|
62
|
+
|
63
|
+
body = doc.root.children.first
|
64
|
+
node = body.children.first
|
65
|
+
eval(deliver_compiled(node), binding).should eql "1, 3, 4"
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should understand multiple statements on the same line" do
|
69
|
+
raw = compile_file("data/multiple_statements.ren")
|
70
|
+
html = eval(raw, binding)
|
71
|
+
html = N(html)
|
72
|
+
(html/"div[@id='content']").text.strip.should =~ /1, 2, 3, 4/
|
73
|
+
(html/"div[@id='content2']").text.strip.should =~ /1, 2/
|
74
|
+
end
|
36
75
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: renshi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nicholas Faiz
|
@@ -9,7 +9,7 @@ autorequire: renshi
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-08-11 00:00:00 +10:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -31,8 +31,6 @@ extensions: []
|
|
31
31
|
extra_rdoc_files:
|
32
32
|
- README
|
33
33
|
files:
|
34
|
-
- lib/renshi
|
35
|
-
- lib/renshi/attribute_expressions
|
36
34
|
- lib/renshi/attribute_expressions/each.rb
|
37
35
|
- lib/renshi/attribute_expressions/else.rb
|
38
36
|
- lib/renshi/attribute_expressions/elsif.rb
|
@@ -41,7 +39,7 @@ files:
|
|
41
39
|
- lib/renshi/attribute_expressions/unless.rb
|
42
40
|
- lib/renshi/attribute_expressions/while.rb
|
43
41
|
- lib/renshi/attribute_expressions.rb
|
44
|
-
- lib/renshi/frameworks
|
42
|
+
- lib/renshi/frameworks/generic.rb
|
45
43
|
- lib/renshi/frameworks/rails.rb
|
46
44
|
- lib/renshi/frameworks.rb
|
47
45
|
- lib/renshi/node.rb
|
@@ -51,6 +49,8 @@ files:
|
|
51
49
|
- README
|
52
50
|
has_rdoc: true
|
53
51
|
homepage: http://treefallinginthewoods.com
|
52
|
+
licenses: []
|
53
|
+
|
54
54
|
post_install_message:
|
55
55
|
rdoc_options: []
|
56
56
|
|
@@ -71,12 +71,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
71
71
|
requirements: []
|
72
72
|
|
73
73
|
rubyforge_project:
|
74
|
-
rubygems_version: 1.3.
|
74
|
+
rubygems_version: 1.3.5
|
75
75
|
signing_key:
|
76
|
-
specification_version:
|
76
|
+
specification_version: 3
|
77
77
|
summary: Renshi is a lightweight XHTML template language, inspired by Python's Genshi and build on Nokogiri.
|
78
78
|
test_files:
|
79
|
-
- spec/data
|
80
79
|
- spec/data/attribute_values_parsed.ren
|
81
80
|
- spec/data/for.ren
|
82
81
|
- spec/data/hello_world1.ren
|
@@ -85,6 +84,10 @@ test_files:
|
|
85
84
|
- spec/data/if_3.ren
|
86
85
|
- spec/data/ifelse.ren
|
87
86
|
- spec/data/ifelsifelse.ren
|
87
|
+
- spec/data/inner_braces.ren
|
88
|
+
- spec/data/multiple_statements.ren
|
89
|
+
- spec/data/quots.ren
|
90
|
+
- spec/data/quots2.ren
|
88
91
|
- spec/data/while.ren
|
89
92
|
- spec/data/while_lt.ren
|
90
93
|
- spec/data/white_space.ren
|