couch_docs 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/.bnsignore ADDED
@@ -0,0 +1,16 @@
1
+ # The list of files that should be ignored by Mr Bones.
2
+ # Lines that start with '#' are comments.
3
+ #
4
+ # A .gitignore file can be used instead by setting it as the ignore
5
+ # file in your Rakefile:
6
+ #
7
+ # PROJ.ignore_file = '.gitignore'
8
+ #
9
+ # For a project with a C extension, the following would be a good set of
10
+ # exclude patterns (uncomment them if you want to use them):
11
+ # *.[oa]
12
+ # *~
13
+ announcement.txt
14
+ coverage
15
+ doc
16
+ pkg
data/.gitignore CHANGED
@@ -1,2 +1,4 @@
1
- pkg
2
- couch_docs-*.gem
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in couch_docs.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,27 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ couch_docs (1.2.2)
5
+ directory_watcher (~> 1.3.0)
6
+ json (~> 1.4.0)
7
+ rest-client (~> 1.6.0)
8
+
9
+ GEM
10
+ remote: http://rubygems.org/
11
+ specs:
12
+ directory_watcher (1.3.2)
13
+ json (1.4.6)
14
+ mime-types (1.16)
15
+ rest-client (1.6.1)
16
+ mime-types (>= 1.16)
17
+ rspec (1.3.1)
18
+
19
+ PLATFORMS
20
+ ruby
21
+
22
+ DEPENDENCIES
23
+ couch_docs!
24
+ directory_watcher (~> 1.3.0)
25
+ json (~> 1.4.0)
26
+ rest-client (~> 1.6.0)
27
+ rspec (~> 1.3.0)
data/History.txt CHANGED
@@ -1,3 +1,15 @@
1
+ == 1.3.0 / 2011-01-01
2
+
3
+ * Attachment support
4
+
5
+ * Attachments for the foo document would be stored in the foo sub-directory (the foo document itself is stored as foo.json).
6
+
7
+ * Rudimentary mime-type support.
8
+
9
+ * Works with dumping and pushing.
10
+
11
+ * Minor bug fixes.
12
+
1
13
  == 1.2.1 / 2010-04-21
2
14
 
3
15
  * Update README to reflect changes in 1.2. No code changes.
data/README.rdoc CHANGED
@@ -1,49 +1,68 @@
1
- couch_docs
2
- by Chris Strom
3
- http://github.com/eee-c/couch_docs
4
- (used to be couch_design_docs)
1
+ = NAME
5
2
 
6
- == DESCRIPTION:
3
+ couch_docs - Manage CouchDB views and documents from the filesystem.
7
4
 
8
- Manage CouchDB views and documents.
5
+ Author: Chris Strom
9
6
 
10
- == FEATURES/PROBLEMS:
7
+ http://github.com/eee-c/couch_docs
8
+
9
+ (formerly couch_design_docs)
10
+
11
+ = DESCRIPTION
12
+
13
+ I have two primary use cases: uploading fixture data for integration testing and editing CouchDB documents in my favorite editor. It could also be useful as a cheap back-up mechanism, though it likely would not work well with large datasets (see PROBLEMS/FUTURE).
14
+
15
+ == Fixture Strategy
16
+
17
+ Before an integration test, I create a new CouchDB database with a randomly generated name. I use couch_docs to upload fixture data to that database:
18
+
19
+ CouchDocs.put_dir(DB_URL, DIRECTORY)
20
+
21
+ Then, I allow the test run to proceed normally. At the end of the run, I teardown the test database.
22
+
23
+ Since CouchDB database creation / destruction is so cheap, this allows for quite fast integration tests -- even including necessary design documents.
24
+
25
+ == Editing with Emacs
26
+
27
+ I much prefer editing JSON documents in Emacs (lesser editors should work) over using the Futon web interface built into CouchDB. To initiate this, I watch the directory containing the JSON document(s):
28
+
29
+ couch-docs push -w localhost:5984/db
30
+
31
+ The -w option uses directory watcher to watch for any changes on the file system. When a change occurs, the updated document is loaded to CouchDB immediately. This is very useful for rapid editing/prototyping of documents that are subsequently used in another medium (e.g. for a blog post).
32
+
33
+ = FEATURES
11
34
 
12
35
  * Upload JSON documents stored on the filesystem into a CouchDB
13
36
  database.
14
37
 
15
- * Map <tt>.js</tt> files stored on the filesystem
16
- (e.g. <tt>_design/recipes/count_by_month/map.js</tt>) into CouchDB
17
- design documents.
38
+ * Map <tt>.js</tt> files stored on the filesystem
39
+ (e.g. <tt>_design/recipes/count_by_month/map.js</tt>) into CouchDB
40
+ design documents.
18
41
 
19
- * Support for the <tt>!code</tt> macro from couchapp (useful for
20
- DRYing up map/reduce views as well as list/show documents.
42
+ * Support for the <tt>!code</tt> macro from couchapp (useful for
43
+ DRYing up map/reduce views as well as list/show documents.
21
44
 
22
45
  * Dump documents stored in CouchDB to the filesystem.
23
46
 
24
- * Script (couch-docs) to restore / backup CouchDB database.
25
-
26
- * Multiple options including a directory watcher for uploading
27
- directory changes
47
+ * Attachments are stored as real files (not inline, mime64 encoded attributes on the JSON document)
28
48
 
29
- * A progress bar would be helpful.
49
+ * De-resolution of <tt>!code</tt> macros.
30
50
 
31
- * Unit testing of view javascript would be very nice.
51
+ * Command line script (<tt>couch-docs</tt>) to push / dump CouchDB database.
32
52
 
33
- * Will almost certainly not work for large datasets (>10k documents).
53
+ * Multiple options including a directory watcher for uploading
54
+ directory changes
34
55
 
35
- == SYNOPSIS:
56
+ = SYNOPSIS
36
57
 
37
58
  From the command line:
38
59
 
39
60
  # For dumping the contents of a CouchDB database to the filesystem
40
- couch-docs dump "http://localhost:5984/db" path/to/dump_dir/
61
+ couch-docs dump http://localhost:5984/db
41
62
 
42
63
  # For loading documents from the filesystem into CouchDB
43
- couch-docs push "http://localhost:5984/db" path/to/dump_dir/
64
+ couch-docs push http://localhost:5984/db
44
65
 
45
- The directory is optional for both dump and push (defaults to the
46
- current working directory).
47
66
 
48
67
  In code:
49
68
 
@@ -65,18 +84,28 @@ Manage CouchDB views and documents.
65
84
 
66
85
  # => JSON dump of every document at DB_URL
67
86
 
68
- == REQUIREMENTS:
87
+ = REQUIREMENTS
69
88
 
70
89
  * CouchDB
71
90
  * JSON
72
91
  * RestClient
73
92
  * DirectoryWatcher
74
93
 
75
- == INSTALL:
94
+ = INSTALL
76
95
 
77
96
  * sudo gem install couch_docs
78
97
 
79
- == LICENSE:
98
+ = PROBLEMS/FUTURE
99
+
100
+ * Does not honor CouchDB document revisions. Instead it deletes previous revisions and uploads an entirely new one.
101
+
102
+ * A progress bar would be helpful.
103
+
104
+ * Unit testing of view javascript would be very nice.
105
+
106
+ * Will almost certainly not work for large datasets (>10k documents).
107
+
108
+ = LICENSE
80
109
 
81
110
  (The MIT License)
82
111
 
data/Rakefile CHANGED
@@ -1,39 +1,20 @@
1
- # Look in the tasks/setup.rb file for the various options that can be
2
- # configured in this Rakefile. The .rake files in the tasks directory
3
- # are where the options are used.
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
4
3
 
5
- begin
6
- require 'bones'
7
- Bones.setup
8
- rescue LoadError
9
- begin
10
- load 'tasks/setup.rb'
11
- rescue LoadError
12
- raise RuntimeError, '### please install the "bones" gem ###'
13
- end
4
+ require 'spec/rake/spectask'
5
+ Spec::Rake::SpecTask.new(:spec) do |spec|
6
+ spec.libs << 'lib' << 'spec'
7
+ spec.spec_files = FileList['spec/**/*_spec.rb']
14
8
  end
15
9
 
16
- ensure_in_path 'lib'
17
- require 'couch_docs'
10
+ task :default => :spec
18
11
 
19
- task :default => 'spec:run'
12
+ require 'rake/rdoctask'
13
+ Rake::RDocTask.new do |rdoc|
14
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
20
15
 
21
- PROJ.name = 'couch_docs'
22
- PROJ.authors = 'Chris Strom'
23
- PROJ.email = 'chris@eeecooks.com'
24
- PROJ.url = 'http://github.com/eee-c/couch_docs'
25
- PROJ.version = CouchDocs::VERSION
26
- PROJ.rubyforge.name = 'couch_docs'
27
-
28
- PROJ.spec.opts << '--color'
29
-
30
- #PROJ.gem.dependencies = %w{json rest-client}
31
- PROJ.gem.development_dependencies << 'rspec'
32
-
33
- PROJ.readme_file = 'README.rdoc'
34
-
35
- depend_on 'rest-client', "~> 1.1.0"
36
- depend_on 'json'
37
- depend_on 'directory_watcher'
38
-
39
- # EOF
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = "couch_docs #{version}"
18
+ rdoc.rdoc_files.include('README*')
19
+ rdoc.rdoc_files.include('lib/**/*.rb')
20
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.2.1
data/couch_docs.gemspec CHANGED
@@ -1,48 +1,27 @@
1
1
  # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "couch_docs/version"
2
4
 
3
5
  Gem::Specification.new do |s|
4
- s.name = %q{couch_docs}
5
- s.version = "1.2.1"
6
-
7
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
- s.authors = ["Chris Strom"]
9
- s.date = %q{2010-04-21}
10
- s.default_executable = %q{couch-docs}
6
+ s.name = "couch_docs"
7
+ s.version = CouchDocs::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Chris Strom"]
10
+ s.email = ["chris@eeecooks.com"]
11
+ s.homepage = "http://github.com/eee-c/couch_docs"
12
+ s.summary = %q{Manage CouchDB views and documents}
11
13
  s.description = %q{Manage CouchDB views and documents.}
12
- s.email = %q{chris@eeecooks.com}
13
- s.executables = ["couch-docs"]
14
- s.extra_rdoc_files = ["History.txt", "README.rdoc", "bin/couch-docs"]
15
- s.files = [".gitignore", "History.txt", "README.rdoc", "Rakefile", "bin/couch-docs", "couch_docs-1.1.0.gem", "couch_docs-1.1.1.gem", "couch_docs-1.2.0.gem", "couch_docs.gemspec", "fixtures/_design/__lib/foo.js", "fixtures/_design/a/b/c.js", "fixtures/_design/a/b/d.js", "fixtures/_design/x/z.js", "fixtures/bar.json", "fixtures/foo.json", "lib/couch_docs.rb", "lib/couch_docs/command_line.rb", "lib/couch_docs/design_directory.rb", "lib/couch_docs/document_directory.rb", "lib/couch_docs/store.rb", "spec/couch_docs_spec.rb", "spec/spec_helper.rb", "test/test_couch_docs.rb"]
16
- s.homepage = %q{http://github.com/eee-c/couch_docs}
17
- s.rdoc_options = ["--main", "README.rdoc"]
14
+
15
+ s.rubyforge_project = "couch_docs"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
20
  s.require_paths = ["lib"]
19
- s.rubyforge_project = %q{couch_docs}
20
- s.rubygems_version = %q{1.3.6}
21
- s.summary = %q{Manage CouchDB views and documents}
22
- s.test_files = ["test/test_couch_docs.rb"]
23
21
 
24
- if s.respond_to? :specification_version then
25
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
26
- s.specification_version = 3
22
+ s.add_development_dependency "rspec", ["~> 1.3.0"]
27
23
 
28
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
29
- s.add_runtime_dependency(%q<rest-client>, ["~> 1.1.0"])
30
- s.add_runtime_dependency(%q<json>, [">= 1.2.0"])
31
- s.add_runtime_dependency(%q<directory_watcher>, [">= 1.3.1"])
32
- s.add_development_dependency(%q<bones>, [">= 2.5.0"])
33
- s.add_development_dependency(%q<rspec>, [">= 0"])
34
- else
35
- s.add_dependency(%q<rest-client>, ["~> 1.1.0"])
36
- s.add_dependency(%q<json>, [">= 1.2.0"])
37
- s.add_dependency(%q<directory_watcher>, [">= 1.3.1"])
38
- s.add_dependency(%q<bones>, [">= 2.5.0"])
39
- s.add_dependency(%q<rspec>, [">= 0"])
40
- end
41
- else
42
- s.add_dependency(%q<rest-client>, ["~> 1.1.0"])
43
- s.add_dependency(%q<json>, [">= 1.2.0"])
44
- s.add_dependency(%q<directory_watcher>, [">= 1.3.1"])
45
- s.add_dependency(%q<bones>, [">= 2.5.0"])
46
- s.add_dependency(%q<rspec>, [">= 0"])
47
- end
24
+ s.add_runtime_dependency(%q<rest-client>, ["~> 1.6.0"])
25
+ s.add_runtime_dependency(%q<json>, ["~> 1.4.0"])
26
+ s.add_runtime_dependency(%q<directory_watcher>, ["~> 1.3.0"])
48
27
  end
@@ -0,0 +1 @@
1
+ [{"one": "2"}]
@@ -0,0 +1 @@
1
+ ["!code foo.js"]
@@ -0,0 +1 @@
1
+ {"baz":"3"}
@@ -1,5 +1,7 @@
1
1
  require 'optparse'
2
2
  require 'pp'
3
+
4
+ require 'rubygems'
3
5
  require 'directory_watcher'
4
6
 
5
7
  module CouchDocs
@@ -28,7 +28,7 @@ module CouchDocs
28
28
  # Load
29
29
 
30
30
  def to_hash
31
- Dir["#{couch_view_dir}/**/*.js"].inject({}) do |memo, filename|
31
+ Dir["#{couch_view_dir}/**/*.{js,json}"].inject({}) do |memo, filename|
32
32
  DesignDirectory.
33
33
  a_to_hash(expand_file(filename)).
34
34
  deep_merge(memo)
@@ -36,16 +36,30 @@ module CouchDocs
36
36
  end
37
37
 
38
38
  def expand_file(filename)
39
+ if filename =~ /\.js$/
40
+ name_value_pair = [
41
+ File.basename(filename, '.js'),
42
+ read_js_value(filename)
43
+ ]
44
+ elsif filename =~ /\.json$/
45
+ name_value_pair = [
46
+ File.basename(filename, '.json'),
47
+ read_json_value(filename)
48
+ ]
49
+ end
50
+
51
+ name_value_pair[0].gsub!(/%2F/, '/')
52
+
39
53
  File.dirname(filename).
40
54
  gsub(/#{couch_view_dir}\/?/, '').
41
- split(/\//) +
42
- [
43
- File.basename(filename, '.js').gsub(/%2F/, '/'),
44
- read_value(filename)
45
- ]
55
+ split(/\//) + name_value_pair
56
+ end
57
+
58
+ def read_json_value(filename)
59
+ JSON.parse(File.new(filename).read)
46
60
  end
47
61
 
48
- def read_value(filename)
62
+ def read_js_value(filename)
49
63
  File.
50
64
  readlines(filename).
51
65
  map { |line| process_code_macro(line) }.
@@ -75,18 +89,9 @@ module CouchDocs
75
89
 
76
90
  def save_js(rel_path, key, value)
77
91
  if value.is_a? Hash
78
- value.each_pair do |k, v|
79
- next if k == '_id'
80
- self.save_js([rel_path, key].compact.join('/'), k, v)
81
-
82
- end
92
+ save_js_hash(rel_path, key, value)
83
93
  else
84
- path = couch_view_dir + '/' + rel_path
85
- FileUtils.mkdir_p(path)
86
-
87
- file = File.new("#{path}/#{key.gsub(/\//, '%2F')}.js", "w+")
88
- file.write(remove_code_macros(value))
89
- file.close
94
+ save_js_value(rel_path, key, value)
90
95
  end
91
96
  end
92
97
 
@@ -99,5 +104,26 @@ module CouchDocs
99
104
  js
100
105
  end
101
106
  end
107
+
108
+ private
109
+ def save_js_hash(rel_path, id, hash)
110
+ hash.each_pair do |k, v|
111
+ next if k == '_id'
112
+ self.save_js([rel_path, id].compact.join('/'), k, v)
113
+ end
114
+ end
115
+
116
+ def save_js_value(rel_path, id, value)
117
+ ext = value.is_a?(String) ? "js" : "json"
118
+ value = value.is_a?(String) ? remove_code_macros(value) : value.to_json
119
+
120
+
121
+ path = couch_view_dir + '/' + rel_path
122
+ FileUtils.mkdir_p(path)
123
+
124
+ file = File.new("#{path}/#{id.gsub(/\//, '%2F')}.#{ext}", "w+")
125
+ file.write(value)
126
+ file.close
127
+ end
102
128
  end
103
129
  end
@@ -1,3 +1,5 @@
1
+ require 'base64'
2
+
1
3
  module CouchDocs
2
4
  class DocumentDirectory
3
5
 
@@ -10,16 +12,69 @@ module CouchDocs
10
12
 
11
13
  def each_document
12
14
  Dir["#{couch_doc_dir}/*.json"].each do |filename|
13
- yield [ File.basename(filename, '.json'),
14
- JSON.parse(File.new(filename).read) ]
15
+ id = File.basename(filename, '.json')
16
+ json = JSON.parse(File.new(filename).read)
17
+
18
+ if File.directory? "#{couch_doc_dir}/#{id}"
19
+ json["_attachments"] ||= { }
20
+ Dir["#{couch_doc_dir}/#{id}/*"].each do |attachment|
21
+ next unless File.file? attachment
22
+ attachment_name = File.basename(attachment)
23
+ json["_attachments"][attachment_name] = file_as_attachment(attachment)
24
+ end
25
+ end
15
26
 
27
+ yield [ id, json ]
16
28
  end
17
29
  end
18
30
 
19
31
  def store_document(doc)
20
32
  file = File.new("#{couch_doc_dir}/#{doc['_id']}.json", "w+")
33
+ store_attachments(doc['_id'], doc.delete('_attachments'))
21
34
  file.write(doc.to_json)
22
35
  file.close
23
36
  end
37
+
38
+ def file_as_attachment(file)
39
+ type = mime_type(File.extname(file))
40
+ data = File.read(file)
41
+
42
+ attachment = {
43
+ "data" => Base64.encode64(data).gsub(/\n/, '')
44
+ }
45
+ if type
46
+ attachment.merge!({"content_type" => type})
47
+ end
48
+
49
+ attachment
50
+ end
51
+
52
+ def store_attachments(id, attachments)
53
+ return unless attachments
54
+
55
+ make_attachment_dir(id)
56
+ attachments.each do |filename, opts|
57
+ save_attachment(id, filename, opts['data'])
58
+ end
59
+ end
60
+
61
+ def make_attachment_dir(id)
62
+ FileUtils.mkdir_p "#{couch_doc_dir}/#{id}"
63
+ end
64
+
65
+ def save_attachment(id, filename, data)
66
+ file = File.new "#{couch_doc_dir}/#{id}/#{filename}", "w"
67
+ file.write Base64.decode64(data)
68
+ file.close
69
+ end
70
+
71
+ private
72
+ def mime_type(extension)
73
+ ({
74
+ ".gif" => "image/gif",
75
+ ".jpg" => "image/jpeg",
76
+ ".png" => "image/png"
77
+ })[extension]
78
+ end
24
79
  end
25
80
  end
@@ -0,0 +1,3 @@
1
+ module CouchDocs
2
+ VERSION = "1.3.0"
3
+ end
data/lib/couch_docs.rb CHANGED
@@ -3,7 +3,6 @@ require 'ostruct'
3
3
  module CouchDocs
4
4
 
5
5
  # :stopdoc:
6
- VERSION = '1.2.1'
7
6
  LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
8
7
  PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
9
8
  # :startdoc: