couchio 0.1.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/LICENSE +22 -0
- data/README.markdown +99 -0
- data/Rakefile +31 -0
- data/lib/couchio.rb +13 -0
- data/lib/couchio/core_ext/dir.rb +44 -0
- data/lib/couchio/core_ext/file.rb +14 -0
- data/lib/couchio/core_ext/hash.rb +5 -0
- data/lib/couchio/core_ext/kernel.rb +4 -0
- data/lib/couchio/core_ext/uri.rb +5 -0
- data/lib/couchio/couch_database.rb +57 -0
- data/lib/couchio/couch_document.rb +76 -0
- data/lib/couchio/couch_io.rb +49 -0
- data/lib/couchio/couch_open.rb +21 -0
- metadata +75 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2008 Loren Segal
|
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.
|
data/README.markdown
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
CouchIO for Ruby
|
2
|
+
================
|
3
|
+
|
4
|
+
Written by Loren Segal in 2008, licensed under MIT license.
|
5
|
+
|
6
|
+
|
7
|
+
SYNOPSYS
|
8
|
+
--------
|
9
|
+
|
10
|
+
CouchIO is a simple VFS for Ruby, adding support to open, read and write
|
11
|
+
CouchDB resource URI's as if they were local files. The API intuitively uses
|
12
|
+
the existing `File` and `Dir` API to read and write. The goal is to emulate
|
13
|
+
Ruby's `Dir` and `File` implementation for the filesystem completely. Not all of
|
14
|
+
the interface has been implemented, but basic read/write support is currently
|
15
|
+
available. In addition to database support, CouchIO has support for both documents
|
16
|
+
and attachments.
|
17
|
+
|
18
|
+
|
19
|
+
USAGE
|
20
|
+
-----
|
21
|
+
|
22
|
+
### Accessing Databases & Documents ###
|
23
|
+
|
24
|
+
In CouchIO, the `Dir` filesystem equivalent of a directory is a CouchDB database.
|
25
|
+
The equivalent of a `File` is a CouchDB document *or* a CouchDB attachment, depending
|
26
|
+
on how the data is written.
|
27
|
+
|
28
|
+
The only difference between accessing files and directories and accessing documents and
|
29
|
+
databases is that you need to specify the full URI including the "couch://" scheme prefix,
|
30
|
+
not "http://" (to differentiate from `open-uri` support).
|
31
|
+
|
32
|
+
### Writing Documents & Attachments ###
|
33
|
+
|
34
|
+
Like with writing to files, you need to use the 'a' append mode flag if you wish to append
|
35
|
+
to the file. If you do not use this for a Couch resource, it will overwrite all of your contents.
|
36
|
+
|
37
|
+
To write data to a document, your data **must** be a `Hash`. To write data to an _attachment_,
|
38
|
+
your data must be a `String`. As you will see in the examples, writing a string will force
|
39
|
+
the resource to be saved as an attachment while writing hash contents will save it as a document.
|
40
|
+
|
41
|
+
### Note About Saving ###
|
42
|
+
|
43
|
+
Saving is done when the file is closed. There is a `save` method that can be manually called,
|
44
|
+
however, to maintain filesystem independence, it should never be called.
|
45
|
+
|
46
|
+
|
47
|
+
EXAMPLES
|
48
|
+
--------
|
49
|
+
|
50
|
+
List databases:
|
51
|
+
|
52
|
+
Dir.entries("couch://localhost:5984/") #=> ['todo', 'test']
|
53
|
+
|
54
|
+
Create a database:
|
55
|
+
|
56
|
+
Dir.mkdir("couch://localhost:5984/xyzzy")
|
57
|
+
|
58
|
+
Delete a database:
|
59
|
+
|
60
|
+
Dir.unlink("couch://localhost:5984/xyzzy")
|
61
|
+
|
62
|
+
List documents in a database:
|
63
|
+
|
64
|
+
Dir.entries("couch://localhost:5984/todo") #=> ['fix_car', 'wash_dishes', ...]
|
65
|
+
# - OR -
|
66
|
+
Dir.open("couch://localhost:5984/todo").each do |p|
|
67
|
+
puts p
|
68
|
+
end
|
69
|
+
|
70
|
+
Open and read a document:
|
71
|
+
|
72
|
+
File.open("couch://localhost:5984/todo/fix_car").read
|
73
|
+
#=> {'_id' => 'fix_car', 'text' => 'Need to fix car!', 'time' => 'June 20th 2008 10:55AM'}
|
74
|
+
# Same as: File.read("couch://localhost:5984/todo/fix_car")
|
75
|
+
|
76
|
+
Open a document for append-writing:
|
77
|
+
|
78
|
+
File.open("couch://localhost:5984/todo/fix_car", 'wa') do |f|
|
79
|
+
f.write 'text' => 'Need to fix car NOW!!!'
|
80
|
+
end
|
81
|
+
File.read("couch://localhost:5984/todo/fix_car")
|
82
|
+
#=> {'_id' => 'fix_car', 'text' => 'Need to fix car NOW!!!', 'time' => 'June 20th 2008 10:55AM'}
|
83
|
+
|
84
|
+
Write an attachment (document must exist):
|
85
|
+
|
86
|
+
File.open("couch://localhost:5984/test/doc/foo.txt", 'w') do |f|
|
87
|
+
f.write "hello world"
|
88
|
+
end
|
89
|
+
|
90
|
+
Read an attachment:
|
91
|
+
|
92
|
+
File.read("couch://localhost:5984/test/doc/foo.txt") #=> "hello world"
|
93
|
+
|
94
|
+
|
95
|
+
COPYRIGHT
|
96
|
+
---------
|
97
|
+
|
98
|
+
CouchIO is free software written by **Loren Segal** under the MIT license. If
|
99
|
+
you meet him, give him props.
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'spec'
|
4
|
+
require 'spec/rake/spectask'
|
5
|
+
require 'yard'
|
6
|
+
|
7
|
+
WINDOWS = (PLATFORM =~ /win32|cygwin/ ? true : false) rescue false
|
8
|
+
SUDO = WINDOWS ? '' : 'sudo'
|
9
|
+
|
10
|
+
task :default => :specs
|
11
|
+
|
12
|
+
load 'couchio.gemspec'
|
13
|
+
Rake::GemPackageTask.new(SPEC) do |pkg|
|
14
|
+
pkg.gem_spec = SPEC
|
15
|
+
pkg.need_zip = true
|
16
|
+
pkg.need_tar = true
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "Install the gem locally"
|
20
|
+
task :install => :package do
|
21
|
+
sh "#{SUDO} gem install pkg/#{SPEC.name}-#{SPEC.version}.gem --local"
|
22
|
+
end
|
23
|
+
|
24
|
+
desc "Run all specs"
|
25
|
+
Spec::Rake::SpecTask.new("specs") do |t|
|
26
|
+
$DEBUG = true if ENV['DEBUG']
|
27
|
+
t.spec_opts = ["--format", "specdoc", "--colour"]
|
28
|
+
t.spec_files = Dir["spec/**/*_spec.rb"].sort
|
29
|
+
end
|
30
|
+
|
31
|
+
YARD::Rake::YardocTask.new
|
data/lib/couchio.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'json'
|
3
|
+
require 'uri'
|
4
|
+
require 'net/http'
|
5
|
+
require 'base64'
|
6
|
+
|
7
|
+
root = File.join(File.dirname(__FILE__), 'couchio')
|
8
|
+
|
9
|
+
['couch_io', 'couch_open', 'couch_database', 'couch_document'].each do |f|
|
10
|
+
require File.join(root, f)
|
11
|
+
end
|
12
|
+
|
13
|
+
Dir[root + '/core_ext/*.rb'].each {|f| require f }
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class << Dir
|
2
|
+
alias couch_orig_open open
|
3
|
+
alias couch_orig_new new
|
4
|
+
remove_method :open
|
5
|
+
include CouchOpen
|
6
|
+
|
7
|
+
alias couch_orig_entries entries
|
8
|
+
def entries(dirname)
|
9
|
+
if dirname.index("couch://") == 0
|
10
|
+
CouchDatabase.new(dirname).map
|
11
|
+
else
|
12
|
+
couch_orig_entries(dirname)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
alias couch_orig_foreach foreach
|
17
|
+
def foreach(dirname, &block)
|
18
|
+
if dirname.index("couch://") == 0
|
19
|
+
CouchDatabase.new(dirname).each {|f| yield f }
|
20
|
+
else
|
21
|
+
couch_orig_foreach(dirname, &block)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
alias couch_orig_rmdir rmdir
|
26
|
+
def rmdir(dirname)
|
27
|
+
if dirname.index("couch://") == 0
|
28
|
+
CouchDatabase.new(dirname).delete
|
29
|
+
else
|
30
|
+
couch_orig_rmdir(dirname)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
alias unlink rmdir
|
34
|
+
alias delete rmdir
|
35
|
+
|
36
|
+
alias couch_orig_mkdir mkdir
|
37
|
+
def mkdir(dirname, *args)
|
38
|
+
if dirname.index("couch://") == 0
|
39
|
+
CouchDatabase.new(dirname).create
|
40
|
+
else
|
41
|
+
couch_orig_mkdir(dirname, *args)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class << File
|
2
|
+
alias couch_orig_open open
|
3
|
+
alias couch_orig_new new
|
4
|
+
include CouchOpen
|
5
|
+
|
6
|
+
alias couch_orig_read read
|
7
|
+
def read(filename)
|
8
|
+
if filename.index("couch://") == 0
|
9
|
+
CouchDocument.new(filename).read
|
10
|
+
else
|
11
|
+
couch_orig_read
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
class CouchDatabase < CouchIO
|
2
|
+
include Enumerable
|
3
|
+
|
4
|
+
attr_accessor :pos
|
5
|
+
alias tell pos
|
6
|
+
|
7
|
+
def initialize(*args, &block)
|
8
|
+
super
|
9
|
+
@pos = 0
|
10
|
+
end
|
11
|
+
|
12
|
+
def rewind; @pos = 0; self end
|
13
|
+
def seek(n) @pos = n; self end
|
14
|
+
|
15
|
+
def each
|
16
|
+
rows.each {|row| yield row }
|
17
|
+
end
|
18
|
+
|
19
|
+
def read
|
20
|
+
@rows ||= rows
|
21
|
+
result = @rows[@pos]
|
22
|
+
@pos += 1
|
23
|
+
result
|
24
|
+
end
|
25
|
+
|
26
|
+
def create
|
27
|
+
uri = URI.parse(path)
|
28
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
29
|
+
result = http.send_request('PUT', uri.path)
|
30
|
+
json = JSON.parse(result.body)
|
31
|
+
verify_ok(json)
|
32
|
+
end
|
33
|
+
0
|
34
|
+
end
|
35
|
+
|
36
|
+
def delete
|
37
|
+
uri = URI.parse(path)
|
38
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
39
|
+
result = http.delete(uri.path)
|
40
|
+
json = JSON.parse(result.body)
|
41
|
+
verify_ok(json)
|
42
|
+
end
|
43
|
+
0
|
44
|
+
end
|
45
|
+
alias unlink delete
|
46
|
+
alias rmdir delete
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def rows
|
51
|
+
if URI.parse(path).path =~ /^\/*$/
|
52
|
+
read_json('_all_dbs')
|
53
|
+
else
|
54
|
+
read_json('_all_docs')["rows"].map {|r| r['id'] }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
class CouchDocument < CouchIO
|
2
|
+
def close
|
3
|
+
save
|
4
|
+
end
|
5
|
+
|
6
|
+
def read
|
7
|
+
return @local_copy if @dirty
|
8
|
+
|
9
|
+
json = read_json
|
10
|
+
verify_ok(json)
|
11
|
+
@local_copy = json
|
12
|
+
rescue Errno::ENOENT
|
13
|
+
@local_copy = {'_id' => File.basename(path)}
|
14
|
+
end
|
15
|
+
|
16
|
+
def write(data)
|
17
|
+
read if !@local_copy
|
18
|
+
if @local_copy.is_a?(Hash)
|
19
|
+
@local_copy.delete_if {|k,v| !append && k[0,1] != '_' }
|
20
|
+
end
|
21
|
+
|
22
|
+
if data.is_a?(String)
|
23
|
+
write_string(data)
|
24
|
+
elsif data.is_a?(Hash)
|
25
|
+
write_hash(data)
|
26
|
+
else
|
27
|
+
raise ArgumentError, "expecting String or Hash, got #{data.class}"
|
28
|
+
end
|
29
|
+
@dirty = true
|
30
|
+
end
|
31
|
+
alias print write
|
32
|
+
|
33
|
+
def printf(data, *args)
|
34
|
+
write_string sprintf(data, *args)
|
35
|
+
end
|
36
|
+
|
37
|
+
def write_string(data)
|
38
|
+
CouchDocument.new(File.dirname(path), 'a') do |f|
|
39
|
+
item = f.read
|
40
|
+
item['_attachments'] ||= {}
|
41
|
+
|
42
|
+
if append && item['_attachments'][File.basename(path)]
|
43
|
+
item['_attachments'][File.basename(path)].update({
|
44
|
+
'data' => Base64.b64encode(read.to_s + data.to_s).chomp
|
45
|
+
})
|
46
|
+
else
|
47
|
+
item['_attachments'].update(File.basename(path) => {
|
48
|
+
'content_type' => 'text/plain',
|
49
|
+
'data' => Base64.b64encode(data).chomp
|
50
|
+
})
|
51
|
+
end
|
52
|
+
f.write(item)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
alias puts write_string
|
56
|
+
|
57
|
+
def write_hash(data)
|
58
|
+
data = data.stringify_keys
|
59
|
+
@local_copy.update(data)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def save
|
65
|
+
return unless @local_copy && @local_copy.is_a?(Hash)
|
66
|
+
|
67
|
+
uri = URI.parse(path)
|
68
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
69
|
+
@local_copy.update('_id' => File.basename(path))
|
70
|
+
result = http.send_request('PUT', uri.path, @local_copy.to_json)
|
71
|
+
json = JSON.parse(result.body)
|
72
|
+
verify_ok(json)
|
73
|
+
end
|
74
|
+
@dirty = false
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class AccessError < Exception
|
2
|
+
end
|
3
|
+
|
4
|
+
class CouchIO
|
5
|
+
attr_accessor :path, :readable, :writeable, :append
|
6
|
+
|
7
|
+
def initialize(name, mode = 'r', &block)
|
8
|
+
@path = name
|
9
|
+
@readable = mode.include?('r')
|
10
|
+
@writeable = mode.include?('w')
|
11
|
+
@append = mode.include?('a')
|
12
|
+
|
13
|
+
if block_given?
|
14
|
+
yield(self)
|
15
|
+
close
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def close; end
|
20
|
+
|
21
|
+
def read
|
22
|
+
read_json
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def read_json(extra = nil)
|
28
|
+
uri = extra ? File.join(path, extra) : path
|
29
|
+
data = Net::HTTP.get(URI.parse(uri))
|
30
|
+
|
31
|
+
if data[0,1] =~ /[\[\{]/
|
32
|
+
JSON.parse(data)
|
33
|
+
else
|
34
|
+
data
|
35
|
+
end
|
36
|
+
rescue JSON::ParserError
|
37
|
+
data
|
38
|
+
end
|
39
|
+
|
40
|
+
def verify_ok(json)
|
41
|
+
return unless json.is_a?(Hash)
|
42
|
+
return unless json.has_key?('error')
|
43
|
+
|
44
|
+
case json['error']
|
45
|
+
when 'not_found'
|
46
|
+
raise Errno::ENOENT, path
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module CouchOpen
|
2
|
+
def open(name, mode = 'r', *perm, &block)
|
3
|
+
if name.index("couch://") == 0
|
4
|
+
couch_io_class.new(name, mode, &block)
|
5
|
+
else
|
6
|
+
couch_orig_open(name, mode, *perm, &block)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
alias new open
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def couch_io_class
|
14
|
+
case self.to_s
|
15
|
+
when 'File'
|
16
|
+
CouchDocument
|
17
|
+
when 'Dir'
|
18
|
+
CouchDatabase
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: couchio
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Loren Segal
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-05-20 00:00:00 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: json
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: "0"
|
23
|
+
version:
|
24
|
+
description:
|
25
|
+
email: lsegal@soen.ca
|
26
|
+
executables: []
|
27
|
+
|
28
|
+
extensions: []
|
29
|
+
|
30
|
+
extra_rdoc_files: []
|
31
|
+
|
32
|
+
files:
|
33
|
+
- lib/couchio
|
34
|
+
- lib/couchio/core_ext
|
35
|
+
- lib/couchio/core_ext/dir.rb
|
36
|
+
- lib/couchio/core_ext/file.rb
|
37
|
+
- lib/couchio/core_ext/hash.rb
|
38
|
+
- lib/couchio/core_ext/kernel.rb
|
39
|
+
- lib/couchio/core_ext/uri.rb
|
40
|
+
- lib/couchio/couch_database.rb
|
41
|
+
- lib/couchio/couch_document.rb
|
42
|
+
- lib/couchio/couch_io.rb
|
43
|
+
- lib/couchio/couch_open.rb
|
44
|
+
- lib/couchio.rb
|
45
|
+
- LICENSE
|
46
|
+
- README.markdown
|
47
|
+
- Rakefile
|
48
|
+
has_rdoc: false
|
49
|
+
homepage: http://couchio.soen.ca
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: "0"
|
60
|
+
version:
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
version:
|
67
|
+
requirements: []
|
68
|
+
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 1.1.1
|
71
|
+
signing_key:
|
72
|
+
specification_version: 2
|
73
|
+
summary: Virtual filesystem support for a CouchDB database.
|
74
|
+
test_files: []
|
75
|
+
|