gaga 0.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 +5 -0
- data/Gemfile +4 -0
- data/README.md +43 -0
- data/Rakefile +13 -0
- data/gaga.gemspec +23 -0
- data/lib/gaga.rb +168 -0
- data/lib/gaga/version.rb +3 -0
- data/test/gaga_test.rb +93 -0
- data/test/helper.rb +21 -0
- metadata +74 -0
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
Gaga
|
2
|
+
==========
|
3
|
+
|
4
|
+
Git as a key-value store! Build with [Grit](https://github.com/mojombo/grit), it supports SET, GET, KEYS, and DELETE operations. In addition, we can also get the change history key/values.
|
5
|
+
|
6
|
+
It can easily be enhanced to include other git features such as branches, diffs, etc
|
7
|
+
|
8
|
+
Example:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
|
12
|
+
@gaga = Gaga.new(:path => File.expand_path('..', __FILE__))
|
13
|
+
@gaga.clear
|
14
|
+
|
15
|
+
# SET
|
16
|
+
|
17
|
+
@gaga['lady'] = "gaga"
|
18
|
+
|
19
|
+
# GET
|
20
|
+
|
21
|
+
@gaga['lady'] #=> "gaga"
|
22
|
+
|
23
|
+
# KEYS
|
24
|
+
|
25
|
+
@gaga.keys #=> ['lady']
|
26
|
+
|
27
|
+
# DELETE
|
28
|
+
|
29
|
+
@gaga.delete('lady') #=> 'gaga'
|
30
|
+
|
31
|
+
# LOG
|
32
|
+
|
33
|
+
@gaga.log('key')
|
34
|
+
|
35
|
+
# Produces:
|
36
|
+
|
37
|
+
[
|
38
|
+
{"message"=>"all clear","committer"=>{"name"=>"Matt Sears", "email"=>"matt@mattsears.com"}, "committed_date"=>"2011-09-05..."},
|
39
|
+
{"message"=>"set 'lady' ", "committer"=>{"name"=>"Matt Sears", "email"=>"matt@mattsears.com"}, "committed_date"=>"2011-09-05..."}
|
40
|
+
{"message"=>"delete 'lady' ", "committer"=>{"name"=>"Matt Sears", "email"=>"matt@mattsears.com"}, "committed_date"=>"2011-09-05..."}
|
41
|
+
]
|
42
|
+
|
43
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rake/testtask'
|
5
|
+
task :default => :test
|
6
|
+
|
7
|
+
desc 'Run tests (default)'
|
8
|
+
Rake::TestTask.new(:test) do |t|
|
9
|
+
t.test_files = FileList['test/**/*_test.rb']
|
10
|
+
t.ruby_opts = ['-Itest', '-W0']
|
11
|
+
t.libs << "lib" << "test"
|
12
|
+
t.ruby_opts << '-rubygems' if defined? Gem
|
13
|
+
end
|
data/gaga.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "gaga/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "gaga"
|
7
|
+
s.version = Gaga::VERSION
|
8
|
+
s.authors = ["Matt Sears"]
|
9
|
+
s.email = ["matt@mattsears.com"]
|
10
|
+
s.homepage = "http://github.com/mattsears/gaga"
|
11
|
+
s.summary = %q{Git as a key value store}
|
12
|
+
s.description = %q{Git as a key value store}
|
13
|
+
s.rubyforge_project = "gaga"
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
# specify any dependencies here; for example:
|
21
|
+
# s.add_development_dependency "rspec"
|
22
|
+
s.add_runtime_dependency "grit"
|
23
|
+
end
|
data/lib/gaga.rb
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'grit'
|
3
|
+
require 'gaga/version'
|
4
|
+
|
5
|
+
class Gaga
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
@options = options
|
9
|
+
unless ::File.exists?(File.join(path,'.git'))
|
10
|
+
Grit::Repo.init(path)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Add the value to the to the store
|
15
|
+
#
|
16
|
+
# Example
|
17
|
+
# @store.set('key', 'value')
|
18
|
+
#
|
19
|
+
# Returns nothing
|
20
|
+
def set(key, value)
|
21
|
+
save("set '#{key}'") do |index|
|
22
|
+
index.add(key_for(key), encode(value))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Shortcut for #set
|
27
|
+
#
|
28
|
+
# Example:
|
29
|
+
# @store[key] = 'value'
|
30
|
+
#
|
31
|
+
def []=(key, value)
|
32
|
+
set(key, value)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Retrieve the value for the given key with a default value
|
36
|
+
#
|
37
|
+
# Example:
|
38
|
+
# @store.get(key) #=> value
|
39
|
+
#
|
40
|
+
# Returns the object found in the repo matching the key
|
41
|
+
def get(key, value = nil, *)
|
42
|
+
if head && blob = head.commit.tree / key_for(key)
|
43
|
+
decode(blob.data)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Shortcut for #get
|
48
|
+
#
|
49
|
+
# Example:
|
50
|
+
# @store['key'] #=> value
|
51
|
+
#
|
52
|
+
def [](key)
|
53
|
+
get(key)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns an array of key names contained in store
|
57
|
+
#
|
58
|
+
# Example:
|
59
|
+
# @store.keys #=> ['key1', 'key2']
|
60
|
+
#
|
61
|
+
def keys
|
62
|
+
head.commit.tree.contents.map{|blob| deserialize(blob.name) }
|
63
|
+
end
|
64
|
+
|
65
|
+
# Deletes commits matching the given key
|
66
|
+
#
|
67
|
+
# Example:
|
68
|
+
# @store.delete('key')
|
69
|
+
#
|
70
|
+
# Returns nothing
|
71
|
+
def delete(key, *)
|
72
|
+
self[key].tap do
|
73
|
+
save("deleted #{key}") {|index| index.delete(key_for(key)) }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Deletes all contents of the store
|
78
|
+
#
|
79
|
+
# Returns nothing
|
80
|
+
def clear
|
81
|
+
save("all clear") do |index|
|
82
|
+
if tree = index.current_tree
|
83
|
+
tree.contents.each do |entry|
|
84
|
+
index.delete(key_for(entry.name))
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# The commit log for the given key
|
91
|
+
#
|
92
|
+
# Example:
|
93
|
+
# @store.log('key') #=> [{"message"=>"Updated key"...}]
|
94
|
+
#
|
95
|
+
# Returns Array of commit data
|
96
|
+
def log(key)
|
97
|
+
git.log(branch, key_for(key)).map{ |commit| commit.to_hash }
|
98
|
+
end
|
99
|
+
|
100
|
+
# Find the key if exists in the git repo
|
101
|
+
#
|
102
|
+
# Example:
|
103
|
+
# @store.key? 'key' #=> true
|
104
|
+
#
|
105
|
+
# Returns true if found; false if not found
|
106
|
+
def key?(key)
|
107
|
+
!(head && head.commit.tree / key_for(key)).nil?
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
# Format the given key so that it ensures it's git worthy
|
113
|
+
def key_for(key)
|
114
|
+
key.is_a?(String) ? key : serialize(key)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Given the file path, return a new Grit::Repo if found
|
118
|
+
def git
|
119
|
+
@git ||= Grit::Repo.new(path)
|
120
|
+
end
|
121
|
+
|
122
|
+
# The git branch to use for this store
|
123
|
+
def branch
|
124
|
+
@options[:branch] || 'master'
|
125
|
+
end
|
126
|
+
|
127
|
+
# Checks out the branch on the repo
|
128
|
+
def head
|
129
|
+
git.get_head(branch)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Commits the the value into the git repository with the given commit message
|
133
|
+
def save(message)
|
134
|
+
index = git.index
|
135
|
+
if head
|
136
|
+
commit = head.commit
|
137
|
+
index.current_tree = commit.tree
|
138
|
+
end
|
139
|
+
yield index
|
140
|
+
index.commit(message, :parents => Array(commit), :head => branch) if index.tree.any?
|
141
|
+
end
|
142
|
+
|
143
|
+
# Converts the value to yaml format
|
144
|
+
def encode(value)
|
145
|
+
value.to_yaml
|
146
|
+
end
|
147
|
+
|
148
|
+
# Loads value as a Yaml structure
|
149
|
+
def decode(value)
|
150
|
+
YAML.load(value)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Convert value to byte stream. This allows keys to be objects too
|
154
|
+
def serialize(value)
|
155
|
+
Marshal.dump(value)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Converts value back to an object.
|
159
|
+
def deserialize(value)
|
160
|
+
Marshal.restore(value) rescue value
|
161
|
+
end
|
162
|
+
|
163
|
+
# Given that repo path set in the options, return the expanded file path
|
164
|
+
def path(key = '')
|
165
|
+
@path ||= File.join(File.expand_path(@options[:repo]), key)
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
data/lib/gaga/version.rb
ADDED
data/test/gaga_test.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
require 'helper'
|
3
|
+
|
4
|
+
describe Gaga do
|
5
|
+
|
6
|
+
@types = {
|
7
|
+
"String" => ["lady", "gaga"],
|
8
|
+
"Object" => [{:lady => :gaga}, {:gaga => :ohai}]
|
9
|
+
}
|
10
|
+
|
11
|
+
before do
|
12
|
+
@store = Gaga.new(:repo => tmp_dir)
|
13
|
+
@store.clear
|
14
|
+
end
|
15
|
+
|
16
|
+
@types.each do |type, (key, key2)|
|
17
|
+
|
18
|
+
it "writes String values to keys" do
|
19
|
+
@store[key] = "value"
|
20
|
+
@store[key].must_equal "value"
|
21
|
+
end
|
22
|
+
|
23
|
+
it "reads from keys" do
|
24
|
+
@store[key].must_be_nil
|
25
|
+
end
|
26
|
+
|
27
|
+
it "returns a list of keys" do
|
28
|
+
@store[key] = "value"
|
29
|
+
@store.keys.must_include(key)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "guarantees that a different String value is retrieved" do
|
33
|
+
value = "value"
|
34
|
+
@store[key] = value
|
35
|
+
@store[key].wont_be_same_as(value)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "writes Object values to keys" do
|
39
|
+
@store[key] = {:foo => :bar}
|
40
|
+
@store[key].must_equal({:foo => :bar})
|
41
|
+
end
|
42
|
+
|
43
|
+
it "guarantees that a different Object value is retrieved" do
|
44
|
+
value = {:foo => :bar}
|
45
|
+
@store[key] = value
|
46
|
+
@store[key].wont_be_same_as(:foo => :bar)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "returns false from key? if a key is not available" do
|
50
|
+
@store.key?(key).must_equal false
|
51
|
+
end
|
52
|
+
|
53
|
+
it "returns true from key? if a key is available" do
|
54
|
+
@store[key] = "value"
|
55
|
+
@store.key?(key).must_equal true
|
56
|
+
end
|
57
|
+
|
58
|
+
it "removes and return an element with a key from the store via delete if it exists" do
|
59
|
+
@store[key] = "value"
|
60
|
+
@store.delete(key).must_equal "value"
|
61
|
+
@store.key?(key).must_equal false
|
62
|
+
end
|
63
|
+
|
64
|
+
it "returns nil from delete if an element for a key does not exist" do
|
65
|
+
@store.delete(key).must_be_nil
|
66
|
+
end
|
67
|
+
|
68
|
+
it "removes all keys from the store with clear" do
|
69
|
+
@store[key] = "value"
|
70
|
+
@store[key2] = "value2"
|
71
|
+
@store.clear
|
72
|
+
@store.key?(key).wont_equal true
|
73
|
+
@store.key?(key2).wont_equal true
|
74
|
+
end
|
75
|
+
|
76
|
+
it "does not run the block if the #{type} key is available" do
|
77
|
+
@store[key] = "value"
|
78
|
+
unaltered = "unaltered"
|
79
|
+
@store.get(key) { unaltered = "altered" }
|
80
|
+
unaltered.must_equal "unaltered"
|
81
|
+
end
|
82
|
+
|
83
|
+
it "stores #{key} values with #set" do
|
84
|
+
@store.set(key, "value")
|
85
|
+
@store[key].must_equal "value"
|
86
|
+
end
|
87
|
+
|
88
|
+
it "returns a list of commit history for the key" do
|
89
|
+
@store.log(key).wont_be_empty
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require "bundler"
|
2
|
+
Bundler.setup
|
3
|
+
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'minitest/pride'
|
6
|
+
|
7
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
8
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
9
|
+
|
10
|
+
require 'gaga'
|
11
|
+
|
12
|
+
TMP_DIR = '/tmp/gaga_test'
|
13
|
+
|
14
|
+
def tmp_dir
|
15
|
+
TMP_DIR
|
16
|
+
end
|
17
|
+
|
18
|
+
def remove_tmpdir!(passed_dir = nil)
|
19
|
+
FileUtils.rm_rf(passed_dir || tmp_dir)
|
20
|
+
end
|
21
|
+
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gaga
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Matt Sears
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-10-23 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: grit
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
type: :runtime
|
25
|
+
version_requirements: *id001
|
26
|
+
description: Git as a key value store
|
27
|
+
email:
|
28
|
+
- matt@mattsears.com
|
29
|
+
executables: []
|
30
|
+
|
31
|
+
extensions: []
|
32
|
+
|
33
|
+
extra_rdoc_files: []
|
34
|
+
|
35
|
+
files:
|
36
|
+
- .gitignore
|
37
|
+
- Gemfile
|
38
|
+
- README.md
|
39
|
+
- Rakefile
|
40
|
+
- gaga.gemspec
|
41
|
+
- lib/gaga.rb
|
42
|
+
- lib/gaga/version.rb
|
43
|
+
- test/gaga_test.rb
|
44
|
+
- test/helper.rb
|
45
|
+
homepage: http://github.com/mattsears/gaga
|
46
|
+
licenses: []
|
47
|
+
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: "0"
|
65
|
+
requirements: []
|
66
|
+
|
67
|
+
rubyforge_project: gaga
|
68
|
+
rubygems_version: 1.8.10
|
69
|
+
signing_key:
|
70
|
+
specification_version: 3
|
71
|
+
summary: Git as a key value store
|
72
|
+
test_files:
|
73
|
+
- test/gaga_test.rb
|
74
|
+
- test/helper.rb
|