treet 0.8.2 → 0.10.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 +0 -1
- data/.travis.yml +4 -0
- data/Gemfile +0 -2
- data/Guardfile +19 -0
- data/lib/treet/farm.rb +13 -10
- data/lib/treet/gitfarm.rb +28 -0
- data/lib/treet/gitrepo.rb +180 -0
- data/lib/treet/hash.rb +11 -5
- data/lib/treet/repo.rb +40 -28
- data/lib/treet/version.rb +1 -1
- data/lib/treet.rb +9 -4
- data/spec/json/four.json +8 -0
- data/spec/lib/repo_spec.rb +115 -7
- data/spec/lib/{farm_spec.rb → test_farm.rb} +21 -18
- data/spec/lib/test_gitfarm.rb +63 -0
- data/spec/lib/test_gitrepo.rb +346 -0
- data/spec/test_helper.rb +25 -0
- data/treet.gemspec +7 -5
- metadata +66 -23
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/Guardfile
CHANGED
@@ -1,9 +1,28 @@
|
|
1
1
|
# A sample Guardfile
|
2
2
|
# More info at https://github.com/guard/guard#readme
|
3
3
|
|
4
|
+
guard 'minitest', test_folders: 'spec', test_file_patterns: 'test_*.rb' do
|
5
|
+
# with Minitest::Unit
|
6
|
+
# watch(%r{^spec/lib/test_.*\.rb})
|
7
|
+
|
8
|
+
# watch(%r|^test/(.*)\/?test_(.*)\.rb|)
|
9
|
+
# watch(%r|^lib/(.*)([^/]+)\.rb|) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
|
10
|
+
|
11
|
+
# watch(%r|^lib/otherbase/contacts?.rb|) { "test" }
|
12
|
+
|
13
|
+
# with Minitest::Spec
|
14
|
+
watch(%r|^spec/lib/test_(.*)\.rb|)
|
15
|
+
watch(%r{^lib/treet/(.+)\.rb$}) { |m| "spec/lib/test_#{m[1]}.rb" }
|
16
|
+
watch(%r|^spec/test_helper\.rb|) { "spec" }
|
17
|
+
# watch(%r|^lib/(.*)([^/]+)\.rb|) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
|
18
|
+
# watch(%r|^spec/spec_helper\.rb|) { "spec" }
|
19
|
+
end
|
20
|
+
|
4
21
|
guard 'rspec', :version => 2 do
|
5
22
|
watch(%r{^spec/.+_spec\.rb$})
|
23
|
+
|
6
24
|
watch(%r{^lib/treet/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
7
25
|
watch(%r{^(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
8
26
|
watch('spec/spec_helper.rb') { "spec" }
|
9
27
|
end
|
28
|
+
|
data/lib/treet/farm.rb
CHANGED
@@ -1,23 +1,24 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "securerandom"
|
4
4
|
|
5
5
|
class Treet::Farm
|
6
|
-
attr_reader :root, :xrefkey
|
6
|
+
attr_reader :root, :xrefkey, :repotype
|
7
7
|
|
8
8
|
def initialize(opts)
|
9
9
|
raise Errno::ENOENT unless File.directory?(opts[:root])
|
10
10
|
|
11
11
|
@root = opts[:root]
|
12
12
|
@xrefkey = opts[:xref]
|
13
|
+
@repotype = opts[:repotype] || Treet::Repo
|
13
14
|
end
|
14
15
|
|
15
|
-
def repos
|
16
|
+
def repos(opts = {})
|
16
17
|
@repos_cache ||= Dir.glob("#{root}/*").each_with_object({}) do |subdir,h|
|
17
18
|
# in a Farm we are looking for repositories under the root
|
18
19
|
if File.directory?(subdir)
|
19
20
|
xref = File.basename(subdir)
|
20
|
-
h[xref] =
|
21
|
+
h[xref] = repotype.new(subdir, opts.merge(:xrefkey => xrefkey, :xref => xref))
|
21
22
|
end
|
22
23
|
end
|
23
24
|
end
|
@@ -41,13 +42,13 @@ class Treet::Farm
|
|
41
42
|
array_of_hashes = JSON.load(File.open(jsonfile))
|
42
43
|
Dir.chdir(rootdir) do
|
43
44
|
array_of_hashes.each do |h|
|
44
|
-
uuid =
|
45
|
+
uuid = SecureRandom.uuid
|
45
46
|
thash = Treet::Hash.new(h)
|
46
|
-
thash.to_repo(uuid)
|
47
|
+
thash.to_repo(uuid, opts)
|
47
48
|
end
|
48
49
|
end
|
49
50
|
|
50
|
-
|
51
|
+
self.new(opts)
|
51
52
|
end
|
52
53
|
|
53
54
|
# apply patches to a farm of repos
|
@@ -62,9 +63,11 @@ class Treet::Farm
|
|
62
63
|
end
|
63
64
|
|
64
65
|
# add a new repo, with data from an input hash
|
65
|
-
|
66
|
-
|
66
|
+
# if an :id is provided, then the new repo will be stored under that directory name,
|
67
|
+
# otherwise a unique id will be generated
|
68
|
+
def add(hash, opts = {})
|
69
|
+
uuid = opts[:id] || UUIDTools::UUID.random_create.to_s
|
67
70
|
thash = Treet::Hash.new(hash)
|
68
|
-
thash.to_repo("#{root}/#{uuid}")
|
71
|
+
repos[uuid] = thash.to_repo("#{root}/#{uuid}", opts.merge(:repotype => repotype))
|
69
72
|
end
|
70
73
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
class Treet::Gitfarm < Treet::Farm
|
4
|
+
attr_reader :author
|
5
|
+
|
6
|
+
def initialize(opts)
|
7
|
+
raise ArgumentError, "No git farm without an author for commits" unless opts[:author]
|
8
|
+
super
|
9
|
+
@repotype = Treet::Gitrepo
|
10
|
+
@author = opts[:author]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.plant(opts)
|
14
|
+
super(opts.merge(:repotype => Treet::Gitrepo))
|
15
|
+
end
|
16
|
+
|
17
|
+
def repos
|
18
|
+
super(:author => author)
|
19
|
+
end
|
20
|
+
|
21
|
+
def add(hash, opts = {})
|
22
|
+
repo = super(hash, opts.merge(:author => author))
|
23
|
+
if opts[:tag]
|
24
|
+
repo.tag(opts[:tag])
|
25
|
+
end
|
26
|
+
repo
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require "rugged"
|
4
|
+
require "forwardable"
|
5
|
+
|
6
|
+
class Treet::Gitrepo < Treet::Repo
|
7
|
+
extend Forwardable
|
8
|
+
def_delegators :@gitrepo, :head, :refs, :index
|
9
|
+
|
10
|
+
def initialize(path, opts = {})
|
11
|
+
raise ArgumentError, "author required for updates" unless opts[:author]
|
12
|
+
super
|
13
|
+
|
14
|
+
@author = opts[:author]
|
15
|
+
|
16
|
+
begin
|
17
|
+
@gitrepo = Rugged::Repository.new(root)
|
18
|
+
rescue Rugged::RepositoryError
|
19
|
+
@gitrepo = initialize_gitrepo
|
20
|
+
add_and_commit!
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def tags
|
25
|
+
gitrepo.refs(/tags/)
|
26
|
+
end
|
27
|
+
|
28
|
+
def tag(tagname)
|
29
|
+
refname = "refs/tags/#{tagname}"
|
30
|
+
begin
|
31
|
+
if tag_ref = Rugged::Reference.lookup(gitrepo, refname)
|
32
|
+
# move an existing tag
|
33
|
+
tag_ref.target = head.target
|
34
|
+
else
|
35
|
+
# new tag
|
36
|
+
Rugged::Reference.create(gitrepo, refname, head.target)
|
37
|
+
end
|
38
|
+
rescue Rugged::ReferenceError
|
39
|
+
# invalid string for source, e.g. blank or illegal punctuation (colons)
|
40
|
+
raise ArgumentError "invalid source string '#{tagname}' for repository tagging"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def patch(patchdef)
|
45
|
+
super
|
46
|
+
add_and_commit!
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_hash(opts = {})
|
50
|
+
if opts[:commit]
|
51
|
+
snapshot(opts[:commit])
|
52
|
+
elsif opts[:tag]
|
53
|
+
tag_snapshot(opts[:tag])
|
54
|
+
else
|
55
|
+
# snapshot(head.target)
|
56
|
+
super()
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def entries
|
61
|
+
index.entries.map{|e| e[:path]}
|
62
|
+
end
|
63
|
+
|
64
|
+
def branches
|
65
|
+
refs(/heads/).map(&:name) - ['refs/heads/master']
|
66
|
+
end
|
67
|
+
|
68
|
+
# always branch from tip of master (private representation)
|
69
|
+
def branch(name)
|
70
|
+
Rugged::Reference.create(gitrepo, "refs/heads/#{name}", head.target)
|
71
|
+
end
|
72
|
+
|
73
|
+
def version(opts = {})
|
74
|
+
if tag = opts[:tag]
|
75
|
+
ref = Rugged::Reference.lookup(gitrepo, "refs/tags/#{tag}")
|
76
|
+
ref && ref.target
|
77
|
+
else
|
78
|
+
head.target
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
attr_reader :gitrepo, :author
|
85
|
+
|
86
|
+
def initialize_gitrepo
|
87
|
+
Rugged::Repository.init_at(root, false)
|
88
|
+
end
|
89
|
+
|
90
|
+
# always commits to HEAD
|
91
|
+
def commit!(sha)
|
92
|
+
parent_shas = begin
|
93
|
+
[gitrepo.head.target]
|
94
|
+
rescue Rugged::ReferenceError
|
95
|
+
# this is the first commit
|
96
|
+
[]
|
97
|
+
end
|
98
|
+
|
99
|
+
authorship = author.merge(:time => Time.now)
|
100
|
+
|
101
|
+
sha = Rugged::Commit.create(gitrepo,
|
102
|
+
:message => "",
|
103
|
+
:author => authorship,
|
104
|
+
:committer => authorship,
|
105
|
+
:parents => parent_shas,
|
106
|
+
:tree => sha,
|
107
|
+
:update_ref => "HEAD"
|
108
|
+
)
|
109
|
+
|
110
|
+
sha
|
111
|
+
end
|
112
|
+
|
113
|
+
def add_and_commit!
|
114
|
+
current_index = entries
|
115
|
+
Dir.chdir(root) do
|
116
|
+
current_files = Dir.glob('**/*')
|
117
|
+
|
118
|
+
# additions
|
119
|
+
(current_files - current_index).each do |file|
|
120
|
+
# must add each filename explicitly, `index#add` does not recurse into directories
|
121
|
+
if File.file?(file)
|
122
|
+
index.add(file)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# possible alterations - these changes won't be detected unless we explicitly git-add
|
127
|
+
(current_files & current_index).each do |file|
|
128
|
+
if File.file?(file)
|
129
|
+
index.add(file)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# deletions
|
134
|
+
(current_index - current_files).each do |file|
|
135
|
+
# `index#remove` handles directories
|
136
|
+
index.remove(file)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
index.write
|
141
|
+
tree_sha = index.write_tree
|
142
|
+
commit!(tree_sha)
|
143
|
+
end
|
144
|
+
|
145
|
+
def snapshot(commit_sha)
|
146
|
+
commit = gitrepo.lookup(commit_sha)
|
147
|
+
tree = commit.tree
|
148
|
+
# must traverse the tree: entries are files or subtrees
|
149
|
+
data = {}
|
150
|
+
tree.each do |obj|
|
151
|
+
data[obj[:name]] = case obj[:type]
|
152
|
+
when :blob
|
153
|
+
begin
|
154
|
+
JSON.load(gitrepo.read(obj[:oid]).data)
|
155
|
+
rescue JSON::ParserError
|
156
|
+
raise JSON::ParserError, "bad JSON in blob #{obj[:name]}: #{gitrepo.read(obj[:oid]).data}"
|
157
|
+
end
|
158
|
+
when :tree
|
159
|
+
begin
|
160
|
+
gitrepo.lookup(obj[:oid]).each_with_object([]) do |subobj,d|
|
161
|
+
d << JSON.load(gitrepo.read(subobj[:oid]).data)
|
162
|
+
end
|
163
|
+
rescue JSON::ParserError
|
164
|
+
raise JSON::ParserError, "bad JSON in tree #{obj[:name]}: #{gitrepo.read(obj[:oid]).data}"
|
165
|
+
end
|
166
|
+
else
|
167
|
+
raise TypeError, "UNRECOGNIZED GIT OBJECT TYPE #{obj[:type]}"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# decorate(data)
|
172
|
+
data
|
173
|
+
end
|
174
|
+
|
175
|
+
def tag_snapshot(tagname)
|
176
|
+
tag_ref = Rugged::Reference.lookup(gitrepo, "refs/tags/#{tagname}")
|
177
|
+
raise ArgumentError, "tag '#{tagname}' does not exist in this repo" unless tag_ref
|
178
|
+
snapshot(tag_ref.target)
|
179
|
+
end
|
180
|
+
end
|
data/lib/treet/hash.rb
CHANGED
@@ -22,9 +22,10 @@ class Treet::Hash
|
|
22
22
|
@data = normalize(d)
|
23
23
|
end
|
24
24
|
|
25
|
-
def to_repo(root)
|
25
|
+
def to_repo(root, opts = {})
|
26
26
|
construct(data, root)
|
27
|
-
Treet::Repo
|
27
|
+
repotype = opts[:repotype] || Treet::Repo
|
28
|
+
repotype.new(root, opts)
|
28
29
|
end
|
29
30
|
|
30
31
|
def to_hash
|
@@ -42,8 +43,13 @@ class Treet::Hash
|
|
42
43
|
Treet::Hash.new(newhash)
|
43
44
|
end
|
44
45
|
|
45
|
-
def self.digestify(
|
46
|
-
|
46
|
+
def self.digestify(data)
|
47
|
+
case data
|
48
|
+
when Hash
|
49
|
+
Digest::SHA1.hexdigest(data.to_a.sort.flatten.join)
|
50
|
+
else # String
|
51
|
+
data
|
52
|
+
end
|
47
53
|
end
|
48
54
|
|
49
55
|
private
|
@@ -66,7 +72,7 @@ class Treet::Hash
|
|
66
72
|
case v2
|
67
73
|
when String
|
68
74
|
# create empty file with this name
|
69
|
-
|
75
|
+
FileUtils.touch("#{k}/#{v2}")
|
70
76
|
|
71
77
|
else
|
72
78
|
# store object contents as JSON into a generated filename
|
data/lib/treet/repo.rb
CHANGED
@@ -1,25 +1,24 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
# require 'hashdiff'
|
4
|
-
|
5
3
|
class Treet::Repo
|
6
|
-
attr_reader :root
|
4
|
+
attr_reader :root
|
7
5
|
|
8
6
|
def initialize(path, opts = {})
|
9
|
-
|
7
|
+
raise "Missing or invalid source path #{path}" unless File.directory?(path)
|
10
8
|
|
11
9
|
@root = path
|
12
|
-
raise "Missing or invalid source path #{path}" unless File.directory?(path)
|
13
|
-
@opts = opts
|
14
10
|
end
|
15
11
|
|
16
12
|
def to_hash
|
17
|
-
|
13
|
+
expand(root)
|
14
|
+
end
|
15
|
+
|
16
|
+
def reset
|
17
|
+
# clear any cached data
|
18
18
|
end
|
19
19
|
|
20
20
|
def compare(target)
|
21
21
|
Treet::Hash.diff(to_hash, target.to_hash)
|
22
|
-
# HashDiff.diff(to_hash, hash)
|
23
22
|
end
|
24
23
|
|
25
24
|
# patch keys can look like
|
@@ -28,14 +27,14 @@ class Treet::Repo
|
|
28
27
|
# (address[1] syntax has been eliminated, we recognize array elements by matching the entire content)
|
29
28
|
def self.filefor(keyname)
|
30
29
|
if keyname =~ /\[/
|
31
|
-
keyname
|
30
|
+
keyname = keyname.match(/^(.*)\[\]$/).captures.first
|
32
31
|
[keyname, '', nil]
|
33
32
|
elsif keyname =~ /\./
|
34
33
|
# subelement
|
35
34
|
filename,field = keyname.split('.')
|
36
35
|
['.', filename, field]
|
37
36
|
else
|
38
|
-
[
|
37
|
+
['.', keyname]
|
39
38
|
end
|
40
39
|
end
|
41
40
|
|
@@ -47,24 +46,31 @@ class Treet::Repo
|
|
47
46
|
Dir.chdir(root) do
|
48
47
|
diffs.each do |diff|
|
49
48
|
flag, key, v1, v2 = diff
|
50
|
-
if key =~ /\[/
|
51
|
-
|
52
|
-
elsif key =~ /\./
|
53
|
-
|
54
|
-
else
|
55
|
-
|
56
|
-
end
|
49
|
+
# if key =~ /\[/
|
50
|
+
# keyname = key.match(/^(.*)\[\]$/).captures
|
51
|
+
# elsif key =~ /\./
|
52
|
+
# keyname, subkey = key.match(/^(.*)\.(.*)$/).captures
|
53
|
+
# else
|
54
|
+
# keyname = key
|
55
|
+
# end
|
57
56
|
|
58
57
|
dirname, filename, fieldname = Treet::Repo.filefor(key)
|
59
58
|
filepath = "#{dirname}/#{filename}"
|
59
|
+
|
60
60
|
case flag
|
61
61
|
when '~'
|
62
62
|
# change a value in place
|
63
63
|
# load the current data & overwrite with the new value
|
64
64
|
# idempotent: this will overwrite the file with the same contents
|
65
|
-
|
66
|
-
|
67
|
-
|
65
|
+
if fieldname
|
66
|
+
# hash entry
|
67
|
+
data = File.exists?(filepath) ? JSON.load(File.open(filepath)) : {}
|
68
|
+
data[fieldname] = v1
|
69
|
+
File.open(filepath, "w") {|f| f << JSON.pretty_generate(data)}
|
70
|
+
else
|
71
|
+
# string entry
|
72
|
+
File.open(filepath, "w") {|f| f << v1}
|
73
|
+
end
|
68
74
|
|
69
75
|
when '+'
|
70
76
|
# add something
|
@@ -80,19 +86,31 @@ class Treet::Repo
|
|
80
86
|
# idempotent: this will overwrite the file with the same contents
|
81
87
|
subfile = "#{dirname}/#{Treet::Hash.digestify(v1)}"
|
82
88
|
Dir.mkdir(dirname) unless Dir.exists?(dirname)
|
83
|
-
|
89
|
+
case v1
|
90
|
+
when Hash
|
91
|
+
# hash entry
|
92
|
+
File.open(subfile, "w") {|f| f << JSON.pretty_generate(v1)}
|
93
|
+
else
|
94
|
+
# string entry - create empty file with this name
|
95
|
+
FileUtils.touch(subfile)
|
96
|
+
end
|
84
97
|
end
|
85
98
|
|
86
99
|
when '-'
|
87
100
|
# remove something
|
88
101
|
if fieldname
|
102
|
+
# this is a key in a subhash
|
89
103
|
data = JSON.load(File.open(filepath))
|
90
104
|
data.delete(fieldname)
|
91
105
|
if data.empty?
|
106
|
+
# all keys have been removed, clean up the file
|
92
107
|
File.delete(filename)
|
93
108
|
else
|
94
109
|
File.open(filepath, "w") {|f| f << JSON.pretty_generate(data)}
|
95
110
|
end
|
111
|
+
elsif dirname == "."
|
112
|
+
# this is a top-level string
|
113
|
+
File.delete(filename) if File.exists?(filename) # need the existence check for idempotence
|
96
114
|
else
|
97
115
|
# this is an array, we look for a match on the entire contents via digest
|
98
116
|
subfile = "#{dirname}/#{Treet::Hash.digestify(v1)}"
|
@@ -130,12 +148,6 @@ class Treet::Repo
|
|
130
148
|
|
131
149
|
def expand(path)
|
132
150
|
files = Dir.entries(path).select {|f| f !~ /^\./}
|
133
|
-
|
134
|
-
|
135
|
-
if opts[:xrefkey]
|
136
|
-
hash['xref'] ||= {}
|
137
|
-
hash['xref'][opts[:xrefkey]] = opts[:xref]
|
138
|
-
end
|
139
|
-
hash
|
151
|
+
files.each_with_object({}) {|f,h| h[f] = expand_json("#{path}/#{f}")}
|
140
152
|
end
|
141
153
|
end
|
data/lib/treet/version.rb
CHANGED
data/lib/treet.rb
CHANGED
@@ -6,8 +6,13 @@ unless Kernel.respond_to?(:require_relative)
|
|
6
6
|
end
|
7
7
|
end
|
8
8
|
|
9
|
-
|
9
|
+
%w(version hash repo farm).each do |f|
|
10
|
+
require_relative "treet/#{f}"
|
11
|
+
end
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
13
|
+
# under MacRuby 0.12, the `rugged` gem causes seg fault, sometimes merely on require()
|
14
|
+
unless defined? MACRUBY_VERSION
|
15
|
+
%w(gitrepo gitfarm).each do |f|
|
16
|
+
require_relative "treet/#{f}"
|
17
|
+
end
|
18
|
+
end
|
data/spec/json/four.json
ADDED
data/spec/lib/repo_spec.rb
CHANGED
@@ -9,6 +9,17 @@ describe "Repo" do
|
|
9
9
|
}
|
10
10
|
end
|
11
11
|
|
12
|
+
it "should reload on reset" do
|
13
|
+
repo = Treet::Repo.new("#{File.dirname(__FILE__)}/../repos/one")
|
14
|
+
repo.to_hash.should == {
|
15
|
+
'name' => {'full' => 'John Bigbooté'}
|
16
|
+
}
|
17
|
+
repo.reset
|
18
|
+
repo.to_hash.should == {
|
19
|
+
'name' => {'full' => 'John Bigbooté'}
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
12
23
|
it "should generate optional " do
|
13
24
|
repo = Treet::Repo.new("#{File.dirname(__FILE__)}/../repos/one")
|
14
25
|
repo.to_hash.should == {
|
@@ -41,15 +52,16 @@ describe "Repo" do
|
|
41
52
|
it "should generate file paths correctly from key paths in patches" do
|
42
53
|
Treet::Repo.filefor("name.first").should == [".", "name", "first"]
|
43
54
|
Treet::Repo.filefor("emails[]").should == ['emails', "", nil]
|
55
|
+
Treet::Repo.filefor("topvalue").should == ['.', "topvalue"]
|
44
56
|
end
|
45
57
|
|
46
|
-
it "should
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
end
|
58
|
+
# it "should add xref keys when specified" do
|
59
|
+
# repo = Treet::Repo.new("#{File.dirname(__FILE__)}/../repos/one", :xrefkey => 'foo', :xref => 'bar')
|
60
|
+
# repo.to_hash.should == {
|
61
|
+
# 'name' => {'full' => 'John Bigbooté'},
|
62
|
+
# 'xref' => {'foo' => 'bar'}
|
63
|
+
# }
|
64
|
+
# end
|
53
65
|
|
54
66
|
it "should take patches that add values to missing elements" do
|
55
67
|
hash = Treet::Hash.new("#{File.dirname(__FILE__)}/../json/one.json")
|
@@ -88,4 +100,100 @@ describe "Repo" do
|
|
88
100
|
newhash['foo']['bar'].should == 'new value'
|
89
101
|
end
|
90
102
|
end
|
103
|
+
|
104
|
+
it "should accept patches that update top-level values" do
|
105
|
+
hash = Treet::Hash.new("#{File.dirname(__FILE__)}/../json/four.json")
|
106
|
+
Dir.mktmpdir do |dir|
|
107
|
+
repo = hash.to_repo(dir)
|
108
|
+
repo.patch([
|
109
|
+
[
|
110
|
+
"~",
|
111
|
+
"title",
|
112
|
+
"Updated Title"
|
113
|
+
]
|
114
|
+
])
|
115
|
+
newhash = repo.to_hash
|
116
|
+
newhash['title'].should == 'Updated Title'
|
117
|
+
|
118
|
+
repo.patch([
|
119
|
+
[
|
120
|
+
"-",
|
121
|
+
"datalist[]",
|
122
|
+
"two"
|
123
|
+
]
|
124
|
+
])
|
125
|
+
newhash = repo.to_hash
|
126
|
+
newhash['datalist'].count.should == 2
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should add hash entries inside existing arrays" do
|
131
|
+
hash = Treet::Hash.new(load_json('two'))
|
132
|
+
Dir.mktmpdir do |dir|
|
133
|
+
repo = hash.to_repo(dir)
|
134
|
+
repo.patch([
|
135
|
+
[
|
136
|
+
"+",
|
137
|
+
"emails[]",
|
138
|
+
{"label" => "home", "label" => "myname@gmail.com"}
|
139
|
+
]
|
140
|
+
])
|
141
|
+
newhash = repo.to_hash
|
142
|
+
newhash['emails'].count.should == 3
|
143
|
+
newhash['emails'].each {|x| x.is_a?(Hash).should be_true}
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should clean up after a subhash deletion patch" do
|
148
|
+
hash = Treet::Hash.new("#{File.dirname(__FILE__)}/../json/one.json")
|
149
|
+
Dir.mktmpdir do |dir|
|
150
|
+
repo = hash.to_repo(dir)
|
151
|
+
repo.patch([
|
152
|
+
[
|
153
|
+
"-",
|
154
|
+
"name.full",
|
155
|
+
"oldval"
|
156
|
+
]
|
157
|
+
])
|
158
|
+
newhash = repo.to_hash
|
159
|
+
newhash.keys.should be_empty
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should clean up after a top-level string deletion patch" do
|
164
|
+
hash = Treet::Hash.new("#{File.dirname(__FILE__)}/../json/four.json")
|
165
|
+
Dir.mktmpdir do |dir|
|
166
|
+
repo = hash.to_repo(dir)
|
167
|
+
repo.patch([
|
168
|
+
[
|
169
|
+
"-",
|
170
|
+
"title",
|
171
|
+
"oldval"
|
172
|
+
]
|
173
|
+
])
|
174
|
+
newhash = repo.to_hash
|
175
|
+
newhash.keys.should == ['datalist']
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
it "should create empty files correctly when patch adds elements" do
|
180
|
+
hash = Treet::Hash.new("#{File.dirname(__FILE__)}/../json/four.json")
|
181
|
+
Dir.mktmpdir do |dir|
|
182
|
+
repo = hash.to_repo(dir)
|
183
|
+
repo.patch([
|
184
|
+
[
|
185
|
+
"+",
|
186
|
+
"datalist[]",
|
187
|
+
"two"
|
188
|
+
],
|
189
|
+
[
|
190
|
+
"+",
|
191
|
+
"datalist[]",
|
192
|
+
"seven"
|
193
|
+
]
|
194
|
+
])
|
195
|
+
newhash = repo.to_hash
|
196
|
+
newhash.to_hash['datalist'].should be_include('seven')
|
197
|
+
end
|
198
|
+
end
|
91
199
|
end
|