nagoro 2009.05

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/CHANGELOG +446 -0
  2. data/MANIFEST +51 -0
  3. data/README.markdown +189 -0
  4. data/Rakefile +29 -0
  5. data/bin/nagoro +83 -0
  6. data/doc/COPYING +56 -0
  7. data/doc/GPL +340 -0
  8. data/doc/LEGAL +2 -0
  9. data/example/element/Html.nage +8 -0
  10. data/example/hello.nag +3 -0
  11. data/example/morpher.nag +23 -0
  12. data/lib/nagoro.rb +22 -0
  13. data/lib/nagoro/binding.rb +8 -0
  14. data/lib/nagoro/element.rb +46 -0
  15. data/lib/nagoro/pipe.rb +12 -0
  16. data/lib/nagoro/pipe/base.rb +56 -0
  17. data/lib/nagoro/pipe/compile.rb +30 -0
  18. data/lib/nagoro/pipe/element.rb +70 -0
  19. data/lib/nagoro/pipe/include.rb +36 -0
  20. data/lib/nagoro/pipe/instruction.rb +64 -0
  21. data/lib/nagoro/pipe/localization.rb +60 -0
  22. data/lib/nagoro/pipe/morph.rb +95 -0
  23. data/lib/nagoro/pipe/tidy.rb +49 -0
  24. data/lib/nagoro/scanner.rb +97 -0
  25. data/lib/nagoro/template.rb +89 -0
  26. data/lib/nagoro/tidy.rb +49 -0
  27. data/lib/nagoro/version.rb +3 -0
  28. data/nagoro.gemspec +28 -0
  29. data/spec/core_extensions.rb +33 -0
  30. data/spec/example/hello.rb +13 -0
  31. data/spec/helper.rb +19 -0
  32. data/spec/nagoro/listener/base.rb +19 -0
  33. data/spec/nagoro/pipe/compile.rb +31 -0
  34. data/spec/nagoro/pipe/element.rb +46 -0
  35. data/spec/nagoro/pipe/include.rb +17 -0
  36. data/spec/nagoro/pipe/instruction.rb +32 -0
  37. data/spec/nagoro/pipe/morph.rb +47 -0
  38. data/spec/nagoro/pipe/tidy.rb +23 -0
  39. data/spec/nagoro/template.rb +105 -0
  40. data/spec/nagoro/template/full.nag +26 -0
  41. data/spec/nagoro/template/hello.nag +1 -0
  42. data/tasks/bacon.rake +49 -0
  43. data/tasks/changelog.rake +18 -0
  44. data/tasks/gem.rake +22 -0
  45. data/tasks/gem_installer.rake +76 -0
  46. data/tasks/grancher.rake +12 -0
  47. data/tasks/install_dependencies.rake +6 -0
  48. data/tasks/manifest.rake +4 -0
  49. data/tasks/rcov.rake +19 -0
  50. data/tasks/release.rake +52 -0
  51. data/tasks/reversion.rake +8 -0
  52. metadata +105 -0
@@ -0,0 +1,95 @@
1
+ module Nagoro
2
+ module Pipe
3
+ # Morph is a search and replace system based on parameters of opening
4
+ # tags.
5
+ #
6
+ # Available morphs are at the time of writing:
7
+ # each, filter, foreach, if, times, unless
8
+ #
9
+ # Please take care not to use multiple morphable parameters in one tag, due
10
+ # to restrictions of the implementation of REXML and libxml there is no way
11
+ # to find out which one came first and so the order would get messed up,
12
+ # leading to unpredictable results.
13
+ # You can, however, use any number of other parameter together with morphs.
14
+ #
15
+ # Example for each predefined morph:
16
+ # <a if="@address" href="#@address">#@address</a>
17
+ # # <?r if @address ?>
18
+ # # <a href="#@address">#@address</a>
19
+ # # <?r end ?>
20
+ #
21
+ # <a unless="logged_in?" href="/login">Login to your account</a>
22
+ # # <?r unless logged_in? ?>
23
+ # # <a href="/login">Login to your account
24
+ # # <?r end ?>
25
+ #
26
+ # <div foreach="tag in @tags" class="tag">#{tag}</div>
27
+ # # <?r for tag in @tags ?>
28
+ # # <div class="tag">#{tag}</div>
29
+ # # <?r end ?>
30
+ #
31
+ # <p filter="emoticonize">Hello there :)</p>
32
+ # # <p>#{emoticonize(%<Hello there :)>)}</p>
33
+ #
34
+ # <div times="10" class="shout">I am innocent!</div>
35
+ # # <?r 10.times do |_t| ?>
36
+ # # <div class="shout">I am innocent!</div>
37
+ # # <?r end ?>
38
+ #
39
+ # <div each="@users">#{_e}</div>
40
+ # # <?r @users.each do |_e| ?>
41
+ # # <div>#{_e}</div>
42
+ # # <?r end ?>
43
+ #
44
+ # How to add new morphs:
45
+ # Nagoro::Pipe::Morph['until'] = [
46
+ # '<?r %morph %expression ?>', '<?r end ?>' ]
47
+
48
+ class Morph < Base
49
+ MORPHS = {
50
+ 'each' => [ '<?r %expression.%morph do |_e| ?>', '<?r end ?>' ],
51
+ 'times' => [ '<?r %expression.%morph do |_t| ?>', '<?r end ?>' ],
52
+ 'filter' => [ '<?o %expression(%<', '>) ?>' ],
53
+ 'if' => [ '<?r %morph %expression ?>', '<?r end ?>' ],
54
+ 'unless' => [ '<?r %morph %expression ?>', '<?r end ?>' ],
55
+ 'foreach' => [ '<?r for %expression ?>', '<?r end ?>' ],
56
+ }
57
+
58
+ def tag_start(tag, original_attrs, value_attrs)
59
+ morphs = value_attrs.keys & MORPHS.keys
60
+ return super if morphs.empty?
61
+
62
+ if morphs.size > 1
63
+ raise "Cannot transform multiple morphs: #{value_attrs.inspect} in <#{tag}>"
64
+ elsif morphs.size == 1
65
+ morph = morphs.first
66
+ value = value_attrs.delete(morph)
67
+ original_attrs.delete(morph)
68
+ open = MORPHS[morph][0]
69
+
70
+ append open.gsub(/%morph/, morph).gsub(/%expression/, value)
71
+ @stack << [tag, morph]
72
+ super
73
+ end
74
+ end
75
+
76
+ def tag_end(tag)
77
+ super
78
+ prev, morph = @stack.last
79
+ return unless prev == tag
80
+ append(MORPHS[morph][1])
81
+ @stack.pop
82
+ end
83
+
84
+ class << self
85
+ def []=(key, value)
86
+ MORPHS[key] = value
87
+ end
88
+
89
+ def [](key)
90
+ MORPHS[key]
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,49 @@
1
+ require 'open3'
2
+
3
+ module Nagoro
4
+ module Pipe
5
+ # Small wrapper that pipes the template through tidy.
6
+ #
7
+ # For configuration see the {Tidy::FLAGS}, it's an array of arguments
8
+ # passed to the tidy command.
9
+ #
10
+ # We are not using the ruby tidy library to avoid memory-leaks and
11
+ # dependencies. Please make sure you do have a binary called `tidy` in your
12
+ # `PATH` before using this library.
13
+ #
14
+ # This pipe relies on open3 from the standard library.
15
+ #
16
+ # == Regarding Windows
17
+ #
18
+ # There have been numerous reports that open3 doesn't work on Windows,
19
+ # since it relies on Kernel#fork, as syscall that is not supported on that
20
+ # platform.
21
+ #
22
+ # Two possible solutions exist:
23
+ # * There is a win32-popen implementation in RAA
24
+ # * Kernel#fork works in cygwin.
25
+ #
26
+ # I don't have a copy of windows, so it's hard for me to try either of
27
+ # these options.
28
+ # Please send me a patch if you find a cross-platform way of implementing
29
+ # this functionality.
30
+ class Tidy < Base
31
+ # Possible flags can be found in `tidy -help` and `tidy -help-config`
32
+ FLAGS = %w[-utf8 -asxhtml -quiet -indent --tidy-mark no]
33
+
34
+ def result
35
+ @scanner.stream
36
+ tidy(@body.join)
37
+ end
38
+
39
+ # Pipe given +string+ through the tidy binary.
40
+ #
41
+ # @param [#to_s] string
42
+ # @return [String]
43
+ # @author manveru
44
+ def tidy(string)
45
+ Nagoro::Tidy.tidy(string, FLAGS)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,97 @@
1
+ require 'strscan'
2
+
3
+ module Nagoro
4
+ class Scanner < StringScanner
5
+ TEXT = /[^<>]+/m
6
+ DOCTYPE = /<!DOCTYPE([^>]+)>/m
7
+
8
+ TAG_START = /<([^\s>]+)/
9
+ TAG_END = /<\/([^>]*)>/
10
+ TAG_OPEN_END = /\s*>/
11
+ TAG_CLOSING_END = /\s*\/>/
12
+ TAG_PARAMETER = /\s*([^\s]*)=((['"])(.*?)\3)/um
13
+
14
+ INSTRUCTION_START = /<\?(\S+)/
15
+ INSTRUCTION_END = /(.*?)(\?>)/um
16
+
17
+ RUBY_INTERP_START = /\s*#\{/m
18
+ RUBY_INTERP_TEXT = /[^\{\}]+/m
19
+ RUBY_INTERP_NEST = /\{[^\}]*\}/m
20
+ RUBY_INTERP_END = /(?=\})/
21
+
22
+ COMMENT = /<!--.*?-->/m
23
+
24
+ def initialize(string, callback)
25
+ @callback = callback
26
+ super(string)
27
+ end
28
+
29
+ def stream
30
+ until eos?
31
+ pos = self.pos
32
+ run
33
+ raise(Stuck, "Scanner didn't move: %p" % self) if pos == self.pos
34
+ end
35
+ end
36
+
37
+ def run
38
+ if scan(DOCTYPE ); doctype(self[1])
39
+ elsif scan(INSTRUCTION_START); instruction(self[1])
40
+ elsif scan(COMMENT ); text(matched)
41
+ elsif scan(TAG_END ); tag_end(self[1])
42
+ elsif scan(RUBY_INTERP_START); ruby_interp(matched)
43
+ elsif scan(TAG_START ); tag_start(self[1])
44
+ elsif scan(TEXT ); text(matched)
45
+ end
46
+ end
47
+
48
+ def instruction(name)
49
+ scan(INSTRUCTION_END)
50
+ @callback.instruction(name, self[1])
51
+ end
52
+
53
+ def ruby_interp(string)
54
+ done = false
55
+
56
+ until done
57
+ if scan(RUBY_INTERP_TEXT)
58
+ string << matched
59
+ elsif scan(RUBY_INTERP_NEST)
60
+ string << matched
61
+ elsif scan(RUBY_INTERP_END)
62
+ done = true
63
+ end
64
+ end
65
+
66
+ @callback.text(string)
67
+ end
68
+
69
+ def tag_start(name)
70
+ original_attrs = {}
71
+ value_attrs = {}
72
+
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'
76
+ 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
+ end
82
+
83
+ def tag_end(name)
84
+ @callback.tag_end(name)
85
+ end
86
+
87
+ def text(string)
88
+ @callback.text(string)
89
+ end
90
+
91
+ def doctype(string)
92
+ @callback.doctype(string)
93
+ end
94
+
95
+ class Stuck < Error; end
96
+ end
97
+ end
@@ -0,0 +1,89 @@
1
+ require 'nagoro/scanner'
2
+ require 'nagoro/binding'
3
+ require 'nagoro/element'
4
+ require 'nagoro/pipe'
5
+
6
+ module Nagoro
7
+ DEFAULT_PIPES = [ Pipe::Element, Pipe::Morph, Pipe::Include,
8
+ Pipe::Instruction, Pipe::Compile ]
9
+ DEFAULT_FILE = '<nagoro eval>'
10
+ DEFAULT_LINE = 1
11
+
12
+ class Template
13
+ def self.[](*pipes)
14
+ new(:pipes => pipes.flatten)
15
+ end
16
+
17
+ attr_accessor :binding, :file, :pipes, :compiled
18
+
19
+ def initialize(options = {})
20
+ parse_option(options)
21
+ @compiled = false
22
+ end
23
+
24
+ def parse_option(options = {})
25
+ @binding = options.fetch(:binding, BindingProvider.binding)
26
+ @file = options[:filename] || DEFAULT_FILE
27
+ @line = options[:line] || DEFAULT_LINE
28
+ @pipes = options[:pipes] || DEFAULT_PIPES
29
+ @variables = options[:variables]
30
+ end
31
+
32
+ def compile(io, options = {})
33
+ parse_option(options) unless options.empty?
34
+
35
+ case io
36
+ when String
37
+ io = File.read(io) if io.size < 1024 and File.file?(io)
38
+ @compiled = pipeline(io)
39
+ when StringIO, IO
40
+ @compiled = pipeline(io.read)
41
+ else
42
+ raise(ArgumentError, "Cannot compile %p" % io)
43
+ end
44
+
45
+ return self
46
+ end
47
+
48
+ # use inject?
49
+ def pipeline(io)
50
+ @pipes.each do |pipe|
51
+ if pipe.respond_to?(:new)
52
+ io = pipe.new(io).result
53
+ else
54
+ io = Pipe.const_get(pipe).new(io).result
55
+ end
56
+ end
57
+
58
+ return io
59
+ rescue Scanner::Stuck => exception
60
+ exception.message << " In: #{@file}"
61
+ puts exception
62
+ ''
63
+ end
64
+
65
+ def result(options = {})
66
+ parse_option(options) unless options.empty?
67
+
68
+ if vars = @variables
69
+ 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)
74
+ end
75
+
76
+ eval(@compiled, @binding, @file, @line).strip
77
+ end
78
+
79
+ def tidy_result(options = {})
80
+ final = result(options)
81
+ flags = options.fetch(:flags, Tidy::FLAGS)
82
+ Tidy.tidy(final, flags).strip
83
+ end
84
+
85
+ def render(io, options = {})
86
+ compile(io, options).result
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,49 @@
1
+ require 'open3'
2
+
3
+ module Nagoro
4
+ # Small wrapper that pipes the template through tidy.
5
+ #
6
+ # For configuration see the {Tidy::FLAGS}, it's an array of arguments
7
+ # passed to the tidy command.
8
+ #
9
+ # We are not using the ruby tidy library to avoid memory-leaks and
10
+ # dependencies. Please make sure you do have a binary called `tidy` in your
11
+ # `PATH` before using this library.
12
+ #
13
+ # This pipe relies on open3 from the standard library.
14
+ #
15
+ # == Regarding Windows
16
+ #
17
+ # There have been numerous reports that open3 doesn't work on Windows,
18
+ # since it relies on Kernel#fork, as syscall that is not supported on that
19
+ # platform.
20
+ #
21
+ # Two possible solutions exist:
22
+ # * There is a win32-popen implementation in RAA
23
+ # * Kernel#fork works in cygwin.
24
+ #
25
+ # I don't have a copy of windows, so it's hard for me to try either of
26
+ # these options.
27
+ # Please send me a patch if you find a cross-platform way of implementing
28
+ # this functionality.
29
+
30
+ module Tidy
31
+ # Possible flags can be found in `tidy -help` and `tidy -help-config`
32
+ FLAGS = %w[-utf8 -asxhtml -quiet -indent --tidy-mark no]
33
+
34
+ module_function
35
+
36
+ # Pipe given +string+ through the tidy binary.
37
+ #
38
+ # @param [#to_s] string
39
+ # @return [String]
40
+ # @author manveru
41
+ def tidy(string, flags = FLAGS)
42
+ Open3.popen3('tidy', *flags) do |stdin, stdout, stderr|
43
+ stdin.puts(string)
44
+ stdin.close
45
+ return stdout.read
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,3 @@
1
+ module Nagoro
2
+ VERSION = "2009.05"
3
+ end
data/nagoro.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{nagoro}
5
+ s.version = "2009.05"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
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}
15
+ 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.}
18
+
19
+ if s.respond_to? :specification_version then
20
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
21
+ s.specification_version = 3
22
+
23
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
24
+ else
25
+ end
26
+ else
27
+ end
28
+ end
@@ -0,0 +1,33 @@
1
+ # Extensions for Kernel
2
+
3
+ module Kernel
4
+ # This is similar to +__FILE__+ and +__LINE__+, and returns a String
5
+ # representing the directory of the current file is.
6
+ # Unlike +__FILE__+ the path returned is absolute.
7
+ #
8
+ # This method is convenience for the
9
+ # File.expand_path(File.dirname(__FILE__))
10
+ # idiom.
11
+ #
12
+ unless defined?(__DIR__)
13
+ def __DIR__()
14
+ filename = caller[0][/(.*?):/, 1]
15
+ File.expand_path(File.dirname(filename))
16
+ end
17
+ end
18
+ end
19
+
20
+ # Extensions for String
21
+
22
+ class String
23
+
24
+ # A convenient way to do File.join
25
+ #
26
+ # Example:
27
+ # 'a' / 'b' # -> 'a/b'
28
+ # File.dirname(__FILE__) / 'bar' # -> "ramaze/snippets/string/bar"
29
+
30
+ def / obj
31
+ File.join(self, obj.to_s)
32
+ end
33
+ end