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 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|", r:each="foos, |foo|"..
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. There isn't much to making 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 and Merb or XYZ framework. See renshi/lib/renshi/frameworks
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
- Installation
149
- ============
150
- Renshi is hosted on Rubyforge so 'sudo gem install renshi'. Alternatively, use github to do what you want with it.
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 nightmarish. A colleague mentioned Genshi in Python as ideal, which was when the idea of Renshi was conceived.
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
  ===================
@@ -0,0 +1,7 @@
1
+ module Renshi
2
+ module Frameworks
3
+ module Generic
4
+ end
5
+ end
6
+ end
7
+
@@ -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^" #maybe replace this with a funky unicode char
9
- STRING_START = "^R_START^" #maybe replace this with a funky unicode char
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 &lt; 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
- doc.children.each do |node|
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 self.transform_node(node)
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
- node.content = compiled if compiled
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
- def self.compile(text)
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
- closing_brace_idx = text.rindex("}")
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 Renshi::StandardError
88
- raise SyntaxError, "No closing brace: #{text[(idx +1)..-1]}", caller
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
- closing_brace_idx = text.rindex("]")
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 Renshi::StandardError
100
- raise SyntaxError, "No closing bracket: #{text[(idx +1)..-1]}", caller
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 self.compile_to_buffer(str)
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 self.compile_print_flags(str)
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
@@ -9,7 +9,7 @@ require 'renshi/attribute_expressions'
9
9
  require 'renshi/frameworks'
10
10
 
11
11
  module Renshi
12
- VERSION="0.0.8"
12
+ VERSION="0.0.9"
13
13
 
14
14
  class SyntaxError < StandardError; end
15
15
  end
@@ -1,4 +1,4 @@
1
1
  <body>
2
- $[$i = 1]
2
+ $[i = 1]
3
3
  <div id='content$i'>hello</div>
4
4
  <body>
@@ -0,0 +1,7 @@
1
+ <html>
2
+ <body>
3
+ <div id="content">
4
+ ${display({:a => "1", :b => "flew", :c => "over", {:d => "the", :e => "cuckoo's"}, :f => "nest"})}
5
+ </div>
6
+ </body>
7
+ </html>
@@ -0,0 +1,11 @@
1
+ <html>
2
+ <body>
3
+ <div id="content">
4
+ ${foo "1", "2"}, ${foo "3", "4"}
5
+ </div>
6
+ <div id="content2">
7
+ $[a = 1], $[b = 2]
8
+ ${a}, $b
9
+ </div>
10
+ </body>
11
+ </html>
@@ -0,0 +1,7 @@
1
+ <html>
2
+ <body>
3
+ <div id="content">
4
+ ${foo "1", "2"}
5
+ </div>
6
+ </body>
7
+ </html>
@@ -0,0 +1,10 @@
1
+ <html>
2
+ <body>
3
+ <div id="content">
4
+ ${foo "1", "2"}
5
+ </div>
6
+ <div id="content2">
7
+ ${foo "a", "2"}
8
+ </div>
9
+ </body>
10
+ </html>
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
@@ -17,8 +17,7 @@ def interpret(file, context)
17
17
  end
18
18
 
19
19
  def deliver_compiled(node)
20
- raw = Renshi::Parser.compile(node.text)
21
- raw = Renshi::Parser.compile_to_buffer(raw)
20
+ raw = Renshi::Parser.parse(node.text)
22
21
  end
23
22
 
24
23
  def N(str)
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.8
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-07-13 00:00:00 +10:00
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.1
74
+ rubygems_version: 1.3.5
75
75
  signing_key:
76
- specification_version: 2
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