├── vendor └── sinatra │ ├── test │ ├── views │ │ ├── hello.test │ │ ├── layout2.test │ │ ├── hello.erb │ │ ├── hello.haml │ │ ├── error.sass │ │ ├── foo │ │ │ └── hello.test │ │ ├── layout2.erb │ │ ├── layout2.haml │ │ ├── hello.sass │ │ ├── hello.builder │ │ ├── error.builder │ │ ├── layout2.builder │ │ ├── error.haml │ │ └── error.erb │ ├── data │ │ └── reload_app_file.rb │ ├── sinatra_test.rb │ ├── server_test.rb │ ├── request_test.rb │ ├── response_test.rb │ ├── route_added_hook_test.rb │ ├── builder_test.rb │ ├── contest.rb │ ├── helper.rb │ ├── middleware_test.rb │ ├── erb_test.rb │ ├── result_test.rb │ ├── haml_test.rb │ ├── filter_test.rb │ ├── sass_test.rb │ ├── static_test.rb │ └── extensions_test.rb │ ├── lib │ ├── sinatra │ │ ├── images │ │ │ ├── 404.png │ │ │ └── 500.png │ │ └── main.rb │ └── sinatra.rb │ ├── .gitignore │ ├── LICENSE │ ├── AUTHORS │ └── sinatra.gemspec ├── public └── _utils │ ├── favicon.ico │ ├── image │ ├── bg.png │ ├── add.png │ ├── apply.gif │ ├── hgrad.gif │ ├── load.png │ ├── logo.png │ ├── path.gif │ ├── run.png │ ├── save.png │ ├── thead.gif │ ├── cancel.gif │ ├── compact.png │ ├── delete.png │ ├── grippie.gif │ ├── rarrow.png │ ├── running.png │ ├── spinner.gif │ ├── twisty.gif │ ├── order-asc.gif │ ├── order-desc.gif │ ├── progress.gif │ ├── run-mini.png │ ├── thead-key.gif │ ├── delete-mini.png │ ├── test_failure.gif │ ├── test_success.gif │ ├── sidebar-toggle.png │ ├── toggle-collapse.gif │ └── toggle-expand.gif │ ├── dialog │ ├── _delete_document.html │ ├── _delete_database.html │ ├── _compact_database.html │ ├── _create_database.html │ ├── _upload_attachment.html │ └── _save_view_as.html │ ├── script │ ├── test │ │ ├── form_submit.js │ │ ├── large_docs.js │ │ ├── recreate_doc.js │ │ ├── view_multi_key_temp.js │ │ ├── content_negotiation.js │ │ ├── view_xml.js │ │ ├── utf8.js │ │ ├── reduce_false.js │ │ ├── view_conflicts.js │ │ ├── copy_doc.js │ │ ├── compact.js │ │ ├── http.js │ │ ├── view_multi_key_all_docs.js │ │ ├── view_sandboxing.js │ │ ├── batch_save.js │ │ ├── lots_of_docs.js │ │ ├── conflicts.js │ │ ├── jsonp.js │ │ ├── config.js │ │ ├── design_options.js │ │ ├── etags_head.js │ │ ├── design_paths.js │ │ ├── invalid_docids.js │ │ ├── attachment_views.js │ │ ├── multiple_rows.js │ │ ├── attachment_names.js │ │ ├── all_docs.js │ │ ├── purge.js │ │ ├── rev_stemming.js │ │ ├── bulk_docs.js │ │ ├── etags_views.js │ │ ├── view_offsets.js │ │ ├── erlang_views.js │ │ └── uuids.js │ ├── jquery.cookies.js │ ├── jquery.resizer.js │ ├── jquery.dialog.js │ └── jquery.editinline.js │ ├── _sidebar.html │ ├── couch_tests.html │ ├── index.html │ ├── status.html │ ├── custom_test.html │ └── config.html ├── spec ├── spec_helper.rb ├── document_spec.rb ├── view_spec.rb ├── database_spec.rb └── tree_spec.rb ├── lib ├── httpd │ ├── views.rb │ ├── global.rb │ ├── db.rb │ └── doc.rb ├── query │ ├── server │ │ ├── validate.js │ │ ├── filter.js │ │ ├── state.js │ │ ├── loop.js │ │ └── util.js │ └── query_server.rb ├── booth.rb └── store │ ├── database.rb │ └── tree.rb └── README.md /vendor/sinatra/test/views/hello.test: -------------------------------------------------------------------------------- 1 | Hello World! 2 | -------------------------------------------------------------------------------- /vendor/sinatra/test/views/layout2.test: -------------------------------------------------------------------------------- 1 | Layout 2! 2 | -------------------------------------------------------------------------------- /vendor/sinatra/test/views/hello.erb: -------------------------------------------------------------------------------- 1 | Hello <%= 'World' %> 2 | -------------------------------------------------------------------------------- /vendor/sinatra/test/views/hello.haml: -------------------------------------------------------------------------------- 1 | %h1 Hello From Haml 2 | -------------------------------------------------------------------------------- /vendor/sinatra/test/views/error.sass: -------------------------------------------------------------------------------- 1 | #sass 2 | +argle-bargle 3 | -------------------------------------------------------------------------------- /vendor/sinatra/test/views/foo/hello.test: -------------------------------------------------------------------------------- 1 | from another views directory 2 | -------------------------------------------------------------------------------- /vendor/sinatra/test/views/layout2.erb: -------------------------------------------------------------------------------- 1 | ERB Layout! 2 | <%= yield %> 3 | -------------------------------------------------------------------------------- /vendor/sinatra/test/views/layout2.haml: -------------------------------------------------------------------------------- 1 | %h1 HAML Layout! 2 | %p= yield 3 | -------------------------------------------------------------------------------- /vendor/sinatra/test/views/hello.sass: -------------------------------------------------------------------------------- 1 | #sass 2 | :background-color #FFF 3 | -------------------------------------------------------------------------------- /vendor/sinatra/test/views/hello.builder: -------------------------------------------------------------------------------- 1 | xml.exclaim "You're my boy, #{@name}!" 2 | -------------------------------------------------------------------------------- /vendor/sinatra/test/views/error.builder: -------------------------------------------------------------------------------- 1 | xml.error do 2 | raise "goodbye" 3 | end 4 | -------------------------------------------------------------------------------- /vendor/sinatra/test/views/layout2.builder: -------------------------------------------------------------------------------- 1 | xml.layout do 2 | xml << yield 3 | end 4 | -------------------------------------------------------------------------------- /public/_utils/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/favicon.ico -------------------------------------------------------------------------------- /public/_utils/image/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/bg.png -------------------------------------------------------------------------------- /public/_utils/image/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/add.png -------------------------------------------------------------------------------- /public/_utils/image/apply.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/apply.gif -------------------------------------------------------------------------------- /public/_utils/image/hgrad.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/hgrad.gif -------------------------------------------------------------------------------- /public/_utils/image/load.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/load.png -------------------------------------------------------------------------------- /public/_utils/image/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/logo.png -------------------------------------------------------------------------------- /public/_utils/image/path.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/path.gif -------------------------------------------------------------------------------- /public/_utils/image/run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/run.png -------------------------------------------------------------------------------- /public/_utils/image/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/save.png -------------------------------------------------------------------------------- /public/_utils/image/thead.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/thead.gif -------------------------------------------------------------------------------- /public/_utils/image/cancel.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/cancel.gif -------------------------------------------------------------------------------- /public/_utils/image/compact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/compact.png -------------------------------------------------------------------------------- /public/_utils/image/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/delete.png -------------------------------------------------------------------------------- /public/_utils/image/grippie.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/grippie.gif -------------------------------------------------------------------------------- /public/_utils/image/rarrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/rarrow.png -------------------------------------------------------------------------------- /public/_utils/image/running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/running.png -------------------------------------------------------------------------------- /public/_utils/image/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/spinner.gif -------------------------------------------------------------------------------- /public/_utils/image/twisty.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/twisty.gif -------------------------------------------------------------------------------- /public/_utils/image/order-asc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/order-asc.gif -------------------------------------------------------------------------------- /public/_utils/image/order-desc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/order-desc.gif -------------------------------------------------------------------------------- /public/_utils/image/progress.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/progress.gif -------------------------------------------------------------------------------- /public/_utils/image/run-mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/run-mini.png -------------------------------------------------------------------------------- /public/_utils/image/thead-key.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/thead-key.gif -------------------------------------------------------------------------------- /public/_utils/image/delete-mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/delete-mini.png -------------------------------------------------------------------------------- /public/_utils/image/test_failure.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/test_failure.gif -------------------------------------------------------------------------------- /public/_utils/image/test_success.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/test_success.gif -------------------------------------------------------------------------------- /public/_utils/image/sidebar-toggle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/sidebar-toggle.png -------------------------------------------------------------------------------- /public/_utils/image/toggle-collapse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/toggle-collapse.gif -------------------------------------------------------------------------------- /public/_utils/image/toggle-expand.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/public/_utils/image/toggle-expand.gif -------------------------------------------------------------------------------- /vendor/sinatra/test/data/reload_app_file.rb: -------------------------------------------------------------------------------- 1 | $reload_count += 1 2 | 3 | $reload_app.get('/') { 'Hello from reload file' } 4 | -------------------------------------------------------------------------------- /vendor/sinatra/lib/sinatra/images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/vendor/sinatra/lib/sinatra/images/404.png -------------------------------------------------------------------------------- /vendor/sinatra/lib/sinatra/images/500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/booth/master/vendor/sinatra/lib/sinatra/images/500.png -------------------------------------------------------------------------------- /vendor/sinatra/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | /pkg 4 | /book 5 | /doc/api 6 | /doc/*.html 7 | .#* 8 | \#* 9 | .emacs* 10 | -------------------------------------------------------------------------------- /vendor/sinatra/test/views/error.haml: -------------------------------------------------------------------------------- 1 | %h1 Hello From Haml 2 | = raise 'goodbye' unless defined?(french) && french 3 | = raise 'au revoir' if defined?(french) && french 4 | -------------------------------------------------------------------------------- /vendor/sinatra/test/views/error.erb: -------------------------------------------------------------------------------- 1 | Hello <%= 'World' %> 2 | <% raise 'Goodbye' unless defined?(french) && french %> 3 | <% raise 'Au revoir' if defined?(french) && french %> 4 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "rubygems" 2 | require "spec" # Satisfies Autotest and anyone else not using the Rake tasks 3 | 4 | require File.join(File.expand_path(File.dirname(__FILE__)),"..","lib","booth") 5 | 6 | -------------------------------------------------------------------------------- /vendor/sinatra/lib/sinatra.rb: -------------------------------------------------------------------------------- 1 | libdir = File.dirname(__FILE__) 2 | $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir) 3 | 4 | require 'sinatra/base' 5 | require 'sinatra/main' 6 | 7 | use_in_file_templates! 8 | -------------------------------------------------------------------------------- /vendor/sinatra/test/sinatra_test.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/helper' 2 | 3 | class SinatraTest < Test::Unit::TestCase 4 | it 'creates a new Sinatra::Base subclass on new' do 5 | app = 6 | Sinatra.new do 7 | get '/' do 8 | 'Hello World' 9 | end 10 | end 11 | assert_same Sinatra::Base, app.superclass 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/httpd/views.rb: -------------------------------------------------------------------------------- 1 | require 'view' 2 | 3 | # handle temp views 4 | post "/:db/_temp_view/?" do 5 | with_db(params[:db]) do |db| 6 | req = JSON.parse(request.body.read) 7 | v = View.new(db, req["map"], req["reduce"]) 8 | j(200, v.query(View.view_params(params))) 9 | end 10 | end 11 | 12 | # ddoc views should be similar, 13 | # just keep a ref to the view around 14 | # so we can update it. 15 | -------------------------------------------------------------------------------- /lib/query/server/validate.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | var Validate = { 14 | validate : function(funSrc, newDoc, oldDoc, userCtx) { 15 | var validateFun = compileFunction(funSrc); 16 | try { 17 | validateFun(newDoc, oldDoc, userCtx); 18 | print("1"); 19 | } catch (error) { 20 | respond(error); 21 | } 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /vendor/sinatra/lib/sinatra/main.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | 3 | module Sinatra 4 | class Application < Base 5 | 6 | # we assume that the first file that requires 'sinatra' is the 7 | # app_file. all other path related options are calculated based 8 | # on this path by default. 9 | set :app_file, caller_files.first || $0 10 | 11 | set :run, Proc.new { $0 == app_file } 12 | 13 | if run? && ARGV.any? 14 | require 'optparse' 15 | OptionParser.new { |op| 16 | op.on('-x') { set :lock, true } 17 | op.on('-e env') { |val| set :environment, val.to_sym } 18 | op.on('-s server') { |val| set :server, val } 19 | op.on('-p port') { |val| set :port, val.to_i } 20 | }.parse!(ARGV.dup) 21 | end 22 | 23 | at_exit do 24 | raise $! if $! 25 | run! if run? 26 | end 27 | end 28 | end 29 | 30 | include Sinatra::Delegator 31 | -------------------------------------------------------------------------------- /lib/query/server/filter.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | var Filter = { 14 | filter : function(docs, req, userCtx) { 15 | var results = []; 16 | try { 17 | for (var i=0; i < docs.length; i++) { 18 | results.push((funs[0](docs[i], req, userCtx) && true) || false); 19 | }; 20 | respond([true, results]); 21 | } catch (error) { 22 | respond(error); 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /public/_utils/dialog/_delete_document.html: -------------------------------------------------------------------------------- 1 | 15 |
16 |

Delete Document

17 |
18 |

19 | Are you sure you want to delete this document? 20 |

21 |
22 |
23 | 24 | 25 |
26 |
27 | -------------------------------------------------------------------------------- /public/_utils/dialog/_delete_database.html: -------------------------------------------------------------------------------- 1 | 15 |
16 |

Delete Database

17 |
18 |

19 | Are you sure you want to delete this database? Note that this is an 20 | irreversible operation! 21 |

22 |
23 |
24 | 25 | 26 |
27 |
28 | -------------------------------------------------------------------------------- /public/_utils/dialog/_compact_database.html: -------------------------------------------------------------------------------- 1 | 15 |
16 |

Compact Database

17 |
18 |

19 | Compacting a database removes deleted documents and previous revisions. 20 | It is an irreversible operation and my take 21 | a while to complete for large databases. 22 |

23 |
24 |
25 | 26 | 27 |
28 |
29 | -------------------------------------------------------------------------------- /vendor/sinatra/test/server_test.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/helper' 2 | 3 | module Rack::Handler 4 | class Mock 5 | extend Test::Unit::Assertions 6 | 7 | def self.run(app, options={}) 8 | assert(app < Sinatra::Base) 9 | assert_equal 9001, options[:Port] 10 | assert_equal 'foo.local', options[:Host] 11 | yield new 12 | end 13 | 14 | def stop 15 | end 16 | end 17 | 18 | register 'mock', 'Rack::Handler::Mock' 19 | end 20 | 21 | class ServerTest < Test::Unit::TestCase 22 | setup do 23 | mock_app { 24 | set :server, 'mock' 25 | set :host, 'foo.local' 26 | set :port, 9001 27 | } 28 | $stdout = File.open('/dev/null', 'wb') 29 | end 30 | 31 | def teardown 32 | $stdout = STDOUT 33 | end 34 | 35 | it "locates the appropriate Rack handler and calls ::run" do 36 | @app.run! 37 | end 38 | 39 | it "sets options on the app before running" do 40 | @app.run! :sessions => true 41 | assert @app.sessions? 42 | end 43 | 44 | it "falls back on the next server handler when not found" do 45 | @app.run! :server => %w[foo bar mock] 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /vendor/sinatra/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007, 2008, 2009 Blake Mizerany 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /public/_utils/script/test/form_submit.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | // Do some basic tests. 14 | couchTests.form_submit = function(debug) { 15 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 16 | db.deleteDb(); 17 | db.createDb(); 18 | 19 | // PUT on existing DB should return 412 instead of 500 20 | var json = "{}"; 21 | var xhr = CouchDB.request("POST", "/test_suite_db/baz", {body: json}); 22 | T(xhr.status == 415); 23 | result = JSON.parse(xhr.responseText); 24 | T(result.error, "bad_content_type"); 25 | T(result.reason, "Invalid Content-Type header for form upload"); 26 | }; 27 | -------------------------------------------------------------------------------- /vendor/sinatra/test/request_test.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/helper' 2 | 3 | class RequestTest < Test::Unit::TestCase 4 | it 'responds to #user_agent' do 5 | request = Sinatra::Request.new({'HTTP_USER_AGENT' => 'Test'}) 6 | assert request.respond_to?(:user_agent) 7 | assert_equal 'Test', request.user_agent 8 | end 9 | 10 | it 'parses POST params when Content-Type is form-dataish' do 11 | request = Sinatra::Request.new( 12 | 'REQUEST_METHOD' => 'PUT', 13 | 'CONTENT_TYPE' => 'application/x-www-form-urlencoded', 14 | 'rack.input' => StringIO.new('foo=bar') 15 | ) 16 | assert_equal 'bar', request.params['foo'] 17 | end 18 | 19 | it 'is secure when the url scheme is https' do 20 | request = Sinatra::Request.new('rack.url_scheme' => 'https') 21 | assert request.secure? 22 | end 23 | 24 | it 'is not secure when the url scheme is http' do 25 | request = Sinatra::Request.new('rack.url_scheme' => 'http') 26 | assert !request.secure? 27 | end 28 | 29 | it 'respects X-Forwarded-Proto header for proxied SSL' do 30 | request = Sinatra::Request.new('HTTP_X_FORWARDED_PROTO' => 'https') 31 | assert request.secure? 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /public/_utils/script/test/large_docs.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.large_docs = function(debug) { 14 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 15 | db.deleteDb(); 16 | db.createDb(); 17 | if (debug) debugger; 18 | 19 | var longtext = "0123456789\n"; 20 | 21 | for (var i=0; i<10; i++) { 22 | longtext = longtext + longtext 23 | } 24 | T(db.save({"longtest":longtext}).ok); 25 | T(db.save({"longtest":longtext}).ok); 26 | T(db.save({"longtest":longtext}).ok); 27 | T(db.save({"longtest":longtext}).ok); 28 | 29 | // query all documents, and return the doc.foo member as a key. 30 | results = db.query(function(doc){ 31 | emit(null, doc.longtest); 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /lib/query/server/state.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | // globals used by other modules and functions 14 | var funs = []; // holds functions used for computation 15 | var funsrc = []; // holds function source for debug info 16 | var query_config = {}; 17 | var State = (function() { 18 | return { 19 | reset : function(config) { 20 | // clear the globals and run gc 21 | funs = []; 22 | funsrc = []; 23 | query_config = config; 24 | gc(); 25 | print("true"); // indicates success 26 | }, 27 | addFun : function(newFun) { 28 | // Compile to a function and add it to funs array 29 | funsrc.push(newFun); 30 | funs.push(compileFunction(newFun)); 31 | print("true"); 32 | } 33 | } 34 | })(); 35 | -------------------------------------------------------------------------------- /public/_utils/dialog/_create_database.html: -------------------------------------------------------------------------------- 1 | 15 |
16 |

Create New Database

17 |
18 |

19 | Please enter the name of the database. Note that only lowercase 20 | characters (a-z), digits (0-9), or any of the 21 | characters _, $, (, ), +, 22 | -, and / are allowed. 23 |

24 | 25 | 26 | 27 |
28 |
29 |
30 | 31 | 32 |
33 |
34 | -------------------------------------------------------------------------------- /public/_utils/script/test/recreate_doc.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.recreate_doc = function(debug) { 14 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 15 | db.deleteDb(); 16 | db.createDb(); 17 | if (debug) debugger; 18 | 19 | // First create a new document with the ID "foo", and delete it again 20 | var doc = {_id: "foo", a: "bar", b: 42}; 21 | T(db.save(doc).ok); 22 | T(db.deleteDoc(doc).ok); 23 | 24 | // Now create a new document with the same ID, save it, and then modify it 25 | // This should work fine, but currently results in a conflict error, at 26 | // least "sometimes" 27 | for (var i = 0; i < 10; i++) { 28 | doc = {_id: "foo"}; 29 | T(db.save(doc).ok); 30 | doc = db.open("foo"); 31 | doc.a = "baz"; 32 | T(db.save(doc).ok); 33 | T(db.deleteDoc(doc).rev != undefined); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /vendor/sinatra/test/response_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require File.dirname(__FILE__) + '/helper' 4 | 5 | class ResponseTest < Test::Unit::TestCase 6 | setup do 7 | @response = Sinatra::Response.new 8 | end 9 | 10 | it "initializes with 200, text/html, and empty body" do 11 | assert_equal 200, @response.status 12 | assert_equal 'text/html', @response['Content-Type'] 13 | assert_equal [], @response.body 14 | end 15 | 16 | it 'uses case insensitive headers' do 17 | @response['content-type'] = 'application/foo' 18 | assert_equal 'application/foo', @response['Content-Type'] 19 | assert_equal 'application/foo', @response['CONTENT-TYPE'] 20 | end 21 | 22 | it 'writes to body' do 23 | @response.body = 'Hello' 24 | @response.write ' World' 25 | assert_equal 'Hello World', @response.body 26 | end 27 | 28 | [204, 304].each do |status_code| 29 | it "removes the Content-Type header and body when response status is #{status_code}" do 30 | @response.status = status_code 31 | @response.body = ['Hello World'] 32 | assert_equal [status_code, {}, []], @response.finish 33 | end 34 | end 35 | 36 | it 'Calculates the Content-Length using the bytesize of the body' do 37 | @response.body = ['Hello', 'World!', '✈'] 38 | status, headers, body = @response.finish 39 | assert_equal '14', headers['Content-Length'] 40 | assert_equal @response.body, body 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /public/_utils/_sidebar.html: -------------------------------------------------------------------------------- 1 | 15 | 37 | -------------------------------------------------------------------------------- /public/_utils/dialog/_upload_attachment.html: -------------------------------------------------------------------------------- 1 | 15 |
16 |

Upload Attachment

17 |
18 |

19 | Please select the file you want to upload as an attachment to this 20 | document. Please note that this will result in the immediate creation of 21 | a new revision of the document, so it's not necessary to save the 22 | document after the upload. 23 |

24 | 25 | 26 | 27 | 28 | 29 |
 
30 |
31 |
32 | 33 | 34 | 35 |
36 |
37 | -------------------------------------------------------------------------------- /public/_utils/script/test/view_multi_key_temp.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.view_multi_key_temp = function(debug) { 14 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 15 | db.deleteDb(); 16 | db.createDb(); 17 | if (debug) debugger; 18 | 19 | var docs = makeDocs(0, 100); 20 | db.bulkSave(docs); 21 | 22 | var queryFun = function(doc) { emit(doc.integer, doc.integer) }; 23 | var reduceFun = function (keys, values) { return sum(values); }; 24 | 25 | var keys = [10,15,30,37,50]; 26 | var rows = db.query(queryFun, null, {}, keys).rows; 27 | for(var i=0; iTesting XML"}); 20 | db.save({content: "Testing E4X"}); 21 | 22 | var results = db.query( 23 | "function(doc) {\n" + 24 | " var xml = new XML(doc.content);\n" + 25 | " emit(xml.title.text(), null);\n" + 26 | "}"); 27 | T(results.total_rows == 2); 28 | T(results.rows[0].key == "Testing E4X"); 29 | T(results.rows[1].key == "Testing XML"); 30 | 31 | var results = db.query( 32 | "function(doc) {\n" + 33 | " var xml = new XML(doc.content);\n" + 34 | " emit(xml.title.@id, null);\n" + 35 | "}"); 36 | T(results.total_rows == 2); 37 | T(results.rows[0].key == "e4x"); 38 | T(results.rows[1].key == "xml"); 39 | }; 40 | -------------------------------------------------------------------------------- /public/_utils/dialog/_save_view_as.html: -------------------------------------------------------------------------------- 1 | 15 |
16 |

Save View As…

17 |
18 |

19 | You can save this function code as a permanent view in the database. Just 20 | enter or select the design document and the name of the view below. Note 21 | that if you choose an existing view, it will be overwritten! 22 |

23 | 24 | 25 | 26 | 27 | 28 | 29 |
_design/
30 |
31 |
32 | 33 | 34 |
35 |
36 | -------------------------------------------------------------------------------- /public/_utils/script/test/utf8.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.utf8 = function(debug) { 14 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 15 | db.deleteDb(); 16 | db.createDb(); 17 | if (debug) debugger; 18 | 19 | var texts = []; 20 | 21 | texts[0] = "1. Ascii: hello" 22 | texts[1] = "2. Russian: На берегу пустынных волн" 23 | texts[2] = "3. Math: ∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i)," 24 | texts[3] = "4. Geek: STARGΛ̊TE SG-1" 25 | texts[4] = "5. Braille: ⡌⠁⠧⠑ ⠼⠁⠒ ⡍⠜⠇⠑⠹⠰⠎ ⡣⠕⠌" 26 | 27 | // check that we can save a reload with full fidelity 28 | for (var i=0; i @error, "reason" => @reason}) 38 | end 39 | end 40 | 41 | BOOTH_UUID = UUID.new 42 | 43 | 44 | # TODO Help! I want code reloading during dev. 45 | 46 | set :public, File.join(filepath,"..","public") 47 | set :show_exceptions, false 48 | set :raise_errors, false 49 | set :lock, false 50 | 51 | error(BoothError) do 52 | be = @env['sinatra.error'] 53 | [be.code, {}, be.to_json] 54 | end 55 | 56 | error(Sinatra::NotFound) do 57 | [404, {}, {"error"=>"not_found", "reason" => "missing handler"}.to_json] 58 | end 59 | 60 | error ::Exception do 61 | be = @env['sinatra.error'] 62 | [500,{}, {"error"=>"internal_error", "reason" => be.to_s}.to_json] 63 | end 64 | 65 | load 'global.rb' 66 | load 'db.rb' 67 | load 'doc.rb' 68 | load 'views.rb' 69 | 70 | -------------------------------------------------------------------------------- /public/_utils/script/test/reduce_false.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.reduce_false = function(debug) { 14 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 15 | db.deleteDb(); 16 | db.createDb(); 17 | if (debug) debugger; 18 | 19 | var numDocs = 5; 20 | var docs = makeDocs(1,numDocs + 1); 21 | db.bulkSave(docs); 22 | var summate = function(N) {return (N+1)*N/2;}; 23 | 24 | var designDoc = { 25 | _id:"_design/test", 26 | language: "javascript", 27 | views: { 28 | summate: {map:"function (doc) {emit(doc.integer, doc.integer)};", 29 | reduce:"function (keys, values) { return sum(values); };"}, 30 | } 31 | }; 32 | T(db.save(designDoc).ok); 33 | 34 | // Test that the reduce works 35 | var res = db.view('test/summate'); 36 | T(res.rows.length == 1 && res.rows[0].value == summate(5)); 37 | 38 | //Test that we get our docs back 39 | res = db.view('test/summate', {reduce: false}); 40 | T(res.rows.length == 5); 41 | for(var i=0; i<5; i++) 42 | { 43 | T(res.rows[i].value == i+1); 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /lib/httpd/global.rb: -------------------------------------------------------------------------------- 1 | # render couchdb's classic JSON welcome screen 2 | # this is where redirect to other servers would go 3 | get '/' do 4 | j 200, "couchdb"=>"Welcome","version"=>"0" 5 | end 6 | 7 | # just a stub for the tests, as booth is in-memory only 8 | post '/:db/_ensure_full_commit' do 9 | j 200, "ok" => true 10 | end 11 | 12 | # also stubs 13 | post '/_restart' do 14 | j 200, "ok" => true 15 | end 16 | put '/_config/*' do 17 | j 200, "ok" => true 18 | end 19 | get '/_config/*' do 20 | j 200, "ok" => true 21 | end 22 | 23 | 24 | # the uuid service works just like couchdb 25 | get "/_uuids" do 26 | uuid = BOOTH_UUID 27 | count = if params[:count] 28 | params[:count].to_i 29 | else 30 | 1 31 | end 32 | uuids = (1..count).collect{uuid.generate} 33 | j(200, {"uuids" => uuids},{ 34 | "Cache-Control" => "no-cache", 35 | "Pragma" => "no-cache", 36 | "Etag" => uuid.generate 37 | }) 38 | end 39 | 40 | 41 | # json error handling 42 | def je code, name, message 43 | j code, {"error" => name, "reason" => message} 44 | end 45 | 46 | # json ok handling 47 | def j code, json, h = {} 48 | status code 49 | content_type "json" 50 | headers h 51 | json.to_json 52 | end 53 | 54 | def changes rows 55 | status 200 56 | content_type "json" 57 | "{\"results\":[\n#{change_rows(rows)}],\n\"last_seq\":#{rows.length}}\n" 58 | end 59 | 60 | def change_rows rows 61 | b = "" 62 | rows.each do |r| 63 | b = b + "#{r.to_json},\n" 64 | end 65 | b 66 | end 67 | 68 | # parse request 69 | def jbody message = "Request body must be a JSON object" 70 | json = JSON.parse(request.body.read) 71 | if !json || json.is_a?(Array) 72 | raise BoothError.new(400, "bad_request", message); 73 | end 74 | json 75 | end -------------------------------------------------------------------------------- /public/_utils/script/test/view_conflicts.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.view_conflicts = function(debug) { 14 | var dbA = new CouchDB("test_suite_db_a", {"X-Couch-Full-Commit":"false"}); 15 | dbA.deleteDb(); 16 | dbA.createDb(); 17 | var dbB = new CouchDB("test_suite_db_b", {"X-Couch-Full-Commit":"false"}); 18 | dbB.deleteDb(); 19 | dbB.createDb(); 20 | if (debug) debugger; 21 | 22 | var docA = {_id: "foo", bar: 42}; 23 | T(dbA.save(docA).ok); 24 | CouchDB.replicate(dbA.name, dbB.name); 25 | 26 | var docB = dbB.open("foo"); 27 | docB.bar = 43; 28 | dbB.save(docB); 29 | docA.bar = 41; 30 | dbA.save(docA); 31 | CouchDB.replicate(dbA.name, dbB.name); 32 | 33 | var doc = dbB.open("foo", {conflicts: true}); 34 | T(doc._conflicts.length == 1); 35 | var conflictRev = doc._conflicts[0]; 36 | if (doc.bar == 41) { // A won 37 | T(conflictRev == docB._rev); 38 | } else { // B won 39 | T(doc.bar == 43); 40 | T(conflictRev == docA._rev); 41 | } 42 | 43 | var results = dbB.query(function(doc) { 44 | if (doc._conflicts) { 45 | emit(doc._id, doc._conflicts); 46 | } 47 | }); 48 | T(results.rows[0].value[0] == conflictRev); 49 | }; 50 | -------------------------------------------------------------------------------- /vendor/sinatra/test/builder_test.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/helper' 2 | require 'builder' 3 | 4 | class BuilderTest < Test::Unit::TestCase 5 | def builder_app(&block) 6 | mock_app { 7 | set :views, File.dirname(__FILE__) + '/views' 8 | get '/', &block 9 | } 10 | get '/' 11 | end 12 | 13 | it 'renders inline Builder strings' do 14 | builder_app { builder 'xml.instruct!' } 15 | assert ok? 16 | assert_equal %{\n}, body 17 | end 18 | 19 | it 'renders inline blocks' do 20 | builder_app { 21 | @name = "Frank & Mary" 22 | builder do |xml| 23 | xml.couple @name 24 | end 25 | } 26 | assert ok? 27 | assert_equal "Frank & Mary\n", body 28 | end 29 | 30 | it 'renders .builder files in views path' do 31 | builder_app { 32 | @name = "Blue" 33 | builder :hello 34 | } 35 | assert ok? 36 | assert_equal %(You're my boy, Blue!\n), body 37 | end 38 | 39 | it "renders with inline layouts" do 40 | mock_app { 41 | layout do 42 | %(xml.layout { xml << yield }) 43 | end 44 | get('/') { builder %(xml.em 'Hello World') } 45 | } 46 | get '/' 47 | assert ok? 48 | assert_equal "\nHello World\n\n", body 49 | end 50 | 51 | it "renders with file layouts" do 52 | builder_app { 53 | builder %(xml.em 'Hello World'), :layout => :layout2 54 | } 55 | assert ok? 56 | assert_equal "\nHello World\n\n", body 57 | end 58 | 59 | it "raises error if template not found" do 60 | mock_app { 61 | get('/') { builder :no_such_template } 62 | } 63 | assert_raise(Errno::ENOENT) { get('/') } 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /public/_utils/script/jquery.cookies.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | (function($) { 14 | $.cookies = $.cookies || {} 15 | $.extend($.cookies, { 16 | 17 | /* Return the value of a cookie. */ 18 | get: function(name, defaultValue) { 19 | var nameEq = name + "="; 20 | var parts = document.cookie.split(';'); 21 | for (var i = 0; i < parts.length; i++) { 22 | var part = parts[i].replace(/^\s+/, ""); 23 | if (part.indexOf(nameEq) == 0) { 24 | return unescape(part.substring(nameEq.length, part.length)); 25 | } 26 | } 27 | return defaultValue !== undefined ? defaultValue : null; 28 | }, 29 | 30 | /* Create or update a cookie. */ 31 | set: function(name, value, path, days) { 32 | var params = []; 33 | if (path) { 34 | params.push("; path=" + path); 35 | } 36 | if (days) { 37 | var date = new Date(); 38 | date.setTime(date.getTime() + (days * 24*60*60*1000)); 39 | params.push("; expires=" + date.toGMTString()); 40 | } 41 | document.cookie = name + "=" + escape(value) + params.join(); 42 | }, 43 | 44 | /* Remove a cookie. */ 45 | remove: function(name, path) { 46 | $.cookies.set(name, "", path, -1); 47 | } 48 | 49 | }); 50 | })(jQuery); 51 | -------------------------------------------------------------------------------- /public/_utils/script/test/copy_doc.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.copy_doc = function(debug) { 14 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 15 | db.deleteDb(); 16 | db.createDb(); 17 | if (debug) debugger; 18 | 19 | // copy a doc 20 | T(db.save({_id:"doc_to_be_copied",v:1}).ok); 21 | var xhr = CouchDB.request("COPY", "/test_suite_db/doc_to_be_copied", { 22 | headers: {"Destination":"doc_that_was_copied"} 23 | }); 24 | 25 | T(xhr.status == 201); 26 | T(db.open("doc_that_was_copied").v == 1); 27 | 28 | // COPY with existing target 29 | T(db.save({_id:"doc_to_be_copied2",v:1}).ok); 30 | var doc = db.save({_id:"doc_to_be_overwritten",v:2}); 31 | T(doc.ok); 32 | 33 | // error condition 34 | var xhr = CouchDB.request("COPY", "/test_suite_db/doc_to_be_copied2", { 35 | headers: {"Destination":"doc_to_be_overwritten"} 36 | }); 37 | T(xhr.status == 409); // conflict 38 | 39 | var rev = db.open("doc_to_be_overwritten")._rev; 40 | var xhr = CouchDB.request("COPY", "/test_suite_db/doc_to_be_copied2", { 41 | headers: {"Destination":"doc_to_be_overwritten?rev=" + rev} 42 | }); 43 | T(xhr.status == 201); 44 | 45 | var over = db.open("doc_to_be_overwritten"); 46 | T(rev != over._rev); 47 | T(over.v == 1); 48 | }; 49 | -------------------------------------------------------------------------------- /vendor/sinatra/test/contest.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | 3 | # Test::Unit loads a default test if the suite is empty, and the only 4 | # purpose of that test is to fail. As having empty contexts is a common 5 | # practice, we decided to overwrite TestSuite#empty? in order to 6 | # allow them. Having a failure when no tests have been defined seems 7 | # counter-intuitive. 8 | class Test::Unit::TestSuite 9 | unless method_defined?(:empty?) 10 | def empty? 11 | false 12 | end 13 | end 14 | end 15 | 16 | # We added setup, test and context as class methods, and the instance 17 | # method setup now iterates on the setup blocks. Note that all setup 18 | # blocks must be defined with the block syntax. Adding a setup instance 19 | # method defeats the purpose of this library. 20 | class Test::Unit::TestCase 21 | def self.setup(&block) 22 | setup_blocks << block 23 | end 24 | 25 | def setup 26 | self.class.setup_blocks.each do |block| 27 | instance_eval(&block) 28 | end 29 | end 30 | 31 | def self.context(name, &block) 32 | subclass = Class.new(self.superclass) 33 | subclass.setup_blocks.unshift(*setup_blocks) 34 | subclass.class_eval(&block) 35 | const_set(context_name(name), subclass) 36 | end 37 | 38 | def self.test(name, &block) 39 | define_method(test_name(name), &block) 40 | end 41 | 42 | class << self 43 | alias_method :should, :test 44 | alias_method :describe, :context 45 | end 46 | 47 | private 48 | 49 | def self.setup_blocks 50 | @setup_blocks ||= [] 51 | end 52 | 53 | def self.context_name(name) 54 | "Test#{sanitize_name(name).gsub(/(^| )(\w)/) { $2.upcase }}".to_sym 55 | end 56 | 57 | def self.test_name(name) 58 | "test_#{sanitize_name(name).gsub(/\s+/,'_')}".to_sym 59 | end 60 | 61 | def self.sanitize_name(name) 62 | name.gsub(/\W+/, ' ').strip 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /public/_utils/script/test/compact.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.compact = function(debug) { 14 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 15 | db.deleteDb(); 16 | db.createDb(); 17 | if (debug) debugger; 18 | var docs = makeDocs(0, 20); 19 | db.bulkSave(docs); 20 | 21 | var binAttDoc = { 22 | _id: "bin_doc", 23 | _attachments:{ 24 | "foo.txt": { 25 | content_type:"text/plain", 26 | data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" 27 | } 28 | } 29 | } 30 | 31 | T(db.save(binAttDoc).ok); 32 | 33 | var originalsize = db.info().disk_size; 34 | 35 | for(var i in docs) { 36 | db.deleteDoc(docs[i]); 37 | } 38 | T(db.ensureFullCommit().ok); 39 | var deletesize = db.info().disk_size; 40 | T(deletesize > originalsize); 41 | 42 | T(db.compact().ok); 43 | T(db.last_req.status == 202); 44 | // compaction isn't instantaneous, loop until done 45 | while (db.info().compact_running) {}; 46 | 47 | T(db.ensureFullCommit().ok); 48 | restartServer(); 49 | var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt"); 50 | T(xhr.responseText == "This is a base64 encoded text") 51 | T(xhr.getResponseHeader("Content-Type") == "text/plain") 52 | T(db.info().doc_count == 1); 53 | T(db.info().disk_size < deletesize); 54 | 55 | }; 56 | -------------------------------------------------------------------------------- /vendor/sinatra/test/helper.rb: -------------------------------------------------------------------------------- 1 | ENV['RACK_ENV'] = 'test' 2 | 3 | begin 4 | require 'rack' 5 | rescue LoadError 6 | require 'rubygems' 7 | require 'rack' 8 | end 9 | 10 | testdir = File.dirname(__FILE__) 11 | $LOAD_PATH.unshift testdir unless $LOAD_PATH.include?(testdir) 12 | 13 | libdir = File.dirname(File.dirname(__FILE__)) + '/lib' 14 | $LOAD_PATH.unshift libdir unless $LOAD_PATH.include?(libdir) 15 | 16 | require 'contest' 17 | require 'rack/test' 18 | require 'sinatra/base' 19 | 20 | class Sinatra::Base 21 | # Allow assertions in request context 22 | include Test::Unit::Assertions 23 | end 24 | 25 | Sinatra::Base.set :environment, :test 26 | 27 | class Test::Unit::TestCase 28 | include Rack::Test::Methods 29 | 30 | class << self 31 | alias_method :it, :test 32 | end 33 | 34 | alias_method :response, :last_response 35 | 36 | setup do 37 | Sinatra::Base.set :environment, :test 38 | end 39 | 40 | # Sets up a Sinatra::Base subclass defined with the block 41 | # given. Used in setup or individual spec methods to establish 42 | # the application. 43 | def mock_app(base=Sinatra::Base, &block) 44 | @app = Sinatra.new(base, &block) 45 | end 46 | 47 | def app 48 | Rack::Lint.new(@app) 49 | end 50 | 51 | def body 52 | response.body.to_s 53 | end 54 | 55 | # Delegate other missing methods to response. 56 | def method_missing(name, *args, &block) 57 | if response && response.respond_to?(name) 58 | response.send(name, *args, &block) 59 | else 60 | super 61 | end 62 | end 63 | 64 | # Also check response since we delegate there. 65 | def respond_to?(symbol, include_private=false) 66 | super || (response && response.respond_to?(symbol, include_private)) 67 | end 68 | 69 | # Do not output warnings for the duration of the block. 70 | def silence_warnings 71 | $VERBOSE, v = nil, $VERBOSE 72 | yield 73 | ensure 74 | $VERBOSE = v 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /vendor/sinatra/test/middleware_test.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/helper' 2 | 3 | class MiddlewareTest < Test::Unit::TestCase 4 | setup do 5 | @app = mock_app(Sinatra::Default) { 6 | get '/*' do 7 | response.headers['X-Tests'] = env['test.ran']. 8 | map { |n| n.split('::').last }. 9 | join(', ') 10 | env['PATH_INFO'] 11 | end 12 | } 13 | end 14 | 15 | class MockMiddleware < Struct.new(:app) 16 | def call(env) 17 | (env['test.ran'] ||= []) << self.class.to_s 18 | app.call(env) 19 | end 20 | end 21 | 22 | class UpcaseMiddleware < MockMiddleware 23 | def call(env) 24 | env['PATH_INFO'] = env['PATH_INFO'].upcase 25 | super 26 | end 27 | end 28 | 29 | it "is added with Sinatra::Application.use" do 30 | @app.use UpcaseMiddleware 31 | get '/hello-world' 32 | assert ok? 33 | assert_equal '/HELLO-WORLD', body 34 | end 35 | 36 | class DowncaseMiddleware < MockMiddleware 37 | def call(env) 38 | env['PATH_INFO'] = env['PATH_INFO'].downcase 39 | super 40 | end 41 | end 42 | 43 | it "runs in the order defined" do 44 | @app.use UpcaseMiddleware 45 | @app.use DowncaseMiddleware 46 | get '/Foo' 47 | assert_equal "/foo", body 48 | assert_equal "UpcaseMiddleware, DowncaseMiddleware", response['X-Tests'] 49 | end 50 | 51 | it "resets the prebuilt pipeline when new middleware is added" do 52 | @app.use UpcaseMiddleware 53 | get '/Foo' 54 | assert_equal "/FOO", body 55 | @app.use DowncaseMiddleware 56 | get '/Foo' 57 | assert_equal '/foo', body 58 | assert_equal "UpcaseMiddleware, DowncaseMiddleware", response['X-Tests'] 59 | end 60 | 61 | it "works when app is used as middleware" do 62 | @app.use UpcaseMiddleware 63 | @app = @app.new 64 | get '/Foo' 65 | assert_equal "/FOO", body 66 | assert_equal "UpcaseMiddleware", response['X-Tests'] 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /public/_utils/script/test/http.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.http = function(debug) { 14 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 15 | db.deleteDb(); 16 | 17 | // bug COUCHDB-100: DELETE on non-existent DB returns 500 instead of 404 18 | db.deleteDb(); 19 | 20 | db.createDb(); 21 | 22 | // PUT on existing DB should return 412 instead of 500 23 | if (debug) debugger; 24 | 25 | var xhr = CouchDB.request("PUT", "/test_suite_db/test", {body: "{}"}); 26 | var host = CouchDB.host; 27 | 28 | TEquals("http://" + host + "/test_suite_db/test", 29 | xhr.getResponseHeader("Location"), 30 | "should include ip address"); 31 | 32 | xhr = CouchDB.request("PUT", "/test_suite_db/test2", { 33 | body: "{}", 34 | headers: {"X-Forwarded-Host": "mysite.com"} 35 | }); 36 | 37 | TEquals("http://mysite.com/test_suite_db/test2", 38 | xhr.getResponseHeader("Location"), 39 | "should include X-Forwarded-Host"); 40 | 41 | run_on_modified_server([{ 42 | section:"httpd", 43 | key:"x_forwarded_host", 44 | value:"X-Host"}], 45 | function() { 46 | xhr = CouchDB.request("PUT", "/test_suite_db/test3", { 47 | body: "{}", 48 | headers: {"X-Host": "mysite2.com"} 49 | }); 50 | TEquals("http://mysite2.com/test_suite_db/test3", 51 | xhr.getResponseHeader("Location"), 52 | "should include X-Host"); 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /public/_utils/script/test/view_multi_key_all_docs.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.view_multi_key_all_docs = function(debug) { 14 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 15 | db.deleteDb(); 16 | db.createDb(); 17 | if (debug) debugger; 18 | 19 | var docs = makeDocs(0, 100); 20 | db.bulkSave(docs); 21 | 22 | var keys = ["10","15","30","37","50"]; 23 | var rows = db.allDocs({},keys).rows; 24 | T(rows.length == keys.length); 25 | for(var i=0; i' } 14 | assert ok? 15 | assert_equal '2', body 16 | end 17 | 18 | it 'renders .erb files in views path' do 19 | erb_app { erb :hello } 20 | assert ok? 21 | assert_equal "Hello World\n", body 22 | end 23 | 24 | it 'takes a :locals option' do 25 | erb_app { 26 | locals = {:foo => 'Bar'} 27 | erb '<%= foo %>', :locals => locals 28 | } 29 | assert ok? 30 | assert_equal 'Bar', body 31 | end 32 | 33 | it "renders with inline layouts" do 34 | mock_app { 35 | layout { 'THIS. IS. <%= yield.upcase %>!' } 36 | get('/') { erb 'Sparta' } 37 | } 38 | get '/' 39 | assert ok? 40 | assert_equal 'THIS. IS. SPARTA!', body 41 | end 42 | 43 | it "renders with file layouts" do 44 | erb_app { 45 | erb 'Hello World', :layout => :layout2 46 | } 47 | assert ok? 48 | assert_equal "ERB Layout!\nHello World\n", body 49 | end 50 | 51 | it "renders erb with blocks" do 52 | mock_app { 53 | def container 54 | @_out_buf << "THIS." 55 | yield 56 | @_out_buf << "SPARTA!" 57 | end 58 | def is; "IS." end 59 | get '/' do 60 | erb '<% container do %> <%= is %> <% end %>' 61 | end 62 | } 63 | get '/' 64 | assert ok? 65 | assert_equal 'THIS. IS. SPARTA!', body 66 | end 67 | 68 | it "can be used in a nested fashion for partials and whatnot" do 69 | mock_app { 70 | template(:inner) { "<%= 'hi' %>" } 71 | template(:outer) { "<%= erb :inner %>" } 72 | get '/' do 73 | erb :outer 74 | end 75 | } 76 | 77 | get '/' 78 | assert ok? 79 | assert_equal 'hi', body 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /public/_utils/script/test/batch_save.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.batch_save = function(debug) { 14 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 15 | db.deleteDb(); 16 | db.createDb(); 17 | if (debug) debugger; 18 | 19 | // commit should work fine with no batches 20 | T(db.ensureFullCommit().ok); 21 | 22 | // PUT a doc with ?batch=ok 23 | T(db.save({_id:"0",a:1,b:1}, {batch : "ok"}).ok); 24 | 25 | // test that response is 202 Accepted 26 | T(db.last_req.status == 202); 27 | 28 | T(db.allDocs().total_rows == 0); 29 | 30 | restartServer(); 31 | 32 | // lost the updates 33 | T(db.allDocs().total_rows == 0); 34 | 35 | T(db.save({_id:"0",a:1,b:1}, {batch : "ok"}).ok); 36 | T(db.save({_id:"1",a:1,b:1}, {batch : "ok"}).ok); 37 | T(db.save({_id:"2",a:1,b:1}, {batch : "ok"}).ok); 38 | 39 | T(db.ensureFullCommit().ok); 40 | T(db.allDocs().total_rows == 3); 41 | 42 | // repeat the tests for POST 43 | var resp = db.request("POST", db.uri + "?batch=ok", {body: JSON.stringify({a:1})}); 44 | T(JSON.parse(resp.responseText).ok); 45 | 46 | // test that response is 202 Accepted 47 | T(resp.status == 202); 48 | 49 | T(db.allDocs().total_rows == 3); 50 | // restartServer(); 51 | // // lost the POSTed doc 52 | // T(db.allDocs().total_rows == 3); 53 | 54 | var resp = db.request("POST", db.uri + "?batch=ok", {body: JSON.stringify({a:1})}); 55 | T(JSON.parse(resp.responseText).ok); 56 | 57 | T(db.ensureFullCommit().ok); 58 | T(db.allDocs().total_rows == 5); 59 | 60 | }; 61 | -------------------------------------------------------------------------------- /spec/document_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.expand_path(File.dirname(__FILE__)),"spec_helper"); 2 | 3 | describe "Doc" do 4 | before(:each) do 5 | @d = Document.new({ 6 | "_id" => "awesome", 7 | "foo" => "bar" 8 | }) 9 | end 10 | it "should have an id" do 11 | @d.id.should == "awesome" 12 | end 13 | it "should have a rev" do 14 | @d.rev.should_not be_nil 15 | end 16 | it "should have body" do 17 | @d.body["foo"].should == "bar" 18 | end 19 | describe "updating it with a matching rev" do 20 | before(:each) do 21 | @r = @d.rev 22 | @d.update({ 23 | "_id" => "awesome", 24 | "_rev" => @r, 25 | "foo" => "box" 26 | }) 27 | end 28 | it "should get a new rev" do 29 | @d.rev.should_not == @r 30 | end 31 | it "should update fields" do 32 | @d.body["foo"].should == "box" 33 | end 34 | end 35 | describe "deleting a doc" do 36 | before(:each) do 37 | @r = @d.rev 38 | @d.update({ 39 | "_id" => "awesome", 40 | "_rev" => @r, 41 | "_deleted" => true 42 | }) 43 | end 44 | it "should be deleted" do 45 | @d.deleted.should be_true 46 | end 47 | it "should update without a rev" do 48 | @d.update({ 49 | "_id" => "awesome", 50 | "totally_new" => "yeah" 51 | }) 52 | @d.jh["totally_new"].should == "yeah" 53 | end 54 | end 55 | describe "updating it with a conflict" do 56 | before(:each) do 57 | @r = @d.rev 58 | @d.update({ 59 | "_id" => "awesome", 60 | "_rev" => "@r", 61 | "foo" => "conflict" 62 | },{ 63 | :all_or_nothing => "true" 64 | }) 65 | @cfts = @d.jh(:conflicts => "true")["_conflicts"] 66 | end 67 | it "should have conflict_revs" do 68 | @cfts.length.should == 1 69 | end 70 | it "should load conflict revs" do 71 | @d.jh({:rev => @cfts[0]})["_rev"].should == @cfts[0] 72 | @d.jh["_rev"].should_not == @cfts[0] 73 | end 74 | end 75 | it "should have no conflicts" do 76 | @d.conflicts.should == [] 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /public/_utils/script/test/lots_of_docs.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | // test saving a semi-large quanitity of documents and do some view queries. 14 | couchTests.lots_of_docs = function(debug) { 15 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 16 | db.deleteDb(); 17 | db.createDb(); 18 | if (debug) debugger; 19 | 20 | // keep number lowish for now to keep tests fasts. Crank up manually to 21 | // to really test. 22 | var numDocsToCreate = 500; 23 | 24 | for(var i=0; i < numDocsToCreate; i += 100) { 25 | var createNow = Math.min(numDocsToCreate - i, 100); 26 | var docs = makeDocs(i, i + createNow); 27 | db.bulkSave(docs); 28 | } 29 | 30 | // query all documents, and return the doc.integer member as a key. 31 | results = db.query(function(doc){ emit(doc.integer, null) }); 32 | 33 | T(results.total_rows == numDocsToCreate); 34 | 35 | // validate the keys are ordered ascending 36 | for(var i=0; i 'foo/bar'}, 'Hello World'] 58 | end 59 | } 60 | 61 | get '/' 62 | assert_equal 205, status 63 | assert_equal 'foo/bar', response['Content-Type'] 64 | assert_equal 'Hello World', body 65 | end 66 | 67 | it "sets status and body when result is a two-tuple" do 68 | mock_app { 69 | get '/' do 70 | [409, 'formula of'] 71 | end 72 | } 73 | 74 | get '/' 75 | assert_equal 409, status 76 | assert_equal 'formula of', body 77 | end 78 | 79 | it "raises a TypeError when result is a non two or three tuple Array" do 80 | mock_app { 81 | get '/' do 82 | [409, 'formula of', 'something else', 'even more'] 83 | end 84 | } 85 | 86 | assert_raise(TypeError) { get '/' } 87 | end 88 | 89 | it "sets status when result is a Fixnum status code" do 90 | mock_app { 91 | get('/') { 205 } 92 | } 93 | 94 | get '/' 95 | assert_equal 205, status 96 | assert_equal '', body 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /public/_utils/script/test/config.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.config = function(debug) { 14 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 15 | db.deleteDb(); 16 | db.createDb(); 17 | if (debug) debugger; 18 | 19 | // test that /_config returns all the settings 20 | var xhr = CouchDB.request("GET", "/_config"); 21 | var config = JSON.parse(xhr.responseText); 22 | 23 | /* 24 | if we run on standard ports, we can't extract 25 | the number from the URL. Instead we try to guess 26 | from the protocol what port we are running on. 27 | If we can't guess, we don't test for the port. 28 | Overengineering FTW. 29 | */ 30 | var server_port = CouchDB.host.split(':'); 31 | if(server_port.length == 1 && CouchDB.inBrowser) { 32 | var proto = window.location.protocol; 33 | if(proto == "http:") { 34 | port = 80; 35 | } 36 | if(proto == "https:") { 37 | port = 443; 38 | } 39 | } else { 40 | port = server_port.pop(); 41 | } 42 | 43 | if(port) { 44 | T(config.httpd.port == port); 45 | } 46 | 47 | T(config.couchdb.database_dir); 48 | T(config.daemons.httpd); 49 | T(config.httpd_global_handlers._config); 50 | T(config.log.level); 51 | T(config.query_servers.javascript); 52 | 53 | // test that settings can be altered 54 | xhr = CouchDB.request("PUT", "/_config/test/foo",{ 55 | body : JSON.stringify("bar"), 56 | headers: {"X-Couch-Persist": "false"} 57 | }); 58 | T(xhr.status == 200); 59 | xhr = CouchDB.request("GET", "/_config/test"); 60 | config = JSON.parse(xhr.responseText); 61 | T(config.foo == "bar"); 62 | 63 | // you can get a single key 64 | xhr = CouchDB.request("GET", "/_config/test/foo"); 65 | config = JSON.parse(xhr.responseText); 66 | T(config == "bar"); 67 | }; 68 | -------------------------------------------------------------------------------- /lib/query/query_server.rb: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | # use this file except in compliance with the License. You may obtain a copy of 3 | # the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations under 11 | # the License. 12 | 13 | require 'json' 14 | 15 | class QueryServer 16 | Command = "couchjs #{JS_SERVER_PATH}" 17 | def self.run(trace = false) 18 | puts "launching #{Command}" if trace 19 | if block_given? 20 | IO.popen(Command, "r+") do |io| 21 | qs = QueryServer.new(io, trace) 22 | result = yield qs 23 | qs.close 24 | result 25 | end 26 | else 27 | io = IO.popen(Command, "r+") 28 | QueryServer.new(io, trace) 29 | end 30 | end 31 | def initialize io, trace = false 32 | @qsio = io 33 | @trace = trace 34 | end 35 | def close 36 | @qsio.close 37 | end 38 | def reset! 39 | run(["reset"]) 40 | end 41 | def add_fun(fun) 42 | run(["add_fun", fun]) 43 | end 44 | def get_chunks 45 | resp = jsgets 46 | raise "not a chunk" unless resp.first == "chunks" 47 | return resp[1] 48 | end 49 | def run json 50 | rrun json 51 | jsgets 52 | end 53 | def rrun json 54 | line = json.to_json 55 | puts "run: #{line}" if @trace 56 | @qsio.puts line 57 | end 58 | def rgets 59 | resp = @qsio.gets 60 | puts "got: #{resp}" if @trace 61 | resp 62 | end 63 | def jsgets 64 | resp = rgets 65 | # err = @qserr.gets 66 | # puts "err: #{err}" if err 67 | if resp 68 | begin 69 | rj = JSON.parse("[#{resp.chomp}]")[0] 70 | rescue JSON::ParserError 71 | puts "JSON ERROR (dump under trace mode)" 72 | # puts resp.chomp 73 | while resp = rgets 74 | # puts resp.chomp 75 | end 76 | end 77 | if rj.respond_to?(:[]) && rj.is_a?(Array) 78 | if rj[0] == "log" 79 | log = rj[1] 80 | puts "log: #{log}" if @trace 81 | rj = jsgets 82 | end 83 | end 84 | rj 85 | else 86 | raise "no response" 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /spec/view_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.expand_path(File.dirname(__FILE__)),"spec_helper"); 2 | 3 | describe "View" do 4 | before(:each) do 5 | @db = Database.new 6 | @db.put({ 7 | "_id" => "zoo", 8 | "foo" => "baz" 9 | }) 10 | # make a map-only view based on the db 11 | map = "function(doc) { emit(doc.foo, null); };" 12 | @v = View.new(@db, map) 13 | end 14 | it "should emit the doc" do 15 | result = @v.query 16 | result[:rows].length.should == 1 17 | result[:rows].first[:key].should == "baz" 18 | end 19 | end 20 | 21 | # populate the db with collated docs 22 | describe "View with collated docs" do 23 | before(:each) do 24 | @db = Database.new 25 | @keys = [ 26 | nil, 27 | false, true, 28 | 1, 2, 3.4, 5, 29 | "a", "A", "aa", "b", "Ba", "bb", 30 | ["a"], ["b"], ["b","c"], ["b", "c", "a"], 31 | {"a" => 1}, {"a" => 2}, {"b" => 1}, {"b" => 2}, 32 | {"b" => 2, "a" => 1}, {"b"=> 2, "c"=> 2} 33 | ] 34 | 35 | @keys.reverse.each_with_index do |key, i| 36 | @db.put({"_id" => i.to_s, "foo" => key}) 37 | end 38 | 39 | # make a map-only view based on the db 40 | map = "function(doc) { emit(doc.foo, null); };" 41 | @v = View.new(@db, map) 42 | end 43 | it "should collate properly" do 44 | # puts "view collate" 45 | result = @v.query 46 | result[:rows].each_with_index do |row, i| 47 | # puts "row #{row.inspect}" 48 | row[:key].should == @keys[i] 49 | end 50 | end 51 | it "should support key ranges" do 52 | puts "view key ranges" 53 | rows = [] 54 | @v.query("startkey" => "aa", "endkey" => "bb") do |row| 55 | rows << row 56 | end 57 | rows[0][:key].should == "aa" 58 | rows.length.should == 4 59 | rows[3][:key].should == "bb" 60 | end 61 | it "should support short key ranges" do 62 | result = @v.query("startkey" => "aa", "endkey" => "aa", "inclusive_end" => "true") 63 | result[:rows].length.should == 1 64 | result[:rows][0][:key].should == "aa" 65 | end 66 | it "should support key lookups" do 67 | result = @v.query("key" => "aa") 68 | result[:rows].length.should == 1 69 | result[:rows][0][:key].should == "aa" 70 | end 71 | it "should support exclusive end key ranges" do 72 | result = @v.query("startkey" => "aa", "endkey" => "bb", "inclusive_end" => "false") 73 | result[:rows].last[:key].should == "Ba" 74 | result[:rows].length.should == 3 75 | end 76 | end -------------------------------------------------------------------------------- /vendor/sinatra/AUTHORS: -------------------------------------------------------------------------------- 1 | Sinatra was designed and developed by Blake Mizerany (bmizerany) in 2 | California. Continued development would not be possible without the ongoing 3 | financial support provided by [Heroku](http://heroku.com) and the emotional 4 | support provided by Adam Wiggins (adamwiggins) of Heroku, Chris Wanstrath (defunkt), 5 | PJ Hyett (pjhyett), and the rest of the GitHub crew. 6 | 7 | Special thanks to the following extraordinary individuals, who-out which 8 | Sinatra would not be possible: 9 | 10 | * Ryan Tomayko (rtomayko) for constantly fixing whitespace errors 60d5006 11 | * Ezra Zygmuntowicz (ezmobius) for initial help and letting Blake steal 12 | some of merbs internal code. 13 | * Christopher Schneid (cschneid) for The Book, the blog (gittr.com), 14 | irclogger.com, and a bunch of useful patches. 15 | * Markus Prinz (cypher) for patches over the years, caring about 16 | the README, and hanging in there when times were rough. 17 | * Simon Rozet (sr) for a ton of doc patches, HAML options, and all that 18 | advocacy stuff he's going to do for 1.0. 19 | * Erik Kastner (kastner) for fixing `MIME_TYPES` under Rack 0.5. 20 | * Ben Bleything (bleything) for caring about HTTP status codes and doc fixes. 21 | * Igal Koshevoy (igal) for root path detection under Thin/Passenger. 22 | * Jon Crosby (jcrosby) for coffee breaks, doc fixes, and just because, man. 23 | * Karel Minarik (karmi) for screaming until the website came back up. 24 | * Jeremy Evans (jeremyevans) for unbreaking optional path params (twice!) 25 | * The GitHub guys for stealing Blake's table. 26 | * Nickolas Means (nmeans) for Sass template support. 27 | * Victor Hugo Borja (vic) for splat'n routes specs and doco. 28 | * Avdi Grimm (avdi) for basic RSpec support. 29 | * Jack Danger Canty for a more accurate root directory and for making me 30 | watch [this](http://www.youtube.com/watch?v=ueaHLHgskkw) just now. 31 | * Mathew Walker for making escaped paths work with static files. 32 | * Millions of Us for having the problem that led to Sinatra's conception. 33 | * Songbird for the problems that helped Sinatra's future become realized. 34 | * Rick Olson (technoweenie) for the killer plug at RailsConf '08. 35 | * Steven Garcia for the amazing custom artwork you see on 404's and 500's 36 | * Pat Nakajima (nakajima) for fixing non-nested params in nested params Hash's. 37 | 38 | 39 | and last but not least: 40 | 41 | * Frank Sinatra (chairman of the board) for having so much class he 42 | deserves a web-framework named after him. 43 | -------------------------------------------------------------------------------- /public/_utils/script/test/design_options.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.design_options = function(debug) { 14 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 15 | db.deleteDb(); 16 | db.createDb(); 17 | if (debug) debugger; 18 | 19 | //// test the includes_design option 20 | var map = "function (doc) {emit(null, doc._id);}"; 21 | var withseq = "function(doc) {emit(doc._local_seq, null)}" 22 | 23 | // we need a design doc even to test temp views with it 24 | var designDoc = { 25 | _id:"_design/fu", 26 | language: "javascript", 27 | options: { 28 | include_design: true, 29 | local_seq: true 30 | }, 31 | views: { 32 | data: {"map": map}, 33 | with_seq : {"map" : withseq} 34 | } 35 | }; 36 | T(db.save(designDoc).ok); 37 | 38 | // should work for temp views 39 | var rows = db.query(map, null, {options:{include_design: true}}).rows; 40 | T(rows.length == 1); 41 | T(rows[0].value == "_design/fu"); 42 | 43 | rows = db.query(map).rows; 44 | T(rows.length == 0); 45 | 46 | // when true, should include design docs in views 47 | rows = db.view("fu/data").rows; 48 | T(rows.length == 1); 49 | T(rows[0].value == "_design/fu"); 50 | 51 | // when false, should not 52 | designDoc.options.include_design = false; 53 | delete designDoc._rev; 54 | designDoc._id = "_design/bingo"; 55 | T(db.save(designDoc).ok); 56 | rows = db.view("bingo/data").rows; 57 | T(rows.length == 0); 58 | 59 | // should default to false 60 | delete designDoc.options; 61 | delete designDoc._rev; 62 | designDoc._id = "_design/bango"; 63 | T(db.save(designDoc).ok); 64 | rows = db.view("bango/data").rows; 65 | T(rows.length == 0); 66 | 67 | // should also have local_seq in the view 68 | var resp = db.save({}); 69 | rows = db.view("fu/with_seq").rows; 70 | T(rows[0].key == 1) 71 | T(rows[1].key == 2) 72 | var doc = db.open(resp.id); 73 | db.deleteDoc(doc); 74 | }; 75 | -------------------------------------------------------------------------------- /public/_utils/script/test/etags_head.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.etags_head = function(debug) { 14 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 15 | db.deleteDb(); 16 | db.createDb(); 17 | if (debug) debugger; 18 | 19 | var xhr; 20 | 21 | // create a new doc 22 | xhr = CouchDB.request("PUT", "/test_suite_db/1", { 23 | body: "{}" 24 | }); 25 | T(xhr.status == 201); 26 | 27 | // extract the ETag header values 28 | var etag = xhr.getResponseHeader("etag"); 29 | 30 | // get the doc and verify the headers match 31 | xhr = CouchDB.request("GET", "/test_suite_db/1"); 32 | T(etag == xhr.getResponseHeader("etag")); 33 | 34 | // 'head' the doc and verify the headers match 35 | xhr = CouchDB.request("HEAD", "/test_suite_db/1", { 36 | headers: {"if-none-match": "s"} 37 | }); 38 | T(etag == xhr.getResponseHeader("etag")); 39 | 40 | // replace a doc 41 | xhr = CouchDB.request("PUT", "/test_suite_db/1", { 42 | body: "{}", 43 | headers: {"if-match": etag} 44 | }); 45 | T(xhr.status == 201); 46 | 47 | // extract the new ETag value 48 | var etagOld= etag; 49 | etag = xhr.getResponseHeader("etag"); 50 | 51 | // fail to replace a doc 52 | xhr = CouchDB.request("PUT", "/test_suite_db/1", { 53 | body: "{}" 54 | }); 55 | T(xhr.status == 409); 56 | 57 | // verify get w/Etag 58 | xhr = CouchDB.request("GET", "/test_suite_db/1", { 59 | headers: {"if-none-match": etagOld} 60 | }); 61 | T(xhr.status == 200); 62 | xhr = CouchDB.request("GET", "/test_suite_db/1", { 63 | headers: {"if-none-match": etag} 64 | }); 65 | T(xhr.status == 304); 66 | 67 | // fail to delete a doc 68 | xhr = CouchDB.request("DELETE", "/test_suite_db/1", { 69 | headers: {"if-match": etagOld} 70 | }); 71 | T(xhr.status == 409); 72 | 73 | //now do it for real 74 | xhr = CouchDB.request("DELETE", "/test_suite_db/1", { 75 | headers: {"if-match": etag} 76 | }); 77 | T(xhr.status == 200); 78 | }; 79 | -------------------------------------------------------------------------------- /public/_utils/script/test/design_paths.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.design_paths = function(debug) { 14 | if (debug) debugger; 15 | var dbNames = ["test_suite_db", "test_suite_db/with_slashes"]; 16 | for (var i=0; i < dbNames.length; i++) { 17 | var db = new CouchDB(dbNames[i]); 18 | var dbName = encodeURIComponent(dbNames[i]); 19 | db.deleteDb(); 20 | db.createDb(); 21 | 22 | // create a ddoc w bulk_docs 23 | db.bulkSave([{ 24 | _id : "_design/test", 25 | views : { 26 | "testing" : { 27 | "map" : "function(){emit(1,1)}" 28 | } 29 | } 30 | }]); 31 | 32 | // ddoc is getable 33 | var xhr = CouchDB.request("GET", "/"+dbName+"/_design/test"); 34 | var resp = JSON.parse(xhr.responseText); 35 | T(resp._id == "_design/test"); 36 | 37 | // it's at 2 urls... 38 | var xhr = CouchDB.request("GET", "/"+dbName+"/_design%2Ftest"); 39 | var resp = JSON.parse(xhr.responseText); 40 | T(resp._id == "_design/test"); 41 | 42 | // ensure that views are addressable 43 | resp = db.view("test/testing") 44 | T(resp.total_rows == 0) 45 | 46 | // create a ddoc by putting to url with raw slash 47 | var xhr = CouchDB.request("PUT", "/"+dbName+"/_design/test2",{ 48 | body : JSON.stringify({ 49 | _id : "_design/test2", 50 | views : { 51 | "testing" : { 52 | "map" : "function(){emit(1,1)}" 53 | } 54 | } 55 | }) 56 | }); 57 | 58 | // ddoc is getable 59 | var xhr = CouchDB.request("GET", "/"+dbName+"/_design/test2"); 60 | var resp = JSON.parse(xhr.responseText); 61 | T(resp._id == "_design/test2"); 62 | 63 | // it's at 2 urls... 64 | var xhr = CouchDB.request("GET", "/"+dbName+"/_design%2Ftest2"); 65 | var resp = JSON.parse(xhr.responseText); 66 | T(resp._id == "_design/test2"); 67 | 68 | // ensure that views are addressable 69 | resp = db.view("test2/testing"); 70 | T(resp.total_rows == 0); 71 | }; 72 | }; 73 | -------------------------------------------------------------------------------- /spec/database_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.expand_path(File.dirname(__FILE__)),"spec_helper"); 2 | 3 | describe "Database" do 4 | before(:each) do 5 | @db = Database.new 6 | @db.put({ 7 | "_id" => "foo", 8 | "bam" => "baz" 9 | }) 10 | end 11 | it "should count docs" do 12 | @db.doc_count.should == 1 13 | end 14 | it "should accept docs" do 15 | d = @db.get("foo") 16 | d.id.should == "foo" 17 | d.body["bam"].should == "baz" 18 | end 19 | # move rev handling to doc spec 20 | # it "should apply revs" do 21 | # d = @db.get("foo") 22 | # d.rev.should_not be_empty 23 | # end 24 | it "should apply a sequence" do 25 | @db.put({ 26 | "_id" => "bar", 27 | "bam" => "dog" 28 | }) 29 | d = @db.get("foo") 30 | d.seq.should == 1 31 | d2 = @db.get("bar") 32 | d2.seq.should == 2 33 | @db.seq.should == 2 34 | end 35 | # it "should fail updates with a bad rev" do 36 | # lambda { 37 | # @db.put({ 38 | # "_id" => "foo", 39 | # "_rev" => "555", 40 | # "bam" => "duck" 41 | # }) 42 | # }.should raise_error 43 | # end 44 | # it "should allow updates with a good rev" do 45 | # d = @db.get("foo") 46 | # d["bam"].should == "baz" 47 | # new_rev = @db.put({ 48 | # "_id" => "foo", 49 | # "_rev" => d.rev, 50 | # "bam" => "duck" 51 | # }) 52 | # dx = @db.get("foo") 53 | # dx.rev.should == new_rev 54 | # new_rev.should != d.rev 55 | # dx["bam"].should == "duck" 56 | # end 57 | describe "seq" do 58 | before(:each) do 59 | @db.put({ 60 | "_id" => "bar", 61 | "bam" => "dog" 62 | }) 63 | end 64 | it "should increment on doc" do 65 | d = @db.get("foo") 66 | d.seq.should == 1 67 | @db.put({ 68 | "_id" => "foo", 69 | "_rev" => d.rev, 70 | "bam" => "duck" 71 | }) 72 | d = @db.get("foo") 73 | d.seq.should == 3 74 | end 75 | it "should be viewable" do 76 | a = [] 77 | @db.by_seq({:startkey => 0}) do |k, v| 78 | a << v.id 79 | end 80 | a[0].should == "foo" 81 | a[1].should == "bar" 82 | end 83 | it "should be sparse" do 84 | d = @db.get("foo") 85 | d.seq.should == 1 86 | @db.put({ 87 | "_id" => "foo", 88 | "_rev" => d.rev, 89 | "bam" => "duck" 90 | }) 91 | a = [] 92 | @db.by_seq({:startkey => 0}) do |k, v| 93 | a << v.id 94 | end 95 | a[0].should == "bar" 96 | a[1].should == "foo" 97 | end 98 | end 99 | end -------------------------------------------------------------------------------- /vendor/sinatra/test/haml_test.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/helper' 2 | require 'haml' 3 | 4 | class HAMLTest < Test::Unit::TestCase 5 | def haml_app(&block) 6 | mock_app { 7 | set :views, File.dirname(__FILE__) + '/views' 8 | get '/', &block 9 | } 10 | get '/' 11 | end 12 | 13 | it 'renders inline HAML strings' do 14 | haml_app { haml '%h1 Hiya' } 15 | assert ok? 16 | assert_equal "

Hiya

\n", body 17 | end 18 | 19 | it 'renders .haml files in views path' do 20 | haml_app { haml :hello } 21 | assert ok? 22 | assert_equal "

Hello From Haml

\n", body 23 | end 24 | 25 | it "renders with inline layouts" do 26 | mock_app { 27 | layout { %q(%h1= 'THIS. IS. ' + yield.upcase) } 28 | get('/') { haml '%em Sparta' } 29 | } 30 | get '/' 31 | assert ok? 32 | assert_equal "

THIS. IS. SPARTA

\n", body 33 | end 34 | 35 | it "renders with file layouts" do 36 | haml_app { 37 | haml 'Hello World', :layout => :layout2 38 | } 39 | assert ok? 40 | assert_equal "

HAML Layout!

\n

Hello World

\n", body 41 | end 42 | 43 | it "raises error if template not found" do 44 | mock_app { 45 | get('/') { haml :no_such_template } 46 | } 47 | assert_raise(Errno::ENOENT) { get('/') } 48 | end 49 | 50 | it "passes HAML options to the Haml engine" do 51 | mock_app { 52 | get '/' do 53 | haml "!!!\n%h1 Hello World", :format => :html5 54 | end 55 | } 56 | get '/' 57 | assert ok? 58 | assert_equal "\n

Hello World

\n", body 59 | end 60 | 61 | it "passes default HAML options to the Haml engine" do 62 | mock_app { 63 | set :haml, {:format => :html5} 64 | get '/' do 65 | haml "!!!\n%h1 Hello World" 66 | end 67 | } 68 | get '/' 69 | assert ok? 70 | assert_equal "\n

Hello World

\n", body 71 | end 72 | 73 | it "merges the default HAML options with the overrides and passes them to the Haml engine" do 74 | mock_app { 75 | set :haml, {:format => :html5, :attr_wrapper => '"'} # default HAML attr are 76 | get '/' do 77 | haml "!!!\n%h1{:class => :header} Hello World" 78 | end 79 | get '/html4' do 80 | haml "!!!\n%h1{:class => 'header'} Hello World", :format => :html4 81 | end 82 | } 83 | get '/' 84 | assert ok? 85 | assert_equal "\n

Hello World

\n", body 86 | get '/html4' 87 | assert ok? 88 | assert_match(/^ doc.id, :rev => doc.rev} 66 | end 67 | end 68 | 69 | # get a document by id 70 | def get docid, params={} 71 | doc = get_doc(docid, params) 72 | if !doc 73 | raise BoothError.new(404, "not_found", "missing doc '#{docid}'"); 74 | elsif doc.deleted 75 | raise BoothError.new(404, "not_found", "deleted doc '#{docid}'"); 76 | else 77 | doc 78 | end 79 | end 80 | 81 | # delete a document by id 82 | def delete docid, rev 83 | doc = { 84 | "_id" => docid, 85 | "_rev" => rev, 86 | "_deleted" => true 87 | } 88 | new_rev = put doc 89 | @doc_count -= 1 90 | new_rev 91 | end 92 | 93 | private 94 | 95 | # this used to handle conflicts and stuff 96 | # before I pushed that code into document.rb 97 | def get_doc docid, params={} 98 | @by_docid[docid] 99 | end 100 | end -------------------------------------------------------------------------------- /vendor/sinatra/sinatra.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.specification_version = 2 if s.respond_to? :specification_version= 3 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 4 | 5 | s.name = 'sinatra' 6 | s.version = '0.10.1' 7 | s.date = '2009-10-08' 8 | 9 | s.description = "Classy web-development dressed in a DSL" 10 | s.summary = "Classy web-development dressed in a DSL" 11 | 12 | s.authors = ["Blake Mizerany", "Ryan Tomayko", "Simon Rozet"] 13 | s.email = "sinatrarb@googlegroups.com" 14 | 15 | # = MANIFEST = 16 | s.files = %w[ 17 | AUTHORS 18 | CHANGES 19 | LICENSE 20 | README.jp.rdoc 21 | README.rdoc 22 | Rakefile 23 | lib/sinatra.rb 24 | lib/sinatra/base.rb 25 | lib/sinatra/images/404.png 26 | lib/sinatra/images/500.png 27 | lib/sinatra/main.rb 28 | lib/sinatra/showexceptions.rb 29 | lib/tilt.rb 30 | sinatra.gemspec 31 | test/base_test.rb 32 | test/builder_test.rb 33 | test/contest.rb 34 | test/data/reload_app_file.rb 35 | test/erb_test.rb 36 | test/extensions_test.rb 37 | test/filter_test.rb 38 | test/haml_test.rb 39 | test/helper.rb 40 | test/helpers_test.rb 41 | test/mapped_error_test.rb 42 | test/middleware_test.rb 43 | test/options_test.rb 44 | test/request_test.rb 45 | test/response_test.rb 46 | test/result_test.rb 47 | test/route_added_hook_test.rb 48 | test/routing_test.rb 49 | test/sass_test.rb 50 | test/server_test.rb 51 | test/sinatra_test.rb 52 | test/static_test.rb 53 | test/templates_test.rb 54 | test/views/error.builder 55 | test/views/error.erb 56 | test/views/error.haml 57 | test/views/error.sass 58 | test/views/foo/hello.test 59 | test/views/hello.builder 60 | test/views/hello.erb 61 | test/views/hello.haml 62 | test/views/hello.sass 63 | test/views/hello.test 64 | test/views/layout2.builder 65 | test/views/layout2.erb 66 | test/views/layout2.haml 67 | test/views/layout2.test 68 | ] 69 | # = MANIFEST = 70 | 71 | s.test_files = s.files.select {|path| path =~ /^test\/.*_test.rb/} 72 | 73 | s.extra_rdoc_files = %w[README.rdoc LICENSE] 74 | s.add_dependency 'rack', '>= 1.0' 75 | s.add_development_dependency 'shotgun', '>= 0.3', '< 1.0' 76 | s.add_development_dependency 'rack-test', '>= 0.3.0' 77 | 78 | s.has_rdoc = true 79 | s.homepage = "http://sinatra.rubyforge.org" 80 | s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Sinatra", "--main", "README.rdoc"] 81 | s.require_paths = %w[lib] 82 | s.rubyforge_project = 'sinatra' 83 | s.rubygems_version = '1.1.1' 84 | end 85 | -------------------------------------------------------------------------------- /public/_utils/script/test/invalid_docids.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.invalid_docids = function(debug) { 14 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 15 | db.deleteDb(); 16 | db.createDb(); 17 | if (debug) debugger; 18 | 19 | // Test _local explicitly first. 20 | T(db.save({"_id": "_local/foo"}).ok); 21 | T(db.open("_local/foo")._id == "_local/foo"); 22 | 23 | var urls = [ 24 | "/test_suite_db/_local", 25 | "/test_suite_db/_local/", 26 | "/test_suite_db/_local%2F", 27 | "/test_suite_db/_local/foo/bar", 28 | ]; 29 | 30 | urls.forEach(function(u) { 31 | var res = db.request("PUT", u, {"body": "{}"}); 32 | T(res.status == 400); 33 | T(JSON.parse(res.responseText).error == "bad_request"); 34 | }); 35 | 36 | //Test non-string 37 | try { 38 | db.save({"_id": 1}); 39 | T(1 == 0, "doc id must be string"); 40 | } catch(e) { 41 | T(db.last_req.status == 400); 42 | T(e.error == "bad_request"); 43 | } 44 | 45 | // Via PUT with _id not in body. 46 | var res = res = db.request("PUT", "/test_suite_db/_other", {"body": "{}"}); 47 | T(res.status == 400); 48 | T(JSON.parse(res.responseText).error == "bad_request"); 49 | 50 | // Accidental POST to form handling code. 51 | res = db.request("POST", "/test_suite_db/_tmp_view", {"body": "{}"}); 52 | T(res.status == 400); 53 | T(JSON.parse(res.responseText).error == "bad_request"); 54 | 55 | // Test invalid _prefix 56 | try { 57 | db.save({"_id": "_invalid"}); 58 | T(1 == 0, "doc id may not start with underscore"); 59 | } catch(e) { 60 | T(db.last_req.status == 400); 61 | T(e.error == "bad_request"); 62 | } 63 | 64 | // Test _bulk_docs explicitly. 65 | var docs = [{"_id": "_design/foo"}, {"_id": "_local/bar"}]; 66 | db.bulkSave(docs); 67 | docs.forEach(function(d) {T(db.open(d._id)._id == d._id);}); 68 | 69 | docs = [{"_id": "_invalid"}]; 70 | try { 71 | db.bulkSave(docs); 72 | T(1 == 0, "doc id may not start with underscore, even in bulk docs"); 73 | } catch(e) { 74 | T(db.last_req.status == 400); 75 | T(e.error == "bad_request"); 76 | } 77 | }; 78 | -------------------------------------------------------------------------------- /vendor/sinatra/test/filter_test.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/helper' 2 | 3 | class FilterTest < Test::Unit::TestCase 4 | it "executes filters in the order defined" do 5 | count = 0 6 | mock_app do 7 | get('/') { 'Hello World' } 8 | before { 9 | assert_equal 0, count 10 | count = 1 11 | } 12 | before { 13 | assert_equal 1, count 14 | count = 2 15 | } 16 | end 17 | 18 | get '/' 19 | assert ok? 20 | assert_equal 2, count 21 | assert_equal 'Hello World', body 22 | end 23 | 24 | it "allows filters to modify the request" do 25 | mock_app { 26 | get('/foo') { 'foo' } 27 | get('/bar') { 'bar' } 28 | before { request.path_info = '/bar' } 29 | } 30 | 31 | get '/foo' 32 | assert ok? 33 | assert_equal 'bar', body 34 | end 35 | 36 | it "can modify instance variables available to routes" do 37 | mock_app { 38 | before { @foo = 'bar' } 39 | get('/foo') { @foo } 40 | } 41 | 42 | get '/foo' 43 | assert ok? 44 | assert_equal 'bar', body 45 | end 46 | 47 | it "allows redirects in filters" do 48 | mock_app { 49 | before { redirect '/bar' } 50 | get('/foo') do 51 | fail 'before block should have halted processing' 52 | 'ORLY?!' 53 | end 54 | } 55 | 56 | get '/foo' 57 | assert redirect? 58 | assert_equal '/bar', response['Location'] 59 | assert_equal '', body 60 | end 61 | 62 | it "does not modify the response with its return value" do 63 | mock_app { 64 | before { 'Hello World!' } 65 | get '/foo' do 66 | assert_equal [], response.body 67 | 'cool' 68 | end 69 | } 70 | 71 | get '/foo' 72 | assert ok? 73 | assert_equal 'cool', body 74 | end 75 | 76 | it "does modify the response with halt" do 77 | mock_app { 78 | before { halt 302, 'Hi' } 79 | get '/foo' do 80 | "should not happen" 81 | end 82 | } 83 | 84 | get '/foo' 85 | assert_equal 302, response.status 86 | assert_equal 'Hi', body 87 | end 88 | 89 | it "gives you access to params" do 90 | mock_app { 91 | before { @foo = params['foo'] } 92 | get('/foo') { @foo } 93 | } 94 | 95 | get '/foo?foo=cool' 96 | assert ok? 97 | assert_equal 'cool', body 98 | end 99 | 100 | it "runs filters defined in superclasses" do 101 | base = Class.new(Sinatra::Base) 102 | base.before { @foo = 'hello from superclass' } 103 | 104 | mock_app(base) { 105 | get('/foo') { @foo } 106 | } 107 | 108 | get '/foo' 109 | assert_equal 'hello from superclass', body 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /vendor/sinatra/test/sass_test.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/helper' 2 | require 'sass' 3 | 4 | class SassTest < Test::Unit::TestCase 5 | def sass_app(&block) 6 | mock_app { 7 | set :views, File.dirname(__FILE__) + '/views' 8 | get '/', &block 9 | } 10 | get '/' 11 | end 12 | 13 | it 'renders inline Sass strings' do 14 | sass_app { sass "#sass\n :background-color #FFF\n" } 15 | assert ok? 16 | assert_equal "#sass {\n background-color: #FFF; }\n", body 17 | end 18 | 19 | it 'renders .sass files in views path' do 20 | sass_app { sass :hello } 21 | assert ok? 22 | assert_equal "#sass {\n background-color: #FFF; }\n", body 23 | end 24 | 25 | it 'ignores the layout option' do 26 | sass_app { sass :hello, :layout => :layout2 } 27 | assert ok? 28 | assert_equal "#sass {\n background-color: #FFF; }\n", body 29 | end 30 | 31 | it "raises error if template not found" do 32 | mock_app { 33 | get('/') { sass :no_such_template } 34 | } 35 | assert_raise(Errno::ENOENT) { get('/') } 36 | end 37 | 38 | it "passes SASS options to the Sass engine" do 39 | sass_app { 40 | sass "#sass\n :background-color #FFF\n :color #000\n", :style => :compact 41 | } 42 | assert ok? 43 | assert_equal "#sass { background-color: #FFF; color: #000; }\n", body 44 | end 45 | 46 | it "passes default SASS options to the Sass engine" do 47 | mock_app { 48 | set :sass, {:style => :compact} # default Sass style is :nested 49 | get '/' do 50 | sass "#sass\n :background-color #FFF\n :color #000\n" 51 | end 52 | } 53 | get '/' 54 | assert ok? 55 | assert_equal "#sass { background-color: #FFF; color: #000; }\n", body 56 | end 57 | 58 | it "merges the default SASS options with the overrides and passes them to the Sass engine" do 59 | mock_app { 60 | set :sass, {:style => :compact, :attribute_syntax => :alternate } # default Sass attribute_syntax is :normal (with : in front) 61 | get '/' do 62 | sass "#sass\n background-color: #FFF\n color: #000\n" 63 | end 64 | get '/raised' do 65 | sass "#sass\n :background-color #FFF\n :color #000\n", :style => :expanded # retains global attribute_syntax settings 66 | end 67 | get '/expanded_normal' do 68 | sass "#sass\n :background-color #FFF\n :color #000\n", :style => :expanded, :attribute_syntax => :normal 69 | end 70 | } 71 | get '/' 72 | assert ok? 73 | assert_equal "#sass { background-color: #FFF; color: #000; }\n", body 74 | assert_raise(Sass::SyntaxError) { get('/raised') } 75 | get '/expanded_normal' 76 | assert ok? 77 | assert_equal "#sass {\n background-color: #FFF;\n color: #000;\n}\n", body 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /vendor/sinatra/test/static_test.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/helper' 2 | 3 | class StaticTest < Test::Unit::TestCase 4 | setup do 5 | mock_app { 6 | set :static, true 7 | set :public, File.dirname(__FILE__) 8 | } 9 | end 10 | 11 | it 'serves GET requests for files in the public directory' do 12 | get "/#{File.basename(__FILE__)}" 13 | assert ok? 14 | assert_equal File.read(__FILE__), body 15 | assert_equal File.size(__FILE__).to_s, response['Content-Length'] 16 | assert response.headers.include?('Last-Modified') 17 | end 18 | 19 | it 'produces a body that can be iterated over multiple times' do 20 | env = Rack::MockRequest.env_for("/#{File.basename(__FILE__)}") 21 | status, headers, body = @app.call(env) 22 | buf1, buf2 = [], [] 23 | body.each { |part| buf1 << part } 24 | body.each { |part| buf2 << part } 25 | assert_equal buf1.join, buf2.join 26 | assert_equal File.read(__FILE__), buf1.join 27 | end 28 | 29 | it 'serves HEAD requests for files in the public directory' do 30 | head "/#{File.basename(__FILE__)}" 31 | assert ok? 32 | assert_equal '', body 33 | assert_equal File.size(__FILE__).to_s, response['Content-Length'] 34 | assert response.headers.include?('Last-Modified') 35 | end 36 | 37 | %w[POST PUT DELETE].each do |verb| 38 | it "does not serve #{verb} requests" do 39 | send verb.downcase, "/#{File.basename(__FILE__)}" 40 | assert_equal 404, status 41 | end 42 | end 43 | 44 | it 'serves files in preference to custom routes' do 45 | @app.get("/#{File.basename(__FILE__)}") { 'Hello World' } 46 | get "/#{File.basename(__FILE__)}" 47 | assert ok? 48 | assert body != 'Hello World' 49 | end 50 | 51 | it 'does not serve directories' do 52 | get "/" 53 | assert not_found? 54 | end 55 | 56 | it 'passes to the next handler when the static option is disabled' do 57 | @app.set :static, false 58 | get "/#{File.basename(__FILE__)}" 59 | assert not_found? 60 | end 61 | 62 | it 'passes to the next handler when the public option is nil' do 63 | @app.set :public, nil 64 | get "/#{File.basename(__FILE__)}" 65 | assert not_found? 66 | end 67 | 68 | it '404s when a file is not found' do 69 | get "/foobarbaz.txt" 70 | assert not_found? 71 | end 72 | 73 | it 'serves files when .. path traverses within public directory' do 74 | get "/data/../#{File.basename(__FILE__)}" 75 | assert ok? 76 | assert_equal File.read(__FILE__), body 77 | end 78 | 79 | it '404s when .. path traverses outside of public directory' do 80 | mock_app { 81 | set :static, true 82 | set :public, File.dirname(__FILE__) + '/data' 83 | } 84 | get "/../#{File.basename(__FILE__)}" 85 | assert not_found? 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /public/_utils/script/test/attachment_views.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.attachment_views= function(debug) { 14 | 15 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 16 | db.deleteDb(); 17 | db.createDb(); 18 | if (debug) debugger; 19 | 20 | // count attachments in a view 21 | 22 | db.bulkSave(makeDocs(0, 10)); 23 | 24 | db.bulkSave(makeDocs(10, 20, { 25 | _attachments:{ 26 | "foo.txt": { 27 | content_type:"text/plain", 28 | data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" 29 | } 30 | } 31 | })); 32 | 33 | db.bulkSave(makeDocs(20, 30, { 34 | _attachments:{ 35 | "foo.txt": { 36 | content_type:"text/plain", 37 | data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" 38 | }, 39 | "bar.txt": { 40 | content_type:"text/plain", 41 | data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" 42 | } 43 | } 44 | })); 45 | 46 | db.bulkSave(makeDocs(30, 40, { 47 | _attachments:{ 48 | "foo.txt": { 49 | content_type:"text/plain", 50 | data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" 51 | }, 52 | "bar.txt": { 53 | content_type:"text/plain", 54 | data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" 55 | }, 56 | "baz.txt": { 57 | content_type:"text/plain", 58 | data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" 59 | } 60 | } 61 | })); 62 | 63 | var mapFunction = function(doc) { 64 | var count = 0; 65 | 66 | for(var idx in doc._attachments) { 67 | count = count + 1; 68 | } 69 | 70 | emit(parseInt(doc._id), count); 71 | } 72 | 73 | var reduceFunction = function(key, values) { 74 | return sum(values); 75 | } 76 | 77 | var result = db.query(mapFunction, reduceFunction); 78 | 79 | T(result.rows.length == 1); 80 | T(result.rows[0].value == 60); 81 | 82 | var result = db.query(mapFunction, reduceFunction, { 83 | startkey:10, 84 | endkey:19 85 | }); 86 | 87 | T(result.rows.length == 1); 88 | T(result.rows[0].value == 10); 89 | 90 | var result = db.query(mapFunction, reduceFunction, { 91 | startkey:20, 92 | endkey:29 93 | }); 94 | 95 | T(result.rows.length == 1); 96 | T(result.rows[0].value == 20); 97 | 98 | }; 99 | -------------------------------------------------------------------------------- /public/_utils/script/test/multiple_rows.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.multiple_rows = function(debug) { 14 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 15 | db.deleteDb(); 16 | db.createDb(); 17 | if (debug) debugger; 18 | 19 | var nc = {_id:"NC", cities:["Charlotte", "Raleigh"]}; 20 | var ma = {_id:"MA", cities:["Boston", "Lowell", "Worcester", "Cambridge", "Springfield"]}; 21 | var fl = {_id:"FL", cities:["Miami", "Tampa", "Orlando", "Springfield"]}; 22 | 23 | T(db.save(nc).ok); 24 | T(db.save(ma).ok); 25 | T(db.save(fl).ok); 26 | 27 | var generateListOfCitiesAndState = "function(doc) {" + 28 | " for (var i = 0; i < doc.cities.length; i++)" + 29 | " emit(doc.cities[i] + \", \" + doc._id, null);" + 30 | "}"; 31 | 32 | var results = db.query(generateListOfCitiesAndState); 33 | var rows = results.rows; 34 | 35 | T(rows[0].key == "Boston, MA"); 36 | T(rows[1].key == "Cambridge, MA"); 37 | T(rows[2].key == "Charlotte, NC"); 38 | T(rows[3].key == "Lowell, MA"); 39 | T(rows[4].key == "Miami, FL"); 40 | T(rows[5].key == "Orlando, FL"); 41 | T(rows[6].key == "Raleigh, NC"); 42 | T(rows[7].key == "Springfield, FL"); 43 | T(rows[8].key == "Springfield, MA"); 44 | T(rows[9].key == "Tampa, FL"); 45 | T(rows[10].key == "Worcester, MA"); 46 | 47 | // add another city to NC 48 | nc.cities.push("Wilmington"); 49 | T(db.save(nc).ok); 50 | 51 | var results = db.query(generateListOfCitiesAndState); 52 | var rows = results.rows; 53 | 54 | T(rows[0].key == "Boston, MA"); 55 | T(rows[1].key == "Cambridge, MA"); 56 | T(rows[2].key == "Charlotte, NC"); 57 | T(rows[3].key == "Lowell, MA"); 58 | T(rows[4].key == "Miami, FL"); 59 | T(rows[5].key == "Orlando, FL"); 60 | T(rows[6].key == "Raleigh, NC"); 61 | T(rows[7].key == "Springfield, FL"); 62 | T(rows[8].key == "Springfield, MA"); 63 | T(rows[9].key == "Tampa, FL"); 64 | T(rows[10].key == "Wilmington, NC"); 65 | T(rows[11].key == "Worcester, MA"); 66 | 67 | // now delete MA 68 | T(db.deleteDoc(ma).ok); 69 | 70 | var results = db.query(generateListOfCitiesAndState); 71 | var rows = results.rows; 72 | 73 | T(rows[0].key == "Charlotte, NC"); 74 | T(rows[1].key == "Miami, FL"); 75 | T(rows[2].key == "Orlando, FL"); 76 | T(rows[3].key == "Raleigh, NC"); 77 | T(rows[4].key == "Springfield, FL"); 78 | T(rows[5].key == "Tampa, FL"); 79 | T(rows[6].key == "Wilmington, NC"); 80 | }; 81 | -------------------------------------------------------------------------------- /public/_utils/couch_tests.html: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | Test Suite 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 42 | 43 |
44 |

45 | Overview 46 | Test Suite 47 |

48 |
49 |
    50 |
  • 51 |
  • 52 |
  • 53 |
54 |

55 | Note: Each of the tests will block the browser. If the 56 | connection to your CouchDB server is slow, running the tests will take 57 | some time, and you'll not be able to do much with your browser while 58 | a test is being executed. 59 |

60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 |
Tests
NameStatusElapsed TimeDetails
79 | 80 |
81 |
82 | 83 | -------------------------------------------------------------------------------- /vendor/sinatra/test/extensions_test.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/helper' 2 | 3 | class ExtensionsTest < Test::Unit::TestCase 4 | module FooExtensions 5 | def foo 6 | end 7 | 8 | private 9 | def im_hiding_in_ur_foos 10 | end 11 | end 12 | 13 | module BarExtensions 14 | def bar 15 | end 16 | end 17 | 18 | module BazExtensions 19 | def baz 20 | end 21 | end 22 | 23 | module QuuxExtensions 24 | def quux 25 | end 26 | end 27 | 28 | module PainExtensions 29 | def foo=(name); end 30 | def bar?(name); end 31 | def fizz!(name); end 32 | end 33 | 34 | it 'will add the methods to the DSL for the class in which you register them and its subclasses' do 35 | Sinatra::Base.register FooExtensions 36 | assert Sinatra::Base.respond_to?(:foo) 37 | 38 | Sinatra::Default.register BarExtensions 39 | assert Sinatra::Default.respond_to?(:bar) 40 | assert Sinatra::Default.respond_to?(:foo) 41 | assert !Sinatra::Base.respond_to?(:bar) 42 | end 43 | 44 | it 'allows extending by passing a block' do 45 | Sinatra::Base.register { 46 | def im_in_ur_anonymous_module; end 47 | } 48 | assert Sinatra::Base.respond_to?(:im_in_ur_anonymous_module) 49 | end 50 | 51 | it 'will make sure any public methods added via Default#register are delegated to Sinatra::Delegator' do 52 | Sinatra::Default.register FooExtensions 53 | assert Sinatra::Delegator.private_instance_methods. 54 | map { |m| m.to_sym }.include?(:foo) 55 | assert !Sinatra::Delegator.private_instance_methods. 56 | map { |m| m.to_sym }.include?(:im_hiding_in_ur_foos) 57 | end 58 | 59 | it 'will handle special method names' do 60 | Sinatra::Default.register PainExtensions 61 | assert Sinatra::Delegator.private_instance_methods. 62 | map { |m| m.to_sym }.include?(:foo=) 63 | assert Sinatra::Delegator.private_instance_methods. 64 | map { |m| m.to_sym }.include?(:bar?) 65 | assert Sinatra::Delegator.private_instance_methods. 66 | map { |m| m.to_sym }.include?(:fizz!) 67 | end 68 | 69 | it 'will not delegate methods on Base#register' do 70 | Sinatra::Base.register QuuxExtensions 71 | assert !Sinatra::Delegator.private_instance_methods.include?("quux") 72 | end 73 | 74 | it 'will extend the Sinatra::Default application by default' do 75 | Sinatra.register BazExtensions 76 | assert !Sinatra::Base.respond_to?(:baz) 77 | assert Sinatra::Default.respond_to?(:baz) 78 | end 79 | 80 | module BizzleExtension 81 | def bizzle 82 | bizzle_option 83 | end 84 | 85 | def self.registered(base) 86 | fail "base should be BizzleApp" unless base == BizzleApp 87 | fail "base should have already extended BizzleExtension" unless base.respond_to?(:bizzle) 88 | base.set :bizzle_option, 'bizzle!' 89 | end 90 | end 91 | 92 | class BizzleApp < Sinatra::Base 93 | end 94 | 95 | it 'sends .registered to the extension module after extending the class' do 96 | BizzleApp.register BizzleExtension 97 | assert_equal 'bizzle!', BizzleApp.bizzle_option 98 | assert_equal 'bizzle!', BizzleApp.bizzle 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Booth. In-Memory Ruby CouchDB 2 | 3 | Booth is version 0.2. Booth is pre-alpha software right now. The latest milestone reached is support for most Futon database and document operations. 4 | 5 | ## The Test Suite 6 | 7 | Booth is an attempt to get the [CouchDB Test Suite](http://127.0.0.1:5984/_utils/couch_tests.html) to pass against an alternate implementation. 8 | 9 | This should hella easy because the CouchDB Test Suite just runs in the browser. 10 | 11 | ## Protocol 12 | 13 | We think the CouchDB runtime can be implemented with varying levels of support. 14 | 15 | Level 1 would be a basic key value REST API. This is shared in common with a set of existing tools. 16 | 17 | Level 2 includes Map Reduce views. 18 | 19 | Level 3 is support for replication with CouchDB instances. 20 | 21 | Level 4 is support for CouchDB's JavaScript server runtime environment and APIs. 22 | 23 | ## Booth 24 | 25 | Booth is an implementation of CouchDB in Ruby. It's meant as an illustration of CouchDB as a protocol. 26 | 27 | Q: CouchDB uses a robust append only B-Tree implementation for storage, what does Booth use for persistence? 28 | A: Nothing. Booth stores data in Ruby hashes and arrays. 29 | 30 | Q: Does it scale? 31 | A: Who the fuck cares? 32 | 33 | Q: How can I help? 34 | A: Start shouting about Booth on Twitter and I'll probably notice. 35 | 36 | Q: Why "Booth"? 37 | A: It's named after [Special Agent Seeley Booth](http://en.wikipedia.org/wiki/Seeley_Booth) from Bones. 38 | 39 | ## Get Started 40 | 41 | Install the following gems, if you haven't: 42 | 43 | gem install json 44 | gem install sinatra 45 | gem install uuid 46 | gem install cgi 47 | 48 | Booth is just a Sinatra server, so to start it, run: 49 | 50 | ruby lib/booth.rb 51 | 52 | Visit your Booth Futon's test suite page: [http://127.0.0.1:4567/_utils/couch_tests.html](http://127.0.0.1:4567/_utils/couch_tests.html) 53 | 54 | Or just play around in [Futon](http://127.0.0.1:4567/_utils/). 55 | 56 | ## Patches Very Welcome 57 | 58 | Fork. Pick a test, get to green. If you know CouchDB a little, you should be able to tell from it which tests will be easy to fix based on the ones that run. 59 | 60 | Some tests require commenting out -- there are certain features (for instance: keeping old revs around) that Booth doesn't bother with, as it is an in-memory store. 61 | 62 | Also -- incremental improvements help a lot. Even getting one more assertion to pass is a patch I'll merge. 63 | 64 | There is a Ruby RSpec suite you can run like this: 65 | 66 | spec spec/ 67 | 68 | But I prefer to run it with autospec 69 | 70 | sudo gem install ZenTest 71 | autospec 72 | 73 | ### Which Patches? 74 | 75 | Replication is not implemented yet. It'll be cool when it is b/c the replicator will work with both Booth and CouchDB endpoints. (You can even replicate between 2 real Couches with the Booth replicator.) 76 | 77 | Show and List are almost hooked up. They need the httpd portion - the query server can basically do the work already. 78 | 79 | Temp views work but Design Doc views aren't implemented yet, although the groundwork is laid. This would be an ambitious patch but not too invasive, if you are feeling like a hackathon. 80 | 81 | Also we need a way to echo chunks to the client instead of buffering. Maybe drop to raw Rack. 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /public/_utils/script/test/attachment_names.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.attachment_names = function(debug) { 14 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 15 | db.deleteDb(); 16 | db.createDb(); 17 | if (debug) debugger; 18 | 19 | // punting on unicode name checks for now 20 | // var binAttDoc = { 21 | // _id: "bin_doc", 22 | // _attachments:{ 23 | // "foo\x80txt": { 24 | // content_type:"text/plain", 25 | // data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" 26 | // } 27 | // } 28 | // } 29 | // 30 | // // inline attachments 31 | // try { 32 | // db.save(binAttDoc); 33 | // TEquals(1, 2, "Attachment name with non UTF-8 encoding saved. Should never show!"); 34 | // } catch (e) { 35 | // TEquals("bad_request", e.error, "attachment_name: inline attachments"); 36 | // TEquals("Attachment name is not UTF-8 encoded", e.reason, "attachment_name: inline attachments"); 37 | // } 38 | // 39 | // 40 | // // standalone docs 41 | // var bin_data = "JHAPDO*AU£PN ){(3u[d 93DQ9¡€])} ææøo'∂ƒæ≤çæππ•¥∫¶®#†π¶®¥π€ª®˙π8np"; 42 | // 43 | // var xhr = (CouchDB.request("PUT", "/test_suite_db/bin_doc3/attachment\x80txt", { 44 | // headers:{"Content-Type":"text/plain;charset=utf-8"}, 45 | // body:bin_data 46 | // })); 47 | // 48 | // var resp = JSON.parse(xhr.responseText); 49 | // TEquals(400, xhr.status, "attachment_name: standalone API"); 50 | // TEquals("bad_request", resp.error, "attachment_name: standalone API"); 51 | // TEquals("Attachment name is not UTF-8 encoded", resp.reason, "attachment_name: standalone API"); 52 | // 53 | // 54 | // // bulk docs 55 | // var docs = { docs: [binAttDoc] }; 56 | // 57 | // var xhr = CouchDB.request("POST", "/test_suite_db/_bulk_docs", { 58 | // body: JSON.stringify(docs) 59 | // }); 60 | // 61 | // var resp = JSON.parse(xhr.responseText); 62 | // TEquals(400, xhr.status, "attachment_name: bulk docs"); 63 | // TEquals("bad_request", resp.error, "attachment_name: bulk docs"); 64 | // TEquals("Attachment name is not UTF-8 encoded", resp.reason, "attachment_name: bulk docs"); 65 | 66 | 67 | // leading underscores 68 | var binAttDoc = { 69 | _id: "bin_doc2", 70 | _attachments:{ 71 | "_foo.txt": { 72 | content_type:"text/plain", 73 | data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" 74 | } 75 | } 76 | } 77 | 78 | try { 79 | db.save(binAttDoc); 80 | TEquals(1, 2, "Attachment name with leading underscore saved. Should never show!"); 81 | } catch (e) { 82 | TEquals("bad_request", e.error, "attachment_name: leading underscore"); 83 | TEquals("Attachment name can't start with '_'", e.reason, "attachment_name: leading underscore"); 84 | } 85 | 86 | // todo: form uploads, waiting for cmlenz' test case for form uploads 87 | 88 | }; 89 | -------------------------------------------------------------------------------- /public/_utils/script/test/all_docs.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.all_docs = function(debug) { 14 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 15 | db.deleteDb(); 16 | db.createDb(); 17 | if (debug) debugger; 18 | 19 | // Create some more documents. 20 | // Notice the use of the ok member on the return result. 21 | T(db.save({_id:"0",a:1,b:1}).ok); 22 | T(db.save({_id:"3",a:4,b:16}).ok); 23 | T(db.save({_id:"1",a:2,b:4}).ok); 24 | T(db.save({_id:"2",a:3,b:9}).ok); 25 | 26 | // Check the all docs 27 | var results = db.allDocs(); 28 | var rows = results.rows; 29 | 30 | T(results.total_rows == results.rows.length); 31 | 32 | for(var i=0; i < rows.length; i++) { 33 | T(rows[i].id >= "0" && rows[i].id <= "4"); 34 | } 35 | 36 | // Check _all_docs with descending=true 37 | var desc = db.allDocs({descending:true}); 38 | T(desc.total_rows == desc.rows.length); 39 | 40 | // Check _all_docs offset 41 | // var all = db.allDocs({startkey:"2"}); 42 | // T(all.offset == 2); 43 | // Booth doesn't have offset yet. 44 | 45 | // check that the docs show up in the seq view in the order they were created 46 | var changes = db.changes(); 47 | var ids = ["0","3","1","2"]; 48 | for (var i=0; i < changes.results.length; i++) { 49 | var row = changes.results[i]; 50 | T(row.id == ids[i], "seq order"); 51 | }; 52 | 53 | // it should work in reverse as well 54 | changes = db.changes({descending:true}); 55 | ids = ["2","1","3","0"]; 56 | for (var i=0; i < changes.results.length; i++) { 57 | var row = changes.results[i]; 58 | T(row.id == ids[i], "descending=true"); 59 | }; 60 | 61 | // check that deletions also show up right 62 | var doc1 = db.open("1"); 63 | var deleted = db.deleteDoc(doc1); 64 | T(deleted.ok); 65 | changes = db.changes(); 66 | // the deletion should make doc id 1 have the last seq num 67 | T(changes.results.length == 4); 68 | T(changes.results[3].id == "1"); 69 | T(changes.results[3].deleted); 70 | 71 | // do an update 72 | var doc2 = db.open("3"); 73 | doc2.updated = "totally"; 74 | db.save(doc2); 75 | changes = db.changes(); 76 | 77 | // the update should make doc id 3 have the last seq num 78 | T(changes.results.length == 4); 79 | T(changes.results[3].id == "3"); 80 | 81 | // ok now lets see what happens with include docs 82 | changes = db.changes({include_docs: true}); 83 | T(changes.results.length == 4); 84 | T(changes.results[3].id == "3"); 85 | T(changes.results[3].doc.updated == "totally"); 86 | 87 | T(changes.results[2].doc); 88 | T(changes.results[2].doc._deleted); 89 | 90 | // test the all docs collates sanely 91 | db.save({_id: "Z", foo: "Z"}); 92 | db.save({_id: "a", foo: "a"}); 93 | // console.log(db.allDocs({startkey: "A"}).rows); 94 | var rows = db.allDocs({startkey: "Z", endkey: "Z "}).rows; 95 | T(rows.length == 1); 96 | }; 97 | -------------------------------------------------------------------------------- /public/_utils/script/test/purge.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.purge = function(debug) { 14 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 15 | db.deleteDb(); 16 | db.createDb(); 17 | if (debug) debugger; 18 | 19 | /* 20 | purge is not to be confused with a document deletion. It removes the 21 | document and all edit history from the local instance of the database. 22 | */ 23 | 24 | var numDocs = 10; 25 | 26 | var designDoc = { 27 | _id:"_design/test", 28 | language: "javascript", 29 | views: { 30 | all_docs_twice: {map: "function(doc) { emit(doc.integer, null); emit(doc.integer, null) }"}, 31 | single_doc: {map: "function(doc) { if (doc._id == \"1\") { emit(1, null) }}"} 32 | } 33 | } 34 | 35 | T(db.save(designDoc).ok); 36 | 37 | db.bulkSave(makeDocs(1, numDocs + 1)); 38 | 39 | // go ahead and validate the views before purging 40 | var rows = db.view("test/all_docs_twice").rows; 41 | for (var i = 0; i < numDocs; i++) { 42 | T(rows[2*i].key == i+1); 43 | T(rows[(2*i)+1].key == i+1); 44 | } 45 | T(db.view("test/single_doc").total_rows == 1); 46 | 47 | var info = db.info(); 48 | var doc1 = db.open("1"); 49 | var doc2 = db.open("2"); 50 | 51 | // purge the documents 52 | var xhr = CouchDB.request("POST", "/test_suite_db/_purge", { 53 | body: JSON.stringify({"1":[doc1._rev], "2":[doc2._rev]}), 54 | }); 55 | T(xhr.status == 200); 56 | 57 | var newInfo = db.info(); 58 | // purging increments the update sequence 59 | T(info.update_seq+1 == newInfo.update_seq); 60 | // and it increments the purge_seq 61 | T(info.purge_seq+1 == newInfo.purge_seq); 62 | 63 | var result = JSON.parse(xhr.responseText); 64 | T(result.purged["1"][0] == doc1._rev); 65 | T(result.purged["2"][0] == doc2._rev); 66 | 67 | T(db.open("1") == null); 68 | T(db.open("2") == null); 69 | 70 | var rows = db.view("test/all_docs_twice").rows; 71 | for (var i = 2; i < numDocs; i++) { 72 | T(rows[2*(i-2)].key == i+1); 73 | T(rows[(2*(i-2))+1].key == i+1); 74 | } 75 | T(db.view("test/single_doc").total_rows == 0); 76 | 77 | // purge documents twice in a row without loading views 78 | // (causes full view rebuilds) 79 | 80 | var doc3 = db.open("3"); 81 | var doc4 = db.open("4"); 82 | 83 | xhr = CouchDB.request("POST", "/test_suite_db/_purge", { 84 | body: JSON.stringify({"3":[doc3._rev]}), 85 | }); 86 | 87 | T(xhr.status == 200); 88 | 89 | xhr = CouchDB.request("POST", "/test_suite_db/_purge", { 90 | body: JSON.stringify({"4":[doc4._rev]}), 91 | }); 92 | 93 | T(xhr.status == 200); 94 | 95 | var rows = db.view("test/all_docs_twice").rows; 96 | for (var i = 4; i < numDocs; i++) { 97 | T(rows[2*(i-4)].key == i+1); 98 | T(rows[(2*(i-4))+1].key == i+1); 99 | } 100 | T(db.view("test/single_doc").total_rows == 0); 101 | }; 102 | -------------------------------------------------------------------------------- /public/_utils/index.html: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | Overview 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 51 | 52 | 53 |
54 |

Overview

55 |
56 |
    57 |
  • 58 |
59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 87 | 88 | 89 |
Databases
NameSizeNumber of DocumentsUpdate Seq
75 |
76 | | 77 | | 83 | 84 |
85 | 86 |
90 |
91 | 92 |
93 | 94 | 95 | -------------------------------------------------------------------------------- /public/_utils/script/jquery.resizer.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | (function($) { 14 | 15 | $.fn.makeResizable = function(options) { 16 | options = options || {}; 17 | options.always = options.always || false; 18 | options.grippie = options.grippie || null; 19 | options.horizontal = options.horizontal || false; 20 | options.minWidth = options.minWidth || 100; 21 | options.maxWidth = options.maxWidth || null; 22 | options.vertical = options.vertical || false; 23 | options.minHeight = options.minHeight || 32; 24 | options.maxHeight = options.maxHeight || null; 25 | 26 | return this.each(function() { 27 | if ($(this).is("textarea") && !options.always && 28 | $.browser.safari && parseInt($.browser.version) >= 522) 29 | return this; // safari3 and later provides textarea resizing natively 30 | 31 | var grippie = options.grippie; 32 | if (!grippie) grippie = $("
").appendTo(this.parentNode); 33 | grippie.addClass("grippie"); 34 | if (options.horizontal && options.vertical) { 35 | grippie.css("cursor", "nwse-resize"); 36 | } else if (options.horizontal) { 37 | grippie.css("cursor", "col-resize"); 38 | } else if (options.vertical) { 39 | grippie.css("cursor", "row-resize"); 40 | } 41 | 42 | var elem = $(this); 43 | grippie.mousedown(function(e) { 44 | var pos = {x: e.screenX, y: e.screenY}; 45 | var dimensions = {width: elem.width(), height: elem.height()}; 46 | $(document) 47 | .mousemove(function(e) { 48 | if (options.horizontal) { 49 | var offset = e.screenX - pos.x; 50 | if (offset) { 51 | var newWidth = dimensions.width + offset; 52 | if (newWidth >= options.minWidth && 53 | (!options.maxWidth || newWidth <= options.maxWidth)) { 54 | elem.width(newWidth); 55 | dimensions.width = newWidth; 56 | } 57 | pos.x = e.screenX; 58 | } 59 | } 60 | if (options.vertical) { 61 | var offset = e.screenY - pos.y; 62 | if (offset) { 63 | var newHeight = dimensions.height + offset; 64 | if (newHeight >= options.minHeight && 65 | (!options.maxHeight || newHeight <= options.maxHeight)) { 66 | elem.height(newHeight); 67 | dimensions.height = newHeight; 68 | } 69 | pos.y = e.screenY; 70 | } 71 | } 72 | document.onselectstart = function() { return false }; // for IE 73 | return false; 74 | }) 75 | .one("mouseup", function() { 76 | $(document).unbind("mousemove"); 77 | document.onselectstart = null; // for IE 78 | }); 79 | return true; 80 | }); 81 | }); 82 | } 83 | 84 | })(jQuery); 85 | -------------------------------------------------------------------------------- /public/_utils/script/test/rev_stemming.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.rev_stemming = function(debug) { 14 | var db = new CouchDB("test_suite_db_a", {"X-Couch-Full-Commit":"false"}); 15 | db.deleteDb(); 16 | db.createDb(); 17 | var dbB = new CouchDB("test_suite_db_b", {"X-Couch-Full-Commit":"false"}); 18 | dbB.deleteDb(); 19 | dbB.createDb(); 20 | if (debug) debugger; 21 | 22 | var newLimit = 5; 23 | 24 | T(db.getDbProperty("_revs_limit") == 1000); 25 | 26 | var doc = {_id:"foo",foo:0} 27 | for( var i=0; i < newLimit + 1; i++) { 28 | doc.foo++; 29 | T(db.save(doc).ok); 30 | } 31 | var doc0 = db.open("foo", {revs:true}); 32 | T(doc0._revisions.ids.length == newLimit + 1); 33 | 34 | var docBar = {_id:"bar",foo:0} 35 | for( var i=0; i < newLimit + 1; i++) { 36 | docBar.foo++; 37 | T(db.save(docBar).ok); 38 | } 39 | T(db.open("bar", {revs:true})._revisions.ids.length == newLimit + 1); 40 | 41 | T(db.setDbProperty("_revs_limit", newLimit).ok); 42 | 43 | for( var i=0; i < newLimit + 1; i++) { 44 | doc.foo++; 45 | T(db.save(doc).ok); 46 | } 47 | doc0 = db.open("foo", {revs:true}); 48 | T(doc0._revisions.ids.length == newLimit); 49 | 50 | 51 | // If you replicate after you make more edits than the limit, you'll 52 | // cause a spurious edit conflict. 53 | CouchDB.replicate("test_suite_db_a", "test_suite_db_b"); 54 | var docB1 = dbB.open("foo",{conflicts:true}) 55 | T(docB1._conflicts == null); 56 | 57 | for( var i=0; i < newLimit - 1; i++) { 58 | doc.foo++; 59 | T(db.save(doc).ok); 60 | } 61 | 62 | // one less edit than limit, no conflict 63 | CouchDB.replicate("test_suite_db_a", "test_suite_db_b"); 64 | var docB1 = dbB.open("foo",{conflicts:true}) 65 | T(docB1._conflicts == null); 66 | 67 | //now we hit the limit 68 | for( var i=0; i < newLimit; i++) { 69 | doc.foo++; 70 | T(db.save(doc).ok); 71 | } 72 | 73 | CouchDB.replicate("test_suite_db_a", "test_suite_db_b"); 74 | 75 | var docB2 = dbB.open("foo",{conflicts:true}); 76 | 77 | // we have a conflict, but the previous replicated rev is always the losing 78 | // conflict 79 | T(docB2._conflicts[0] == docB1._rev) 80 | 81 | // We having already updated bar before setting the limit, so it's still got 82 | // a long rev history. compact to stem the revs. 83 | 84 | T(db.open("bar", {revs:true})._revisions.ids.length == newLimit + 1); 85 | 86 | T(db.compact().ok); 87 | 88 | // compaction isn't instantaneous, loop until done 89 | while (db.info().compact_running) {}; 90 | 91 | // force reload because ETags don't honour compaction 92 | var req = db.request("GET", "/test_suite_db_a/bar?revs=true", { 93 | headers:{"if-none-match":"pommes"} 94 | }); 95 | 96 | var finalDoc = JSON.parse(req.responseText); 97 | TEquals(newLimit, finalDoc._revisions.ids.length, 98 | "should return a truncated revision list"); 99 | }; 100 | -------------------------------------------------------------------------------- /public/_utils/script/test/bulk_docs.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.bulk_docs = function(debug) { 14 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 15 | db.deleteDb(); 16 | db.createDb(); 17 | if (debug) debugger; 18 | 19 | var docs = makeDocs(5); 20 | 21 | // Create the docs 22 | var results = db.bulkSave(docs); 23 | 24 | T(results.length == 5); 25 | for (var i = 0; i < 5; i++) { 26 | T(results[i].id == docs[i]._id); 27 | T(results[i].rev); 28 | // Update the doc 29 | docs[i].string = docs[i].string + ".00"; 30 | } 31 | 32 | // Save the docs 33 | results = db.bulkSave(docs); 34 | T(results.length == 5); 35 | for (i = 0; i < 5; i++) { 36 | T(results[i].id == i.toString()); 37 | 38 | // set the delete flag to delete the docs in the next step 39 | docs[i]._deleted = true; 40 | } 41 | 42 | // now test a bulk update with a conflict 43 | // open and save 44 | var doc = db.open("0"); 45 | db.save(doc); 46 | 47 | // Now bulk delete the docs 48 | results = db.bulkSave(docs); 49 | 50 | // doc "0" should be a conflict 51 | T(results.length == 5); 52 | T(results[0].id == "0"); 53 | T(results[0].error == "conflict"); 54 | T(results[0].rev === undefined); // no rev member when a conflict 55 | 56 | // but the rest are not 57 | for (i = 1; i < 5; i++) { 58 | T(results[i].id == i.toString()); 59 | T(results[i].rev) 60 | T(db.open(docs[i]._id) == null); 61 | } 62 | 63 | // now force a conflict to to save 64 | 65 | // save doc 0, this will cause a conflict when we save docs[0] 66 | var doc = db.open("0"); 67 | docs[0] = db.open("0") 68 | db.save(doc); 69 | 70 | docs[0].shooby = "dooby"; 71 | 72 | // Now save the bulk docs, When we use all_or_nothing, we don't get conflict 73 | // checking, all docs are saved regardless of conflict status, or none are 74 | // saved. 75 | results = db.bulkSave(docs,{all_or_nothing:true}); 76 | T(results.error === undefined); 77 | 78 | var doc = db.open("0", {conflicts:true}); 79 | var docConflict = db.open("0", {rev:doc._conflicts[0]}); 80 | 81 | T(doc.shooby == "dooby" || docConflict.shooby == "dooby"); 82 | 83 | // verify creating a document with no id returns a new id 84 | var req = CouchDB.request("POST", "/test_suite_db/_bulk_docs", { 85 | body: JSON.stringify({"docs": [{"foo":"bar"}]}) 86 | }); 87 | results = JSON.parse(req.responseText); 88 | 89 | T(results[0].id != ""); 90 | T(results[0].rev != ""); 91 | 92 | 93 | // Regression test for failure on update/delete 94 | var newdoc = {"_id": "foobar", "body": "baz"}; 95 | T(db.save(newdoc).ok); 96 | update = {"_id": newdoc._id, "_rev": newdoc._rev, "body": "blam"}; 97 | torem = {"_id": newdoc._id, "_rev": newdoc._rev, "_deleted": true}; 98 | results = db.bulkSave([update, torem]); 99 | T(results[0].error == "conflict" || results[1].error == "conflict"); 100 | }; 101 | -------------------------------------------------------------------------------- /lib/httpd/db.rb: -------------------------------------------------------------------------------- 1 | 2 | # a magic block that gets your db for you. 3 | def with_db db 4 | if Booth[db] 5 | yield Booth[db] 6 | else 7 | je(404, "not_found", "No database: #{db}") 8 | end 9 | end 10 | 11 | # for futon to list all dbs 12 | get "/_all_dbs/?" do 13 | a=[] 14 | Booth.each do |k, v| 15 | a << k 16 | end 17 | j(200, a) 18 | end 19 | 20 | # create a database 21 | put "/:db/?" do 22 | db = params[:db] 23 | if Booth[db] 24 | je(412, "db_exists", "The database already exists.") 25 | else 26 | Booth[db] = Database.new 27 | j(201, {"ok" => true}, {"Location" => "/#{CGI.escape(db)}"}) 28 | end 29 | end 30 | 31 | # get database info 32 | get "/:db/?" do 33 | with_db(params[:db]) do |db| 34 | j(200, { 35 | :db_name => params[:db], 36 | :doc_count => db.doc_count, 37 | :disk_size => (db.doc_count * 339.2) 38 | }) 39 | end 40 | end 41 | 42 | # delete a database 43 | delete "/:db/?" do 44 | db = params[:db] 45 | if Booth[db] 46 | Booth.delete(db) 47 | j(200, {"ok" => true}) 48 | else 49 | je(404, "not_found", "No database: #{db}") 50 | end 51 | end 52 | 53 | # upload docs in batch 54 | post "/:db/_bulk_docs" do 55 | with_db(params[:db]) do |db| 56 | j = jbody 57 | docs = j["docs"] 58 | # handle replication requests 59 | params[:all_or_nothing] = "true" if j["all_or_nothing"] 60 | results = docs.collect do |doc| 61 | if !doc["_id"] 62 | doc["_id"] = BOOTH_UUID.generate 63 | end 64 | begin 65 | db.put(doc, params) 66 | rescue BoothError => e 67 | e 68 | end 69 | end 70 | j(200, results) 71 | end 72 | end 73 | 74 | # view of all docs in the database 75 | get "/:db/_all_docs" do 76 | with_db(params[:db]) do |db| 77 | rows = [] 78 | db.all_docs(View.view_params(params)) do |docid, doc| 79 | rows << { 80 | "id" => docid, 81 | "key" => docid, 82 | "value" => { 83 | "rev" => doc.rev 84 | } 85 | } 86 | end 87 | j(200, {"rows" => rows,"total_rows" => db.doc_count}) 88 | end 89 | end 90 | # you can post query definitions to the all docs view as well 91 | post "/:db/_all_docs" do 92 | with_db(params[:db]) do |db| 93 | query = jbody 94 | unless query["keys"].is_a?(Array) 95 | raise BoothError.new(400, "bad_request", "`keys` member must be a array."); 96 | end 97 | end 98 | end 99 | 100 | 101 | # Feed of changes to the database: 102 | # Choose your own adventure. 103 | # 104 | # This changes feed is not yet continuous 105 | # I'm sure it'd involve some completely sane 106 | # Ruby process management. It might be a good hack 107 | # for someone who's into that sort of thing. 108 | # 109 | # Not a bad place to start for techniques: 110 | # http://tomayko.com/writings/unicorn-is-unix 111 | # 112 | get "/:db/_changes" do 113 | with_db(params[:db]) do |db| 114 | rows = [] 115 | db.by_seq(params) do |seq, doc| 116 | r = { 117 | "id" => doc.id, 118 | "seq" => seq, 119 | "changes" => [{ 120 | "rev" => doc.rev 121 | }] 122 | } 123 | if params[:include_docs] == "true" 124 | r["doc"] = doc.jh 125 | end 126 | r["deleted"] = true if doc.deleted 127 | rows << r 128 | end 129 | # if params[:feed] == "continuous" 130 | # j(200, {"results" => rows,"total_rows" => db.doc_count,"last_seq" => db.seq}) 131 | changes rows 132 | end 133 | end 134 | 135 | 136 | -------------------------------------------------------------------------------- /lib/httpd/doc.rb: -------------------------------------------------------------------------------- 1 | # document access 2 | 3 | get "/:db/:docid/?" do 4 | docid = params[:docid] 5 | with_db(params[:db]) do |db| 6 | doc = db.get(docid, params) 7 | # etags here needs a test to make me uncomment it 8 | # etag(doc.etag) 9 | if doc 10 | j(200, doc.jh(params)) 11 | else 12 | je(404, 'not_found', "No doc with id: #{docid}") 13 | end 14 | end 15 | end 16 | 17 | # create a doc without an id 18 | post "/:db/?" do 19 | with_db(params[:db]) do |db| 20 | doc = jbody("Document must be a JSON object") 21 | if !doc["_id"] 22 | doc["_id"] = BOOTH_UUID.generate 23 | end 24 | resp = db.put(doc) 25 | resp["ok"] = true 26 | j(201, resp, { 27 | "Location" => ["",params[:db],doc["_id"]].join('/') 28 | }) 29 | end 30 | end 31 | 32 | # update a document or create with an id 33 | put "/:db/:docid/?" do 34 | docid = params[:docid] 35 | with_db(params[:db]) do |db| 36 | doc = jbody("Document must be a JSON object") 37 | 38 | # here's the action 39 | doc["_id"] = docid 40 | resp = db.put(doc) 41 | 42 | # build the response (rev etc) 43 | resp["ok"] = true 44 | j(201, resp, { 45 | "Location" => ["",params[:db],docid].join('/') 46 | }) 47 | end 48 | end 49 | 50 | # delete a document 51 | delete "/:db/:docid/?" do 52 | docid = params[:docid] 53 | rev = params[:rev] 54 | with_db(params[:db]) do |db| 55 | doc = db.get(docid) 56 | if doc 57 | new_rev = db.delete(docid, rev) 58 | j(200, {"ok" => true, :id => docid, :rev => new_rev}) 59 | else 60 | je(404, 'not_found', "No doc with id: #{docid}") 61 | end 62 | end 63 | end 64 | 65 | # attachment handling 66 | 67 | # handles slashes for design doc attachements 68 | def docid_att_name(params) 69 | if params[:docid] == "_design" 70 | ps = params[:splat][0].split('/') 71 | ["_design/#{ps.shift}", ps.join('/')] 72 | else 73 | [params[:docid], params[:splat][0]] 74 | end 75 | end 76 | 77 | # get an attachment 78 | get "/:db/:docid/*" do 79 | docid, att_name = docid_att_name(params) 80 | with_db(params[:db]) do |db| 81 | doc = db.get(docid) 82 | etag(doc.rev) 83 | att = doc.attachment(att_name) 84 | headers({ 85 | "content-type" => att["content_type"], 86 | "Etag" => doc.etag 87 | }) 88 | att["data"] 89 | end 90 | end 91 | 92 | # create or update an attachment on a doc 93 | put "/:db/:docid/*" do 94 | docid = params[:docid] 95 | rev = params[:rev] 96 | att_name = params[:splat][0] 97 | with_db(params[:db]) do |db| 98 | begin 99 | doc = db.get(docid) 100 | rescue BoothError 101 | db.put({"_id" => docid}) 102 | doc = db.get(docid) 103 | rev = doc.rev 104 | end 105 | # create the attachment format (this could be packaged) 106 | att = {} 107 | att["data"] = request.body.read 108 | att["length"] = att["data"].length 109 | att["content_type"] = @env["CONTENT_TYPE"] 110 | new_rev = doc.attachment_put(rev, att_name, att) 111 | headers("Location" => ["",params[:db],docid,att_name].join('/')) 112 | j(201, {"ok" => true, :id => docid, :rev => new_rev}) 113 | end 114 | end 115 | 116 | # delete a document 117 | delete "/:db/:docid/*" do 118 | docid = params[:docid] 119 | rev = params[:rev] 120 | att_name = params[:splat][0] 121 | with_db(params[:db]) do |db| 122 | doc = db.get(docid) 123 | new_rev = doc.attachment_put(rev, att_name, nil) 124 | # headers("Location" => ["",params[:db],docid,att_name].join('/')) 125 | j(200, {"ok" => true, :id => docid, :rev => new_rev}) 126 | end 127 | end -------------------------------------------------------------------------------- /public/_utils/script/test/etags_views.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.etags_views = function(debug) { 14 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 15 | db.deleteDb(); 16 | db.createDb(); 17 | if (debug) debugger; 18 | 19 | var designDoc = { 20 | _id:"_design/etags", 21 | language: "javascript", 22 | views : { 23 | basicView : { 24 | map : stringFun(function(doc) { 25 | emit(doc.integer, doc.string); 26 | }) 27 | }, 28 | withReduce : { 29 | map : stringFun(function(doc) { 30 | emit(doc.integer, doc.string); 31 | }), 32 | reduce : stringFun(function(keys, values, rereduce) { 33 | if (rereduce) { 34 | return sum(values); 35 | } else { 36 | return values.length; 37 | } 38 | }) 39 | } 40 | } 41 | } 42 | T(db.save(designDoc).ok); 43 | var xhr; 44 | var docs = makeDocs(0, 10); 45 | db.bulkSave(docs); 46 | 47 | // verify get w/Etag on map view 48 | xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/basicView"); 49 | T(xhr.status == 200); 50 | var etag = xhr.getResponseHeader("etag"); 51 | xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/basicView", { 52 | headers: {"if-none-match": etag} 53 | }); 54 | T(xhr.status == 304); 55 | // TODO GET with keys (when that is available) 56 | 57 | // reduce view 58 | xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/withReduce"); 59 | T(xhr.status == 200); 60 | var etag = xhr.getResponseHeader("etag"); 61 | xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/withReduce", { 62 | headers: {"if-none-match": etag} 63 | }); 64 | T(xhr.status == 304); 65 | 66 | // all docs 67 | xhr = CouchDB.request("GET", "/test_suite_db/_all_docs"); 68 | T(xhr.status == 200); 69 | var etag = xhr.getResponseHeader("etag"); 70 | xhr = CouchDB.request("GET", "/test_suite_db/_all_docs", { 71 | headers: {"if-none-match": etag} 72 | }); 73 | T(xhr.status == 304); 74 | 75 | // _changes 76 | xhr = CouchDB.request("GET", "/test_suite_db/_changes"); 77 | T(xhr.status == 200); 78 | var etag = xhr.getResponseHeader("etag"); 79 | xhr = CouchDB.request("GET", "/test_suite_db/_changes", { 80 | headers: {"if-none-match": etag} 81 | }); 82 | T(xhr.status == 304); 83 | 84 | // list etag 85 | // in the list test for now 86 | 87 | // A new database should have unique _all_docs etags. 88 | db.deleteDb(); 89 | db.createDb(); 90 | db.save({a: 1}); 91 | xhr = CouchDB.request("GET", "/test_suite_db/_all_docs"); 92 | var etag = xhr.getResponseHeader("etag"); 93 | db.deleteDb(); 94 | db.createDb(); 95 | db.save({a: 2}); 96 | xhr = CouchDB.request("GET", "/test_suite_db/_all_docs"); 97 | var new_etag = xhr.getResponseHeader("etag"); 98 | T(etag != new_etag); 99 | // but still be cacheable 100 | xhr = CouchDB.request("GET", "/test_suite_db/_all_docs"); 101 | T(new_etag == xhr.getResponseHeader("etag")); 102 | 103 | }; 104 | -------------------------------------------------------------------------------- /public/_utils/status.html: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | Status 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |

29 | Overview 30 | Status 31 |

32 |
33 |
34 | 38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
Active Tasks
TypeObjectPIDStatus
49 | 50 |
51 |
52 | 106 | 107 | -------------------------------------------------------------------------------- /public/_utils/script/test/view_offsets.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.view_offsets = function(debug) { 14 | if (debug) debugger; 15 | 16 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 17 | db.deleteDb(); 18 | db.createDb(); 19 | 20 | var designDoc = { 21 | _id : "_design/test", 22 | views : { 23 | offset : { 24 | map : "function(doc) { emit([doc.letter, doc.number], doc); }", 25 | } 26 | } 27 | }; 28 | T(db.save(designDoc).ok); 29 | 30 | var docs = [ 31 | {_id : "a1", letter : "a", number : 1, foo: "bar"}, 32 | {_id : "a2", letter : "a", number : 2, foo: "bar"}, 33 | {_id : "a3", letter : "a", number : 3, foo: "bar"}, 34 | {_id : "b1", letter : "b", number : 1, foo: "bar"}, 35 | {_id : "b2", letter : "b", number : 2, foo: "bar"}, 36 | {_id : "b3", letter : "b", number : 3, foo: "bar"}, 37 | {_id : "b4", letter : "b", number : 4, foo: "bar"}, 38 | {_id : "b5", letter : "b", number : 5, foo: "bar"}, 39 | {_id : "c1", letter : "c", number : 1, foo: "bar"}, 40 | {_id : "c2", letter : "c", number : 2, foo: "bar"}, 41 | ]; 42 | db.bulkSave(docs); 43 | 44 | var check = function(startkey, offset) { 45 | var opts = {startkey: startkey, descending: true}; 46 | T(db.view("test/offset", opts).offset == offset); 47 | }; 48 | 49 | [ 50 | [["c", 2], 0], 51 | [["c", 1], 1], 52 | [["b", 5], 2], 53 | [["b", 4], 3], 54 | [["b", 3], 4], 55 | [["b", 2], 5], 56 | [["b", 1], 6], 57 | [["a", 3], 7], 58 | [["a", 2], 8], 59 | [["a", 1], 9] 60 | ].forEach(function(row){ check(row[0], row[1]);}); 61 | 62 | var runTest = function () { 63 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 64 | db.deleteDb(); 65 | db.createDb(); 66 | 67 | var designDoc = { 68 | _id : "_design/test", 69 | views : { 70 | offset : { 71 | map : "function(doc) { emit([doc.letter, doc.number], doc);}", 72 | } 73 | } 74 | }; 75 | T(db.save(designDoc).ok); 76 | 77 | var docs = [ 78 | {_id : "a1", letter : "a", number : 1, foo : "bar"}, 79 | {_id : "a2", letter : "a", number : 2, foo : "bar"}, 80 | {_id : "a3", letter : "a", number : 3, foo : "bar"}, 81 | {_id : "b1", letter : "b", number : 1, foo : "bar"}, 82 | {_id : "b2", letter : "b", number : 2, foo : "bar"}, 83 | {_id : "b3", letter : "b", number : 3, foo : "bar"}, 84 | {_id : "b4", letter : "b", number : 4, foo : "bar"}, 85 | {_id : "b5", letter : "b", number : 5, foo : "bar"}, 86 | {_id : "c1", letter : "c", number : 1, foo : "bar"}, 87 | {_id : "c2", letter : "c", number : 2, foo : "bar"} 88 | ]; 89 | db.bulkSave(docs); 90 | 91 | var res1 = db.view("test/offset", { 92 | startkey: ["b",4], startkey_docid: "b4", endkey: ["b"], 93 | limit: 2, descending: true, skip: 1 94 | }) 95 | 96 | var res2 = db.view("test/offset", {startkey: ["c", 3]}); 97 | var res3 = db.view("test/offset", { 98 | startkey: ["b", 6], 99 | endkey: ["b", 7] 100 | }); 101 | 102 | return res1.offset == 4 && res2.offset == docs.length && res3.offset == 8; 103 | 104 | }; 105 | 106 | for(var i = 0; i < 15; i++) T(runTest()); 107 | } 108 | 109 | -------------------------------------------------------------------------------- /public/_utils/script/test/erlang_views.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.erlang_views = function(debug) { 14 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 15 | db.deleteDb(); 16 | db.createDb(); 17 | if (debug) debugger; 18 | 19 | 20 | 21 | run_on_modified_server( 22 | [{section: "native_query_servers", 23 | key: "erlang", 24 | value: "{couch_native_process, start_link, []}"}], 25 | function() { 26 | // Note we just do some basic 'smoke tests' here - the 27 | // test/query_server_spec.rb tests have more comprehensive tests 28 | var doc = {_id: "1", integer: 1, string: "str1", array: [1, 2, 3]}; 29 | T(db.save(doc).ok); 30 | 31 | var mfun = 'fun({Doc}) -> ' + 32 | ' K = proplists:get_value(<<"integer">>, Doc, null), ' + 33 | ' V = proplists:get_value(<<"string">>, Doc, null), ' + 34 | ' Emit(K, V) ' + 35 | 'end.'; 36 | 37 | // emitting a key value that is undefined should result in that row not 38 | // being included in the view results 39 | var results = db.query(mfun, null, null, null, "erlang"); 40 | T(results.total_rows == 1); 41 | T(results.rows[0].key == 1); 42 | T(results.rows[0].value == "str1"); 43 | 44 | // check simple reduction - another doc with same key. 45 | var doc = {_id: "2", integer: 1, string: "str2"}; 46 | T(db.save(doc).ok); 47 | rfun = "fun(Keys, Values, ReReduce) -> length(Values) end." 48 | results = db.query(mfun, rfun, null, null, "erlang"); 49 | T(results.rows[0].value == 2); 50 | 51 | // simple 'list' tests 52 | var designDoc = { 53 | _id:"_design/erlview", 54 | language: "erlang", 55 | shows: { 56 | simple: 57 | 'fun(Doc, {Req}) -> ' + 58 | ' {Info} = proplists:get_value(<<"info">>, Req, {[]}), ' + 59 | ' Purged = proplists:get_value(<<"purge_seq">>, Info, -1), ' + 60 | ' Verb = proplists:get_value(<<"verb">>, Req, <<"not_get">>), ' + 61 | ' R = list_to_binary(io_lib:format("~b - ~s", [Purged, Verb])), ' + 62 | ' {[{<<"code">>, 200}, {<<"headers">>, {[]}}, {<<"body">>, R}]} ' + 63 | 'end.' 64 | }, 65 | lists: { 66 | simple_list : 67 | 'fun(Head, {Req}) -> ' + 68 | ' Send(<<"head">>), ' + 69 | ' Fun = fun({Row}, _) -> ' + 70 | ' Val = proplists:get_value(<<"value">>, Row, -1), ' + 71 | ' Send(list_to_binary(integer_to_list(Val))), ' + 72 | ' {ok, nil} ' + 73 | ' end, ' + 74 | ' {ok, _} = FoldRows(Fun, nil), ' + 75 | ' <<"tail">> ' + 76 | 'end. ' 77 | }, 78 | views: { 79 | simple_view : { 80 | map: mfun, 81 | reduce: rfun 82 | } 83 | } 84 | }; 85 | T(db.save(designDoc).ok); 86 | 87 | var url = "/test_suite_db/_design/erlview/_show/simple/1"; 88 | var xhr = CouchDB.request("GET", url); 89 | T(xhr.status == 200, "standard get should be 200"); 90 | T(xhr.responseText == "0 - GET"); 91 | 92 | var url = "/test_suite_db/_design/erlview/_list/simple_list/simple_view"; 93 | var xhr = CouchDB.request("GET", url); 94 | T(xhr.status == 200, "standard get should be 200"); 95 | T(xhr.responseText == "head2tail"); 96 | }); 97 | }; 98 | -------------------------------------------------------------------------------- /lib/query/server/util.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | toJSON.subs = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', 14 | '\r': '\\r', '"' : '\\"', '\\': '\\\\'}; 15 | toJSON.dispatcher = { 16 | "Array": function(v) { 17 | var buf = []; 18 | for (var i = 0; i < v.length; i++) { 19 | buf.push(toJSON(v[i])); 20 | } 21 | return "[" + buf.join(",") + "]"; 22 | }, 23 | "Boolean": function(v) { 24 | return v.toString(); 25 | }, 26 | "Date": function(v) { 27 | var f = function(n) { return n < 10 ? '0' + n : n }; 28 | return '"' + v.getUTCFullYear() + '-' + 29 | f(v.getUTCMonth() + 1) + '-' + 30 | f(v.getUTCDate()) + 'T' + 31 | f(v.getUTCHours()) + ':' + 32 | f(v.getUTCMinutes()) + ':' + 33 | f(v.getUTCSeconds()) + 'Z"'; 34 | }, 35 | "Number": function(v) { 36 | return isFinite(v) ? v.toString() : "null"; 37 | }, 38 | "Object": function(v) { 39 | //if (v === null) return "null"; 40 | var buf = []; 41 | for (var k in v) { 42 | if (!v.hasOwnProperty(k) || typeof(k) !== "string" || v[k] === undefined) { 43 | continue; 44 | } 45 | buf.push(toJSON(k) + ": " + toJSON(v[k])); 46 | } 47 | return "{" + buf.join(",") + "}"; 48 | }, 49 | "String": function(v) { 50 | if (/["\\\x00-\x1f]/.test(v)) { 51 | v = v.replace(/([\x00-\x1f\\"])/g, function(a, b) { 52 | var c = toJSON.subs[b]; 53 | if (c) return c; 54 | c = b.charCodeAt(); 55 | return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16); 56 | }); 57 | } 58 | return '"' + v + '"'; 59 | } 60 | }; 61 | 62 | function toJSON(val) { 63 | if (typeof(val) == "undefined") { 64 | throw "Cannot encode 'undefined' value as JSON"; 65 | } 66 | if (typeof(val) == "xml") { // E4X support 67 | val = val.toXMLString(); 68 | } 69 | if (val === null) { return "null"; } 70 | return (toJSON.dispatcher[val.constructor.name])(val); 71 | } 72 | 73 | function compileFunction(source) { 74 | try { 75 | var functionObject = sandbox ? evalcx(source, sandbox) : eval(source); 76 | } catch (err) { 77 | throw {error: "compilation_error", 78 | reason: err.toString() + " (" + source + ")"}; 79 | } 80 | if (typeof(functionObject) == "function") { 81 | return functionObject; 82 | } else { 83 | throw {error: "compilation_error", 84 | reason: "expression does not eval to a function. (" + source + ")"}; 85 | } 86 | } 87 | 88 | function recursivelySeal(obj) { 89 | seal(obj); 90 | for (var propname in obj) { 91 | if (typeof doc[propname] == "object") { 92 | recursivelySeal(doc[propname]); 93 | } 94 | } 95 | } 96 | 97 | // prints the object as JSON, and rescues and logs any toJSON() related errors 98 | function respond(obj) { 99 | try { 100 | print(toJSON(obj)); 101 | } catch(e) { 102 | log("Error converting object to JSON: " + e.toString()); 103 | } 104 | }; 105 | 106 | log = function(message) { 107 | // return; 108 | if (typeof message == "undefined") { 109 | message = "Error: attempting to log message of 'undefined'."; 110 | } else if (typeof message != "string") { 111 | message = toJSON(message); 112 | } 113 | respond(["log", message]); 114 | }; 115 | -------------------------------------------------------------------------------- /spec/tree_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.expand_path(File.dirname(__FILE__)),"spec_helper"); 2 | 3 | describe "Tree" do 4 | before(:each) do 5 | @t = Tree.new 6 | keys = %w{g d b a f c e} 7 | keys.each do |x| 8 | @t[x] = x * 2; 9 | end 10 | end 11 | it "should return values" do 12 | @t["f"].should == "ff" 13 | end 14 | it "should do a keyscan" do 15 | a = []; 16 | @t.fold() do |k, v| 17 | a << k 18 | end 19 | a[0].should == "a" 20 | a.length.should == 7 21 | end 22 | it "should do a keyscan from a startkey" do 23 | a = []; 24 | @t.fold({ 25 | "startkey" => "c" 26 | }) do |k, v| 27 | a << k 28 | end 29 | a[0].should == "c" 30 | a[1].should == "d" 31 | a[2].should == "e" 32 | end 33 | it "should do a keyscan from a startkey to an endkey" do 34 | a = []; 35 | @t.fold({ 36 | "startkey" => "c", 37 | "endkey" => "d" 38 | }) do |k, v| 39 | a << k 40 | end 41 | a[0].should == "c" 42 | a[1].should == "d" 43 | a[2].should be_nil 44 | end 45 | it "should have inclusive_end=false" do 46 | a = []; 47 | @t.fold({ 48 | "startkey" => "c", 49 | "endkey" => "d", 50 | "inclusive_end" => "false" 51 | }) do |k, v| 52 | a << k 53 | end 54 | a[0].should == "c" 55 | a[1].should be_nil 56 | end 57 | it "should do a keyscan from startkey to endkey" do 58 | a = []; 59 | @t["Z"] = "foo" 60 | @t["D"] = "fox" 61 | @t.fold({ 62 | "startkey" => "B", 63 | "endkey" => "d" 64 | }) do |k, v| 65 | a << k 66 | end 67 | a[0].should == "D" 68 | a.last.should == "d" 69 | a.length.should == 6 70 | end 71 | it "should work with a late startkey" do 72 | @t["0"] = "x" 73 | @t["1"] = "x" 74 | @t["2"] = "x" 75 | a = [] 76 | @t.fold("startkey" => "c") do |k,v| 77 | a << k 78 | end 79 | a.should == ["c", "d", "e", "f", "g"] 80 | end 81 | it "should do a descending keyscan" do 82 | a = []; 83 | @t.fold("descending" => "true") do |k, v| 84 | a << k 85 | end 86 | a[0].should == "g" 87 | a[1].should == "f" 88 | end 89 | end 90 | 91 | describe "mixed-key tree" do 92 | it "should return proper key objects" do 93 | t = Tree.new do |a,b| 94 | if a.class == b.class 95 | a < b 96 | else 97 | a.class < b.class 98 | end 99 | end 100 | t[4] = "ok" 101 | t["b"] = "bee" 102 | r = [] 103 | t.fold do |key, value| 104 | r << key 105 | end 106 | r[0].should == 4 107 | r[1].should == "b" 108 | t["b"].should == "bee" 109 | end 110 | end 111 | describe "custom tree" do 112 | before(:each) do 113 | @t = Tree.new do |a,b| 114 | if a.class == b.class 115 | a < b 116 | else 117 | a.class < b.class 118 | end 119 | end 120 | keys = %w{g d b a f c e} 121 | keys.each do |x| 122 | @t[x] = x * 2; 123 | end 124 | end 125 | it "should collate properly" do 126 | @t[0] = "0" 127 | @t[3] = "3" 128 | @t["3"] = 3 129 | s = [] 130 | @t.fold do |k, v| 131 | s << k 132 | end 133 | s[0].should == "3" 134 | s.last.should == 3 135 | end 136 | end 137 | 138 | describe "gapped tree" do 139 | before(:each) do 140 | @t = Tree.new do |a, b| 141 | View.less_string(a,b) 142 | end 143 | keys = %w{0 3 2 a Z} 144 | keys.each do |x| 145 | @t[x] = "ok"; 146 | end 147 | end 148 | it "should do a keyscan" do 149 | a = []; 150 | @t.fold() do |k, v| 151 | a << k 152 | end 153 | a[0].should == "0" 154 | a[1].should == "2" 155 | a[4].should == "Z" 156 | a.length.should == 5 157 | end 158 | it "should scan from a startkey" do 159 | a = []; 160 | @t.fold({ 161 | "startkey" => "4" 162 | }) do |k, v| 163 | a << k 164 | end 165 | a[0].should == "a" 166 | a[1].should == "Z" 167 | end 168 | end -------------------------------------------------------------------------------- /public/_utils/custom_test.html: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | Custom Test 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 74 | 75 |
76 |

77 | Overview 78 | Test Suite 79 | Custom Test 80 |

81 | 82 |
83 |
84 |
85 | Test Function 86 |
87 | 88 | 101 |
89 | 100 |
102 |
103 | 104 |    105 |
106 |
107 |
108 |
109 | 110 | -------------------------------------------------------------------------------- /public/_utils/config.html: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | Configuration 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 75 | 76 |
77 |

78 | Overview 79 | Configuration 80 |

81 |
82 |

83 | Note: Some configuration options may require 84 | restarting the server to take effect after modification. 85 |

86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 |
Configuration
SectionOptionValue
96 | 97 |
98 |
99 | 100 | -------------------------------------------------------------------------------- /public/_utils/script/jquery.dialog.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | (function($) { 14 | 15 | $.fn.centerBox = function() { 16 | return this.each(function() { 17 | var s = this.style; 18 | s.left = (($(window).width() - $(this).width()) / 2) + "px"; 19 | s.top = (($(window).height() - $(this).height()) / 2) + "px"; 20 | }); 21 | } 22 | 23 | $.showDialog = function(url, options) { 24 | options = options || {}; 25 | options.load = options.load || function() {}; 26 | options.cancel = options.cancel || function() {}; 27 | options.validate = options.validate || function() { return true }; 28 | options.submit = options.submit || function() {}; 29 | 30 | var overlay = $('
') 31 | .css("opacity", "0"); 32 | var dialog = $(''); 33 | if ($.browser.msie) { 34 | var frame = $('') 35 | .css("opacity", "0").appendTo(document.body); 36 | if (parseInt($.browser.version)<7) { 37 | dialog.css("position", "absolute"); 38 | overlay.css("position", "absolute"); 39 | $("html,body").css({width: "100%", height: "100%"}); 40 | } 41 | } 42 | overlay.appendTo(document.body).fadeTo(100, 0.6); 43 | dialog.appendTo(document.body).addClass("loading").centerBox().fadeIn(400); 44 | 45 | $(document).keydown(function(e) { 46 | if (e.keyCode == 27) dismiss(); // dismiss on escape key 47 | }); 48 | function dismiss() { 49 | dialog.fadeOut("fast", function() { 50 | $("#dialog, #overlay, #overlay-frame").remove(); 51 | }); 52 | $(document).unbind("keydown"); 53 | } 54 | overlay.click(function() { dismiss(); }); 55 | 56 | function showError(name, message) { 57 | var input = dialog.find(":input[name=" + name + "]"); 58 | input.addClass("error").next("div.error").remove(); 59 | $('
').text(message).insertAfter(input); 60 | } 61 | 62 | $.get(url, function(html) { 63 | $(html).appendTo(dialog); 64 | dialog.removeClass("loading").addClass("loaded").centerBox().each(function() { 65 | options.load(dialog.children()[0]); 66 | $(":input:first", dialog).each(function() { this.focus() }); 67 | $("button.cancel", dialog).click(function() { // dismiss on cancel 68 | dismiss(); 69 | options.cancel(); 70 | }); 71 | $("form", dialog).submit(function(e) { // invoke callback on submit 72 | e.preventDefault(); 73 | dialog.find("div.error").remove().end().find(".error").removeClass("error"); 74 | var data = {}; 75 | $.each($("form :input", dialog).serializeArray(), function(i, field) { 76 | data[field.name] = field.value; 77 | }); 78 | $("form :file", dialog).each(function() { 79 | data[this.name] = this.value; // file inputs need special handling 80 | }); 81 | options.submit(data, function callback(errors) { 82 | if (errors == null || errors == {}) { 83 | dismiss(); 84 | } else { 85 | for (var name in errors) { 86 | showError(name, errors[name]); 87 | } 88 | } 89 | }); 90 | return false; 91 | }); 92 | }); 93 | }); 94 | } 95 | 96 | })(jQuery); 97 | -------------------------------------------------------------------------------- /public/_utils/script/test/uuids.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | couchTests.uuids = function(debug) { 14 | var etags = []; 15 | var testHashBustingHeaders = function(xhr) { 16 | T(xhr.getResponseHeader("Cache-Control").match(/no-cache/)); 17 | T(xhr.getResponseHeader("Pragma") == "no-cache"); 18 | 19 | var newetag = xhr.getResponseHeader("ETag"); 20 | T(etags.indexOf(newetag) < 0); 21 | etags[etags.length] = newetag; 22 | 23 | // Removing the time based tests as they break easily when 24 | // running CouchDB on a remote server in regards to the browser 25 | // running the Futon test suite. 26 | // 27 | //var currentTime = new Date(); 28 | //var expiresHeader = Date.parse(xhr.getResponseHeader("Expires")); 29 | //var dateHeader = Date.parse(xhr.getResponseHeader("Date")); 30 | 31 | //T(expiresHeader < currentTime); 32 | //T(currentTime - dateHeader < 3000); 33 | }; 34 | 35 | var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); 36 | db.deleteDb(); 37 | db.createDb(); 38 | if (debug) debugger; 39 | 40 | // a single UUID without an explicit count 41 | var xhr = CouchDB.request("GET", "/_uuids"); 42 | T(xhr.status == 200); 43 | var result = JSON.parse(xhr.responseText); 44 | T(result.uuids.length == 1); 45 | var first = result.uuids[0]; 46 | testHashBustingHeaders(xhr); 47 | 48 | // a single UUID with an explicit count 49 | xhr = CouchDB.request("GET", "/_uuids?count=1"); 50 | T(xhr.status == 200); 51 | result = JSON.parse(xhr.responseText); 52 | T(result.uuids.length == 1); 53 | var second = result.uuids[0]; 54 | T(first != second); 55 | 56 | // no collisions with 1,000 UUIDs 57 | xhr = CouchDB.request("GET", "/_uuids?count=1000"); 58 | T(xhr.status == 200); 59 | result = JSON.parse(xhr.responseText); 60 | T( result.uuids.length == 1000 ); 61 | var seen = {}; 62 | for(var i in result.uuids) { 63 | var id = result.uuids[i]; 64 | T(seen[id] === undefined); 65 | seen[id] = 1; 66 | } 67 | 68 | return "ok"; // details, details 69 | 70 | // ensure we return a 405 on POST 71 | xhr = CouchDB.request("POST", "/_uuids?count=1000"); 72 | T(xhr.status != 405); // this fix belongs in sinatra 73 | 74 | // Test sequential uuids 75 | var seq_testfun = function() { 76 | xhr = CouchDB.request("GET", "/_uuids?count=1000"); 77 | T(xhr.status == 200); 78 | result = JSON.parse(xhr.responseText); 79 | for(var i = 1; i < result.uuids.length; i++) { 80 | T(result.uuids[i].length == 32); 81 | T(result.uuids[i-1] < result.uuids[i], "Sequential uuids are ordered."); 82 | } 83 | }; 84 | 85 | run_on_modified_server([{ 86 | "section": "uuids", 87 | "key": "algorithm", 88 | "value": "sequential", 89 | }], 90 | seq_testfun 91 | ); 92 | 93 | // Test utc_random uuids 94 | var utc_testfun = function() { 95 | xhr = CouchDB.request("GET", "/_uuids?count=1000"); 96 | T(xhr.status == 200); 97 | result = JSON.parse(xhr.responseText); 98 | for(var i = 1; i < result.uuids.length; i++) { 99 | T(result.uuids[i].length == 32); 100 | var u1 = result.uuids[i-1].substr(0, 13); 101 | var u2 = result.uuids[i].substr(0, 13); 102 | T(u1 < u2, "UTC uuids are roughly ordered."); 103 | } 104 | }; 105 | 106 | run_on_modified_server([{ 107 | "section": "uuids", 108 | "key": "algorithm", 109 | "value": "utc_random" 110 | }], 111 | utc_testfun 112 | ); 113 | 114 | }; 115 | -------------------------------------------------------------------------------- /public/_utils/script/jquery.editinline.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | (function($) { 14 | 15 | function startEditing(elem, options) { 16 | var editable = $(elem); 17 | var origHtml = editable.html(); 18 | var origText = options.populate($.trim(editable.text())); 19 | 20 | if (!options.begin.apply(elem, [origText])) { 21 | return; 22 | } 23 | 24 | var input = options.createInput.apply(elem, [origText]) 25 | .addClass("editinline").val(origText) 26 | .dblclick(function() { return false; }) 27 | .keydown(function(evt) { 28 | switch (evt.keyCode) { 29 | case 13: { // return 30 | if (!input.is("textarea")) applyChange(evt.keyCode); 31 | break; 32 | } 33 | case 27: { // escape 34 | cancelChange(evt.keyCode); 35 | break; 36 | } 37 | case 9: { // tab 38 | if (!input.is("textarea")) { 39 | applyChange(evt.keyCode); 40 | return false; 41 | } 42 | } 43 | } 44 | }); 45 | if (options.acceptOnBlur) { 46 | input.blur(function() { 47 | return applyChange(); 48 | }); 49 | } 50 | 51 | function applyChange(keyCode) { 52 | var newText = input.val(); 53 | if (newText == origText) { 54 | cancelChange(keyCode); 55 | return true; 56 | } 57 | if ((!options.allowEmpty && !newText.length) || 58 | !options.validate.apply(elem, [newText, origText])) { 59 | input.addClass("invalid"); 60 | return false; 61 | } 62 | input.remove(); 63 | tools.remove(); 64 | options.accept.apply(elem, [newText, origText]); 65 | editable.removeClass("editinline-container"); 66 | options.end.apply(elem, [keyCode]); 67 | return true; 68 | } 69 | 70 | function cancelChange(keyCode) { 71 | options.cancel.apply(elem, [origText]); 72 | editable.html(origHtml).removeClass("editinline-container"); 73 | options.end.apply(elem, [keyCode]); 74 | } 75 | 76 | var tools = $(""); 77 | $("") 78 | .text(options.acceptLabel).click(applyChange).appendTo(tools); 79 | $("") 80 | .text(options.cancelLabel).click(cancelChange).appendTo(tools) 81 | 82 | editable.html("").append(tools).append(input) 83 | .addClass("editinline-container"); 84 | options.prepareInput.apply(elem, [input[0]]); 85 | input.each(function() { this.focus(); this.select(); }); 86 | } 87 | 88 | $.fn.makeEditable = function(options) { 89 | options = $.extend({ 90 | allowEmpty: true, 91 | acceptLabel: "", 92 | cancelLabel: "", 93 | toolTip: "Double click to edit", 94 | acceptOnBlur: true, 95 | 96 | // callbacks 97 | begin: function() { return true }, 98 | accept: function(newValue, oldValue) {}, 99 | cancel: function(oldValue) {}, 100 | createInput: function(value) { return $("") }, 101 | prepareInput: function(input) {}, 102 | end: function(keyCode) {}, 103 | populate: function(value) { return value }, 104 | validate: function() { return true } 105 | }, options || {}); 106 | 107 | return this.each(function() { 108 | $(this).attr("title", options.toolTip).dblclick(function() { 109 | startEditing(this, options); 110 | }); 111 | }); 112 | } 113 | 114 | })(jQuery); 115 | -------------------------------------------------------------------------------- /lib/store/tree.rb: -------------------------------------------------------------------------------- 1 | # This is a basic binary tree 2 | # You could build a btree that maintains this API. 3 | # If you did that'd be cool. 4 | class Tree 5 | 6 | attr_accessor :left 7 | attr_accessor :right 8 | attr_accessor :key 9 | attr_accessor :value 10 | 11 | class PassedEnd < StandardError 12 | end 13 | 14 | def initialize(k=nil, v=nil, &less) 15 | # set this to true to make it spew debug logging 16 | @trace = false 17 | trace "new k #{k.inspect}" 18 | @left = nil 19 | @right = nil 20 | @key = k 21 | @value = v 22 | @less = less || lambda do |a,b| 23 | a < b 24 | end 25 | end 26 | 27 | # this is helpful for debugging 28 | def to_s 29 | "[" + 30 | if left then left.to_s + "," else "" end + 31 | key.inspect + 32 | if right then "," + right.to_s else "" end + "]" 33 | end 34 | 35 | # spew traversal logs 36 | def trace m 37 | puts m if @trace 38 | end 39 | 40 | # set a key/value 41 | def []= k, v 42 | insert(k, v) 43 | end 44 | # get a value by key 45 | def [] k 46 | n = search(k) 47 | n && n.value 48 | end 49 | 50 | # visit the members of the btree in 51 | # sorted order. this is the basis of 52 | # replication and incremental map reduce. 53 | def fold opts={}, &b 54 | keys = opts.keys 55 | if keys.include?("startkey") 56 | sk = opts["startkey"] 57 | else 58 | sk = :none 59 | end 60 | if keys.include?("endkey") 61 | ek = opts["endkey"] 62 | else 63 | ek = :none 64 | end 65 | fwd = opts["descending"] != "true" 66 | inc_end = (opts["inclusive_end"] != "false") 67 | trace "sk #{sk.inspect}" 68 | trace "ek #{ek.inspect}" 69 | trace "inc_end #{inc_end.inspect}" 70 | if (!fwd) 71 | # we switch start and end key 72 | # when the direction is backwards 73 | foldint(ek, sk, inc_end, fwd, &b) 74 | else 75 | foldint(sk, ek, inc_end, fwd, &b) 76 | end 77 | rescue PassedEnd 78 | end 79 | 80 | protected 81 | 82 | # this is the internal tree index traversal algorithm. 83 | # it's probably sub-optimal, but it works. 84 | def foldint sk, ek, inc_end, fwd, &b 85 | front = fwd ? @left : @right 86 | back = fwd ? @right : @left 87 | trace "fold preorder @key #{@key.inspect}" 88 | front.foldint(sk, ek, inc_end, fwd, &b) if front && 89 | ((sk == :none) || !@less.call(@key, sk)) 90 | trace "fold inorder @key #{@key.inspect}" 91 | if (ek != :none) 92 | if inc_end 93 | lt = @less.call(ek, @key) 94 | trace "inc_end lt #{lt} ek #{ek} @key #{@key}" 95 | raise PassedEnd if lt 96 | else 97 | lte = !@less.call(@key, ek) 98 | trace "exc_end lte #{lte.inspect} ek #{ek.inspect} @key #{@key.inspect}" 99 | raise PassedEnd if lte 100 | end 101 | end 102 | if (sk == :none) || !@less.call(@key, sk) 103 | trace "fold yield @key #{@key.inspect}" 104 | b.call(@key, @value) 105 | end 106 | trace "fold prepostorder @key #{@key.inspect}" 107 | back.foldint(sk, ek, inc_end, fwd, &b) if back 108 | # if back && ((sk == :none) || !@less.call(back.key, sk)) 109 | trace "fold postorder @key #{@key.inspect}" 110 | end 111 | 112 | # insert a value at a key 113 | # will replace the old value 114 | def insert(k, v) 115 | trace "insert k #{k} @key #{@key}" 116 | if @key == nil || @key == k 117 | @key = k 118 | @value = v 119 | elsif @less.call(k, @key) 120 | if @left == nil 121 | @left = Tree.new k, v, &@less 122 | else 123 | @left.insert k, v 124 | end 125 | else 126 | if @right == nil 127 | @right = Tree.new k, v, &@less 128 | else 129 | @right.insert k, v 130 | end 131 | end 132 | end 133 | 134 | # return the node for a given key 135 | def search(k) 136 | if self.key == k 137 | return self 138 | else 139 | ltree = left != nil ? left.search(k) : nil 140 | return ltree if ltree != nil 141 | rtree = right != nil ? right.search(k) : nil 142 | return rtree if rtree != nil 143 | end 144 | nil 145 | end 146 | 147 | end 148 | --------------------------------------------------------------------------------