couch_docs 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: