nagoro 2009.05 → 2013.03

Sign up to get free protection for your applications and to get access to all the features.
data/MANIFEST CHANGED
@@ -1,3 +1,4 @@
1
+ AUTHORS
1
2
  CHANGELOG
2
3
  MANIFEST
3
4
  README.markdown
@@ -39,12 +40,10 @@ spec/nagoro/pipe/tidy.rb
39
40
  spec/nagoro/template.rb
40
41
  spec/nagoro/template/full.nag
41
42
  spec/nagoro/template/hello.nag
43
+ tasks/authors.rake
42
44
  tasks/bacon.rake
43
45
  tasks/changelog.rake
44
46
  tasks/gem.rake
45
- tasks/gem_installer.rake
46
- tasks/grancher.rake
47
- tasks/install_dependencies.rake
48
47
  tasks/manifest.rake
49
48
  tasks/rcov.rake
50
49
  tasks/release.rake
@@ -3,14 +3,17 @@
3
3
 
4
4
  # About Nagoro
5
5
 
6
- Nagoro is a templating engine for XHTML based on different parsing engines.
7
- It featues a modular code layout and is used in the Ramaze web framework.
6
+ Nagoro is a templating engine for HTML and XML, consequently also for XHTML.
8
7
 
9
8
  Nagoro consists of a series of so-called pipes to produce valid ruby from the
10
9
  templates that are eventually evaluated with custom binding.
11
10
 
12
11
  All functionality of Nagoro is carefully tested by a series of specs to avoid
13
- breakage and give a good overview of nagoros capabilities.
12
+ breakage and give a good overview of Nagoro's capabilities.
13
+
14
+ Parsing is done by a fine-tuned StringScanner that avoids actually checking
15
+ validity of the documents, this way you can use Ruby interpolation without
16
+ having to enclose it into CDATA sections.
14
17
 
15
18
  # Features Overview
16
19
 
@@ -157,6 +160,55 @@ And of course the same works for `Nagoro::render`.
157
160
  Examples can be found in the /example directory.
158
161
 
159
162
 
163
+ # Future plans
164
+
165
+ ## Various backends
166
+
167
+ Using Nokogiri, Hpricot, REXML, or libxml-ruby as backends for Nagoro.
168
+
169
+ I actually implemented backends in REXML and libxml-ruby in previous versions
170
+ of Nagoro, but their insistence on well-formed markup made them unsuitable for
171
+ the style of interpolation required.
172
+
173
+ As a quick example, given a document that contains `<h1>#{1 < 10}</h1>`.
174
+
175
+ Nokogiri would produce `<h1>#{1</h1>` in HTML mode. In XML mode it will produce
176
+ `<h1>#{1 10}</h1>`. Both are useless interpretations of the document.
177
+
178
+ The libxml-ruby binding doesn't allow for relaxed parsing, and will fail to
179
+ parse. Since it's just a thin binding, there is no way to monkeypatch the
180
+ underlying parser, while it might still be possible to achieve something
181
+ similar using FFI or DL, the way that Nokogiri parses leaves me little hope
182
+ that I could actually bend it to my will.
183
+
184
+ REXML usually fails as well, and the monkeypatching required to make it parse
185
+ the input document would far exceed the amount of code required for our custom
186
+ parser, not to mention that the speed of REXML would still be very hard to
187
+ tolerate, the only argument for it seems to be that it's in stdlib.
188
+
189
+ Hpricot on the other hand doesn't provide a SaX API, and so isn't suited very
190
+ well to the pipe style of Nagoro, but is still the best candidate as it will
191
+ parse Nagoro documents correctly (in most cases).
192
+
193
+ That's just a quick run-down, I spent almost 2 weeks banging my head against
194
+ REXML and libxml-ruby.
195
+ The reason I avoided Hpricot is that I don't want to have a full Node
196
+ representation in memory, it's already questionable if having the whole
197
+ document as a String in memory is a good design choice.
198
+
199
+ ## Stream parsing
200
+
201
+ This is impossible with StringScanner, it is hard-wired to check that you
202
+ actually give it a String (in C via the StringValue macro).
203
+ It would be possible with real SaX parser, but due to their limitations pointed
204
+ out above, we cannot use them.
205
+
206
+ An option would be to implement a parser in pure Ruby.
207
+ I will leave that to other people, the documents I process fit comfortably into
208
+ memory, and if you are processing larger documents, you will most likely have
209
+ to utilize something like XSLT.
210
+
211
+
160
212
  # And thanks to...
161
213
 
162
214
  This list is by no means a full listing of all these people, but I try to
@@ -174,7 +226,7 @@ get a good coverage despite that.
174
226
  * George Moschovitis a.k.a. gmosx
175
227
 
176
228
  For the Nitro web framework. Its templating engine has been the inspiration
177
- for nagoro.
229
+ for Nagoro.
178
230
 
179
231
  * Jonathan Buch a.k.a. Kashia
180
232
 
data/Rakefile CHANGED
@@ -1,12 +1,11 @@
1
- require 'rake'
2
1
  require 'rake/clean'
3
- require 'rake/gempackagetask'
2
+ require 'rubygems/package_task'
4
3
  require 'time'
5
4
  require 'date'
6
5
 
7
6
  PROJECT_SPECS = Dir['spec/{nagoro,example}/**/*.rb']
8
7
  PROJECT_MODULE = 'Nagoro'
9
- PROJECT_VERSION = ENV['VERSION'] || Date.today.strftime("%Y.%m.%d")
8
+ PROJECT_VERSION = (ENV['VERSION'] || Date.today.strftime('%Y.%m.%d')).dup
10
9
 
11
10
  GEMSPEC = Gem::Specification.new{|s|
12
11
  s.name = 'nagoro'
@@ -6,12 +6,12 @@
6
6
  <?r list = [true, false, true] ?>
7
7
 
8
8
  True items in list:
9
- <div for="cond in list">
9
+ <div foreach="cond in list">
10
10
  <h1 if="cond">#{cond}</h1>
11
11
  </div>
12
12
 
13
13
  False items in list:
14
- <div for="cond in list">
14
+ <div foreach="cond in list">
15
15
  <h1 unless="cond">#{cond}</h1>
16
16
  </div>
17
17
 
@@ -1,9 +1,6 @@
1
1
  module Nagoro
2
2
  module Pipe
3
3
  class Base
4
- EMPTY_TAG = %w[ area base basefont br col frame hr
5
- img input isindex link meta param ]
6
-
7
4
  def initialize(io)
8
5
  @body, @stack = [], []
9
6
  @scanner = Scanner.new(io, self)
@@ -14,21 +11,16 @@ module Nagoro
14
11
  @body.join
15
12
  end
16
13
 
14
+ def tag(tag, original_attrs, value_attrs)
15
+ append "#{tag_with(tag, original_attrs)} />"
16
+ end
17
+
17
18
  def tag_start(tag, original_attrs, value_attrs)
18
- case tag
19
- when *EMPTY_TAG
20
- append "#{tag_with(tag, original_attrs)} />"
21
- else
22
- append "#{tag_with(tag, original_attrs)}>"
23
- end
19
+ append "#{tag_with(tag, original_attrs)}>"
24
20
  end
25
21
 
26
22
  def tag_end(tag)
27
- case tag
28
- when *EMPTY_TAG
29
- else
30
- append "</#{tag}>"
31
- end
23
+ append "</#{tag}>"
32
24
  end
33
25
 
34
26
  def text(string)
@@ -44,7 +36,7 @@ module Nagoro
44
36
  end
45
37
 
46
38
  def tag_with(tag, hash)
47
- "<#{tag}#{hash.map{|k,v| %( #{k}=#{v}) }.join}"
39
+ "<#{tag}#{hash.map{|k,v| (v == nil) ? %( #{k}) : %( #{k}=#{v}) }.join}"
48
40
  end
49
41
 
50
42
  def doctype(string)
@@ -14,6 +14,29 @@ module Nagoro
14
14
  class ElementStruct < Struct.new(:tag, :attrs, :element, :content)
15
15
  end
16
16
 
17
+ def tag(tag, original_attrs, value_attrs)
18
+ if element = ELEMENTS[tag]
19
+ estruct = ElementStruct.new(tag, value_attrs, element, [])
20
+ elsif tag =~ /^[A-Z]/
21
+ warn "Element: '<#{tag}>' not found."
22
+ super
23
+ else
24
+ super
25
+ end
26
+
27
+ if estruct
28
+ attrs, element, content = estruct.values_at(1..3)
29
+
30
+ if element.respond_to?(:call)
31
+ append element.call(content.join, attrs)
32
+ else
33
+ instance = element.new(content.join)
34
+ instance.params = translate_attrs(instance, estruct.attrs)
35
+ append instance.render
36
+ end
37
+ end
38
+ end
39
+
17
40
  def tag_start(tag, original_attrs, value_attrs)
18
41
  if element = ELEMENTS[tag]
19
42
  @stack << ElementStruct.new(tag, value_attrs, element, [])
@@ -12,6 +12,15 @@ module Nagoro
12
12
  # <include src="some_file.xhtml" />
13
13
 
14
14
  class Include < Base
15
+ def tag(tag, original_attrs, value_attrs)
16
+ if tag == 'include'
17
+ filename = value_attrs['href'] || value_attrs.fetch('src')
18
+ append contents(filename)
19
+ else
20
+ super
21
+ end
22
+ end
23
+
15
24
  def tag_start(tag, original_attrs, value_attrs)
16
25
  if tag == 'include'
17
26
  filename = value_attrs['href'] || value_attrs.fetch('src')
@@ -49,7 +49,7 @@ module Nagoro
49
49
  MORPHS = {
50
50
  'each' => [ '<?r %expression.%morph do |_e| ?>', '<?r end ?>' ],
51
51
  'times' => [ '<?r %expression.%morph do |_t| ?>', '<?r end ?>' ],
52
- 'filter' => [ '<?o %expression(%<', '>) ?>' ],
52
+ 'filter' => [ '<?r %expression(%<', '>) ?>' ],
53
53
  'if' => [ '<?r %morph %expression ?>', '<?r end ?>' ],
54
54
  'unless' => [ '<?r %morph %expression ?>', '<?r end ?>' ],
55
55
  'foreach' => [ '<?r for %expression ?>', '<?r end ?>' ],
@@ -2,14 +2,16 @@ require 'strscan'
2
2
 
3
3
  module Nagoro
4
4
  class Scanner < StringScanner
5
- TEXT = /[^<>]+/m
5
+ TEXT = /[^<>#]+/m
6
+ HASH_TEXT = /#[^\{<]#{TEXT}/m
7
+ HASH_CHAR = /#/
6
8
  DOCTYPE = /<!DOCTYPE([^>]+)>/m
7
9
 
8
10
  TAG_START = /<([^\s>]+)/
9
11
  TAG_END = /<\/([^>]*)>/
10
12
  TAG_OPEN_END = /\s*>/
11
13
  TAG_CLOSING_END = /\s*\/>/
12
- TAG_PARAMETER = /\s*([^\s]*)=((['"])(.*?)\3)/um
14
+ TAG_PARAMETER = /\s*([^\s=]*)=((['"])(.*?)\3)/um
13
15
 
14
16
  INSTRUCTION_START = /<\?(\S+)/
15
17
  INSTRUCTION_END = /(.*?)(\?>)/um
@@ -18,6 +20,7 @@ module Nagoro
18
20
  RUBY_INTERP_TEXT = /[^\{\}]+/m
19
21
  RUBY_INTERP_NEST = /\{[^\}]*\}/m
20
22
  RUBY_INTERP_END = /(?=\})/
23
+ RUBY_TAG_INTERP = /\s*(#\{.*?\})/m
21
24
 
22
25
  COMMENT = /<!--.*?-->/m
23
26
 
@@ -42,6 +45,7 @@ module Nagoro
42
45
  elsif scan(RUBY_INTERP_START); ruby_interp(matched)
43
46
  elsif scan(TAG_START ); tag_start(self[1])
44
47
  elsif scan(TEXT ); text(matched)
48
+ elsif scan(HASH_CHAR ); hash_char(matched)
45
49
  end
46
50
  end
47
51
 
@@ -70,14 +74,22 @@ module Nagoro
70
74
  original_attrs = {}
71
75
  value_attrs = {}
72
76
 
73
- while scan(TAG_PARAMETER)
74
- original_attrs[self[1]] = self[2] # <a href="foo"> gives 'href'=>'"foo"'
75
- value_attrs[ self[1]] = self[4] # <a href="foo"> gives 'href'=>'foo'
77
+ done = false
78
+ while not done
79
+ if scan(RUBY_TAG_INTERP)
80
+ original_attrs[self[1]] = nil
81
+ value_attrs[self[1]] = nil
82
+ elsif scan(TAG_PARAMETER)
83
+ original_attrs[self[1]] = self[2] # <a href="foo"> gives 'href'=>'"foo"'
84
+ value_attrs[ self[1]] = self[4] # <a href="foo"> gives 'href'=>'foo'
85
+ elsif scan(TAG_CLOSING_END)
86
+ @callback.tag(name, original_attrs, value_attrs)
87
+ done = true
88
+ elsif scan(TAG_OPEN_END)
89
+ @callback.tag_start(name, original_attrs, value_attrs)
90
+ done = true
91
+ end
76
92
  end
77
-
78
- @callback.tag_start(name, original_attrs, value_attrs)
79
- return @callback.tag_end(name) if scan(TAG_CLOSING_END)
80
- scan(TAG_OPEN_END)
81
93
  end
82
94
 
83
95
  def tag_end(name)
@@ -85,6 +97,13 @@ module Nagoro
85
97
  end
86
98
 
87
99
  def text(string)
100
+ if scan(HASH_TEXT)
101
+ string << matched
102
+ end
103
+ @callback.text(string)
104
+ end
105
+
106
+ def hash_char(string)
88
107
  @callback.text(string)
89
108
  end
90
109
 
@@ -50,6 +50,8 @@ module Nagoro
50
50
  @pipes.each do |pipe|
51
51
  if pipe.respond_to?(:new)
52
52
  io = pipe.new(io).result
53
+ elsif pipe.respond_to?(:call)
54
+ io = pipe.call(io)
53
55
  else
54
56
  io = Pipe.const_get(pipe).new(io).result
55
57
  end
@@ -67,10 +69,9 @@ module Nagoro
67
69
 
68
70
  if vars = @variables
69
71
  obj = eval('self', @binding)
70
- obj.instance_variable_set('@_nagoro_ivs', vars)
71
- eval(%q(
72
- @_nagoro_ivs.each{|key, value| instance_variable_set("@#{key}", value) }
73
- ), @binding)
72
+ vars.each do |key, value|
73
+ obj.instance_variable_set("@#{key}", value)
74
+ end
74
75
  end
75
76
 
76
77
  eval(@compiled, @binding, @file, @line).strip
@@ -1,3 +1,3 @@
1
1
  module Nagoro
2
- VERSION = "2009.05"
2
+ VERSION = "2013.03"
3
3
  end
@@ -1,26 +1,24 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
3
  Gem::Specification.new do |s|
4
- s.name = %q{nagoro}
5
- s.version = "2009.05"
4
+ s.name = "nagoro"
5
+ s.version = "2013.03"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Michael 'manveru' Fellinger"]
9
- s.date = %q{2009-05-07}
10
- s.description = %q{An extendible and fast templating engine in pure ruby.}
11
- s.email = %q{m.fellinger@gmail.com}
12
- s.files = ["CHANGELOG", "MANIFEST", "README.markdown", "Rakefile", "bin/nagoro", "doc/COPYING", "doc/GPL", "doc/LEGAL", "example/element/Html.nage", "example/hello.nag", "example/morpher.nag", "lib/nagoro.rb", "lib/nagoro/binding.rb", "lib/nagoro/element.rb", "lib/nagoro/pipe.rb", "lib/nagoro/pipe/base.rb", "lib/nagoro/pipe/compile.rb", "lib/nagoro/pipe/element.rb", "lib/nagoro/pipe/include.rb", "lib/nagoro/pipe/instruction.rb", "lib/nagoro/pipe/localization.rb", "lib/nagoro/pipe/morph.rb", "lib/nagoro/pipe/tidy.rb", "lib/nagoro/scanner.rb", "lib/nagoro/template.rb", "lib/nagoro/tidy.rb", "lib/nagoro/version.rb", "nagoro.gemspec", "spec/core_extensions.rb", "spec/example/hello.rb", "spec/helper.rb", "spec/nagoro/listener/base.rb", "spec/nagoro/pipe/compile.rb", "spec/nagoro/pipe/element.rb", "spec/nagoro/pipe/include.rb", "spec/nagoro/pipe/instruction.rb", "spec/nagoro/pipe/morph.rb", "spec/nagoro/pipe/tidy.rb", "spec/nagoro/template.rb", "spec/nagoro/template/full.nag", "spec/nagoro/template/hello.nag", "tasks/bacon.rake", "tasks/changelog.rake", "tasks/gem.rake", "tasks/gem_installer.rake", "tasks/grancher.rake", "tasks/install_dependencies.rake", "tasks/manifest.rake", "tasks/rcov.rake", "tasks/release.rake", "tasks/reversion.rake"]
13
- s.has_rdoc = true
14
- s.homepage = %q{http://github.com/manveru/nagoro}
9
+ s.date = "2013-03-22"
10
+ s.description = "An extendible and fast templating engine in pure ruby."
11
+ s.email = "m.fellinger@gmail.com"
12
+ s.files = ["AUTHORS", "CHANGELOG", "MANIFEST", "README.markdown", "Rakefile", "bin/nagoro", "doc/COPYING", "doc/GPL", "doc/LEGAL", "example/element/Html.nage", "example/hello.nag", "example/morpher.nag", "lib/nagoro.rb", "lib/nagoro/binding.rb", "lib/nagoro/element.rb", "lib/nagoro/pipe.rb", "lib/nagoro/pipe/base.rb", "lib/nagoro/pipe/compile.rb", "lib/nagoro/pipe/element.rb", "lib/nagoro/pipe/include.rb", "lib/nagoro/pipe/instruction.rb", "lib/nagoro/pipe/localization.rb", "lib/nagoro/pipe/morph.rb", "lib/nagoro/pipe/tidy.rb", "lib/nagoro/scanner.rb", "lib/nagoro/template.rb", "lib/nagoro/tidy.rb", "lib/nagoro/version.rb", "nagoro.gemspec", "spec/core_extensions.rb", "spec/example/hello.rb", "spec/helper.rb", "spec/nagoro/listener/base.rb", "spec/nagoro/pipe/compile.rb", "spec/nagoro/pipe/element.rb", "spec/nagoro/pipe/include.rb", "spec/nagoro/pipe/instruction.rb", "spec/nagoro/pipe/morph.rb", "spec/nagoro/pipe/tidy.rb", "spec/nagoro/template.rb", "spec/nagoro/template/full.nag", "spec/nagoro/template/hello.nag", "tasks/authors.rake", "tasks/bacon.rake", "tasks/changelog.rake", "tasks/gem.rake", "tasks/manifest.rake", "tasks/rcov.rake", "tasks/release.rake", "tasks/reversion.rake"]
13
+ s.homepage = "http://github.com/manveru/nagoro"
15
14
  s.require_paths = ["lib"]
16
- s.rubygems_version = %q{1.3.2}
17
- s.summary = %q{An extendible and fast templating engine in pure ruby.}
15
+ s.rubygems_version = "1.8.25"
16
+ s.summary = "An extendible and fast templating engine in pure ruby."
18
17
 
19
18
  if s.respond_to? :specification_version then
20
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
21
19
  s.specification_version = 3
22
20
 
23
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
21
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
24
22
  else
25
23
  end
26
24
  else
@@ -1,4 +1,4 @@
1
- require 'spec/helper'
1
+ require File.expand_path('../../helper', __FILE__)
2
2
 
3
3
  describe "example/hello.nag" do
4
4
  behaves_like 'xpath'
@@ -1,5 +1,5 @@
1
- require 'lib/nagoro'
2
- require 'spec/core_extensions'
1
+ require File.expand_path('../../lib/nagoro', __FILE__)
2
+ require File.expand_path('../core_extensions', __FILE__)
3
3
 
4
4
  require 'stringio'
5
5
  require 'rexml/document'
@@ -1,4 +1,4 @@
1
- require 'spec/helper'
1
+ require File.expand_path('../../../helper', __FILE__)
2
2
 
3
3
  describe "Nagoro::Listener::Base" do
4
4
  def base(string)
@@ -1,4 +1,4 @@
1
- require 'spec/helper'
1
+ require File.expand_path('../../../helper', __FILE__)
2
2
 
3
3
  describe "Nagoro::Pipe::Compile" do
4
4
  def render(obj)
@@ -28,4 +28,22 @@ describe "Nagoro::Pipe::Compile" do
28
28
  should 'not fail on > inside ruby instruction' do
29
29
  render('#{{:hi => :there}[:hi]}').should == 'there'
30
30
  end
31
+
32
+ should 'compile #<tag>' do
33
+ render('#<br />').should == '#<br />'
34
+ end
35
+
36
+ should 'compile interpreter inside tag' do
37
+ tag = %q~<option value="a" #{'selected="selected"'}>A</option>~
38
+ res = render(tag)
39
+ res.should =~ /^<option /
40
+ res.should =~ />A<\/option>/
41
+ res.should =~ /value="a"/
42
+ res.should =~ /selected="selected"/
43
+ end
44
+
45
+ should 'compile #< in the middle of a text' do
46
+ render('Article number: #<br />').should == 'Article number: #<br />'
47
+ end
48
+
31
49
  end
@@ -1,4 +1,4 @@
1
- require 'spec/helper'
1
+ require File.expand_path('../../../helper', __FILE__)
2
2
 
3
3
  describe "Nagoro::Pipe::Element" do
4
4
  behaves_like 'xpath'
@@ -37,6 +37,11 @@ describe "Nagoro::Pipe::Element" do
37
37
  should == '(Page: "(SideBar: \\"\\")")'
38
38
  end
39
39
 
40
+ it 'should compile nested instructions in the middle of a text' do
41
+ compile('<Page> x #{test :x => 1}</Page>').
42
+ should == '(Page: " x \\#{test :x => 1}")'
43
+ end
44
+
40
45
  it 'should render file-elements' do
41
46
  doc = compile(File.read('example/hello.nag'))
42
47
  doc.should.not.be.empty