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 CHANGED
@@ -15,4 +15,3 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
- *.json
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - rbx-19mode
data/Gemfile CHANGED
@@ -2,5 +2,3 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in treet.gemspec
4
4
  gemspec
5
-
6
- gem 'thor'
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 "uuidtools"
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] = Treet::Repo.new(subdir, :xrefkey => xrefkey, :xref => 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 = UUIDTools::UUID.random_create.to_s
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
- Treet::Farm.new(:root => rootdir, :xref => opts[:xref])
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
- def add(hash)
66
- uuid = UUIDTools::UUID.random_create.to_s
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.new(root)
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(hash)
46
- Digest::SHA1.hexdigest(hash.to_a.sort.flatten.join)
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
- File.open("#{k}/#{v2}", "w")
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, :hash, :opts
4
+ attr_reader :root
7
5
 
8
6
  def initialize(path, opts = {})
9
- # TODO: validate that path exists and is a directory (symlinks should work)
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
- @hash ||= expand(root)
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, is_array, index = keyname.match(/^(.*)(\[\])$/).captures
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
- [nil, keyname]
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
- keyname, is_array = key.match(/^(.*)(\[\])$/).captures
52
- elsif key =~ /\./
53
- keyname, subkey = key.match(/^(.*)\.(.*)$/).captures
54
- else
55
- keyname = key
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
- data = File.exists?(filepath) ? JSON.load(File.open(filepath)) : {}
66
- data[fieldname] = v1
67
- File.open(filepath, "w") {|f| f << JSON.pretty_generate(data)}
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
- File.open(subfile, "w") {|f| f << JSON.pretty_generate(v1)}
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
- hash = files.each_with_object({}) {|f,h| h[f] = expand_json("#{path}/#{f}")}
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
@@ -1,3 +1,3 @@
1
1
  module Treet
2
- VERSION = "0.8.2"
2
+ VERSION = "0.10.2"
3
3
  end
data/lib/treet.rb CHANGED
@@ -6,8 +6,13 @@ unless Kernel.respond_to?(:require_relative)
6
6
  end
7
7
  end
8
8
 
9
- require_relative "treet/version"
9
+ %w(version hash repo farm).each do |f|
10
+ require_relative "treet/#{f}"
11
+ end
10
12
 
11
- require_relative "treet/repo"
12
- require_relative "treet/hash"
13
- require_relative "treet/farm"
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
@@ -0,0 +1,8 @@
1
+ {
2
+ "title": "This is the title",
3
+ "datalist": [
4
+ "one",
5
+ "two",
6
+ "three"
7
+ ]
8
+ }
@@ -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 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
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