metahash 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ .rspec
5
+ pkg/*
6
+ vendor
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in metahash.gemspec
4
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,3 @@
1
+ module Metahash
2
+ VERSION = "0.0.1"
3
+ end
data/lib/metahash.rb ADDED
@@ -0,0 +1,91 @@
1
+ require 'metahash/version'
2
+ require 'rubygems'
3
+ require 'bson'
4
+ require 'json'
5
+ module Metahash
6
+ class Metahash
7
+ BOM = (JSON '["\uFEFF"]')[0]
8
+ SEARCH_BYTE_OFFSET = 4
9
+ SEARCH_BYTE_SEQUENCE = "\003bson-\357\273\277"
10
+ def initialize(path,options={})
11
+ raise ArgumentError unless File.exists? path
12
+ @path = path
13
+ @options = options
14
+ bson = read_for_search File.open(@path,"rb")
15
+ if bson
16
+ @bson_range = bson[:range]
17
+ @bson_size = bson[:size]
18
+ @bytes = bson[:bytes]
19
+ end
20
+ self
21
+ end
22
+ # returns current state of metahash, false if no tag attahed, true if tag attached
23
+ def tagged?
24
+ !@bson_size.nil?
25
+ end
26
+
27
+ # returns a hash
28
+ def to_h
29
+ return nil if @bytes.nil?
30
+ packet = BSON.deserialize(BSON::ByteBuffer.new(@bytes[@bson_range]))
31
+ packet["bson-#{BOM}"]["object"]
32
+ end
33
+
34
+ # allow transparent usage like hash
35
+ # TODO: This is ridiculous, truly a monkey patch to provide cool functionality, please implement hash correctly
36
+ def method_missing(method,*args)
37
+ if {}.respond_to?(method)
38
+ hash = to_h||{}
39
+ result = hash.send(method,*args)
40
+ write hash
41
+ result
42
+ else
43
+ super
44
+ end
45
+ end
46
+ def write(obj)
47
+ byte_string = BSON.serialize(wrap_obj(obj)).to_s
48
+ if @bytes
49
+ #take 0..bson_start
50
+ #serialize object
51
+ #with bson_end..-1
52
+ @bytes=@bytes[0..@bson_range.first]+byte_string+@bytes[@bson_range.last..-1]
53
+ #write and reinitialize
54
+ File.truncate(@path,0)
55
+ File.open(@path,"wb+") do |file|
56
+ file.write(@bytes)
57
+ end
58
+ else
59
+ File.open(@path,"ab+") do |file|
60
+ file.write(byte_string)
61
+ end
62
+ end
63
+ initialize(@path,@options)
64
+ end
65
+ private
66
+
67
+ # wraps obj in Meta Hash header
68
+ def wrap_obj(obj)
69
+ meta_bson = {
70
+ "bson-#{BOM}"=>{
71
+ "version"=>VERSION,
72
+ "ns"=>["http://json-schema.org/card.properties","http://groups.google.com/group/json-schema/browse_thread/thread/dd1a8c9e55035c67?pli=1"],
73
+ "object"=> obj
74
+ }
75
+ }
76
+ end
77
+ def read_for_search(io)
78
+ bytes = io.read
79
+ search_position = bytes.index(SEARCH_BYTE_SEQUENCE)
80
+ return nil unless search_position
81
+ bson_start = search_position - SEARCH_BYTE_OFFSET
82
+ bson_size = bytes[bson_start..(search_position-1)].unpack("S")[0]
83
+ bson_end = bson_start + bson_size
84
+ return {
85
+ :range => bson_start..bson_end,
86
+ :size => bson_size,
87
+ :bytes => bytes
88
+ }
89
+ end
90
+ end
91
+ end
data/metahash.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "metahash/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "metahash"
7
+ s.version = Metahash::VERSION
8
+ s.authors = ["Thomas Devol"]
9
+ s.email = ["thomas.devol@gmail.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Provides metadata management for many file types}
12
+ s.description = %q{Successor to exif/id3/xmp for metadata management}
13
+
14
+ s.rubyforge_project = "metahash"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency "rspec"
22
+ s.add_development_dependency "ruby-debug"
23
+ s.add_runtime_dependency "bson"
24
+ s.add_runtime_dependency "json"
25
+ # specify any dependencies here; for example:
26
+ # s.add_development_dependency "rspec"
27
+ # s.add_runtime_dependency "rest-client"
28
+ end
@@ -0,0 +1,132 @@
1
+ require 'metahash'
2
+ require 'ruby-debug'
3
+ module Metahash
4
+ describe Metahash do
5
+ before {
6
+ @test_path = File.dirname(__FILE__)+"/test.txt"
7
+ @test_data = {"hello"=>"world"}
8
+ }
9
+
10
+ context "generally" do #high level expectations
11
+
12
+ it "should be able to append to a new file" do
13
+
14
+ mh = Metahash.new @test_path
15
+ mh.write({"hello"=>"world"})
16
+
17
+ mh = Metahash.new @test_path
18
+ obj = mh.to_h
19
+ obj.should be_a(Hash)
20
+ obj["hello"].should == "world"
21
+
22
+ end
23
+ it "should be able to modify an already tagged file" do
24
+ mh = Metahash.new @test_path
25
+ mh.write({"hello"=>"world"})
26
+
27
+ mh = Metahash.new @test_path
28
+ mh.write({"hello"=>"holmes"})
29
+
30
+ mh = Metahash.new @test_path
31
+ obj = mh.to_h
32
+ obj.should be_a(Hash)
33
+ obj["hello"].should == "holmes"
34
+ end
35
+ end
36
+ describe "specifically" do
37
+ let(:new_metahash_with_untagged_file) do
38
+ f = File.open @test_path,"w+"
39
+ f.write "Hello World"
40
+ f.close
41
+ Metahash.new @test_path
42
+ end
43
+ let(:new_metahash_with_tagged_file) do
44
+ mh = new_metahash_with_untagged_file
45
+ mh.write @test_data
46
+ mh
47
+ end
48
+
49
+ describe "#initialize" do
50
+ it "should throw an error on an invalid path" do
51
+ invalid_path = rand().to_s+@test_path
52
+ expect{
53
+ mh = Metahash.new invalid_path
54
+ }.to raise_error(ArgumentError)
55
+ end
56
+ it "should not throw an error on a valid path" do
57
+ lambda {
58
+ new_metahash_with_untagged_file
59
+ }.should_not raise_error(ArgumentError)
60
+ end
61
+ end
62
+ describe "#tagged?" do
63
+ it "should return false for untagged files" do
64
+ new_metahash_with_untagged_file.should_not be_tagged
65
+ end
66
+ it "should return true for tagged files" do
67
+ new_metahash_with_tagged_file.should be_tagged
68
+ end
69
+ end
70
+ describe "#write" do
71
+ context "with an untagged file" do
72
+ subject &:new_metahash_with_untagged_file
73
+ it "should call wrap obj and serialize" do
74
+ subject.should_receive(:wrap_obj).with(@test_data)
75
+ BSON.should_receive :serialize
76
+ subject.write @test_data
77
+ end
78
+ it "should call initialize after write" do
79
+ subject.should_receive(:initialize).exactly(1).times
80
+ subject.write @test_data
81
+ end
82
+ end
83
+ context "with a tagged file" do
84
+ subject &:new_metahash_with_tagged_file
85
+ it "should truncate already tagged file" do
86
+ File.should_receive(:truncate).with(@test_path,0)
87
+ subject.write({"hello"=>"holmes"})
88
+ end
89
+ end
90
+ end
91
+ describe "#to_h" do
92
+ context "with an untagged file" do
93
+ subject &:new_metahash_with_untagged_file
94
+ it "should return nil" do
95
+ subject.to_h.should be_nil
96
+ end
97
+ end
98
+ context "with a tagged file" do
99
+ subject &:new_metahash_with_tagged_file
100
+ it "should return test data" do
101
+ subject.to_h.should == @test_data
102
+ end
103
+ end
104
+ end
105
+ describe "#method_missing" do
106
+ context "with a tagged file" do
107
+ subject &:new_metahash_with_tagged_file
108
+ it "should read just like a hash" do
109
+ subject["hello"].should == "world"
110
+ end
111
+ it "should write and persist" do
112
+ #debugger
113
+ subject["foo"] = "bar"
114
+ subject["foo"].should == "bar"
115
+ end
116
+ end
117
+ context "with an untagged file" do
118
+ subject &:new_metahash_with_untagged_file
119
+ it "should just work" do
120
+ subject["artist"] = "dave matthews"
121
+
122
+ mh = Metahash.new @test_path
123
+ mh["artist"].should == "dave matthews"
124
+ end
125
+ end
126
+
127
+
128
+ end
129
+ end
130
+
131
+ end
132
+ end
data/spec/test.txt ADDED
Binary file
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: metahash
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Thomas Devol
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-09-30 00:00:00 -05:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :development
31
+ version_requirements: *id001
32
+ - !ruby/object:Gem::Dependency
33
+ name: ruby-debug
34
+ prerelease: false
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ segments:
40
+ - 0
41
+ version: "0"
42
+ type: :development
43
+ version_requirements: *id002
44
+ - !ruby/object:Gem::Dependency
45
+ name: bson
46
+ prerelease: false
47
+ requirement: &id003 !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ segments:
52
+ - 0
53
+ version: "0"
54
+ type: :runtime
55
+ version_requirements: *id003
56
+ - !ruby/object:Gem::Dependency
57
+ name: json
58
+ prerelease: false
59
+ requirement: &id004 !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ segments:
64
+ - 0
65
+ version: "0"
66
+ type: :runtime
67
+ version_requirements: *id004
68
+ description: Successor to exif/id3/xmp for metadata management
69
+ email:
70
+ - thomas.devol@gmail.com
71
+ executables: []
72
+
73
+ extensions: []
74
+
75
+ extra_rdoc_files: []
76
+
77
+ files:
78
+ - .gitignore
79
+ - Gemfile
80
+ - Rakefile
81
+ - lib/metahash.rb
82
+ - lib/metahash/version.rb
83
+ - metahash.gemspec
84
+ - spec/metahash_spec.rb
85
+ - spec/test.txt
86
+ has_rdoc: true
87
+ homepage: ""
88
+ licenses: []
89
+
90
+ post_install_message:
91
+ rdoc_options: []
92
+
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ segments:
100
+ - 0
101
+ version: "0"
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ segments:
107
+ - 0
108
+ version: "0"
109
+ requirements: []
110
+
111
+ rubyforge_project: metahash
112
+ rubygems_version: 1.3.6
113
+ signing_key:
114
+ specification_version: 3
115
+ summary: Provides metadata management for many file types
116
+ test_files:
117
+ - spec/metahash_spec.rb
118
+ - spec/test.txt