geist 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.
@@ -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
+