couchapp 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,176 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
@@ -0,0 +1,27 @@
1
+ # CouchApp: Standalone CouchDB Application Development Made Simple
2
+
3
+ CouchApp is a set of helpers and a [jQuery](http://jquery.com) plugin that conspire to get you up and running on [CouchDB](http://couchdb.org) quickly and correctly. It is maintained and designed by CouchDB committers and community-members to bring clarity and order to the freedom of CouchDB's document-based approach.
4
+
5
+ CouchApp *is by no means the only way to use CouchDB*. CouchDB's technical roots make it well suited for **very large installations**. CouchApp concentrates instead on a more personal use case: **developing and deploying standalone applications to CouchDB instances around the web.** There are apps you can build with server-side components that you can't build with just CouchApp. But by the same token, there are apps you can build on CouchApp alone that you can't build any other way.
6
+
7
+ ## Begin Here
8
+
9
+ Once you run `couchapp generate relax && cd relax`, you're ready to get started. Views are found in the `views` directory, attachments are stored in the `_attachments` directory, forms are stored in `forms`, and the generation script drops some more files in with additional information about how you can build `_design/` docs using your text editor.
10
+
11
+ ## Usage
12
+
13
+ about push... and db syntax
14
+
15
+ !code and !json
16
+
17
+ There are a few apps out there already using CouchApp. Please send a pull request adding yours to the list if you're using it too.
18
+
19
+ ## Apps Using CouchApp
20
+
21
+ * [Sofa](http://github.com/jchris/sofa)
22
+ * [Couch-Wiki](http://github.com/janl/couch-wiki)
23
+ * [CouchDB Twitter Client](http://github.com/jchris/couchdb-twitter-client)
24
+
25
+ ## License
26
+
27
+ CouchApp is licensed under the [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0)
@@ -0,0 +1,118 @@
1
+ require 'rake'
2
+ require "rake/rdoctask"
3
+ require 'spec/rake/spectask'
4
+ require 'rake/gempackagetask'
5
+
6
+ require File.join(File.expand_path(File.dirname(__FILE__)),
7
+ 'ruby','lib','couchapp')
8
+
9
+ spec = Gem::Specification.new do |s|
10
+ s.name = "couchapp"
11
+ s.version = CouchApp::VERSION
12
+ s.summary = "Standalone CouchDB Application Development Made Simple"
13
+ s.email = "jchris@apache.org"
14
+ s.homepage = "http://github.com/jchris/couchapp"
15
+ s.description = "CouchApp is a set of helpers and a jQuery plugin that conspire to get you up and running on CouchDB quickly and correctly. It brings clarity and order to the freedom of CouchDB’s document-based approach."
16
+ s.has_rdoc = true
17
+ s.authors = ["J Chris Anderson", "Jan Lehnardt", "Greg Borenstein"]
18
+ s.files = %w( LICENSE README.md Rakefile ruby/bin/couchapp) +
19
+ Dir["app-template/**/*.*"] + Dir["ruby/{bin,lib,spec}/**/*.*"] -
20
+ Dir["ruby/spec/scratch"] - Dir["ruby/spec/scratch/**/*"]
21
+ s.extra_rdoc_files = %w( README.md LICENSE )
22
+ s.require_path = "ruby/lib"
23
+ s.bindir = 'ruby/bin'
24
+ s.executables << 'couchapp'
25
+
26
+
27
+ dependencies = "
28
+
29
+ json
30
+ JSON implementation for Ruby using fast compiled bindings
31
+ http://json.rubyforge.org/
32
+ >=1.1.3
33
+
34
+ json_pure
35
+ JSON implementation for Ruby using pure ruby processing, which is required for JRuby
36
+ http://json.rubyforge.org/
37
+ >=1.1.3
38
+
39
+ couchrest
40
+ Lean and RESTful interface to CouchDB.
41
+ http://github.com/jchris/couchrest
42
+ >= 0.11.0
43
+
44
+ "
45
+
46
+ # mime-types
47
+ # MIME Types manages a MIME Content-Type that will return the Content-Type for a given filename.
48
+ # http://mime-types.rubyforge.org/
49
+ # >=1.15.0
50
+
51
+ dependencies = dependencies.strip.gsub(/^ +/,'').split(/\n\n/).map{|x|x.split(/\n/)}
52
+
53
+ dependencies.each{|d|
54
+ name, uri, desc, version, z = d
55
+ s.add_dependency(name,version)
56
+ }
57
+
58
+ end
59
+
60
+ ::Rake::GemPackageTask.new(spec) { |p| p.gem_spec = spec }
61
+
62
+ desc "Run all specs"
63
+ Spec::Rake::SpecTask.new('spec') do |t|
64
+ t.spec_files = FileList['ruby/spec/**/*_spec.rb']
65
+ end
66
+
67
+ desc "Print specdocs"
68
+ Spec::Rake::SpecTask.new(:doc) do |t|
69
+ t.spec_opts = ["--format", "specdoc"]
70
+ t.spec_files = FileList['ruby/spec/*_spec.rb']
71
+ end
72
+
73
+ desc "Generate the rdoc"
74
+ Rake::RDocTask.new do |rdoc|
75
+ files = ["README.md", "LICENSE", "ruby/lib/**/*.rb"]
76
+ rdoc.rdoc_files.add(files)
77
+ rdoc.main = "README.md"
78
+ rdoc.title = "CouchApp: Standalone CouchDB Application Development Made Simple"
79
+ end
80
+
81
+
82
+ desc "Run the rspec"
83
+ task :default => :spec
84
+
85
+
86
+ desc "Update Github Gemspec"
87
+ task :gemspec do
88
+ skip_fields = %w(new_platform original_platform)
89
+ integer_fields = %w(specification_version)
90
+
91
+ result = "Gem::Specification.new do |s|\n"
92
+ spec.instance_variables.each do |ivar|
93
+ value = spec.instance_variable_get(ivar)
94
+ name = ivar.split("@").last
95
+ next if skip_fields.include?(name) || value.nil? || value == "" || (value.respond_to?(:empty?) && value.empty?)
96
+ if name == "dependencies"
97
+ value.each do |d|
98
+ dep, *ver = d.to_s.split(" ")
99
+ result << " s.add_dependency #{dep.inspect}, #{ /\(([^\,]*)/ . match(ver.join(" "))[1].inspect}\n"
100
+ end
101
+ else
102
+ case value
103
+ when Array
104
+ value = name != "files" ? value.inspect : value.inspect.split(",").join(",\n")
105
+ when Fixnum
106
+ # leave as-is
107
+ when String
108
+ value = value.to_i if integer_fields.include?(name)
109
+ value = value.inspect
110
+ else
111
+ value = value.to_s.inspect
112
+ end
113
+ result << " s.#{name} = #{value}\n"
114
+ end
115
+ end
116
+ result << "end"
117
+ File.open(File.join(File.dirname(__FILE__), "#{spec.name}.gemspec"), "w"){|f| f << result}
118
+ end
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Generated CouchApp</title>
5
+ <link rel="stylesheet" href="screen.css" type="text/css">
6
+ </head>
7
+ <body>
8
+ <h1>Generated CouchApp</h1>
9
+ <ul id="view"></ul>
10
+ </body>
11
+ <script src="/_utils/script/json2.js"></script>
12
+ <script src="/_utils/script/jquery.js?1.2.6"></script>
13
+ <script src="/_utils/script/jquery.couch.js?0.8.0"></script>
14
+ <script type="text/javascript" charset="utf-8">
15
+ $(function() {
16
+ var dbname = document.location.href.split('/')[3];
17
+ var design = unescape(document.location.href).split('/')[5];
18
+ var DB = $.couch.db(dbname);
19
+ DB.view(design+"/example",{success: function(json) {
20
+ $("#view").html(json.rows.map(function(row) {
21
+ return '<li>'+row.key+'</li>';
22
+ }).join(''));
23
+ }});
24
+ });
25
+ </script>
26
+ </html>
@@ -0,0 +1 @@
1
+ /* add styles here */
@@ -0,0 +1,11 @@
1
+ Couchapp will create a field on your document corresponding to any directories you make within the application directory, with the text of any files found as key/value pairs.
2
+
3
+ Also, any files that end in .json will be treated as json rather than text, and put in the corresponding field. Also note that file.json, file.js, or file.txt will be stored under the "file" key, so don't make collisions in the filesystem unless you want unpredictable results.
4
+
5
+ Of course you know that the views directory will be treated specially and -map and -reduce files will be mapped to the map and reduce functions. And the _attachments directory will be treated strangely as well.
6
+
7
+ doc.json is a special case, it is treated as json and its keys are applied to the document root; eg it does not result in a "doc" field on your design document. If you need a doc field on the design document, you'll have to define it with a doc.json like so: {"doc":"value for doc field"}
8
+
9
+ ps: each design document only has one language key: it will be set based on the file extensions in the views directory. CouchDB defaults to Javascript, so that's what you'll get if you don't define any views. You can override it with the doc.json file, but don't say we didn't warn you.
10
+
11
+ Oh yeah it's recommended that you delete this file.
@@ -0,0 +1 @@
1
+ function stddev() {};
@@ -0,0 +1,32 @@
1
+ // Simple JavaScript Templating
2
+ // John Resig - http://ejohn.org/ - MIT Licensed
3
+ var cache = {};
4
+
5
+ function template(str, data){
6
+ // Figure out if we're getting a template, or if we need to
7
+ // load the template - and be sure to cache the result.
8
+ var fn = cache[str] ||
9
+
10
+ // Generate a reusable function that will serve as a template
11
+ // generator (and which will be cached).
12
+ new Function("obj",
13
+ "var p=[],print=function(){p.push.apply(p,arguments);};" +
14
+
15
+ // Introduce the data as local variables using with(){}
16
+ "with(obj){p.push('" +
17
+
18
+ // Convert the template into pure JavaScript
19
+ str
20
+ .replace(/[\r\t\n]/g, " ")
21
+ .replace(/'(?=[^%]*%>)/g,"\t")
22
+ .split("'").join("\\'")
23
+ .split("\t").join("'")
24
+ .replace(/<%=(.+?)%>/g, "',$1,'")
25
+ .split("<%").join("');")
26
+ .split("%>").join("p.push('")
27
+ + "');}return p.join('');");
28
+ cache[str] = fn;
29
+
30
+ // Provide some basic currying to the user
31
+ return data ? fn( data ) : fn;
32
+ };
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Generated CouchApp Form Template</title>
5
+ </head>
6
+ <body>
7
+ <div id="header">
8
+ <h2><a href="index.html">Back to index</a></h2>
9
+ </div>
10
+ <div id="content">
11
+ <h1><% doc.title %></h1>
12
+ </div>
13
+ </body>
14
+ <script src="/_utils/script/json2.js"></script>
15
+ <script src="/_utils/script/jquery.js?1.2.6"></script>
16
+ <script src="/_utils/script/jquery.couch.js?0.8.0"></script>
17
+ <script src="jquery.couchapp.js"></script>
18
+ <script src="blog.js"></script>
19
+ <script type="text/javascript" charset="utf-8">
20
+ $.CouchApp(function(app) {
21
+ var docid = document.location.pathname.split('/').pop();
22
+ // hey you could run a query to load more information from views
23
+ });
24
+ </script>
25
+ <script src="showdown.js"></script>
26
+ </html>
@@ -0,0 +1,16 @@
1
+ function(doc, req) {
2
+ // !code lib.helpers.template
3
+ // !json lib.templates
4
+
5
+ respondWith(req, {
6
+ html : function() {
7
+ var html = template(lib.templates.example, doc);
8
+ return {body:html}
9
+ },
10
+ xml : function() {
11
+ return {
12
+ body : <xml><node value={doc.title}/></xml>
13
+ }
14
+ }
15
+ })
16
+ };
@@ -0,0 +1,9 @@
1
+ // an example map function, emits the doc id
2
+ // and the list of keys it contains
3
+ // !code lib.helpers.math
4
+
5
+ function(doc) {
6
+ var k, keys = []
7
+ for (k in doc) keys.push(k);
8
+ emit(doc._id, keys);
9
+ };
@@ -0,0 +1,10 @@
1
+ // example reduce function to count the
2
+ // number of rows in a given key range.
3
+
4
+ function(keys, values, rereduce) {
5
+ if (rereduce) {
6
+ return sum(values);
7
+ } else {
8
+ return values.length;
9
+ }
10
+ };
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'rubygems'
5
+ require 'couchrest'
6
+ require File.expand_path(File.dirname(__FILE__)) + '/../lib/couchapp'
7
+
8
+ options = {
9
+ :loud => true,
10
+ }
11
+
12
+ opts = OptionParser.new do |opts|
13
+ opts.banner = "Usage: #$0 [options] (push|generate)"
14
+ opts.on('-q', '--quiet', "Omit extra debug info") do
15
+ options[:loud] = false
16
+ end
17
+ opts.on_tail('-h', '--help', "Display detailed help and exit") do
18
+ puts opts
19
+ exit
20
+ end
21
+ end
22
+
23
+ opts.parse!(ARGV)
24
+
25
+ case ARGV.shift
26
+ when /generate/
27
+ appname = ARGV.shift
28
+ current = Dir.getwd
29
+ appdir = File.join(current, appname)
30
+ puts "Generating a new CouchApp in #{appdir}"
31
+ CouchApp::FileManager.generate_app(appdir, options[:loud])
32
+
33
+ when /push/
34
+ dirname = ARGV.shift
35
+ current = Dir.getwd
36
+ dir = File.expand_path(File.join(current, dirname))
37
+ dirapp = File.split(dir).last
38
+ if ARGV.length == 2
39
+ appname = ARGV.shift
40
+ dbstring = ARGV.shift
41
+ elsif ARGV.length == 1
42
+ appname = dirapp
43
+ dbstring = ARGV.shift
44
+ else
45
+ puts opts
46
+ puts "push dirname [appname] database"
47
+ exit(0)
48
+ end
49
+ CouchRest.database!(dbstring)
50
+ dbspec = CouchRest.parse(dbstring)
51
+ fm = CouchApp::FileManager.new(dbspec[:database], dbspec[:host])
52
+ fm.push_app(dir, appname)
53
+
54
+ when /pull/
55
+ puts "pull is not yet implemented"
56
+
57
+ else
58
+ puts opts
59
+ puts "please specify a command"
60
+ end
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'couchrest'
3
+ require 'digest/md5'
4
+
5
+ module CouchApp
6
+
7
+ VERSION = '0.1.4'
8
+
9
+ end
10
+
11
+ require File.expand_path(File.dirname(__FILE__)) + '/file_manager'
@@ -0,0 +1,232 @@
1
+ module CouchApp
2
+
3
+ class FileManager
4
+ attr_reader :db
5
+ attr_accessor :loud
6
+
7
+ LANGS = {"rb" => "ruby", "js" => "javascript"}
8
+ MIMES = {
9
+ "html" => "text/html",
10
+ "htm" => "text/html",
11
+ "png" => "image/png",
12
+ "gif" => "image/gif",
13
+ "css" => "text/css",
14
+ "js" => "test/javascript",
15
+ "txt" => "text/plain"
16
+ }
17
+
18
+ # Generate an application in the given directory.
19
+ # This is a class method because it doesn't depend on
20
+ # specifying a database.
21
+ def self.generate_app(app_dir, loud = false)
22
+ templatedir = File.join(File.expand_path(File.dirname(__FILE__)),
23
+ '..', '..', 'app-template')
24
+ FileUtils.cp_r(templatedir, app_dir)
25
+ puts Dir[app_dir+'/**/*'] if loud
26
+ end
27
+
28
+ # instance methods
29
+
30
+ def initialize(dbname, host="http://127.0.0.1:5984")
31
+ @db = CouchRest.new(host).database(dbname)
32
+ end
33
+
34
+ # maintain the correspondence between an fs and couch
35
+
36
+ def push_app(appdir, appname)
37
+ libs = []
38
+ viewdir = File.join(appdir,"views")
39
+ attachdir = File.join(appdir,"_attachments")
40
+
41
+ @doc = dir_to_fields(appdir)
42
+ package_shows(@doc["show"]["docs"]) if (@doc["show"] && @doc["show"]["docs"])
43
+ package_views(@doc["views"]) if @doc['views']
44
+
45
+ docid = "_design/#{appname}"
46
+ design = @db.get(docid) rescue {}
47
+ design.merge!(@doc)
48
+ design['_id'] = docid
49
+ # design['language'] = lang if lang
50
+ @db.save(design)
51
+ push_directory(attachdir, docid)
52
+ end
53
+
54
+ def dir_to_fields(dir)
55
+ fields = {}
56
+ (Dir["#{dir}/**/*.*"] -
57
+ Dir["#{dir}/_attachments/**/*.*"]).each do |file|
58
+ farray = file.sub(dir, '').sub(/^\//,'').split('/')
59
+ myfield = fields
60
+ while farray.length > 1
61
+ front = farray.shift
62
+ myfield[front] ||= {}
63
+ myfield = myfield[front]
64
+ end
65
+ fname, fext = farray.shift.split('.')
66
+ fguts = File.open(file).read
67
+ if fext == 'json'
68
+ myfield[fname] = JSON.parse(fguts)
69
+ else
70
+ myfield[fname] = fguts
71
+ end
72
+ end
73
+ return fields
74
+ end
75
+
76
+ def push_directory(push_dir, docid=nil)
77
+ docid ||= push_dir.split('/').reverse.find{|part|!part.empty?}
78
+
79
+ pushfiles = Dir["#{push_dir}/**/*.*"].collect do |f|
80
+ {f.split("#{push_dir}/").last => open(f).read}
81
+ end
82
+
83
+ return if pushfiles.empty?
84
+
85
+ @attachments = {}
86
+ @signatures = {}
87
+ pushfiles.each do |file|
88
+ name = file.keys.first
89
+ value = file.values.first
90
+ @signatures[name] = md5(value)
91
+
92
+ @attachments[name] = {
93
+ "data" => value,
94
+ "content_type" => MIMES[name.split('.').last]
95
+ }
96
+ end
97
+
98
+ doc = @db.get(docid) rescue nil
99
+
100
+ unless doc
101
+ say "creating #{docid}"
102
+ @db.save({"_id" => docid, "_attachments" => @attachments, "signatures" => @signatures})
103
+ return
104
+ end
105
+
106
+ doc["signatures"] ||= {}
107
+ doc["_attachments"] ||= {}
108
+ # remove deleted docs
109
+ to_be_removed = doc["signatures"].keys.select do |d|
110
+ !pushfiles.collect{|p| p.keys.first}.include?(d)
111
+ end
112
+
113
+ to_be_removed.each do |p|
114
+ say "deleting #{p}"
115
+ doc["signatures"].delete(p)
116
+ doc["_attachments"].delete(p)
117
+ end
118
+
119
+ # update existing docs:
120
+ doc["signatures"].each do |path, sig|
121
+ if (@signatures[path] == sig)
122
+ say "no change to #{path}. skipping..."
123
+ else
124
+ say "replacing #{path}"
125
+ doc["signatures"][path] = md5(@attachments[path]["data"])
126
+ doc["_attachments"][path].delete("stub")
127
+ doc["_attachments"][path].delete("length")
128
+ doc["_attachments"][path]["data"] = @attachments[path]["data"]
129
+ doc["_attachments"][path].merge!({"data" => @attachments[path]["data"]} )
130
+ end
131
+ end
132
+
133
+ # add in new files
134
+ new_files = pushfiles.select{|d| !doc["signatures"].keys.include?( d.keys.first) }
135
+
136
+ new_files.each do |f|
137
+ say "creating #{f}"
138
+ path = f.keys.first
139
+ content = f.values.first
140
+ doc["signatures"][path] = md5(content)
141
+
142
+ doc["_attachments"][path] = {
143
+ "data" => content,
144
+ "content_type" => MIMES[path.split('.').last]
145
+ }
146
+ end
147
+
148
+ begin
149
+ @db.save(doc)
150
+ rescue Exception => e
151
+ say e.message
152
+ end
153
+ end
154
+
155
+ private
156
+
157
+ def package_shows(funcs)
158
+ apply_lib(funcs)
159
+ end
160
+
161
+ def package_views(views)
162
+ views.each do |view, funcs|
163
+ apply_lib(funcs)
164
+ end
165
+ end
166
+
167
+ def apply_lib(funcs)
168
+ funcs.each do |k,v|
169
+ next unless v.is_a?(String)
170
+ funcs[k] = process_include(process_require(v))
171
+ end
172
+ end
173
+
174
+ # process requires
175
+ def process_require(f_string)
176
+ f_string.gsub /(\/\/|#)\ ?!code (.*)/ do
177
+ fields = $2.split('.')
178
+ library = @doc
179
+ fields.each do |field|
180
+ library = library[field]
181
+ break unless library
182
+ end
183
+ library
184
+ end
185
+ end
186
+
187
+ # process includes
188
+ def process_include(f_string)
189
+ included = {}
190
+ f_string.gsub /(\/\/|#)\ ?!json (.*)/ do
191
+ fields = $2.split('.')
192
+ library = @doc
193
+ include_to = included
194
+ count = fields.length
195
+ fields.each_with_index do |field, i|
196
+ break unless library[field]
197
+ library = library[field]
198
+ # normal case
199
+ if i+1 < count
200
+ include_to[field] = include_to[field] || {}
201
+ include_to = include_to[field]
202
+ else
203
+ # last one
204
+ include_to[field] = library
205
+ end
206
+ end
207
+
208
+ end
209
+ # puts included.inspect
210
+ rval = if included == {}
211
+ f_string
212
+ else
213
+ varstrings = included.collect do |k, v|
214
+ "var #{k} = #{v.to_json};"
215
+ end
216
+ # just replace the first instance of the macro
217
+ f_string.sub /(\/\/|#)\ ?!json (.*)/, varstrings.join("\n")
218
+ end
219
+
220
+ rval
221
+ end
222
+
223
+
224
+ def say words
225
+ puts words if @loud
226
+ end
227
+
228
+ def md5 string
229
+ Digest::MD5.hexdigest(string)
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,114 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe "couchapp" do
4
+ before(:all) do
5
+ @fixdir = SCRATCH_PATH + '/couchapp-test'
6
+ `rm -rf #{@fixdir}`
7
+ `mkdir -p #{@fixdir}`
8
+ @run = "cd #{@fixdir} && #{COUCHAPP}"
9
+ end
10
+
11
+ describe "--help" do
12
+ it "should output the opts" do
13
+ `#{@run} --help`.should match(/Usage/)
14
+ end
15
+ end
16
+
17
+ describe "generate my-app" do
18
+ before(:all) do
19
+ `#{@run} generate my-app`.should match(/generating/i)
20
+ end
21
+ it "should create an app directory" do
22
+ Dir["#{@fixdir}/*"].select{|x|x =~ /my-app/}.length.should == 1
23
+ end
24
+ it "should create a views directory" do
25
+ Dir["#{@fixdir}/my-app/*"].select{|x|x =~ /views/}.length.should == 1
26
+ end
27
+ it "should create an _attachments directory" do
28
+ Dir["#{@fixdir}/my-app/*"].select{|x|x =~ /_attachments/}.length.should == 1
29
+ Dir["#{@fixdir}/my-app/_attachments/*"].select{|x|x =~ /index.html/}.length.should == 1
30
+ end
31
+ it "should create a show directory" do
32
+ Dir["#{@fixdir}/my-app/*"].select{|x|x =~ /show/}.length.should == 1
33
+ end
34
+ it "should create a forms and libs" do
35
+ Dir["#{@fixdir}/my-app/show/docs/*"].select{|x|x =~ /example-show.js/}.length.should == 1
36
+ Dir["#{@fixdir}/my-app/lib/templates/*"].select{|x|x =~ /example.html/}.length.should == 1
37
+ end
38
+ it "should show deep attachment capabilities" do
39
+ Dir["#{@fixdir}/my-app/_attachments/**/*"].select{|x|x =~ /main.css/}.
40
+ first.should include('style')
41
+ end
42
+ end
43
+
44
+ describe "push my-app #{TESTDB}" do
45
+ before(:all) do
46
+ @cr = CouchRest.new(COUCHHOST)
47
+ @db = @cr.database(TESTDB)
48
+ @db.delete! rescue nil
49
+ @db = @cr.create_db(TESTDB) rescue nil
50
+ `#{@run} generate my-app`
51
+ `#{@run} push my-app #{TESTDB}`
52
+ @doc = @db.get("_design/my-app")
53
+ end
54
+ it "should create the design document with the app name" do
55
+ lambda{@db.get("_design/my-app")}.should_not raise_error
56
+ end
57
+ it "should create the views" do
58
+ @doc['views']['example']['map'].should match(/function/)
59
+ end
60
+ it "should create the view libs" do
61
+ @doc['views']['example']['map'].should match(/stddev/)
62
+ @doc['show']['docs']['example-show'].should_not match(/\"helpers\"/)
63
+ end
64
+ it "should create view for all the views" do
65
+ `mkdir -p #{@fixdir}/my-app/views/more`
66
+ `echo 'moremap' > #{@fixdir}/my-app/views/more/map.js`
67
+ `#{@run} push my-app #{TESTDB}`
68
+ doc = @db.get("_design/my-app")
69
+ doc['views']['more']['map'].should match(/moremap/)
70
+ end
71
+ it "should create the index" do
72
+ @doc['_attachments']['index.html']["content_type"].should == 'text/html'
73
+ end
74
+ it "should push the forms" do
75
+ @doc['show']['docs']['example-show'].should match(/Generated CouchApp Form Template/)
76
+ end
77
+ it "should allow deeper includes" do
78
+ @doc['show']['docs']['example-show'].should_not match(/\"helpers\"/)
79
+ end
80
+ it "deep requires" do
81
+ @doc['show']['docs']['example-show'].should_not match(/\"template\"/)
82
+ @doc['show']['docs']['example-show'].should match(/Resig/)
83
+ end
84
+ end
85
+
86
+ describe "push . #{TESTDB}" do
87
+ before(:all) do
88
+ @cr = CouchRest.new(COUCHHOST)
89
+ @db = @cr.database(TESTDB)
90
+ @db.delete! rescue nil
91
+ @db = @cr.create_db(TESTDB) rescue nil
92
+ `#{@run} generate my-app`
93
+ end
94
+ it "should create the design document" do
95
+ `cd #{@fixdir}/my-app && #{COUCHAPP} push . #{TESTDB}`
96
+ lambda{@db.get("_design/my-app")}.should_not raise_error
97
+ end
98
+ end
99
+
100
+ describe "push my-app my-design #{TESTDB}" do
101
+ before(:all) do
102
+ @cr = CouchRest.new(COUCHHOST)
103
+ @db = @cr.database(TESTDB)
104
+ @db.delete! rescue nil
105
+ @db = @cr.create_db(TESTDB) rescue nil
106
+ `#{@run} generate my-app`
107
+ end
108
+ it "should create the design document" do
109
+ `#{@run} push my-app my-design #{TESTDB}`
110
+ lambda{@db.get("_design/my-design")}.should_not raise_error
111
+ end
112
+ end
113
+ end
114
+
@@ -0,0 +1,207 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe CouchApp::FileManager do
4
+ before(:all) do
5
+ @cr = CouchRest.new(COUCHHOST)
6
+ @db = @cr.database(TESTDB)
7
+ @db.delete! rescue nil
8
+ @db = @cr.create_db(TESTDB) rescue nil
9
+ end
10
+ it "should initialize" do
11
+ @fm = CouchApp::FileManager.new(TESTDB)
12
+ @fm.should_not be_nil
13
+ end
14
+ it "should require a db name" do
15
+ lambda{CouchApp::FileManager.new}.should raise_error
16
+ end
17
+ it "should accept a db name" do
18
+ @fm = CouchApp::FileManager.new(TESTDB, 'http://127.0.0.1')
19
+ @fm.db.name.should == TESTDB
20
+ end
21
+ it "should default to 127.0.0.1 couchdb" do
22
+ @fm = CouchApp::FileManager.new(TESTDB)
23
+ @fm.db.host.should == 'http://127.0.0.1:5984'
24
+ end
25
+ end
26
+
27
+ # describe CouchApp::FileManager, "generating an app" do
28
+ # before(:all) do
29
+ # @appdir = FIXTURE_PATH + '/couchapp/template-app'
30
+ # `rm -rf #{@appdir}`
31
+ # `mkdir -p #{@appdir}`
32
+ # CouchApp::FileManager.generate_app(@appdir)
33
+ # end
34
+ # it "should create an attachments directory" do
35
+ # Dir["#{@appdir}/*"].select{|x|x =~ /_attachments/}.length.should == 1
36
+ # end
37
+ # it "should create a views directory" do
38
+ # Dir["#{@appdir}/*"].select{|x|x =~ /views/}.length.should == 1
39
+ # end
40
+ # it "should create a foo directory" do
41
+ # Dir["#{@appdir}/*"].select{|x|x =~ /foo/}.length.should == 1
42
+ # end
43
+ # it "should create index.html" do
44
+ # html = File.open("#{@appdir}/_attachments/index.html").read
45
+ # html.should match(/DOCTYPE/)
46
+ # end
47
+ # it "should create bar.txt" do
48
+ # html = File.open("#{@appdir}/foo/bar.txt").read
49
+ # html.should match(/Couchapp will/)
50
+ # end
51
+ # it "should create an example view" do
52
+ # map = File.open("#{@appdir}/views/example/map.js").read
53
+ # map.should match(/function\(doc\)/)
54
+ # reduce = File.open("#{@appdir}/views/example/reduce.js").read
55
+ # reduce.should match(/rereduce/)
56
+ # end
57
+ # end
58
+
59
+ describe CouchApp::FileManager, "pushing an app" do
60
+ before(:all) do
61
+ @cr = CouchRest.new(COUCHHOST)
62
+ @db = @cr.database(TESTDB)
63
+ @db.delete! rescue nil
64
+ @db = @cr.create_db(TESTDB) rescue nil
65
+
66
+ @appdir = SCRATCH_PATH + '/generated-app'
67
+
68
+ `rm -rf #{@appdir}`
69
+ `mkdir -p #{@appdir}`
70
+ CouchApp::FileManager.generate_app(@appdir)
71
+
72
+ @fm = CouchApp::FileManager.new(TESTDB, COUCHHOST)
73
+ r = @fm.push_app(@appdir, "couchapp")
74
+ end
75
+ it "should create a design document" do
76
+ lambda{@db.get("_design/couchapp")}.should_not raise_error
77
+ end
78
+ # it "should create the views" do
79
+ # doc = @db.get("_design/couchapp")
80
+ # doc['views']['example']['map'].should match(/function/)
81
+ # end
82
+ # it "should create the index" do
83
+ # doc = @db.get("_design/couchapp")
84
+ # doc['_attachments']['index.html']["content_type"].should == 'text/html'
85
+ # end
86
+ # it "should push bar.txt and pals" do
87
+ # FileUtils.mkdir_p("#{@appdir}/foo")
88
+ # File.open("#{@appdir}/foo/test.json",'w') do |f|
89
+ # f.write("[1,2,3,4]")
90
+ # end
91
+ # r = @fm.push_app(@appdir, "couchapp")
92
+ # doc = @db.get("_design/couchapp")
93
+ # doc["foo"].should_not be_nil
94
+ # doc["foo"]["bar"].should include("Couchapp will")
95
+ # doc["foo"]["test"].should == [1,2,3,4]
96
+ # end
97
+ it "should push json as json" do
98
+ File.open("#{@appdir}/test.json",'w') do |f|
99
+ f.write("[1,2,3,4]")
100
+ end
101
+ r = @fm.push_app(@appdir, "couchapp")
102
+ doc = @db.get("_design/couchapp")
103
+ doc['test'].should == [1,2,3,4]
104
+ end
105
+ it "handles not having a forms directory" do
106
+ `rm -rf #{@appdir}/forms`
107
+ lambda { @fm.push_app(@appdir, "couchapp") }.should_not raise_error
108
+ `mkdir -p #{@appdir}/forms`
109
+ end
110
+ it "handles not having a forms directory" do
111
+ `rm -rf #{@appdir}/forms`
112
+ lambda { @fm.push_app(@appdir, "couchapp") }.should_not raise_error
113
+ `mkdir -p #{@appdir}/forms`
114
+ end
115
+ end
116
+
117
+
118
+ # describe CouchApp::FileManager, "pushing views" do
119
+ # before(:all) do
120
+ # @cr = CouchRest.new(COUCHHOST)
121
+ # @db = @cr.database(TESTDB)
122
+ # @db.delete! rescue nil
123
+ # @db = @cr.create_db(TESTDB) rescue nil
124
+ #
125
+ # @fm = CouchApp::FileManager.new(TESTDB, COUCHHOST)
126
+ # @view_dir = FIXTURE_PATH + '/views'
127
+ # ds = @fm.push_views(@view_dir)
128
+ # @design = @db.get("_design/test_view")
129
+ # end
130
+ # it "should create a design document for each folder" do
131
+ # @design["views"].should_not be_nil
132
+ # end
133
+ # it "should push a map and reduce view" do
134
+ # @design["views"]["test"]["map"].should_not be_nil
135
+ # @design["views"]["test"]["reduce"].should_not be_nil
136
+ # end
137
+ # it "should push a map only view" do
138
+ # @design["views"]["only"]["map"].should_not be_nil
139
+ # @design["views"]["only"]["reduce"].should be_nil
140
+ # end
141
+ # it "should include library files" do
142
+ # @design["views"]["only"]["map"].should include("globalLib")
143
+ # @design["views"]["only"]["map"].should include("justThisView")
144
+ # end
145
+ # it "should not create extra design docs" do
146
+ # docs = @db.documents(:startkey => '_design', :endkey => '_design/ZZZZZZ')
147
+ # docs['total_rows'].should == 1
148
+ # end
149
+ # end
150
+
151
+ # describe CouchApp::FileManager, "pushing a directory with id" do
152
+ # before(:all) do
153
+ # @cr = CouchRest.new(COUCHHOST)
154
+ # @db = @cr.database(TESTDB)
155
+ # @db.delete! rescue nil
156
+ # @db = @cr.create_db(TESTDB) rescue nil
157
+ #
158
+ # @fm = CouchApp::FileManager.new(TESTDB, COUCHHOST)
159
+ # @push_dir = FIXTURE_PATH + '/attachments'
160
+ # ds = @fm.push_directory(@push_dir, 'attached')
161
+ # end
162
+ # it "should create a document for the folder" do
163
+ # @db.get("attached")
164
+ # end
165
+ # it "should make attachments" do
166
+ # doc = @db.get("attached")
167
+ # doc["_attachments"]["test.html"].should_not be_nil
168
+ # end
169
+ # it "should set the content type" do
170
+ # doc = @db.get("attached")
171
+ # doc["_attachments"]["test.html"]["content_type"].should == "text/html"
172
+ # end
173
+ # end
174
+
175
+ # describe CouchApp::FileManager, "pushing a directory without id" do
176
+ # before(:all) do
177
+ # @cr = CouchRest.new(COUCHHOST)
178
+ # @db = @cr.database(TESTDB)
179
+ # @db.delete! rescue nil
180
+ # @db = @cr.create_db(TESTDB) rescue nil
181
+ #
182
+ # @fm = CouchApp::FileManager.new(TESTDB, COUCHHOST)
183
+ # @push_dir = FIXTURE_PATH + '/attachments'
184
+ # ds = @fm.push_directory(@push_dir)
185
+ # end
186
+ # it "should use the dirname" do
187
+ # doc = @db.get("attachments")
188
+ # doc["_attachments"]["test.html"].should_not be_nil
189
+ # end
190
+ # end
191
+ #
192
+ # describe CouchApp::FileManager, "pushing a directory/ without id" do
193
+ # before(:all) do
194
+ # @cr = CouchRest.new(COUCHHOST)
195
+ # @db = @cr.database(TESTDB)
196
+ # @db.delete! rescue nil
197
+ # @db = @cr.create_db(TESTDB) rescue nil
198
+ #
199
+ # @fm = CouchApp::FileManager.new(TESTDB, COUCHHOST)
200
+ # @push_dir = FIXTURE_PATH + '/attachments/'
201
+ # ds = @fm.push_directory(@push_dir)
202
+ # end
203
+ # it "should use the dirname" do
204
+ # doc = @db.get("attachments")
205
+ # doc["_attachments"]["test.html"].should_not be_nil
206
+ # end
207
+ # end
@@ -0,0 +1,6 @@
1
+ --colour
2
+ --format
3
+ progress
4
+ --loadby
5
+ mtime
6
+ --reverse
@@ -0,0 +1,18 @@
1
+ require "rubygems"
2
+ require "spec" # Satisfies Autotest and anyone else not using the Rake tasks
3
+ require "couchrest"
4
+
5
+ require File.expand_path(File.dirname(__FILE__)) + '/../lib/couchapp'
6
+
7
+ COUCHAPP = ARGV[0] || File.expand_path(File.dirname(__FILE__)) + '/../bin/couchapp'
8
+ SCRATCH_PATH = File.dirname(__FILE__) + '/scratch'
9
+ COUCHHOST = "http://127.0.0.1:5984"
10
+ TESTDB = 'couchapp-test'
11
+
12
+ def reset_test_db!
13
+ cr = CouchRest.new(COUCHHOST)
14
+ db = cr.database(TESTDB)
15
+ db.delete! rescue nil
16
+ db = cr.create_db(TESTDB) rescue nin
17
+ db
18
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: couchapp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.4
5
+ platform: ruby
6
+ authors:
7
+ - J Chris Anderson
8
+ - Jan Lehnardt
9
+ - Greg Borenstein
10
+ autorequire:
11
+ bindir: ruby/bin
12
+ cert_chain: []
13
+
14
+ date: 2009-01-13 00:00:00 -08:00
15
+ default_executable:
16
+ dependencies:
17
+ - !ruby/object:Gem::Dependency
18
+ name: json
19
+ type: :runtime
20
+ version_requirement:
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 1.1.3
26
+ version:
27
+ - !ruby/object:Gem::Dependency
28
+ name: json_pure
29
+ type: :runtime
30
+ version_requirement:
31
+ version_requirements: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: 1.1.3
36
+ version:
37
+ - !ruby/object:Gem::Dependency
38
+ name: couchrest
39
+ type: :runtime
40
+ version_requirement:
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: 0.11.0
46
+ version:
47
+ description: "CouchApp is a set of helpers and a jQuery plugin that conspire to get you up and running on CouchDB quickly and correctly. It brings clarity and order to the freedom of CouchDB\xE2\x80\x99s document-based approach."
48
+ email: jchris@apache.org
49
+ executables:
50
+ - couchapp
51
+ extensions: []
52
+
53
+ extra_rdoc_files:
54
+ - README.md
55
+ - LICENSE
56
+ files:
57
+ - LICENSE
58
+ - README.md
59
+ - Rakefile
60
+ - ruby/bin/couchapp
61
+ - app-template/_attachments/index.html
62
+ - app-template/_attachments/style/main.css
63
+ - app-template/foo/bar.txt
64
+ - app-template/lib/helpers/math.js
65
+ - app-template/lib/helpers/template.js
66
+ - app-template/lib/templates/example.html
67
+ - app-template/show/docs/example-show.js
68
+ - app-template/views/example/map.js
69
+ - app-template/views/example/reduce.js
70
+ - ruby/lib/couchapp.rb
71
+ - ruby/lib/file_manager.rb
72
+ - ruby/spec/couchapp_spec.rb
73
+ - ruby/spec/file_manager_spec.rb
74
+ - ruby/spec/spec.opts
75
+ - ruby/spec/spec_helper.rb
76
+ has_rdoc: "true"
77
+ homepage: http://github.com/jchris/couchapp
78
+ post_install_message:
79
+ rdoc_options: []
80
+
81
+ require_paths:
82
+ - ruby/lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: "0"
88
+ version:
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: "0"
94
+ version:
95
+ requirements: []
96
+
97
+ rubyforge_project:
98
+ rubygems_version: 1.2.0
99
+ signing_key:
100
+ specification_version: 2
101
+ summary: Standalone CouchDB Application Development Made Simple
102
+ test_files: []
103
+