couchio 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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,5 @@
1
+ class Hash
2
+ def stringify_keys
3
+ Hash[*map {|k,v| [k.to_s, v] }.flatten]
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ module Kernel
2
+ alias couch_orig_open open
3
+ include CouchOpen
4
+ end
@@ -0,0 +1,5 @@
1
+ Net::HTTP.version_1_2
2
+
3
+ module URI
4
+ @@schemes['COUCH'] = URI::HTTP
5
+ 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
+