rid 0.3.0
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/.gitignore +21 -0
- data/.gitmodules +3 -0
- data/LICENSE +20 -0
- data/README.rdoc +93 -0
- data/Rakefile +72 -0
- data/bin/rid +7 -0
- data/lib/rid.rb +43 -0
- data/lib/rid/actions/base.rb +15 -0
- data/lib/rid/actions/pull.rb +28 -0
- data/lib/rid/actions/push.rb +53 -0
- data/lib/rid/actions/routes.rb +41 -0
- data/lib/rid/attachments.rb +59 -0
- data/lib/rid/commands.rb +41 -0
- data/lib/rid/commands/destroy.rb +9 -0
- data/lib/rid/commands/generate.rb +9 -0
- data/lib/rid/commands/pull.rb +4 -0
- data/lib/rid/commands/push.rb +4 -0
- data/lib/rid/commands/routes.rb +4 -0
- data/lib/rid/design_document.rb +179 -0
- data/lib/rid/generators.rb +63 -0
- data/lib/rid/generators/application/USAGE +10 -0
- data/lib/rid/generators/application/application_generator.rb +51 -0
- data/lib/rid/generators/application/templates/README +1 -0
- data/lib/rid/generators/application/templates/_attachments/index.html +11 -0
- data/lib/rid/generators/application/templates/_attachments/stylesheets/application.css +25 -0
- data/lib/rid/generators/application/templates/_id +1 -0
- data/lib/rid/generators/application/templates/gitignore +0 -0
- data/lib/rid/generators/application/templates/lib/mustache.js +305 -0
- data/lib/rid/generators/application/templates/ridrc +1 -0
- data/lib/rid/generators/application/templates/validate_doc_update.js +3 -0
- data/lib/rid/generators/base.rb +66 -0
- data/lib/rid/generators/list/USAGE +8 -0
- data/lib/rid/generators/list/list_generator.rb +9 -0
- data/lib/rid/generators/list/templates/list.js +29 -0
- data/lib/rid/generators/named_base.rb +22 -0
- data/lib/rid/generators/scaffold/USAGE +10 -0
- data/lib/rid/generators/scaffold/scaffold_generator.rb +28 -0
- data/lib/rid/generators/show/USAGE +8 -0
- data/lib/rid/generators/show/show_generator.rb +9 -0
- data/lib/rid/generators/show/templates/show.js +20 -0
- data/lib/rid/generators/validation/USAGE +9 -0
- data/lib/rid/generators/validation/templates/validate_doc_update.js +3 -0
- data/lib/rid/generators/validation/validation_generator.rb +34 -0
- data/lib/rid/generators/view/USAGE +8 -0
- data/lib/rid/generators/view/templates/map.js +5 -0
- data/lib/rid/generators/view/view_generator.rb +17 -0
- data/lib/rid/makros.rb +105 -0
- data/lib/rid/version.rb +3 -0
- data/rid.gemspec +113 -0
- data/spec/rid/design_document_spec.rb +329 -0
- data/spec/rid_spec.rb +7 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +9 -0
- metadata +187 -0
data/lib/rid/commands.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
if ARGV.empty?
|
2
|
+
ARGV << '--help'
|
3
|
+
end
|
4
|
+
|
5
|
+
HELP_TEXT = <<-EOT
|
6
|
+
Usage: rid COMMAND [ARGS]
|
7
|
+
|
8
|
+
The most common rid commands are:
|
9
|
+
generate Generate new code (short-cut alias: "g")
|
10
|
+
push Push application code to Couchdb
|
11
|
+
pull Pull latest application code from Couchdb
|
12
|
+
routes List application urls
|
13
|
+
|
14
|
+
In addition to those, there are:
|
15
|
+
destroy Undo code generated with "generate"
|
16
|
+
|
17
|
+
All commands can be run with -h for more information.
|
18
|
+
EOT
|
19
|
+
|
20
|
+
|
21
|
+
case ARGV.shift
|
22
|
+
when 'g', 'generate'
|
23
|
+
require 'rid/commands/generate'
|
24
|
+
when 'destroy'
|
25
|
+
require 'rid/commands/destroy'
|
26
|
+
when 'push'
|
27
|
+
require 'rid/commands/push'
|
28
|
+
when 'pull'
|
29
|
+
require 'rid/commands/pull'
|
30
|
+
when 'routes'
|
31
|
+
require 'rid/commands/routes'
|
32
|
+
|
33
|
+
when '--help', '-h'
|
34
|
+
puts HELP_TEXT
|
35
|
+
when '--version', '-v'
|
36
|
+
require 'rid/version'
|
37
|
+
puts "Rid #{Rid::VERSION}"
|
38
|
+
else
|
39
|
+
puts "Error: Command not recognized"
|
40
|
+
puts HELP_TEXT
|
41
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'rid/attachments'
|
2
|
+
require 'rid/makros'
|
3
|
+
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
module Rid
|
7
|
+
class DesignDocument
|
8
|
+
# Files that should have a .js extension
|
9
|
+
JAVASCRIPT_FILES = %w[
|
10
|
+
validate_doc_update
|
11
|
+
lists/*
|
12
|
+
shows/*
|
13
|
+
updates/*
|
14
|
+
views/*/*
|
15
|
+
]
|
16
|
+
|
17
|
+
# Files that should not be included in document
|
18
|
+
EXCLUDE_FILES = %w[
|
19
|
+
README
|
20
|
+
]
|
21
|
+
|
22
|
+
include Attachments
|
23
|
+
include Makros
|
24
|
+
|
25
|
+
attr_accessor :hash
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
@hash = {}
|
29
|
+
end
|
30
|
+
|
31
|
+
# Read document from a filesystem.
|
32
|
+
#
|
33
|
+
# Takes a filename,
|
34
|
+
# many filenames,
|
35
|
+
# or an array of filenames
|
36
|
+
# and assign the return value of a yielded block to the hash.
|
37
|
+
#
|
38
|
+
# Nested hashes
|
39
|
+
# like { "hash" => { "key" => "value" } }
|
40
|
+
# can be constructed if the filename contains a slash (/),
|
41
|
+
# eg "hash/key".
|
42
|
+
#
|
43
|
+
def read(*filenames, &block)
|
44
|
+
filenames.flatten.uniq.each do |filename|
|
45
|
+
# skip exclude files
|
46
|
+
next if EXCLUDE_FILES.include?(filename)
|
47
|
+
|
48
|
+
key = filename.dup
|
49
|
+
# strip extname from javascript files
|
50
|
+
key.sub!(/\.js$/, '') if filename =~ /#{JAVASCRIPT_FILES.join('|')}/
|
51
|
+
|
52
|
+
set_hash_at key, block.call(filename)
|
53
|
+
end
|
54
|
+
|
55
|
+
map_attachments!
|
56
|
+
inject_makros!
|
57
|
+
end
|
58
|
+
|
59
|
+
# Write document to a filesystem
|
60
|
+
#
|
61
|
+
# Takes a directoy as startpoint (default is nil),
|
62
|
+
# a document hash (default is the design documents hash)
|
63
|
+
# and recursively yields all keys and values to the given block.
|
64
|
+
#
|
65
|
+
# Nested hashes
|
66
|
+
# like { "hash" => { "key" => "value" } }
|
67
|
+
# will result in the yielded filename
|
68
|
+
# "hash/key".
|
69
|
+
#
|
70
|
+
# The key "_attachments" has a special meaning:
|
71
|
+
# the value holds base64 encoded data as well as other metadata.
|
72
|
+
# This data will gets decoded and used as value for the key.
|
73
|
+
#
|
74
|
+
def write(directory = nil, doc = nil, &block)
|
75
|
+
reduce_attachments!
|
76
|
+
reject_makros!
|
77
|
+
|
78
|
+
doc ||= hash
|
79
|
+
doc.each do |key, value|
|
80
|
+
filename = directory ? File.join(directory, key) : key.dup
|
81
|
+
if value.is_a?(Hash)
|
82
|
+
write(filename, value, &block)
|
83
|
+
else
|
84
|
+
# append extname to javascript files
|
85
|
+
filename << '.js' if filename =~ /#{JAVASCRIPT_FILES.join('|')}/
|
86
|
+
block.call(filename, value)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns a JSON string representation of the documents hash
|
92
|
+
#
|
93
|
+
def json
|
94
|
+
hash.to_json
|
95
|
+
end
|
96
|
+
|
97
|
+
# Build the documents hash from a JSON string
|
98
|
+
#
|
99
|
+
def json=(json)
|
100
|
+
self.hash = JSON.parse(json)
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
# Accessor for id
|
105
|
+
def id
|
106
|
+
hash["_id"] || Rid.id
|
107
|
+
end
|
108
|
+
|
109
|
+
# Accessor for rev
|
110
|
+
def rev
|
111
|
+
hash["_rev"] || Rid.rev
|
112
|
+
end
|
113
|
+
|
114
|
+
# Updates rev in documents hash
|
115
|
+
def rev=(new_rev)
|
116
|
+
hash["_rev"] = new_rev
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
# Accessor for rid database
|
121
|
+
def database
|
122
|
+
@database ||= Rid.database
|
123
|
+
end
|
124
|
+
|
125
|
+
# Base URL for document
|
126
|
+
def base_url
|
127
|
+
@base_url ||= File.join(database, id)
|
128
|
+
end
|
129
|
+
|
130
|
+
# URL for accessing design document
|
131
|
+
#
|
132
|
+
# Takes an optional options hash
|
133
|
+
# which gets converted to url encoded options
|
134
|
+
# and appended to the documents base url
|
135
|
+
#
|
136
|
+
def url(options = {})
|
137
|
+
base_url + build_options_string(options)
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
def hash_at(path)
|
143
|
+
current_hash = hash
|
144
|
+
|
145
|
+
parts = path.split('/')
|
146
|
+
key = parts.pop
|
147
|
+
|
148
|
+
parts.each do |part|
|
149
|
+
current_hash[part] ||= {}
|
150
|
+
current_hash = current_hash[part]
|
151
|
+
end
|
152
|
+
|
153
|
+
current_hash[key]
|
154
|
+
end
|
155
|
+
|
156
|
+
def set_hash_at(path, value)
|
157
|
+
current_hash = hash
|
158
|
+
|
159
|
+
parts = path.split('/')
|
160
|
+
key = parts.pop
|
161
|
+
|
162
|
+
parts.each do |part|
|
163
|
+
current_hash[part] ||= {}
|
164
|
+
current_hash = current_hash[part]
|
165
|
+
end
|
166
|
+
|
167
|
+
current_hash[key] = value
|
168
|
+
end
|
169
|
+
|
170
|
+
def build_options_string(options)
|
171
|
+
return '' if options.empty?
|
172
|
+
options_array = []
|
173
|
+
options.each do |key, value|
|
174
|
+
options_array << URI.escape([key, value].join('='))
|
175
|
+
end
|
176
|
+
'?' + options_array.join("&")
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'rid'
|
2
|
+
require 'rid/generators/base'
|
3
|
+
|
4
|
+
module Rid
|
5
|
+
module Generators
|
6
|
+
# Receives a name, arguments and the behavior to invoke the generator.
|
7
|
+
# It's used as the default entry point for generate and destroy commands.
|
8
|
+
def self.invoke(name, args = ARGV, config = {})
|
9
|
+
if klass = lookup(name.to_s)
|
10
|
+
args << "--help" if args.empty? && klass.arguments.any? { |a| a.required? }
|
11
|
+
klass.start(args, config)
|
12
|
+
else
|
13
|
+
puts "Could not find generator #{name}."
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Show help message with available generators.
|
18
|
+
def self.help(command = 'generate')
|
19
|
+
path = File.expand_path("../generators/*/*_generator.rb", __FILE__)
|
20
|
+
generators = Dir.glob(path)
|
21
|
+
generators.sort!
|
22
|
+
generators.map! { |f| File.basename(f) }
|
23
|
+
generators.map! { |n| n.sub!(/_generator\.rb$/, '') }
|
24
|
+
longest_name_size = generators.map { |g| g.size }.sort.last
|
25
|
+
generators.map! { |g| "%s # %s" % [g.ljust(longest_name_size), lookup(g).info] }
|
26
|
+
|
27
|
+
puts "Usage: rid #{command} GENERATOR [args] [options]"
|
28
|
+
puts
|
29
|
+
puts "General options:"
|
30
|
+
puts " -h, [--help] # Print generators options and usage"
|
31
|
+
puts " -p, [--pretend] # Run but do not make any changes"
|
32
|
+
puts " -f, [--force] # Overwrite files that already exist"
|
33
|
+
puts " -s, [--skip] # Skip files that already exist"
|
34
|
+
puts " -q, [--quiet] # Supress status output"
|
35
|
+
puts
|
36
|
+
puts "Please choose a generator below:"
|
37
|
+
puts
|
38
|
+
puts generators
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def self.lookup(name)
|
44
|
+
# real path
|
45
|
+
path = File.expand_path("../generators/#{name}/#{name}_generator.rb", __FILE__)
|
46
|
+
# no matches?
|
47
|
+
unless File.exists?(path)
|
48
|
+
# try to find by prefix
|
49
|
+
found = Dir.glob(File.expand_path("../generators/#{name}*/#{name}*_generator.rb", __FILE__))
|
50
|
+
if found.size == 1
|
51
|
+
path = found.first
|
52
|
+
name = File.basename(path).sub(/_generator\.rb$/, '')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
require path
|
56
|
+
const_get "#{name.classify}Generator"
|
57
|
+
rescue LoadError => e
|
58
|
+
raise unless e.message =~ /#{Regexp.escape(path)}$/
|
59
|
+
rescue Exception => e
|
60
|
+
warn "[WARNING] Could not load generator #{path.inspect}. Error: #{e.message}.\n#{e.backtrace.join("\n")}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
Description:
|
2
|
+
The 'application' generator creates a new Rid application
|
3
|
+
with a default directory structure and configuration
|
4
|
+
at the path you specify.
|
5
|
+
|
6
|
+
Example:
|
7
|
+
rid generate application ~/rid/weblog
|
8
|
+
|
9
|
+
This generates a skeletal Rid installation in ~/rid/weblog.
|
10
|
+
See the README in the newly created application to get going.
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Rid::Generators
|
2
|
+
class ApplicationGenerator < Base
|
3
|
+
argument :app_path, :type => :string
|
4
|
+
|
5
|
+
def create_root
|
6
|
+
self.destination_root = File.expand_path(app_path, destination_root)
|
7
|
+
|
8
|
+
empty_directory '.'
|
9
|
+
FileUtils.cd(destination_root) if File.directory?(destination_root)
|
10
|
+
end
|
11
|
+
|
12
|
+
def create_root_files
|
13
|
+
template "ridrc", ".ridrc"
|
14
|
+
copy_file "README"
|
15
|
+
copy_file "gitignore", ".gitignore" unless options[:skip_git]
|
16
|
+
template "_id"
|
17
|
+
copy_file "validate_doc_update.js"
|
18
|
+
empty_directory "lists"
|
19
|
+
empty_directory "shows"
|
20
|
+
empty_directory "updates"
|
21
|
+
empty_directory "views"
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_attachments_files
|
25
|
+
empty_directory "_attachments"
|
26
|
+
inside "_attachments" do
|
27
|
+
empty_directory "images"
|
28
|
+
empty_directory "javascripts"
|
29
|
+
directory "stylesheets"
|
30
|
+
template "index.html"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_lib_files
|
35
|
+
empty_directory "lib"
|
36
|
+
inside "lib" do
|
37
|
+
copy_file "mustache.js"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def app_name
|
44
|
+
@app_name ||= File.basename(destination_root)
|
45
|
+
end
|
46
|
+
|
47
|
+
def app_title
|
48
|
+
@app_title ||= app_name.humanize
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
# Welcome to Rid!
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<!DOCTYPE HTML>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
5
|
+
<title><%= app_title %></title>
|
6
|
+
<link rel="stylesheet" href="stylesheets/application.css" type="text/css" media="screen" charset="utf-8">
|
7
|
+
</head>
|
8
|
+
<body>
|
9
|
+
<h1>Welcome to <%= app_title %>!</h1>
|
10
|
+
</body>
|
11
|
+
</html>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
body {
|
2
|
+
background-color: #fff;
|
3
|
+
color: #333;
|
4
|
+
margin: 1cm 1.618cm;
|
5
|
+
}
|
6
|
+
|
7
|
+
body, p, ol, ul, td {
|
8
|
+
font-family: verdana, arial, helvetica, sans-serif;
|
9
|
+
font-size: 13px;
|
10
|
+
line-height: 18px;
|
11
|
+
}
|
12
|
+
|
13
|
+
pre {
|
14
|
+
background-color: #eee;
|
15
|
+
padding: 10px;
|
16
|
+
font-size: 11px;
|
17
|
+
}
|
18
|
+
|
19
|
+
a { color: #000; }
|
20
|
+
a:visited { color: #666; }
|
21
|
+
a:hover { color: #fff; background-color:#000; }
|
22
|
+
|
23
|
+
.notice {
|
24
|
+
color: green;
|
25
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
_design/<%= app_name %>
|
File without changes
|
@@ -0,0 +1,305 @@
|
|
1
|
+
/*
|
2
|
+
Shameless port of http://github.com/defunkt/mustache
|
3
|
+
by Jan Lehnardt <jan@apache.org>,
|
4
|
+
Alexander Lang <alex@upstream-berlin.com>,
|
5
|
+
Sebastian Cohnen <sebastian.cohnen@googlemail.com>
|
6
|
+
|
7
|
+
Thanks @defunkt for the awesome code.
|
8
|
+
|
9
|
+
See http://github.com/defunkt/mustache for more info.
|
10
|
+
*/
|
11
|
+
|
12
|
+
var Mustache = function() {
|
13
|
+
var Renderer = function() {};
|
14
|
+
|
15
|
+
Renderer.prototype = {
|
16
|
+
otag: "{{",
|
17
|
+
ctag: "}}",
|
18
|
+
pragmas: {},
|
19
|
+
buffer: [],
|
20
|
+
pragmas_parsed: false,
|
21
|
+
pragmas_implemented: {
|
22
|
+
"IMPLICIT-ITERATOR": true
|
23
|
+
},
|
24
|
+
|
25
|
+
render: function(template, context, partials, in_recursion) {
|
26
|
+
// fail fast
|
27
|
+
if(template.indexOf(this.otag) == -1) {
|
28
|
+
if(in_recursion) {
|
29
|
+
return template;
|
30
|
+
} else {
|
31
|
+
this.send(template);
|
32
|
+
return;
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
if(!in_recursion) {
|
37
|
+
this.buffer = [];
|
38
|
+
}
|
39
|
+
|
40
|
+
if(!this.pragmas_parsed) {
|
41
|
+
template = this.render_pragmas(template);
|
42
|
+
}
|
43
|
+
var html = this.render_section(template, context, partials);
|
44
|
+
if(in_recursion) {
|
45
|
+
return this.render_tags(html, context, partials, in_recursion);
|
46
|
+
}
|
47
|
+
|
48
|
+
this.render_tags(html, context, partials, in_recursion);
|
49
|
+
},
|
50
|
+
|
51
|
+
/*
|
52
|
+
Sends parsed lines
|
53
|
+
*/
|
54
|
+
send: function(line) {
|
55
|
+
if(line != "") {
|
56
|
+
this.buffer.push(line);
|
57
|
+
}
|
58
|
+
},
|
59
|
+
|
60
|
+
/*
|
61
|
+
Looks for %PRAGMAS
|
62
|
+
*/
|
63
|
+
render_pragmas: function(template) {
|
64
|
+
this.pragmas_parsed = true;
|
65
|
+
// no pragmas
|
66
|
+
if(template.indexOf(this.otag + "%") == -1) {
|
67
|
+
return template;
|
68
|
+
}
|
69
|
+
|
70
|
+
var that = this;
|
71
|
+
var regex = new RegExp(this.otag + "%([\\w_-]+) ?([\\w]+=[\\w]+)?"
|
72
|
+
+ this.ctag);
|
73
|
+
return template.replace(regex, function(match, pragma, options) {
|
74
|
+
if(!that.pragmas_implemented[pragma]) {
|
75
|
+
throw({message: "This implementation of mustache doesn't understand the '"
|
76
|
+
+ pragma + "' pragma"});
|
77
|
+
}
|
78
|
+
that.pragmas[pragma] = {};
|
79
|
+
if(options) {
|
80
|
+
var opts = options.split("=");
|
81
|
+
that.pragmas[pragma][opts[0]] = opts[1];
|
82
|
+
}
|
83
|
+
return "";
|
84
|
+
// ignore unknown pragmas silently
|
85
|
+
});
|
86
|
+
},
|
87
|
+
|
88
|
+
/*
|
89
|
+
Tries to find a partial in the global scope and render it
|
90
|
+
*/
|
91
|
+
render_partial: function(name, context, partials) {
|
92
|
+
if(typeof(context[name]) != "object") {
|
93
|
+
throw({message: "subcontext for '" + name + "' is not an object"});
|
94
|
+
}
|
95
|
+
if(!partials || !partials[name]) {
|
96
|
+
throw({message: "unknown_partial '" + name + "'"});
|
97
|
+
}
|
98
|
+
return this.render(partials[name], context[name], partials, true);
|
99
|
+
},
|
100
|
+
|
101
|
+
/*
|
102
|
+
Renders boolean and enumerable sections
|
103
|
+
*/
|
104
|
+
render_section: function(template, context, partials) {
|
105
|
+
if(template.indexOf(this.otag + "#") == -1) {
|
106
|
+
return template;
|
107
|
+
}
|
108
|
+
var that = this;
|
109
|
+
// CSW - Added "+?" so it finds the tighest bound, not the widest
|
110
|
+
var regex = new RegExp(this.otag + "\\#(.+)" + this.ctag +
|
111
|
+
"\\s*([\\s\\S]+?)" + this.otag + "\\/\\1" + this.ctag + "\\s*", "mg");
|
112
|
+
|
113
|
+
// for each {{#foo}}{{/foo}} section do...
|
114
|
+
return template.replace(regex, function(match, name, content) {
|
115
|
+
var value = that.find(name, context);
|
116
|
+
if(that.is_array(value)) { // Enumerable, Let's loop!
|
117
|
+
return that.map(value, function(row) {
|
118
|
+
return that.render(content, that.merge(context,
|
119
|
+
that.create_context(row)), partials, true);
|
120
|
+
}).join("");
|
121
|
+
} else if(value) { // boolean section
|
122
|
+
return that.render(content, context, partials, true);
|
123
|
+
} else {
|
124
|
+
return "";
|
125
|
+
}
|
126
|
+
});
|
127
|
+
},
|
128
|
+
|
129
|
+
/*
|
130
|
+
Replace {{foo}} and friends with values from our view
|
131
|
+
*/
|
132
|
+
render_tags: function(template, context, partials, in_recursion) {
|
133
|
+
// tit for tat
|
134
|
+
var that = this;
|
135
|
+
|
136
|
+
var new_regex = function() {
|
137
|
+
return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\/#]+?)\\1?" +
|
138
|
+
that.ctag + "+", "g");
|
139
|
+
};
|
140
|
+
|
141
|
+
var regex = new_regex();
|
142
|
+
var lines = template.split("\n");
|
143
|
+
for (var i=0; i < lines.length; i++) {
|
144
|
+
lines[i] = lines[i].replace(regex, function(match, operator, name) {
|
145
|
+
switch(operator) {
|
146
|
+
case "!": // ignore comments
|
147
|
+
return match;
|
148
|
+
case "=": // set new delimiters, rebuild the replace regexp
|
149
|
+
that.set_delimiters(name);
|
150
|
+
regex = new_regex();
|
151
|
+
return "";
|
152
|
+
case ">": // render partial
|
153
|
+
return that.render_partial(name, context, partials);
|
154
|
+
case "{": // the triple mustache is unescaped
|
155
|
+
return that.find(name, context);
|
156
|
+
default: // escape the value
|
157
|
+
return that.escape(that.find(name, context));
|
158
|
+
}
|
159
|
+
}, this);
|
160
|
+
if(!in_recursion) {
|
161
|
+
this.send(lines[i]);
|
162
|
+
}
|
163
|
+
}
|
164
|
+
|
165
|
+
if(in_recursion) {
|
166
|
+
return lines.join("\n");
|
167
|
+
}
|
168
|
+
},
|
169
|
+
|
170
|
+
set_delimiters: function(delimiters) {
|
171
|
+
var dels = delimiters.split(" ");
|
172
|
+
this.otag = this.escape_regex(dels[0]);
|
173
|
+
this.ctag = this.escape_regex(dels[1]);
|
174
|
+
},
|
175
|
+
|
176
|
+
escape_regex: function(text) {
|
177
|
+
// thank you Simon Willison
|
178
|
+
if(!arguments.callee.sRE) {
|
179
|
+
var specials = [
|
180
|
+
'/', '.', '*', '+', '?', '|',
|
181
|
+
'(', ')', '[', ']', '{', '}', '\\'
|
182
|
+
];
|
183
|
+
arguments.callee.sRE = new RegExp(
|
184
|
+
'(\\' + specials.join('|\\') + ')', 'g'
|
185
|
+
);
|
186
|
+
}
|
187
|
+
return text.replace(arguments.callee.sRE, '\\$1');
|
188
|
+
},
|
189
|
+
|
190
|
+
/*
|
191
|
+
find `name` in current `context`. That is find me a value
|
192
|
+
from the view object
|
193
|
+
*/
|
194
|
+
find: function(name, context) {
|
195
|
+
name = this.trim(name);
|
196
|
+
if(typeof context[name] === "function") {
|
197
|
+
return context[name].apply(context);
|
198
|
+
}
|
199
|
+
if(context[name] !== undefined) {
|
200
|
+
return context[name];
|
201
|
+
}
|
202
|
+
// silently ignore unkown variables
|
203
|
+
return "";
|
204
|
+
},
|
205
|
+
|
206
|
+
// Utility methods
|
207
|
+
|
208
|
+
/*
|
209
|
+
Does away with nasty characters
|
210
|
+
*/
|
211
|
+
escape: function(s) {
|
212
|
+
return ((s == null) ? "" : s).toString().replace(/[&"<>\\]/g, function(s) {
|
213
|
+
switch(s) {
|
214
|
+
case "&": return "&";
|
215
|
+
case "\\": return "\\\\";;
|
216
|
+
case '"': return '\"';;
|
217
|
+
case "<": return "<";
|
218
|
+
case ">": return ">";
|
219
|
+
default: return s;
|
220
|
+
}
|
221
|
+
});
|
222
|
+
},
|
223
|
+
|
224
|
+
/*
|
225
|
+
Merges all properties of object `b` into object `a`.
|
226
|
+
`b.property` overwrites a.property`
|
227
|
+
*/
|
228
|
+
merge: function(a, b) {
|
229
|
+
var _new = {};
|
230
|
+
for(var name in a) {
|
231
|
+
if(a.hasOwnProperty(name)) {
|
232
|
+
_new[name] = a[name];
|
233
|
+
}
|
234
|
+
};
|
235
|
+
for(var name in b) {
|
236
|
+
if(b.hasOwnProperty(name)) {
|
237
|
+
_new[name] = b[name];
|
238
|
+
}
|
239
|
+
};
|
240
|
+
return _new;
|
241
|
+
},
|
242
|
+
|
243
|
+
// by @langalex, support for arrays of strings
|
244
|
+
create_context: function(_context) {
|
245
|
+
if(this.is_object(_context)) {
|
246
|
+
return _context;
|
247
|
+
} else if(this.pragmas["IMPLICIT-ITERATOR"]) {
|
248
|
+
var iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator || ".";
|
249
|
+
var ctx = {};
|
250
|
+
ctx[iterator] = _context
|
251
|
+
return ctx;
|
252
|
+
}
|
253
|
+
},
|
254
|
+
|
255
|
+
is_object: function(a) {
|
256
|
+
return a && typeof a == "object";
|
257
|
+
},
|
258
|
+
|
259
|
+
is_array: function(a) {
|
260
|
+
return Object.prototype.toString.call(a) === '[object Array]';
|
261
|
+
},
|
262
|
+
|
263
|
+
/*
|
264
|
+
Gets rid of leading and trailing whitespace
|
265
|
+
*/
|
266
|
+
trim: function(s) {
|
267
|
+
return s.replace(/^\s*|\s*$/g, "");
|
268
|
+
},
|
269
|
+
|
270
|
+
/*
|
271
|
+
Why, why, why? Because IE. Cry, cry cry.
|
272
|
+
*/
|
273
|
+
map: function(array, fn) {
|
274
|
+
if (typeof array.map == "function") {
|
275
|
+
return array.map(fn)
|
276
|
+
} else {
|
277
|
+
var r = [];
|
278
|
+
var l = array.length;
|
279
|
+
for(i=0;i<l;i++) {
|
280
|
+
r.push(fn(array[i]));
|
281
|
+
}
|
282
|
+
return r;
|
283
|
+
}
|
284
|
+
}
|
285
|
+
};
|
286
|
+
|
287
|
+
return({
|
288
|
+
name: "mustache.js",
|
289
|
+
version: "0.2.3-dev",
|
290
|
+
|
291
|
+
/*
|
292
|
+
Turns a template and view into HTML
|
293
|
+
*/
|
294
|
+
to_html: function(template, view, partials, send_fun) {
|
295
|
+
var renderer = new Renderer();
|
296
|
+
if(send_fun) {
|
297
|
+
renderer.send = send_fun;
|
298
|
+
}
|
299
|
+
renderer.render(template, view, partials);
|
300
|
+
if(!send_fun) {
|
301
|
+
return renderer.buffer.join("\n");
|
302
|
+
}
|
303
|
+
}
|
304
|
+
});
|
305
|
+
}();
|