treet 0.8.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/Gemfile +6 -0
- data/Guardfile +9 -0
- data/LICENSE +22 -0
- data/README.md +90 -0
- data/Rakefile +8 -0
- data/bin/treet +64 -0
- data/lib/treet/farm.rb +70 -0
- data/lib/treet/hash.rb +219 -0
- data/lib/treet/repo.rb +141 -0
- data/lib/treet/version.rb +3 -0
- data/lib/treet.rb +13 -0
- data/spec/json/bob1.json +24 -0
- data/spec/json/bob2.json +20 -0
- data/spec/json/group.json +15 -0
- data/spec/json/master.json +55 -0
- data/spec/json/one.json +5 -0
- data/spec/json/three.json +17 -0
- data/spec/json/two.json +17 -0
- data/spec/lib/farm_spec.rb +65 -0
- data/spec/lib/hash_spec.rb +132 -0
- data/spec/lib/repo_spec.rb +91 -0
- data/spec/repos/farm1/one/name +1 -0
- data/spec/repos/farm1/two/emails/0 +4 -0
- data/spec/repos/farm1/two/emails/1 +4 -0
- data/spec/repos/farm1/two/name +5 -0
- data/spec/repos/four/emails/0 +4 -0
- data/spec/repos/four/emails/1 +4 -0
- data/spec/repos/four/name +5 -0
- data/spec/repos/one/name +1 -0
- data/spec/repos/three/emails/0 +4 -0
- data/spec/repos/three/emails/1 +4 -0
- data/spec/repos/three/name +5 -0
- data/spec/repos/two/emails/0 +4 -0
- data/spec/repos/two/emails/1 +4 -0
- data/spec/repos/two/name +5 -0
- data/spec/spec_helper.rb +17 -0
- data/treet.gemspec +24 -0
- metadata +195 -0
data/spec/json/bob2.json
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
{
|
2
|
+
"name": {
|
3
|
+
"first": "Bob",
|
4
|
+
"last": "Smith",
|
5
|
+
"full": "Robert Smith"
|
6
|
+
},
|
7
|
+
"emails": [
|
8
|
+
{
|
9
|
+
"label": "home",
|
10
|
+
"email": "bob@newhome.com"
|
11
|
+
},
|
12
|
+
{
|
13
|
+
"label": "work",
|
14
|
+
"email": "bob@work.com"
|
15
|
+
}
|
16
|
+
],
|
17
|
+
"business": {
|
18
|
+
"organization": "Acme Inc."
|
19
|
+
}
|
20
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
{
|
2
|
+
"name": "My Group",
|
3
|
+
"contacts": [
|
4
|
+
"1301BC3D-6630-4C05-A345-DDA5047113FD",
|
5
|
+
"3DBD340E-9E57-487D-9055-AAD9C0716473",
|
6
|
+
"736BA4A8-71DA-434F-814D-FD111D225492",
|
7
|
+
"9FD0E465-4514-42B8-8547-8F51F47444C2",
|
8
|
+
"DC0151AA-09E4-4D8F-9F93-9E94CC36F2DF",
|
9
|
+
"EBADFC69-2254-442E-8EA6-672310454211",
|
10
|
+
"F8F5E33D-E911-4828-B593-30BF8F0D1501"
|
11
|
+
],
|
12
|
+
"xref": {
|
13
|
+
"ab": "F98729F6-AB64-4F99-A833-589562068118"
|
14
|
+
}
|
15
|
+
}
|
@@ -0,0 +1,55 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"name": {
|
4
|
+
"first": "John",
|
5
|
+
"last": "Yaya",
|
6
|
+
"full": "John Yaya"
|
7
|
+
},
|
8
|
+
"emails": [
|
9
|
+
{
|
10
|
+
"label": "work",
|
11
|
+
"email": "johny@yoyodyne.com"
|
12
|
+
}
|
13
|
+
]
|
14
|
+
},
|
15
|
+
{
|
16
|
+
"name": {
|
17
|
+
"first": "John",
|
18
|
+
"last": "Smallberries",
|
19
|
+
"full": "John Smallberries"
|
20
|
+
},
|
21
|
+
"emails": [
|
22
|
+
{
|
23
|
+
"label": "work",
|
24
|
+
"email": "johns@yoyodyne.com"
|
25
|
+
},
|
26
|
+
{
|
27
|
+
"label": "home",
|
28
|
+
"email": "johns@lectroid.com"
|
29
|
+
}
|
30
|
+
],
|
31
|
+
"addresses": [
|
32
|
+
{
|
33
|
+
"label": "home",
|
34
|
+
"planet": "Ten"
|
35
|
+
}
|
36
|
+
]
|
37
|
+
},
|
38
|
+
{
|
39
|
+
"name": {
|
40
|
+
"first": "John",
|
41
|
+
"last": "Bigbooté",
|
42
|
+
"full": "John Bigbooté"
|
43
|
+
},
|
44
|
+
"emails": [
|
45
|
+
{
|
46
|
+
"label": "work",
|
47
|
+
"email": "johnb@yoyodyne.com"
|
48
|
+
},
|
49
|
+
{
|
50
|
+
"label": "home",
|
51
|
+
"email": "johnbig@lectroid.com"
|
52
|
+
}
|
53
|
+
]
|
54
|
+
}
|
55
|
+
]
|
data/spec/json/one.json
ADDED
data/spec/json/two.json
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe "Repository Farm" do
|
5
|
+
it "should export as array of hashes with an xref value" do
|
6
|
+
farm = Treet::Farm.new(:root => "#{File.dirname(__FILE__)}/../repos/farm1", :xref => 'test')
|
7
|
+
farm.export.should == [
|
8
|
+
{
|
9
|
+
'name' => {
|
10
|
+
'full' => 'John Bigbooté'
|
11
|
+
},
|
12
|
+
'xref' => {
|
13
|
+
'test' => 'one'
|
14
|
+
}
|
15
|
+
},
|
16
|
+
{
|
17
|
+
'xref' => {
|
18
|
+
'test' => 'two'
|
19
|
+
},
|
20
|
+
'name' => {
|
21
|
+
'full' => 'John Smallberries',
|
22
|
+
'first' => 'John',
|
23
|
+
'last' => 'Smallberries'
|
24
|
+
},
|
25
|
+
'emails' => [
|
26
|
+
{
|
27
|
+
"label" => "home",
|
28
|
+
"email" => "johns@lectroid.com"
|
29
|
+
},
|
30
|
+
{
|
31
|
+
"label" => "work",
|
32
|
+
"email" => "johns@yoyodyne.com"
|
33
|
+
}
|
34
|
+
]
|
35
|
+
}
|
36
|
+
]
|
37
|
+
end
|
38
|
+
|
39
|
+
it "planting should create a directory of UUID-labeled repos" do
|
40
|
+
farm = Treet::Farm.plant(:json => "#{File.dirname(__FILE__)}/../json/master.json", :root => Dir.mktmpdir)
|
41
|
+
|
42
|
+
Dir.glob("#{farm.root}/*").count.should == 3
|
43
|
+
Dir.glob("#{farm.root}/*/emails/*").count.should == 5
|
44
|
+
Dir.glob("#{farm.root}/*/addresses/*").count.should == 1
|
45
|
+
|
46
|
+
FileUtils.rm_rf(farm.root)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should be retrievable by repo label/xref" do
|
50
|
+
farm = Treet::Farm.new(:root => "#{File.dirname(__FILE__)}/../repos/farm1", :xref => 'test')
|
51
|
+
farm['two'].root.should == "#{File.dirname(__FILE__)}/../repos/farm1/two"
|
52
|
+
farm['two'].to_hash['xref'].should == {'test' => 'two'}
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should take additions" do
|
56
|
+
farm = Treet::Farm.plant(:json => "#{File.dirname(__FILE__)}/../json/master.json", :root => Dir.mktmpdir)
|
57
|
+
|
58
|
+
bob_hash = load_json("bob1")
|
59
|
+
repo = farm.add(bob_hash)
|
60
|
+
repo.root.should =~ /#{farm.root}/
|
61
|
+
Dir.glob("#{farm.root}/*").count.should == 4
|
62
|
+
|
63
|
+
FileUtils.rm_rf(farm.root)
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe "Hash" do
|
5
|
+
it "should inject JSON" do
|
6
|
+
hash = Treet::Hash.new("#{File.dirname(__FILE__)}/../json/one.json")
|
7
|
+
hash.data.should == {
|
8
|
+
'name' => {'full' => 'John Bigbooté'}
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should compare hashes to file trees" do
|
13
|
+
hash = Treet::Hash.new("#{File.dirname(__FILE__)}/../json/one.json")
|
14
|
+
repo = Treet::Repo.new("#{File.dirname(__FILE__)}/../repos/one")
|
15
|
+
hash.compare(repo).should == []
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should generate repo from hash" do
|
19
|
+
hash = Treet::Hash.new("#{File.dirname(__FILE__)}/../json/one.json")
|
20
|
+
|
21
|
+
Dir.mktmpdir do |dir|
|
22
|
+
hash.to_repo(dir)
|
23
|
+
JSON.load(File.open("#{dir}/name")).should == {'full' => 'John Bigbooté'}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should convert arrays to subdirs named with digests" do
|
28
|
+
hash = Treet::Hash.new("#{File.dirname(__FILE__)}/../json/two.json")
|
29
|
+
|
30
|
+
Dir.mktmpdir do |dir|
|
31
|
+
hash.to_repo(dir)
|
32
|
+
Dir.glob("#{dir}/emails/*").count.should == 2
|
33
|
+
emails = Dir.glob("#{dir}/emails/*").map {|f| JSON.load(File.open(f))['email']}.to_set
|
34
|
+
emails.should == ['johns@yoyodyne.com', "johns@lectroid.com"].to_set
|
35
|
+
hash.to_hash['emails'].each do |h|
|
36
|
+
filename = "#{dir}/emails/#{Treet::Hash.digestify(h)}"
|
37
|
+
File.should exist(filename)
|
38
|
+
end
|
39
|
+
# File.read("#{dir}/emails/work/email").should == 'johns@yoyodyne.com'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should compare array objects independently of order" do
|
44
|
+
hash1 = Treet::Hash.new("#{File.dirname(__FILE__)}/../json/two.json")
|
45
|
+
hash2 = Treet::Hash.new("#{File.dirname(__FILE__)}/../json/three.json")
|
46
|
+
|
47
|
+
hash1.compare(hash1).should == []
|
48
|
+
hash1.compare(hash2).should == []
|
49
|
+
|
50
|
+
hash = Treet::Hash.new("#{File.dirname(__FILE__)}/../json/two.json")
|
51
|
+
repo = Treet::Repo.new("#{File.dirname(__FILE__)}/../repos/two")
|
52
|
+
|
53
|
+
hash.compare(repo).should == []
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should find differences independently of order" do
|
57
|
+
hash = Treet::Hash.new("#{File.dirname(__FILE__)}/../json/two.json")
|
58
|
+
repo = Treet::Repo.new("#{File.dirname(__FILE__)}/../repos/three")
|
59
|
+
|
60
|
+
hash.compare(repo).should == [
|
61
|
+
["-", "emails[]", {"label"=>"home", "email"=>"johns@lectroid.com"}],
|
62
|
+
["+", "emails[]", {"label"=>"home", "email"=>"johnsmallberries@lectroid.com"}]
|
63
|
+
]
|
64
|
+
# hash.compare(repo).should == [["~", "email.home.email", "johns@lectroid.com", "johnsmallberries@lectroid.com"]]
|
65
|
+
|
66
|
+
hash = Treet::Hash.new("#{File.dirname(__FILE__)}/../json/two.json")
|
67
|
+
repo = Treet::Repo.new("#{File.dirname(__FILE__)}/../repos/four")
|
68
|
+
|
69
|
+
hash.compare(repo).should == [
|
70
|
+
["-", "emails[]", {"label"=>"home", "email"=>"johns@lectroid.com"}],
|
71
|
+
["+", "emails[]", {"label"=>"home", "email"=>"johnsmallberries@lectroid.com"}]
|
72
|
+
]
|
73
|
+
# hash.compare(repo).should == [["~", "email.home.email", "johns@lectroid.com", "johnsmallberries@lectroid.com"]]
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should expand arrays of strings to empty files" do
|
77
|
+
hash = Treet::Hash.new("#{File.dirname(__FILE__)}/../json/group.json")
|
78
|
+
Dir.mktmpdir do |dir|
|
79
|
+
hash.to_repo(dir)
|
80
|
+
Dir.glob("#{dir}/contacts/*").count.should == 7
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should allow comparison of string (not hash) members" do
|
85
|
+
h1 = Treet::Hash.new({'name' => 'Bob'})
|
86
|
+
h2 = Treet::Hash.new({'name' => 'Sally'})
|
87
|
+
h1.compare(h2).should == [
|
88
|
+
['~', 'name', 'Sally', 'Bob']
|
89
|
+
]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "shallow comparison of hashes" do
|
94
|
+
it "should be blank for identity" do
|
95
|
+
h1 = Treet::Hash.new("#{File.dirname(__FILE__)}/../json/bob1.json")
|
96
|
+
h2 = Treet::Hash.new("#{File.dirname(__FILE__)}/../json/bob1.json")
|
97
|
+
h1.compare(h2).should == []
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should handle keys missing from one or either source hash" do
|
101
|
+
h1 = Treet::Hash.new("#{File.dirname(__FILE__)}/../json/bob1.json")
|
102
|
+
h2 = Treet::Hash.new("#{File.dirname(__FILE__)}/../json/bob2.json")
|
103
|
+
diffs = h1.compare(h2)
|
104
|
+
# diffs.should include(["~", "name.full", "Bob Smith", "Robert Smith"])
|
105
|
+
# diffs.should include(["+", "business.organization", "Acme Inc."])
|
106
|
+
# diffs.should include(["-", "other.notes", "some commentary"])
|
107
|
+
diffs.should == [
|
108
|
+
["~", "name.full", "Robert Smith", "Bob Smith"],
|
109
|
+
["-", "emails[]", {"label"=>"home", "email"=>"bob@home.com"}],
|
110
|
+
["-", "emails[]", {"label"=>"other", "email"=>"bob@vacation.com"}],
|
111
|
+
["+", "emails[]", {"label"=>"home", "email"=>"bob@newhome.com"}],
|
112
|
+
["-", "other.notes", "some commentary"],
|
113
|
+
["+", "business.organization", "Acme Inc."]
|
114
|
+
]
|
115
|
+
|
116
|
+
h3 = h1.patch(diffs)
|
117
|
+
h3.compare(h2).should == []
|
118
|
+
end
|
119
|
+
|
120
|
+
describe "arrays of strings should build lists of filenames" do
|
121
|
+
hash = Treet::Hash.new(
|
122
|
+
:name => 'Foo Bar',
|
123
|
+
:entries => ["abc", "def", "ghi"]
|
124
|
+
)
|
125
|
+
Dir.mktmpdir do |dir|
|
126
|
+
hash.to_repo(dir)
|
127
|
+
Dir.glob("#{dir}/entries/*").count.should == 3
|
128
|
+
File.exist?("#{dir}/entries/def").should == true
|
129
|
+
File.exist?("#{dir}/entries/bogus").should == false
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe "Repo" do
|
5
|
+
it "should convert file trees to hashes" do
|
6
|
+
repo = Treet::Repo.new("#{File.dirname(__FILE__)}/../repos/one")
|
7
|
+
repo.to_hash.should == {
|
8
|
+
'name' => {'full' => 'John Bigbooté'}
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should generate optional " do
|
13
|
+
repo = Treet::Repo.new("#{File.dirname(__FILE__)}/../repos/one")
|
14
|
+
repo.to_hash.should == {
|
15
|
+
'name' => {'full' => 'John Bigbooté'}
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should compare file trees to hashes" do
|
20
|
+
repo = Treet::Repo.new("#{File.dirname(__FILE__)}/../repos/one")
|
21
|
+
repo.compare({'name' => {'full' => 'John Yaya'}}).should == [
|
22
|
+
["~", "name.full", "John Yaya", "John Bigbooté"]
|
23
|
+
]
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should flatten numbered subdirs to arrays" do
|
27
|
+
repo = Treet::Repo.new("#{File.dirname(__FILE__)}/../repos/two")
|
28
|
+
hash = repo.to_hash
|
29
|
+
hash['emails'].to_set.should == [
|
30
|
+
{
|
31
|
+
"label" => "home",
|
32
|
+
"email" => "johns@lectroid.com"
|
33
|
+
},
|
34
|
+
{
|
35
|
+
"label" => "work",
|
36
|
+
"email" => "johns@yoyodyne.com"
|
37
|
+
}
|
38
|
+
].to_set
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should generate file paths correctly from key paths in patches" do
|
42
|
+
Treet::Repo.filefor("name.first").should == [".", "name", "first"]
|
43
|
+
Treet::Repo.filefor("emails[]").should == ['emails', "", nil]
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should added xref keys when specified" do
|
47
|
+
repo = Treet::Repo.new("#{File.dirname(__FILE__)}/../repos/one", :xrefkey => 'foo', :xref => 'bar')
|
48
|
+
repo.to_hash.should == {
|
49
|
+
'name' => {'full' => 'John Bigbooté'},
|
50
|
+
'xref' => {'foo' => 'bar'}
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should take patches that add values to missing elements" do
|
55
|
+
hash = Treet::Hash.new("#{File.dirname(__FILE__)}/../json/one.json")
|
56
|
+
Dir.mktmpdir do |dir|
|
57
|
+
repo = hash.to_repo(dir)
|
58
|
+
repo.patch([
|
59
|
+
[
|
60
|
+
"~",
|
61
|
+
"name.full",
|
62
|
+
"John von Neumann"
|
63
|
+
],
|
64
|
+
[
|
65
|
+
"+",
|
66
|
+
"foo.bar",
|
67
|
+
"new value"
|
68
|
+
]
|
69
|
+
])
|
70
|
+
newhash = repo.to_hash
|
71
|
+
newhash['name']['full'].should == 'John von Neumann'
|
72
|
+
newhash['foo']['bar'].should == 'new value'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should accept patches that edit inside missing subhashes" do
|
77
|
+
hash = Treet::Hash.new("#{File.dirname(__FILE__)}/../json/one.json")
|
78
|
+
Dir.mktmpdir do |dir|
|
79
|
+
repo = hash.to_repo(dir)
|
80
|
+
repo.patch([
|
81
|
+
[
|
82
|
+
"~",
|
83
|
+
"foo.bar",
|
84
|
+
"new value"
|
85
|
+
]
|
86
|
+
])
|
87
|
+
newhash = repo.to_hash
|
88
|
+
newhash['foo']['bar'].should == 'new value'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
{"full":"John Bigbooté"}
|
data/spec/repos/one/name
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
{"full":"John Bigbooté"}
|
data/spec/repos/two/name
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rspec/autorun'
|
2
|
+
|
3
|
+
# Requires supporting ruby files with custom matchers and macros, etc,
|
4
|
+
# in spec/support/ and its subdirectories.
|
5
|
+
# Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
|
6
|
+
|
7
|
+
require File.expand_path('../../lib/treet', __FILE__)
|
8
|
+
|
9
|
+
require "tmpdir"
|
10
|
+
require "fileutils"
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
end
|
14
|
+
|
15
|
+
def load_json(filename)
|
16
|
+
JSON.load(File.open("#{File.dirname(__FILE__)}/json/#{filename}.json"))
|
17
|
+
end
|
data/treet.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/treet/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Jason May"]
|
6
|
+
gem.email = ["jmay@pobox.com"]
|
7
|
+
gem.description = %q{Transform between trees of files and JSON blobs}
|
8
|
+
gem.summary = %q{Transform between trees of files and JSON blobs}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "treet"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Treet::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency 'uuidtools'
|
19
|
+
|
20
|
+
gem.add_development_dependency "rake", "~> 0.9.2"
|
21
|
+
gem.add_development_dependency "rspec", "~> 2.9.0"
|
22
|
+
gem.add_development_dependency "guard-rspec", "~> 0.7.0"
|
23
|
+
gem.add_development_dependency "ruby_gntp", "~> 0.3.4"
|
24
|
+
end
|