From c5397a9b6b8dc2753b1abacac507966b276a3253 Mon Sep 17 00:00:00 2001 From: Geoffrey Grosenbach Date: Thu, 11 Sep 2008 13:29:12 -0700 Subject: [PATCH] Use optparse for couchview. Implemented view generation option. --- .gitignore | 1 + bin/couchview | 144 +++++++++------------------- couchrest.gemspec | 1 - lib/couch_rest/commands.rb | 5 + lib/couch_rest/commands/generate.rb | 71 ++++++++++++++ lib/couch_rest/commands/push.rb | 99 +++++++++++++++++++ 6 files changed, 219 insertions(+), 102 deletions(-) create mode 100644 .gitignore create mode 100644 lib/couch_rest/commands.rb create mode 100644 lib/couch_rest/commands/generate.rb create mode 100644 lib/couch_rest/commands/push.rb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/bin/couchview b/bin/couchview index d319d05..578c81a 100755 --- a/bin/couchview +++ b/bin/couchview @@ -1,111 +1,53 @@ #!/usr/bin/env ruby -commands = %w{push generate} +require 'optparse' +require File.dirname(__FILE__) + "/../lib/couch_rest/commands" -command = ARGV[0] +# Set defaults +options = { + :loud => true, +} -if !commands.include?(command) - puts <<-USAGE -Couchview has two modes: push and generate. Run couchview push or couchview generate for usage documentation. -USAGE - exit -end - -if ARGV.length == 1 - case command - when "generate" - puts <<-GEN -Usage: couchview generate directory design1 design2 design3 ... - -Couchview will create directories and example views for the design documents you specify. - -GEN - when "push" - puts <<-PUSH -== Pushing views with Couchview == - -Usage: couchview push directory dbname - -Couchview expects a specific filesystem layout for your CouchDB views (see -example below). It also supports advanced features like inlining of library -code (so you can keep DRY) as well as avoiding unnecessary document -modification. - -Couchview also solves a problem with CouchDB's view API, which only provides -access to the final reduce side of any views which have both a map and a -reduce function defined. The intermediate map results are often useful for -development and production. CouchDB is smart enough to reuse map indexes for -functions duplicated across views within the same design document. - -For views with a reduce function defined, Couchview creates both a reduce view -and a map-only view, so that you can browse and query the map side as well as -the reduction, with no performance penalty. - -== Example == - -couchview push foo-project/bar-views baz-database - -This will push the views defined in foo-project/bar-views into a database -called baz-database. Couchview expects the views to be defined in files with -names like: - -foo-project/bar-views/my-design/viewname-map.js -foo-project/bar-views/my-design/viewname-reduce.js -foo-project/bar-views/my-design/noreduce-map.js - -Pushed to => http://localhost:5984/baz-database/_design/my-design - -And the design document: - { - "views" : { - "viewname-map" : { - "map" : "### contents of view-name-map.js ###" - }, - "viewname-reduce" : { - "map" : "### contents of view-name-map.js ###", - "reduce" : "### contents of view-name-reduce.js ###" - }, - "noreduce-map" : { - "map" : "### contents of noreduce-map.js ###" - } - } - } - -Couchview will create a design document for each subdirectory of the views -directory specified on the command line. - -== Library Inlining == - -Couchview can optionally inline library code into your views so you only have -to maintain it in one place. It looks for any files named lib.* in your -design-doc directory (for doc specific libs) and in the parent views directory -(for project global libs). These libraries are only inserted into views which -include the text - -//include-lib - -or - -#include-lib - -Couchview is a result of scratching my own itch. I'd be happy to make it more -general, so please contact me at jchris@grabb.it if you'd like to see anything -added or changed. -PUSH +opts = OptionParser.new do |opts| + opts.banner = "Usage: #$0 [options] (push|generate) directory database" + opts.on('-q', '--quiet', "Omit extra debug info") do + options[:loud] = false end + opts.on_tail('-h', '--help [push|generate]', "Display detailed help and exit") do |help_command| + puts opts + case help_command + when "push" + puts CouchRest::Commands::Push.help + when "generate" + puts CouchRest::Commands::Generate.help + end + exit + end +end +opts.parse!(ARGV) + +options[:command] = ARGV.shift +options[:directory] = ARGV.shift +options[:trailing_args] = ARGV + +# There must be a better way to check for extra required args +unless (["push", "generate"].include?(options[:command]) && options[:directory] && options[:trailing_args]) + puts(opts) exit end -require 'rubygems' -require 'couchrest' +# DEBUG +puts options.inspect -if command == 'push' - dirname = ARGV[1] - dbname = ARGV[2] - fm = CouchRest::FileManager.new(dbname) - fm.loud = true - puts "Pushing views from directory #{dirname} to database #{fm.db}" - fm.push_views(dirname) -elsif command == 'generate' - puts "Under construction ;)" +# The options hash now contains the resolved defaults +# and the overrides from the command line. + +# Call your class and send it the options here +# cr = CouchRest::FileManager.new(options[:database_name]) + +case options[:command] +when "push" + CouchRest::Commands::Push.run(options) +when "generate" + CouchRest::Commands::Generate.run(options) end diff --git a/couchrest.gemspec b/couchrest.gemspec index ee7ca6a..1b64895 100644 --- a/couchrest.gemspec +++ b/couchrest.gemspec @@ -20,7 +20,6 @@ Gem::Specification.new do |s| s.bindir = 'bin' s.executables << 'couchview' s.executables << 'couchdir' - s.executables << 'couchcontrol' s.add_dependency("json", [">= 1.1.2"]) s.add_dependency("rest-client", [">= 0.5"]) end diff --git a/lib/couch_rest/commands.rb b/lib/couch_rest/commands.rb new file mode 100644 index 0000000..88369eb --- /dev/null +++ b/lib/couch_rest/commands.rb @@ -0,0 +1,5 @@ +require File.join(File.dirname(__FILE__), "..", "couchrest") + +%w(push generate).each do |filename| + require File.join(File.dirname(__FILE__), "commands", filename) +end diff --git a/lib/couch_rest/commands/generate.rb b/lib/couch_rest/commands/generate.rb new file mode 100644 index 0000000..bc46bcb --- /dev/null +++ b/lib/couch_rest/commands/generate.rb @@ -0,0 +1,71 @@ +require 'fileutils' + +class CouchRest + module Commands + module Generate + + def self.run(options) + directory = options[:directory] + design_names = options[:trailing_args] + + FileUtils.mkdir_p(directory) + filename = File.join(directory, "lib.js") + self.write(filename, <<-FUNC) + // Put global functions here. + // Include in your views with + // + // //include-lib + FUNC + + design_names.each do |design_name| + subdirectory = File.join(directory, design_name) + FileUtils.mkdir_p(subdirectory) + filename = File.join(subdirectory, "sample-map.js") + self.write(filename, <<-FUNC) + function(doc) { + // Keys is first letter of _id + emit(doc._id[0], doc); + } + FUNC + + filename = File.join(subdirectory, "sample-reduce.js") + self.write(filename, <<-FUNC) + function(keys, values) { + // Count the number of keys starting with this letter + return values.length; + } + FUNC + + filename = File.join(subdirectory, "lib.js") + self.write(filename, <<-FUNC) + // Put functions specific to '#{design_name}' here. + // Include in your views with + // + // //include-lib + FUNC + end + end + + def self.help + helpstring = <<-GEN + + Usage: couchview generate directory design1 design2 design3 ... + + Couchview will create directories and example views for the design documents you specify. + + GEN + helpstring.gsub(/^ /, '') + end + + def self.write(filename, contents) + puts "Writing #{filename}" + File.open(filename, "w") do |f| + # Remove leading spaces + contents.gsub!(/^ ( )?/, '') + f.write contents + end + end + + end + end +end diff --git a/lib/couch_rest/commands/push.rb b/lib/couch_rest/commands/push.rb new file mode 100644 index 0000000..a9a2e95 --- /dev/null +++ b/lib/couch_rest/commands/push.rb @@ -0,0 +1,99 @@ +class CouchRest + + module Commands + + module Push + + def self.run(options) + directory = options[:directory] + database = options[:trailing_args].first + + fm = CouchRest::FileManager.new(database) + fm.loud = options[:loud] + puts "Pushing views from directory #{directory} to database #{fm.db}" + fm.push_views(directory) + end + + def self.help + helpstring = <<-GEN + + == Pushing views with Couchview == + + Usage: couchview push directory dbname + + Couchview expects a specific filesystem layout for your CouchDB views (see + example below). It also supports advanced features like inlining of library + code (so you can keep DRY) as well as avoiding unnecessary document + modification. + + Couchview also solves a problem with CouchDB's view API, which only provides + access to the final reduce side of any views which have both a map and a + reduce function defined. The intermediate map results are often useful for + development and production. CouchDB is smart enough to reuse map indexes for + functions duplicated across views within the same design document. + + For views with a reduce function defined, Couchview creates both a reduce view + and a map-only view, so that you can browse and query the map side as well as + the reduction, with no performance penalty. + + == Example == + + couchview push foo-project/bar-views baz-database + + This will push the views defined in foo-project/bar-views into a database + called baz-database. Couchview expects the views to be defined in files with + names like: + + foo-project/bar-views/my-design/viewname-map.js + foo-project/bar-views/my-design/viewname-reduce.js + foo-project/bar-views/my-design/noreduce-map.js + + Pushed to => http://localhost:5984/baz-database/_design/my-design + + And the design document: + { + "views" : { + "viewname-map" : { + "map" : "### contents of view-name-map.js ###" + }, + "viewname-reduce" : { + "map" : "### contents of view-name-map.js ###", + "reduce" : "### contents of view-name-reduce.js ###" + }, + "noreduce-map" : { + "map" : "### contents of noreduce-map.js ###" + } + } + } + + Couchview will create a design document for each subdirectory of the views + directory specified on the command line. + + == Library Inlining == + + Couchview can optionally inline library code into your views so you only have + to maintain it in one place. It looks for any files named lib.* in your + design-doc directory (for doc specific libs) and in the parent views directory + (for project global libs). These libraries are only inserted into views which + include the text + + //include-lib + + or + + #include-lib + + Couchview is a result of scratching my own itch. I'd be happy to make it more + general, so please contact me at jchris@grabb.it if you'd like to see anything + added or changed. + + GEN + helpstring.gsub(/^ /, '') + end + + end + + + end + +end