nagoro 2009.05
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +446 -0
- data/MANIFEST +51 -0
- data/README.markdown +189 -0
- data/Rakefile +29 -0
- data/bin/nagoro +83 -0
- data/doc/COPYING +56 -0
- data/doc/GPL +340 -0
- data/doc/LEGAL +2 -0
- data/example/element/Html.nage +8 -0
- data/example/hello.nag +3 -0
- data/example/morpher.nag +23 -0
- data/lib/nagoro.rb +22 -0
- data/lib/nagoro/binding.rb +8 -0
- data/lib/nagoro/element.rb +46 -0
- data/lib/nagoro/pipe.rb +12 -0
- data/lib/nagoro/pipe/base.rb +56 -0
- data/lib/nagoro/pipe/compile.rb +30 -0
- data/lib/nagoro/pipe/element.rb +70 -0
- data/lib/nagoro/pipe/include.rb +36 -0
- data/lib/nagoro/pipe/instruction.rb +64 -0
- data/lib/nagoro/pipe/localization.rb +60 -0
- data/lib/nagoro/pipe/morph.rb +95 -0
- data/lib/nagoro/pipe/tidy.rb +49 -0
- data/lib/nagoro/scanner.rb +97 -0
- data/lib/nagoro/template.rb +89 -0
- data/lib/nagoro/tidy.rb +49 -0
- data/lib/nagoro/version.rb +3 -0
- data/nagoro.gemspec +28 -0
- data/spec/core_extensions.rb +33 -0
- data/spec/example/hello.rb +13 -0
- data/spec/helper.rb +19 -0
- data/spec/nagoro/listener/base.rb +19 -0
- data/spec/nagoro/pipe/compile.rb +31 -0
- data/spec/nagoro/pipe/element.rb +46 -0
- data/spec/nagoro/pipe/include.rb +17 -0
- data/spec/nagoro/pipe/instruction.rb +32 -0
- data/spec/nagoro/pipe/morph.rb +47 -0
- data/spec/nagoro/pipe/tidy.rb +23 -0
- data/spec/nagoro/template.rb +105 -0
- data/spec/nagoro/template/full.nag +26 -0
- data/spec/nagoro/template/hello.nag +1 -0
- data/tasks/bacon.rake +49 -0
- data/tasks/changelog.rake +18 -0
- data/tasks/gem.rake +22 -0
- data/tasks/gem_installer.rake +76 -0
- data/tasks/grancher.rake +12 -0
- data/tasks/install_dependencies.rake +6 -0
- data/tasks/manifest.rake +4 -0
- data/tasks/rcov.rake +19 -0
- data/tasks/release.rake +52 -0
- data/tasks/reversion.rake +8 -0
- 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
|
data/lib/nagoro/tidy.rb
ADDED
@@ -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
|
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
|