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 +16 -0
- data/.gitignore +4 -2
- data/Gemfile +4 -0
- data/Gemfile.lock +27 -0
- data/History.txt +12 -0
- data/README.rdoc +56 -27
- data/Rakefile +15 -34
- data/VERSION +1 -0
- data/couch_docs.gemspec +19 -40
- data/fixtures/_design/a/e.json +1 -0
- data/fixtures/_design/j/q.json +1 -0
- data/fixtures/baz_with_attachments/spacer.gif +0 -0
- data/fixtures/baz_with_attachments.json +1 -0
- data/lib/couch_docs/command_line.rb +2 -0
- data/lib/couch_docs/design_directory.rb +44 -18
- data/lib/couch_docs/document_directory.rb +57 -2
- data/lib/couch_docs/version.rb +3 -0
- data/lib/couch_docs.rb +0 -1
- data/spec/couch_docs/command_line_spec.rb +170 -0
- data/spec/couch_docs/design_directory_spec.rb +197 -0
- data/spec/couch_docs/document_directory_spec.rb +212 -0
- data/spec/couch_docs/store_spec.rb +98 -0
- data/spec/couch_docs_spec.rb +9 -482
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +3 -5
- metadata +60 -50
- data/couch_docs-1.1.0.gem +0 -0
- data/couch_docs-1.1.1.gem +0 -0
- data/couch_docs-1.2.0.gem +0 -0
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
data/Gemfile
ADDED
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
|
-
|
2
|
-
by Chris Strom
|
3
|
-
http://github.com/eee-c/couch_docs
|
4
|
-
(used to be couch_design_docs)
|
1
|
+
= NAME
|
5
2
|
|
6
|
-
|
3
|
+
couch_docs - Manage CouchDB views and documents from the filesystem.
|
7
4
|
|
8
|
-
|
5
|
+
Author: Chris Strom
|
9
6
|
|
10
|
-
|
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
|
-
|
17
|
-
|
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
|
-
|
20
|
-
|
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
|
-
*
|
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
|
-
*
|
49
|
+
* De-resolution of <tt>!code</tt> macros.
|
30
50
|
|
31
|
-
*
|
51
|
+
* Command line script (<tt>couch-docs</tt>) to push / dump CouchDB database.
|
32
52
|
|
33
|
-
*
|
53
|
+
* Multiple options including a directory watcher for uploading
|
54
|
+
directory changes
|
34
55
|
|
35
|
-
|
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
|
61
|
+
couch-docs dump http://localhost:5984/db
|
41
62
|
|
42
63
|
# For loading documents from the filesystem into CouchDB
|
43
|
-
couch-docs push
|
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
|
-
|
87
|
+
= REQUIREMENTS
|
69
88
|
|
70
89
|
* CouchDB
|
71
90
|
* JSON
|
72
91
|
* RestClient
|
73
92
|
* DirectoryWatcher
|
74
93
|
|
75
|
-
|
94
|
+
= INSTALL
|
76
95
|
|
77
96
|
* sudo gem install couch_docs
|
78
97
|
|
79
|
-
|
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
|
-
|
2
|
-
|
3
|
-
# are where the options are used.
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
4
3
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
17
|
-
require 'couch_docs'
|
10
|
+
task :default => :spec
|
18
11
|
|
19
|
-
|
12
|
+
require 'rake/rdoctask'
|
13
|
+
Rake::RDocTask.new do |rdoc|
|
14
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
20
15
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
5
|
-
s.version
|
6
|
-
|
7
|
-
s.
|
8
|
-
s.
|
9
|
-
s.
|
10
|
-
s.
|
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
|
-
|
13
|
-
s.
|
14
|
-
|
15
|
-
s.files
|
16
|
-
s.
|
17
|
-
s.
|
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
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
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"]
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
{"baz":"3"}
|
@@ -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
|
-
|
44
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
14
|
-
|
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
|