geist 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ .yardoc/
2
+ doc/
3
+ pkg/
@@ -0,0 +1,4 @@
1
+ lib/**/*.rb
2
+ --files Changelog.md,LICENSE
3
+ --private
4
+ --title Geist — API Documentation
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ ## Version 0.1.0
4
+
5
+ **September 22<sup>nd</sup>, 2011**
6
+
7
+ * Converted the Codebrawl contest entry into a gem
8
+
9
+ See the [Git history](https://github.com/koraktor/metior/commits/0.1.0) for
10
+ version 0.1.0
11
+
12
+ ## Codebrawl contest entry
13
+
14
+ **September 1<sup>st</sup>, 2011**
15
+
16
+ See the
17
+ ["Key/value stores" contest](http://codebrawl.com/contests/key-value-stores)
18
+ on Codebrawl to see the initial code
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
@@ -0,0 +1,20 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ geist (0.1.0)
5
+ open4 (~> 1.1.0)
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ open4 (1.1.0)
11
+ rake (0.9.2)
12
+ yard (0.7.2)
13
+
14
+ PLATFORMS
15
+ ruby
16
+
17
+ DEPENDENCIES
18
+ geist!
19
+ rake (~> 0.9.2)
20
+ yard (~> 0.7.2)
data/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ Copyright (c) 2011, Sebastian Staudt
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+ * Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+ * Neither the name of the author nor the names of its contributors
13
+ may be used to endorse or promote products derived from this software
14
+ without specific prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
23
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,61 @@
1
+ Geist
2
+ =====
3
+
4
+ Geist is a Git-backed key-value store written in Ruby.
5
+
6
+ ## Usage
7
+
8
+ require 'geist'
9
+
10
+ g = Geist.new '~/some/storage/path'
11
+ g.set :foo, 'bar'
12
+ g.set :baz, 123
13
+ g.set :name => 'geist', :platform => 'ruby'
14
+
15
+ g.keys #=> ["foo", "name", "platform"]
16
+ g.get :foo #=> "bar"
17
+ g.get :baz #=> 123
18
+ g.get :name, :platform #=> ["geist", "ruby"]
19
+
20
+ g.delete :baz
21
+ g.get :baz #=> nil
22
+
23
+ ## Internals
24
+
25
+ To be honest, the introduction was an outright fabrication. Geist is just a
26
+ Ruby API to misuse Git as a simple key-value store. Git itself is a pretty good
27
+ key-value store used to preserve blob (file), tree (directory), commit and tag
28
+ objects. A key-value store used to store versioned file histories in general.
29
+
30
+ Geist instead uses some low-level Git commands to store arbitrary Ruby objects
31
+ in a Git repository. The Ruby objects to store as values will be marshalled
32
+ into Git blob objects. These objects are referenced with lightweight Git tags
33
+ named by the given key.
34
+
35
+ Git will not double store duplicate values. Instead, the different key tags
36
+ will refer the same Git object.
37
+
38
+ ## Caveats
39
+
40
+ As Geist uses Git tags as keys, only objects with a working `#to_s` method can
41
+ be used as keys. Additionally, based on Git's [ref naming rules][1], Geist
42
+ rejects keys that can't be used as Git tag names, e.g. containing non-printable
43
+ characters or backslashes.
44
+
45
+ ## History
46
+
47
+ Geist was an idea for the Codebrawl contest ["Key/value stores"][2] and made it
48
+ to a honorable 6th place out of 18 contestants with a final score of 3.6.
49
+
50
+ ## License
51
+
52
+ This code is free software; you can redistribute it and/or modify it under the
53
+ terms of the new BSD License. A copy of this license can be found in the
54
+ LICENSE file.
55
+
56
+ ## Credits
57
+
58
+ * Sebastian Staudt – koraktor(at)gmail.com
59
+
60
+ [1]: http://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html
61
+ [2]: http://codebrawl.com/contests/key-value-stores
@@ -0,0 +1,38 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # This code is free software; you can redistribute it and/or modify it under
4
+ # the terms of the new BSD License.
5
+ #
6
+ # Copyright (c) 2011, Sebastian Staudt
7
+
8
+ require 'rubygems/package_task'
9
+
10
+ # Rake tasks for building the gem
11
+ spec = Gem::Specification.load('geist.gemspec')
12
+ Gem::PackageTask.new(spec) do |pkg|
13
+ end
14
+
15
+ # Check if YARD is installed
16
+ begin
17
+ require 'yard'
18
+
19
+ # Create a rake task `:doc to build the documentation using YARD
20
+ YARD::Rake::YardocTask.new do |yardoc|
21
+ yardoc.name = 'doc'
22
+ yardoc.files = [ 'lib/**/*.rb', 'Changelog.md', 'LICENSE', 'README.md' ]
23
+ yardoc.options = [ '--markup', 'markdown', '--private', '--title', 'Geist — API Documentation' ]
24
+ end
25
+ rescue LoadError
26
+ # Create a rake task `:doc` to show that YARD is not installed
27
+ desc 'Generate YARD Documentation (not available)'
28
+ task :doc do
29
+ $stderr.puts 'You need YARD to build the documentation. Install it using `gem install yard`.'
30
+ end
31
+ end
32
+
33
+ # Task for cleaning documentation and package directories
34
+ desc 'Clean documentation and package directories'
35
+ task :clean do
36
+ FileUtils.rm_rf 'doc'
37
+ FileUtils.rm_rf 'pkg'
38
+ end
@@ -0,0 +1,27 @@
1
+ # This code is free software; you can redistribute it and/or modify it under
2
+ # the terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2011, Sebastian Staudt
5
+
6
+ require File.expand_path(File.dirname(__FILE__) + '/lib/geist/version')
7
+
8
+ Gem::Specification.new do |s|
9
+ s.name = 'geist'
10
+ s.version = Geist::VERSION
11
+ s.platform = Gem::Platform::RUBY
12
+ s.authors = [ 'Sebastian Staudt' ]
13
+ s.email = [ 'koraktor@gmail.com' ]
14
+ s.homepage = 'https://github.com/koraktor/geist'
15
+ s.summary = 'A Git-backed key-value store'
16
+ s.description = 'Geist is a Git-backed key-value store that stores Ruby objects into a Git repository.'
17
+
18
+ s.add_dependency 'open4', '~> 1.1.0'
19
+
20
+ s.add_development_dependency 'rake', '~> 0.9.2'
21
+ s.add_development_dependency 'yard', '~> 0.7.2'
22
+
23
+ s.requirements = [ 'git >= 1.6' ]
24
+
25
+ s.files = `git ls-files`.split("\n")
26
+ s.require_paths = [ 'lib' ]
27
+ end
@@ -0,0 +1,153 @@
1
+ # This code is free software; you can redistribute it and/or modify it under
2
+ # the terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2011, Sebastian Staudt
5
+
6
+ require 'fileutils'
7
+
8
+ require 'open4'
9
+
10
+ # Geist is a Git-backed key-value store written in Ruby
11
+ #
12
+ # @author Sebastian Staudt
13
+ class Geist
14
+
15
+ # Create a new Geist instance storing into the given Git repository
16
+ #
17
+ # @param [String] path The path of the Git repository to store objects into
18
+ # @raise [RuntimeError] if the directory is not empty and not a Git
19
+ # repository
20
+ def initialize(path)
21
+ @path = File.expand_path path
22
+
23
+ if !File.exist? @path
24
+ FileUtils.mkdir_p @path
25
+ cmd 'init'
26
+ elsif !cmd('ls-files').success?
27
+ raise "#{@path} is not a Git repository."
28
+ end
29
+ end
30
+
31
+ # Delete the objects with the given keys from the storage
32
+ #
33
+ # @param [#to_s, ...] keys One or more keys where the corresponding values
34
+ # should be removed from the storage
35
+ # @return [Boolean] `true` if the objects have been removed from the
36
+ # repository
37
+ # @todo This does not really remove the objects from the repository but only
38
+ # the tags. This should also trigger Git's garbage collection to remove
39
+ # the objects completely.
40
+ def delete(*keys)
41
+ success = true
42
+
43
+ keys.each do |key|
44
+ if id_for(key).nil?
45
+ success = false
46
+ else
47
+ cmd "tag -d '#{key}'"
48
+ end
49
+ end
50
+
51
+ success
52
+ end
53
+
54
+ # Retrieve the objects with the given keys from the storage
55
+ #
56
+ # @param [#to_s, ...] keys One or more keys where the corresponding values
57
+ # should be loaded from the storage
58
+ # @return [Object, Array<Object>] One or more objects that belong to the
59
+ # given keys
60
+ def get(*keys)
61
+ return nil if keys.empty?
62
+
63
+ values = []
64
+ keys.each do |key|
65
+ value = nil
66
+ status = cmd "show '#{key}'" do |stdin, stdout|
67
+ if select [stdout]
68
+ blob = stdout.read.strip
69
+ value = Marshal.load blob unless blob.empty?
70
+ end
71
+ end
72
+ values << (status.success? ? value : nil)
73
+ end
74
+
75
+ keys.size == 1 ? values.first : values
76
+ end
77
+
78
+ # Returns all keys which have objects available in the storage
79
+ #
80
+ # @return [Array<String>] The keys available in the storage
81
+ def keys
82
+ keys = nil
83
+ cmd 'tag -l' do |stdin, stdout|
84
+ keys = stdout.lines.to_a.map(&:strip) if select [stdout]
85
+ end
86
+ keys
87
+ end
88
+
89
+ # Saves one ore more values into the storage
90
+ #
91
+ # @param [#to_s, Hash<#to_s, Object>] keys The key to use to store the value
92
+ # or a hash of multiple keys and values to store
93
+ # @param [Object] value The object to store as a value (only if a single key
94
+ # is given)
95
+ def set(keys, value = nil)
96
+ keys = { keys => value } unless keys.is_a? Hash
97
+
98
+ keys.each do |key, val|
99
+ if key.to_s.match(/(?:[\s^~:?*\[\\]|\.\.|@\{|(?:\/|\.|\.lock)$|^$)/)
100
+ warn "Warning: Invalid key '#{key}'"
101
+ return
102
+ end
103
+
104
+ delete key unless id_for(key).nil?
105
+
106
+ id = nil
107
+ cmd 'hash-object --stdin -w' do |stdin, stdout|
108
+ stdin.write Marshal.dump(val)
109
+ stdin.close
110
+ id = stdout.read.strip if select [stdout]
111
+ end
112
+
113
+ cmd "tag -f '#{key}' #{id}"
114
+ end
115
+ end
116
+
117
+ private
118
+
119
+ # Execute a Git command in the path of the Git repository of the current
120
+ # Geist instance
121
+ #
122
+ # @param [String] git_cmd The Git command to execute
123
+ # @param [Proc] block A block that can be used to read and write to and from
124
+ # the Git process' STDOUT and STDIN.
125
+ # @return [Process::Status] The exit status of the Git process
126
+ def cmd(git_cmd, &block)
127
+ cmd = "git --git-dir #{@path} #{git_cmd}"
128
+ status = Open4::popen4 cmd do |pid, stdin, stdout, stderr|
129
+ block.call stdin, stdout if block_given?
130
+
131
+ stdin.close unless stdin.closed?
132
+ stdout.close
133
+ stderr.close
134
+ end
135
+ status
136
+ end
137
+
138
+ # Returns the Git SHA ID of the blob where the value for the given key is
139
+ # stored
140
+ #
141
+ # @param [String, #to_s] key The key for which the Git SHA ID should be
142
+ # returned
143
+ # @return [String, nil] The SHA ID of the value's Git blob or `nil` if no
144
+ # such object exists
145
+ def id_for(key)
146
+ id = nil
147
+ status = cmd "rev-parse '#{key}'" do |stdin, stdout|
148
+ id = stdout.read.strip if select [stdout]
149
+ end
150
+ status.success? ? id : nil
151
+ end
152
+
153
+ end
@@ -0,0 +1,11 @@
1
+ # This code is free software; you can redistribute it and/or modify it under
2
+ # the terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2011, Sebastian Staudt
5
+
6
+ class Geist
7
+
8
+ # This is the current version of the Geist gem
9
+ VERSION = '0.1.0' unless const_defined? :VERSION
10
+
11
+ end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: geist
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Sebastian Staudt
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-09-22 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: open4
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 19
29
+ segments:
30
+ - 1
31
+ - 1
32
+ - 0
33
+ version: 1.1.0
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: rake
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 63
45
+ segments:
46
+ - 0
47
+ - 9
48
+ - 2
49
+ version: 0.9.2
50
+ type: :development
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: yard
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ hash: 7
61
+ segments:
62
+ - 0
63
+ - 7
64
+ - 2
65
+ version: 0.7.2
66
+ type: :development
67
+ version_requirements: *id003
68
+ description: Geist is a Git-backed key-value store that stores Ruby objects into a Git repository.
69
+ email:
70
+ - koraktor@gmail.com
71
+ executables: []
72
+
73
+ extensions: []
74
+
75
+ extra_rdoc_files: []
76
+
77
+ files:
78
+ - .gitignore
79
+ - .yardopts
80
+ - Changelog.md
81
+ - Gemfile
82
+ - Gemfile.lock
83
+ - LICENSE
84
+ - README.md
85
+ - Rakefile
86
+ - geist.gemspec
87
+ - lib/geist.rb
88
+ - lib/geist/version.rb
89
+ homepage: https://github.com/koraktor/geist
90
+ licenses: []
91
+
92
+ post_install_message:
93
+ rdoc_options: []
94
+
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ hash: 3
103
+ segments:
104
+ - 0
105
+ version: "0"
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ none: false
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ hash: 3
112
+ segments:
113
+ - 0
114
+ version: "0"
115
+ requirements:
116
+ - git >= 1.6
117
+ rubyforge_project:
118
+ rubygems_version: 1.8.10
119
+ signing_key:
120
+ specification_version: 3
121
+ summary: A Git-backed key-value store
122
+ test_files: []
123
+