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 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
+