treet 0.8.2
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 +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
|