move some couchapp components to couchapp project
This commit is contained in:
parent
3c789ab317
commit
fb613e7dfb
58
bin/couchapp
58
bin/couchapp
|
@ -1,58 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'optparse'
|
||||
require File.expand_path(File.dirname(__FILE__)) + '/../lib/couchrest'
|
||||
|
||||
options = {
|
||||
:loud => true,
|
||||
}
|
||||
|
||||
opts = OptionParser.new do |opts|
|
||||
opts.banner = "Usage: #$0 [options] (push|pull|generate)"
|
||||
opts.on('-q', '--quiet', "Omit extra debug info") do
|
||||
options[:loud] = false
|
||||
end
|
||||
opts.on_tail('-h', '--help', "Display detailed help and exit") do
|
||||
puts opts
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
||||
opts.parse!(ARGV)
|
||||
|
||||
case ARGV.shift
|
||||
when /generate/
|
||||
appname = ARGV.shift
|
||||
current = Dir.getwd
|
||||
appdir = File.join(current, appname)
|
||||
puts "generating couchapp in #{appdir}"
|
||||
CouchRest::FileManager.generate_app(appdir)
|
||||
|
||||
when /push/
|
||||
dirname = ARGV.shift
|
||||
current = Dir.getwd
|
||||
dir = File.expand_path(File.join(current, dirname))
|
||||
dirapp = File.split(dir).last
|
||||
if ARGV.length == 2
|
||||
appname = ARGV.shift
|
||||
dbstring = ARGV.shift
|
||||
elsif ARGV.length == 1
|
||||
appname = dirapp
|
||||
dbstring = ARGV.shift
|
||||
else
|
||||
puts opts
|
||||
puts "push dirname [appname] database"
|
||||
exit(0)
|
||||
end
|
||||
CouchRest.database!(dbstring)
|
||||
dbspec = CouchRest.parse(dbstring)
|
||||
fm = CouchRest::FileManager.new(dbspec[:database], dbspec[:host])
|
||||
fm.push_app(dir, appname)
|
||||
|
||||
when /pull/
|
||||
puts "pull is not yet implemented"
|
||||
|
||||
else
|
||||
puts opts
|
||||
puts "please specify a command"
|
||||
end
|
|
@ -1,26 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Generated CouchApp</title>
|
||||
<link rel="stylesheet" href="screen.css" type="text/css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Generated CouchApp</h1>
|
||||
<ul id="view"></ul>
|
||||
</body>
|
||||
<script src="/_utils/script/json2.js"></script>
|
||||
<script src="/_utils/script/jquery.js?1.2.6"></script>
|
||||
<script src="/_utils/script/jquery.couch.js?0.8.0"></script>
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
$(function() {
|
||||
var dbname = document.location.href.split('/')[3];
|
||||
var design = unescape(document.location.href).split('/')[5];
|
||||
var DB = $.couch.db(dbname);
|
||||
DB.view(design+"/example",{success: function(json) {
|
||||
$("#view").html(json.rows.map(function(row) {
|
||||
return '<li>'+row.key+'</li>';
|
||||
}).join(''));
|
||||
}});
|
||||
});
|
||||
</script>
|
||||
</html>
|
|
@ -1,11 +0,0 @@
|
|||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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"}
|
||||
|
||||
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.
|
||||
|
||||
Oh yeah it's recommended that you delete this file.
|
|
@ -1,16 +0,0 @@
|
|||
function(doc, req) {
|
||||
// !code lib.helpers.template
|
||||
// !json lib.templates
|
||||
|
||||
respondWith(req, {
|
||||
html : function() {
|
||||
var html = template(lib.templates.example, doc);
|
||||
return {body:html}
|
||||
},
|
||||
xml : function() {
|
||||
return {
|
||||
body : <xml><node value={doc.title}/></xml>
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
|
@ -1 +0,0 @@
|
|||
function stddev() {};
|
|
@ -1,32 +0,0 @@
|
|||
// Simple JavaScript Templating
|
||||
// John Resig - http://ejohn.org/ - MIT Licensed
|
||||
var cache = {};
|
||||
|
||||
function template(str, data){
|
||||
// Figure out if we're getting a template, or if we need to
|
||||
// load the template - and be sure to cache the result.
|
||||
var fn = cache[str] ||
|
||||
|
||||
// Generate a reusable function that will serve as a template
|
||||
// generator (and which will be cached).
|
||||
new Function("obj",
|
||||
"var p=[],print=function(){p.push.apply(p,arguments);};" +
|
||||
|
||||
// Introduce the data as local variables using with(){}
|
||||
"with(obj){p.push('" +
|
||||
|
||||
// Convert the template into pure JavaScript
|
||||
str
|
||||
.replace(/[\r\t\n]/g, " ")
|
||||
.replace(/'(?=[^%]*%>)/g,"\t")
|
||||
.split("'").join("\\'")
|
||||
.split("\t").join("'")
|
||||
.replace(/<%=(.+?)%>/g, "',$1,'")
|
||||
.split("<%").join("');")
|
||||
.split("%>").join("p.push('")
|
||||
+ "');}return p.join('');");
|
||||
cache[str] = fn;
|
||||
|
||||
// Provide some basic currying to the user
|
||||
return data ? fn( data ) : fn;
|
||||
};
|
|
@ -1,26 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Generated CouchApp Form Template</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="header">
|
||||
<h2><a href="index.html">Back to index</a></h2>
|
||||
</div>
|
||||
<div id="content">
|
||||
<h1><% doc.title %></h1>
|
||||
</div>
|
||||
</body>
|
||||
<script src="/_utils/script/json2.js"></script>
|
||||
<script src="/_utils/script/jquery.js?1.2.6"></script>
|
||||
<script src="/_utils/script/jquery.couch.js?0.8.0"></script>
|
||||
<script src="jquery.couchapp.js"></script>
|
||||
<script src="blog.js"></script>
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
$.CouchApp(function(app) {
|
||||
var docid = document.location.pathname.split('/').pop();
|
||||
// hey you could run a query to load more information from views
|
||||
});
|
||||
</script>
|
||||
<script src="showdown.js"></script>
|
||||
</html>
|
|
@ -1,9 +0,0 @@
|
|||
// an example map function, emits the doc id
|
||||
// and the list of keys it contains
|
||||
// !code lib.helpers.math
|
||||
|
||||
function(doc) {
|
||||
var k, keys = []
|
||||
for (k in doc) keys.push(k);
|
||||
emit(doc._id, keys);
|
||||
};
|
|
@ -1,10 +0,0 @@
|
|||
// example reduce function to count the
|
||||
// number of rows in a given key range.
|
||||
|
||||
function(keys, values, rereduce) {
|
||||
if (rereduce) {
|
||||
return sum(values);
|
||||
} else {
|
||||
return values.length;
|
||||
}
|
||||
};
|
|
@ -1,233 +0,0 @@
|
|||
require 'digest/md5'
|
||||
|
||||
module CouchRest
|
||||
class FileManager
|
||||
attr_reader :db
|
||||
attr_accessor :loud
|
||||
|
||||
LANGS = {"rb" => "ruby", "js" => "javascript"}
|
||||
MIMES = {
|
||||
"html" => "text/html",
|
||||
"htm" => "text/html",
|
||||
"png" => "image/png",
|
||||
"gif" => "image/gif",
|
||||
"css" => "text/css",
|
||||
"js" => "test/javascript",
|
||||
"txt" => "text/plain"
|
||||
}
|
||||
|
||||
# Generate an application in the given directory.
|
||||
# This is a class method because it doesn't depend on
|
||||
# specifying a database.
|
||||
def self.generate_app(app_dir)
|
||||
templatedir = File.join(File.expand_path(File.dirname(__FILE__)), 'app-template')
|
||||
FileUtils.cp_r(templatedir, app_dir)
|
||||
end
|
||||
|
||||
# instance methods
|
||||
|
||||
def initialize(dbname, host="http://127.0.0.1:5984")
|
||||
@db = CouchRest.new(host).database(dbname)
|
||||
end
|
||||
|
||||
# maintain the correspondence between an fs and couch
|
||||
|
||||
def push_app(appdir, appname)
|
||||
libs = []
|
||||
viewdir = File.join(appdir,"views")
|
||||
attachdir = File.join(appdir,"_attachments")
|
||||
|
||||
@doc = dir_to_fields(appdir)
|
||||
package_forms(@doc["forms"]) if @doc['forms']
|
||||
package_views(@doc["views"]) if @doc['views']
|
||||
|
||||
docid = "_design/#{appname}"
|
||||
design = @db.get(docid) rescue {}
|
||||
design.merge!(@doc)
|
||||
design['_id'] = docid
|
||||
# design['language'] = lang if lang
|
||||
@db.save(design)
|
||||
push_directory(attachdir, docid)
|
||||
end
|
||||
|
||||
def dir_to_fields(dir)
|
||||
fields = {}
|
||||
(Dir["#{dir}/**/*.*"] -
|
||||
Dir["#{dir}/_attachments/**/*.*"]).each do |file|
|
||||
farray = file.sub(dir, '').sub(/^\//,'').split('/')
|
||||
myfield = fields
|
||||
while farray.length > 1
|
||||
front = farray.shift
|
||||
myfield[front] ||= {}
|
||||
myfield = myfield[front]
|
||||
end
|
||||
fname, fext = farray.shift.split('.')
|
||||
fguts = File.open(file).read
|
||||
if fext == 'json'
|
||||
myfield[fname] = JSON.parse(fguts)
|
||||
else
|
||||
myfield[fname] = fguts
|
||||
end
|
||||
end
|
||||
return fields
|
||||
end
|
||||
|
||||
def push_directory(push_dir, docid=nil)
|
||||
docid ||= push_dir.split('/').reverse.find{|part|!part.empty?}
|
||||
|
||||
pushfiles = Dir["#{push_dir}/**/*.*"].collect do |f|
|
||||
{f.split("#{push_dir}/").last => open(f).read}
|
||||
end
|
||||
|
||||
return if pushfiles.empty?
|
||||
|
||||
@attachments = {}
|
||||
@signatures = {}
|
||||
pushfiles.each do |file|
|
||||
name = file.keys.first
|
||||
value = file.values.first
|
||||
@signatures[name] = md5(value)
|
||||
|
||||
@attachments[name] = {
|
||||
"data" => value,
|
||||
"content_type" => MIMES[name.split('.').last]
|
||||
}
|
||||
end
|
||||
|
||||
doc = @db.get(docid) rescue nil
|
||||
|
||||
unless doc
|
||||
say "creating #{docid}"
|
||||
@db.save({"_id" => docid, "_attachments" => @attachments, "signatures" => @signatures})
|
||||
return
|
||||
end
|
||||
|
||||
doc["signatures"] ||= {}
|
||||
doc["_attachments"] ||= {}
|
||||
# remove deleted docs
|
||||
to_be_removed = doc["signatures"].keys.select do |d|
|
||||
!pushfiles.collect{|p| p.keys.first}.include?(d)
|
||||
end
|
||||
|
||||
to_be_removed.each do |p|
|
||||
say "deleting #{p}"
|
||||
doc["signatures"].delete(p)
|
||||
doc["_attachments"].delete(p)
|
||||
end
|
||||
|
||||
# update existing docs:
|
||||
doc["signatures"].each do |path, sig|
|
||||
if (@signatures[path] == sig)
|
||||
say "no change to #{path}. skipping..."
|
||||
else
|
||||
say "replacing #{path}"
|
||||
doc["signatures"][path] = md5(@attachments[path]["data"])
|
||||
doc["_attachments"][path].delete("stub")
|
||||
doc["_attachments"][path].delete("length")
|
||||
doc["_attachments"][path]["data"] = @attachments[path]["data"]
|
||||
doc["_attachments"][path].merge!({"data" => @attachments[path]["data"]} )
|
||||
end
|
||||
end
|
||||
|
||||
# add in new files
|
||||
new_files = pushfiles.select{|d| !doc["signatures"].keys.include?( d.keys.first) }
|
||||
|
||||
new_files.each do |f|
|
||||
say "creating #{f}"
|
||||
path = f.keys.first
|
||||
content = f.values.first
|
||||
doc["signatures"][path] = md5(content)
|
||||
|
||||
doc["_attachments"][path] = {
|
||||
"data" => content,
|
||||
"content_type" => MIMES[path.split('.').last]
|
||||
}
|
||||
end
|
||||
|
||||
begin
|
||||
@db.save(doc)
|
||||
rescue Exception => e
|
||||
say e.message
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def package_forms(funcs)
|
||||
apply_lib(funcs)
|
||||
end
|
||||
|
||||
def package_views(views)
|
||||
views.each do |view, funcs|
|
||||
apply_lib(funcs)
|
||||
end
|
||||
end
|
||||
|
||||
def apply_lib(funcs)
|
||||
funcs.each do |k,v|
|
||||
next unless v.is_a?(String)
|
||||
funcs[k] = process_include(process_require(v))
|
||||
end
|
||||
end
|
||||
|
||||
# process requires
|
||||
def process_require(f_string)
|
||||
f_string.gsub /(\/\/|#)\ ?!code (.*)/ do
|
||||
fields = $2.split('.')
|
||||
library = @doc
|
||||
fields.each do |field|
|
||||
library = library[field]
|
||||
break unless library
|
||||
end
|
||||
library
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def process_include(f_string)
|
||||
|
||||
# process includes
|
||||
included = {}
|
||||
f_string.gsub /(\/\/|#)\ ?!json (.*)/ do
|
||||
fields = $2.split('.')
|
||||
library = @doc
|
||||
include_to = included
|
||||
count = fields.length
|
||||
fields.each_with_index do |field, i|
|
||||
break unless library[field]
|
||||
library = library[field]
|
||||
# normal case
|
||||
if i+1 < count
|
||||
include_to[field] = include_to[field] || {}
|
||||
include_to = include_to[field]
|
||||
else
|
||||
# last one
|
||||
include_to[field] = library
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
# puts included.inspect
|
||||
rval = if included == {}
|
||||
f_string
|
||||
else
|
||||
varstrings = included.collect do |k, v|
|
||||
"var #{k} = #{v.to_json};"
|
||||
end
|
||||
# just replace the first instance of the macro
|
||||
f_string.sub /(\/\/|#)\ ?!json (.*)/, varstrings.join("\n")
|
||||
end
|
||||
|
||||
rval
|
||||
end
|
||||
|
||||
|
||||
def say words
|
||||
puts words if @loud
|
||||
end
|
||||
|
||||
def md5 string
|
||||
Digest::MD5.hexdigest(string)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,111 +0,0 @@
|
|||
require File.dirname(__FILE__) + '/spec_helper'
|
||||
|
||||
describe "couchapp" do
|
||||
before(:all) do
|
||||
@fixdir = FIXTURE_PATH + '/couchapp-test'
|
||||
@couchapp = File.expand_path(File.dirname(__FILE__)) + '/../bin/couchapp'
|
||||
`rm -rf #{@fixdir}`
|
||||
`mkdir -p #{@fixdir}`
|
||||
@run = "cd #{@fixdir} && #{@couchapp}"
|
||||
end
|
||||
|
||||
describe "--help" do
|
||||
it "should output the opts" do
|
||||
`#{@run} --help`.should match(/Usage/)
|
||||
end
|
||||
end
|
||||
|
||||
describe "generate my-app" do
|
||||
before(:all) do
|
||||
`#{@run} generate my-app`.should match(/generating/i)
|
||||
end
|
||||
it "should create an app directory" do
|
||||
Dir["#{@fixdir}/*"].select{|x|x =~ /my-app/}.length.should == 1
|
||||
end
|
||||
it "should create a views directory" do
|
||||
Dir["#{@fixdir}/my-app/*"].select{|x|x =~ /views/}.length.should == 1
|
||||
end
|
||||
it "should create an _attachments directory" do
|
||||
Dir["#{@fixdir}/my-app/*"].select{|x|x =~ /_attachments/}.length.should == 1
|
||||
Dir["#{@fixdir}/my-app/_attachments/*"].select{|x|x =~ /index.html/}.length.should == 1
|
||||
end
|
||||
it "should create a forms directory" do
|
||||
Dir["#{@fixdir}/my-app/*"].select{|x|x =~ /forms/}.length.should == 1
|
||||
end
|
||||
it "should create a forms and libs" do
|
||||
Dir["#{@fixdir}/my-app/forms/*"].select{|x|x =~ /example-form.js/}.length.should == 1
|
||||
Dir["#{@fixdir}/my-app/lib/templates/*"].select{|x|x =~ /example.html/}.length.should == 1
|
||||
end
|
||||
end
|
||||
|
||||
describe "push my-app #{TESTDB}" do
|
||||
before(:all) do
|
||||
@cr = CouchRest.new(COUCHHOST)
|
||||
@db = @cr.database(TESTDB)
|
||||
@db.delete! rescue nil
|
||||
@db = @cr.create_db(TESTDB) rescue nil
|
||||
`#{@run} generate my-app`
|
||||
`#{@run} push my-app #{TESTDB}`
|
||||
@doc = @db.get("_design/my-app")
|
||||
end
|
||||
it "should create the design document with the app name" do
|
||||
lambda{@db.get("_design/my-app")}.should_not raise_error
|
||||
end
|
||||
it "should create the views" do
|
||||
@doc['views']['example']['map'].should match(/function/)
|
||||
end
|
||||
it "should create the view libs" do
|
||||
@doc['views']['example']['map'].should match(/stddev/)
|
||||
@doc['forms']['example-form'].should_not match(/\"helpers\"/)
|
||||
end
|
||||
it "should create view for all the views" do
|
||||
`mkdir -p #{@fixdir}/my-app/views/more`
|
||||
`echo 'moremap' > #{@fixdir}/my-app/views/more/map.js`
|
||||
`#{@run} push my-app #{TESTDB}`
|
||||
doc = @db.get("_design/my-app")
|
||||
doc['views']['more']['map'].should match(/moremap/)
|
||||
end
|
||||
it "should create the index" do
|
||||
@doc['_attachments']['index.html']["content_type"].should == 'text/html'
|
||||
end
|
||||
it "should push the forms" do
|
||||
@doc['forms']['example-form'].should match(/Generated CouchApp Form Template/)
|
||||
end
|
||||
it "should allow deeper includes" do
|
||||
@doc['forms']['example-form'].should_not match(/\"helpers\"/)
|
||||
end
|
||||
it "deep requires" do
|
||||
@doc['forms']['example-form'].should_not match(/\"template\"/)
|
||||
@doc['forms']['example-form'].should match(/Resig/)
|
||||
end
|
||||
end
|
||||
|
||||
describe "push . #{TESTDB}" do
|
||||
before(:all) do
|
||||
@cr = CouchRest.new(COUCHHOST)
|
||||
@db = @cr.database(TESTDB)
|
||||
@db.delete! rescue nil
|
||||
@db = @cr.create_db(TESTDB) rescue nil
|
||||
`#{@run} generate my-app`
|
||||
end
|
||||
it "should create the design document" do
|
||||
`cd #{@fixdir}/my-app && #{@couchapp} push . #{TESTDB}`
|
||||
lambda{@db.get("_design/my-app")}.should_not raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "push my-app my-design #{TESTDB}" do
|
||||
before(:all) do
|
||||
@cr = CouchRest.new(COUCHHOST)
|
||||
@db = @cr.database(TESTDB)
|
||||
@db.delete! rescue nil
|
||||
@db = @cr.create_db(TESTDB) rescue nil
|
||||
`#{@run} generate my-app`
|
||||
end
|
||||
it "should create the design document" do
|
||||
`#{@run} push my-app my-design #{TESTDB}`
|
||||
lambda{@db.get("_design/my-design")}.should_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in a new issue