move some couchapp components to couchapp project

This commit is contained in:
Chris Anderson 2009-01-08 16:48:11 -08:00
parent 3c789ab317
commit fb613e7dfb
11 changed files with 0 additions and 533 deletions

View file

@ -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

View file

@ -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>

View file

@ -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.

View 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>
}
}
})
};

View file

@ -1 +0,0 @@
function stddev() {};

View file

@ -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;
};

View file

@ -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>

View file

@ -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);
};

View file

@ -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;
}
};

View file

@ -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

View file

@ -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