georgi-git_store 0.1

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/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ *~
2
+ doc/*
3
+ pkg/*
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2008 Matthias Georgi <http://www.matthias-georgi.de>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to
5
+ deal in the Software without restriction, including without limitation the
6
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ sell copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,66 @@
1
+ GitStore - using Git as versioned data store in Ruby
2
+ ====================================================
3
+
4
+ GitStore is a small Ruby library, providing an easy interface to the
5
+ version control system [Git][1]. It aims to use Git as a versioned
6
+ data store much like the well known PStore. Basically GitStore checks
7
+ out the repository into a in-memory representation, which can be
8
+ modified and finally committed. In this way your data is stored in a
9
+ folder structure and can be checked out and examined, but the
10
+ application may access the data in a convenient hash-like way.
11
+
12
+ This library is based on [Grit][2], the main technology behind
13
+ [GitHub][3].
14
+
15
+ ## Usage Example
16
+
17
+ First thing you should do, is to initialize a new git repository.
18
+
19
+ git init
20
+
21
+ Now you can instantiate a GitStore instance and store some data. The
22
+ data will be serialized depending on the file extension. So for YAML
23
+ storage you can use the 'yml' extension:
24
+
25
+ class WikiPage < Struct.new(:author, :title, :body); end
26
+ class User < Struct.new(:name); end
27
+
28
+ store = GitStore.new('.')
29
+
30
+ store['users/matthias.yml'] = User.new('Matthias')
31
+ store['pages/home.yml'] = WikiPage.new('matthias', 'Home', 'This is the home page...')
32
+
33
+ store.commit 'Added user and page'
34
+
35
+ Note that direcories will be created automatically by using the path
36
+ syntax. Same for multi arguments hash syntax:
37
+
38
+ store[config', 'wiki.yml'] = { 'name' => 'My Personal Wiki' }
39
+
40
+ In this case the directory config is created automatically and
41
+ the file wiki.yml contains be the YAML representation of the given Hash.
42
+
43
+ ## Iteration
44
+
45
+ Iterating over the stored datat is one of the common use cases, so
46
+ this one is really easy and scales well at the same time, if you user
47
+ a clever directory structure:
48
+
49
+ store['pages/home.yml'] = WikiPage.new('matthias', 'Home', 'This is the home page...')
50
+ store['pages/about.yml'] = WikiPage.new('matthias', About', 'About this site...')
51
+ store['pages/links.yml'] = WikiPage.new('matthias', 'Links', 'Some useful links...')
52
+ store['config/wiki.yml'] = { 'name' => 'My Personal Wiki' }
53
+
54
+ store.each { |obj| ... } # yields all pages and the config hash
55
+ store['pages'].each { |page| ... } # yields only the pages
56
+
57
+ ## References
58
+
59
+ John Wiegley already has done [something similar for Python][4]. His
60
+ implementation has its own git interface, GitStore uses the wonderful
61
+ [Grit][2] library.
62
+
63
+ [1]: http://git.or.cz/
64
+ [2]: http://github.com/mojombo/grit/tree/master
65
+ [3]: http://github.com/
66
+ [4]: http://www.newartisans.com/blog_files/git.versioned.data.store.php
data/git_store.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'git_store'
3
+ s.version = '0.1'
4
+ s.date = '2008-12-17'
5
+ s.summary = 'a simple data store based on git'
6
+ s.author = 'Matthias Georgi'
7
+ s.email = 'matti.georgi@gmail.com'
8
+ s.homepage = 'http://github.com/georgi/git_store'
9
+ s.description = 'A simple git based data store'
10
+ s.require_path = 'lib'
11
+ s.has_rdoc = true
12
+ s.extra_rdoc_files = ['README.md']
13
+ s.files = %w{
14
+ .gitignore
15
+ LICENSE
16
+ README.md
17
+ git_store.gemspec
18
+ lib/git_store.rb
19
+ spec/git_store_spec.rb
20
+ }
21
+ end
22
+
data/lib/git_store.rb ADDED
@@ -0,0 +1,230 @@
1
+ require 'grit'
2
+ require 'erb'
3
+
4
+ class GitStore
5
+
6
+ class DefaultHandler
7
+ def read(id, name, data)
8
+ data
9
+ end
10
+
11
+ def write(data)
12
+ data
13
+ end
14
+ end
15
+
16
+ class YAMLHandler
17
+ def read(id, name, data)
18
+ YAML.load(data)
19
+ end
20
+
21
+ def write(data)
22
+ data.to_yaml
23
+ end
24
+ end
25
+
26
+ class RubyHandler
27
+ def read(id, name, data)
28
+ Object.module_eval(data)
29
+ end
30
+ end
31
+
32
+ class ERBHandler
33
+ def read(id, name, data)
34
+ ERB.new(data)
35
+ end
36
+ end
37
+
38
+ Handler = {
39
+ 'yml' => YAMLHandler.new,
40
+ 'rhtml' => ERBHandler.new,
41
+ 'rxml' => ERBHandler.new,
42
+ 'rb' => RubyHandler.new
43
+ }
44
+
45
+ Handler.default = DefaultHandler.new
46
+
47
+ class Blob
48
+
49
+ attr_reader :id
50
+ attr_accessor :name
51
+
52
+ def initialize(*args)
53
+ if args.first.is_a?(Grit::Blob)
54
+ @blob = args.first
55
+ @id = @blob.id
56
+ @name = @blob.name
57
+ else
58
+ @name = args[0]
59
+ self.data = args[1]
60
+ end
61
+ end
62
+
63
+ def extname
64
+ File.extname(name)[1..-1]
65
+ end
66
+
67
+ def load(data)
68
+ @data = handler.read(id, name, data)
69
+ end
70
+
71
+ def handler
72
+ Handler[extname]
73
+ end
74
+
75
+ def data
76
+ @data or (@blob and load(@blob.data))
77
+ end
78
+
79
+ def data=(data)
80
+ @data = data
81
+ end
82
+
83
+ def to_s
84
+ if handler.respond_to?(:write)
85
+ handler.write(data)
86
+ else
87
+ @blob.data
88
+ end
89
+ end
90
+
91
+ end
92
+
93
+ class Tree
94
+ include Enumerable
95
+
96
+ attr_reader :id, :data
97
+ attr_accessor :name
98
+
99
+ def initialize(name = nil)
100
+ @data = {}
101
+ @name = name
102
+ end
103
+
104
+ def load(tree)
105
+ @id = tree.id
106
+ @name = tree.name
107
+ @data = tree.contents.inject({}) do |hash, file|
108
+ if file.is_a?(Grit::Tree)
109
+ hash[file.name] = (@data[file.name] || Tree.new).load(file)
110
+ else
111
+ hash[file.name] = Blob.new(file)
112
+ end
113
+ hash
114
+ end
115
+ self
116
+ end
117
+
118
+ def inspect
119
+ "#<GitStore::Tree #{@data.inspect}>"
120
+ end
121
+
122
+ def fetch(name)
123
+ name = name.to_s
124
+ entry = @data[name]
125
+ case entry
126
+ when Blob then entry.data
127
+ when Tree then entry
128
+ end
129
+ end
130
+
131
+ def store(name, value)
132
+ name = name.to_s
133
+ if value.is_a?(Tree)
134
+ value.name = name
135
+ @data[name] = value
136
+ else
137
+ @data[name] = Blob.new(name, value)
138
+ end
139
+ end
140
+
141
+ def has_key?(name)
142
+ @data.has_key?(name)
143
+ end
144
+
145
+ def [](*args)
146
+ args = args.first.to_s.split('/') if args.size == 1
147
+ args.inject(self) { |tree, key| tree.fetch(key) or return nil }
148
+ end
149
+
150
+ def []=(*args)
151
+ value = args.pop
152
+ args = args.first.to_s.split('/') if args.size == 1
153
+ tree = args[0..-2].to_a.inject(self) do |tree, key|
154
+ tree.has_key?(key) ? tree.fetch(key) : tree.store(key, Tree.new(key))
155
+ end
156
+ tree.store(args.last, value)
157
+ end
158
+
159
+ def delete(name)
160
+ @data.delete(name)
161
+ end
162
+
163
+ def each(&block)
164
+ @data.values.each do |entry|
165
+ case entry
166
+ when Blob then yield entry.data
167
+ when Tree then entry.each(&block)
168
+ end
169
+ end
170
+ end
171
+
172
+ def each_with_path(path = [], &block)
173
+ @data.each do |name, entry|
174
+ child_path = path + [name]
175
+ case entry
176
+ when Blob then yield entry, child_path.join('/')
177
+ when Tree then entry.each_with_path(child_path, &block)
178
+ end
179
+ end
180
+ end
181
+
182
+ def to_hash
183
+ @data.inject({}) do |hash, (name, entry)|
184
+ hash[name] = entry.is_a?(Tree) ? entry.to_hash : entry.to_s
185
+ hash
186
+ end
187
+ end
188
+
189
+ end
190
+
191
+ attr_reader :repo, :index, :tree
192
+
193
+ def initialize(path, &block)
194
+ @repo = Grit::Repo.new(path)
195
+ @index = Grit::Index.new(@repo)
196
+ @tree = Tree.new
197
+ end
198
+
199
+ def commit(message="")
200
+ index.tree = tree.to_hash
201
+ head = repo.heads.first
202
+ index.commit(message, head ? head.commit.id : nil)
203
+ end
204
+
205
+ def [](*args)
206
+ tree[*args]
207
+ end
208
+
209
+ def []=(*args)
210
+ value = args.pop
211
+ tree[*args] = value
212
+ end
213
+
214
+ def delete(path)
215
+ tree.delete(path)
216
+ end
217
+
218
+ def load
219
+ tree.load(repo.tree)
220
+ end
221
+
222
+ def each(&block)
223
+ tree.each(&block)
224
+ end
225
+
226
+ def each_with_path(&block)
227
+ tree.each_with_path(&block)
228
+ end
229
+
230
+ end
@@ -0,0 +1,117 @@
1
+ $:.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib'))
2
+
3
+ require 'git_store'
4
+ require 'yaml'
5
+
6
+ describe GitStore do
7
+
8
+ REPO = File.expand_path(File.dirname(__FILE__) + '/test_repo')
9
+
10
+ before do
11
+ FileUtils.rm_rf REPO
12
+ Dir.mkdir REPO
13
+ Dir.chdir REPO
14
+ `git init`
15
+ end
16
+
17
+ def store
18
+ @store or
19
+ begin
20
+ @store = GitStore.new(REPO)
21
+ @store.load
22
+ @store
23
+ end
24
+ end
25
+
26
+ def file(file, data)
27
+ FileUtils.mkpath(File.dirname(file))
28
+ open(file, 'w') { |io| io << data }
29
+ `git add #{file}`
30
+ `git commit -m 'added #{file}'`
31
+ File.unlink(file)
32
+ end
33
+
34
+ it 'should load a repo' do
35
+ file 'a', 'Hello'
36
+ file 'b', 'World'
37
+
38
+ store['a'].should == 'Hello'
39
+ store['b'].should == 'World'
40
+ end
41
+
42
+ it 'should load folders' do
43
+ file 'x/a', 'Hello'
44
+ file 'y/b', 'World'
45
+
46
+ store['x'].should be_kind_of(GitStore::Tree)
47
+ store['y'].should be_kind_of(GitStore::Tree)
48
+
49
+ store['x']['a'].should == 'Hello'
50
+ store['y']['b'].should == 'World'
51
+ end
52
+
53
+ it 'should commit added files' do
54
+ store['c'] = 'Hello'
55
+ store['d'] = 'World'
56
+ store.commit
57
+
58
+ `git checkout`
59
+
60
+ File.should be_exist('c')
61
+ File.should be_exist('d')
62
+
63
+ File.read('c').should == 'Hello'
64
+ File.read('d').should == 'World'
65
+ end
66
+
67
+ it 'should load yaml' do
68
+ file 'x/a.yml', '[1, 2, 3, 4]'
69
+
70
+ store['x']['a.yml'].should == [1,2,3,4]
71
+
72
+ store['x']['a.yml'] = [1,2,3,4,5]
73
+
74
+ store.commit
75
+ store.load
76
+
77
+ store['x']['a.yml'].should == [1,2,3,4,5]
78
+ end
79
+
80
+ it 'should resolv paths' do
81
+ file 'x/a', 'Hello'
82
+ file 'y/b', 'World'
83
+
84
+ store['x/a'].should == 'Hello'
85
+ store['y/b'].should == 'World'
86
+
87
+ store['y/b'] = 'Now this'
88
+
89
+ store['y']['b'].should == 'Now this'
90
+ store.commit
91
+ store.load
92
+
93
+ store['y/b'].should == 'Now this'
94
+ end
95
+
96
+ it 'should create new trees' do
97
+ store['new/tree'] = 'This tree'
98
+ store['this', 'tree'] = 'Another'
99
+ store.commit
100
+ store.load
101
+
102
+ store['new/tree'].should == 'This tree'
103
+ store['this/tree'].should == 'Another'
104
+ end
105
+
106
+ it 'should preserve loaded trees' do
107
+ tree = store['tree'] = GitStore::Tree.new
108
+ store['tree']['example'] = 'Example'
109
+ store.commit
110
+ store.load
111
+
112
+ store['tree'].should == tree
113
+ end
114
+
115
+ end
116
+
117
+
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: georgi-git_store
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.1"
5
+ platform: ruby
6
+ authors:
7
+ - Matthias Georgi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-12-17 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A simple git based data store
17
+ email: matti.georgi@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.md
24
+ files:
25
+ - .gitignore
26
+ - LICENSE
27
+ - README.md
28
+ - git_store.gemspec
29
+ - lib/git_store.rb
30
+ - spec/git_store_spec.rb
31
+ has_rdoc: true
32
+ homepage: http://github.com/georgi/git_store
33
+ post_install_message:
34
+ rdoc_options: []
35
+
36
+ require_paths:
37
+ - lib
38
+ required_ruby_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: "0"
43
+ version:
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: "0"
49
+ version:
50
+ requirements: []
51
+
52
+ rubyforge_project:
53
+ rubygems_version: 1.2.0
54
+ signing_key:
55
+ specification_version: 2
56
+ summary: a simple data store based on git
57
+ test_files: []
58
+