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.
Files changed (54) hide show
  1. data/.gitignore +21 -0
  2. data/.gitmodules +3 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +93 -0
  5. data/Rakefile +72 -0
  6. data/bin/rid +7 -0
  7. data/lib/rid.rb +43 -0
  8. data/lib/rid/actions/base.rb +15 -0
  9. data/lib/rid/actions/pull.rb +28 -0
  10. data/lib/rid/actions/push.rb +53 -0
  11. data/lib/rid/actions/routes.rb +41 -0
  12. data/lib/rid/attachments.rb +59 -0
  13. data/lib/rid/commands.rb +41 -0
  14. data/lib/rid/commands/destroy.rb +9 -0
  15. data/lib/rid/commands/generate.rb +9 -0
  16. data/lib/rid/commands/pull.rb +4 -0
  17. data/lib/rid/commands/push.rb +4 -0
  18. data/lib/rid/commands/routes.rb +4 -0
  19. data/lib/rid/design_document.rb +179 -0
  20. data/lib/rid/generators.rb +63 -0
  21. data/lib/rid/generators/application/USAGE +10 -0
  22. data/lib/rid/generators/application/application_generator.rb +51 -0
  23. data/lib/rid/generators/application/templates/README +1 -0
  24. data/lib/rid/generators/application/templates/_attachments/index.html +11 -0
  25. data/lib/rid/generators/application/templates/_attachments/stylesheets/application.css +25 -0
  26. data/lib/rid/generators/application/templates/_id +1 -0
  27. data/lib/rid/generators/application/templates/gitignore +0 -0
  28. data/lib/rid/generators/application/templates/lib/mustache.js +305 -0
  29. data/lib/rid/generators/application/templates/ridrc +1 -0
  30. data/lib/rid/generators/application/templates/validate_doc_update.js +3 -0
  31. data/lib/rid/generators/base.rb +66 -0
  32. data/lib/rid/generators/list/USAGE +8 -0
  33. data/lib/rid/generators/list/list_generator.rb +9 -0
  34. data/lib/rid/generators/list/templates/list.js +29 -0
  35. data/lib/rid/generators/named_base.rb +22 -0
  36. data/lib/rid/generators/scaffold/USAGE +10 -0
  37. data/lib/rid/generators/scaffold/scaffold_generator.rb +28 -0
  38. data/lib/rid/generators/show/USAGE +8 -0
  39. data/lib/rid/generators/show/show_generator.rb +9 -0
  40. data/lib/rid/generators/show/templates/show.js +20 -0
  41. data/lib/rid/generators/validation/USAGE +9 -0
  42. data/lib/rid/generators/validation/templates/validate_doc_update.js +3 -0
  43. data/lib/rid/generators/validation/validation_generator.rb +34 -0
  44. data/lib/rid/generators/view/USAGE +8 -0
  45. data/lib/rid/generators/view/templates/map.js +5 -0
  46. data/lib/rid/generators/view/view_generator.rb +17 -0
  47. data/lib/rid/makros.rb +105 -0
  48. data/lib/rid/version.rb +3 -0
  49. data/rid.gemspec +113 -0
  50. data/spec/rid/design_document_spec.rb +329 -0
  51. data/spec/rid_spec.rb +7 -0
  52. data/spec/spec.opts +1 -0
  53. data/spec/spec_helper.rb +9 -0
  54. metadata +187 -0
@@ -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,9 @@
1
+ require 'rid/generators'
2
+
3
+ if [nil, "-h", "--help"].include?(ARGV.first)
4
+ Rid::Generators.help 'destroy'
5
+ exit
6
+ end
7
+
8
+ name = ARGV.shift
9
+ Rid::Generators.invoke name, ARGV, :behavior => :revoke, :destination_root => Rid.root
@@ -0,0 +1,9 @@
1
+ require 'rid/generators'
2
+
3
+ if [nil, "-h", "--help"].include?(ARGV.first)
4
+ Rid::Generators.help 'generate'
5
+ exit
6
+ end
7
+
8
+ name = ARGV.shift
9
+ Rid::Generators.invoke name, ARGV, :behavior => :invoke, :destination_root => Rid.root
@@ -0,0 +1,4 @@
1
+ require 'rid'
2
+ require 'rid/actions/pull'
3
+
4
+ Rid::Actions::Pull.start ARGV, :destination_root => Rid.root
@@ -0,0 +1,4 @@
1
+ require 'rid'
2
+ require 'rid/actions/push'
3
+
4
+ Rid::Actions::Push.start ARGV, :destination_root => Rid.root
@@ -0,0 +1,4 @@
1
+ require 'rid'
2
+ require 'rid/actions/routes'
3
+
4
+ Rid::Actions::Routes.start ARGV, :destination_root => Rid.root
@@ -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 %>
@@ -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 "&amp;";
215
+ case "\\": return "\\\\";;
216
+ case '"': return '\"';;
217
+ case "<": return "&lt;";
218
+ case ">": return "&gt;";
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
+ }();