rets 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cedf0e419db8f2c813a4c2c97fd210909b9b58af
4
- data.tar.gz: b8eb9ad36844f79503c64f020830ef477586fa20
3
+ metadata.gz: 966f5afd3517f512f4a54b235b7e52aa5437c7b7
4
+ data.tar.gz: 5b29080c3a5c2a6c48c2b4e8c5d32cd51b90add9
5
5
  SHA512:
6
- metadata.gz: e116e7a292cb5e8118cfff090aae8147a431299fd79b81572bf0afb6d093c0152f1ad35c671e2199898ddaf5b8ea12061a011184776179a239b44c8a8baf9b74
7
- data.tar.gz: d8f6da1337852f4ba871eabb531e31765e5a1a1167f57a9aad5b02ce01c6a242d260d7f6e63492e8fd64c89f9cb4da12a1ec344c20b39f519195cf075cdce666
6
+ metadata.gz: bb19f04bc071b36c147cc697b4b0c616be5c47a2e0919f9289d6fac7f4d112f356a0fbe25ca5a0fb56dbac62c8c3aa0d11438411ccafe43c2d5f16972f3f4990
7
+ data.tar.gz: 9d64e1063bbd640985bc0820c58e403b1391d62838bd92f353719caa7805de98e1188e196a96af5f5e92c55691ebeb8c1ee25eb54e26640c6e6aaf01c89be40b
@@ -1,3 +1,14 @@
1
+ ### 0.11.0 / NOT RELEASED YET
2
+
3
+ * fix: fix retry logging
4
+ * feature: allow retries to be configured for all query types in client settings
5
+ * feature: allow configrable wait time between retries
6
+ * feature: detect errors as error messages in a response body delivered with HTTP 200
7
+
8
+ ### 0.10.1 / 2016-05-04
9
+
10
+ * fix: handle invalid codepoints in character references
11
+
1
12
  ### 0.10.0 / 2016-02-29
2
13
 
3
14
  * fix: ensure cookie store exists #133
data/README.md CHANGED
@@ -7,6 +7,8 @@
7
7
  [![Build Status](https://secure.travis-ci.org/estately/rets.png?branch=master)](http://travis-ci.org/estately/rets)
8
8
  A pure-ruby library for fetching data from [RETS] servers.
9
9
 
10
+ If you're looking for a slick CLI interface check out [retscli](https://github.com/summera/retscli), which is an awesome tool for exploring metadata or learning about RETS.
11
+
10
12
  [RETS]: http://www.rets.org
11
13
 
12
14
  ## REQUIREMENTS:
data/Rakefile CHANGED
@@ -13,7 +13,7 @@ Hoe.spec 'rets' do
13
13
  extra_deps << [ "http-cookie", "~> 1.0.0" ]
14
14
  extra_deps << [ "nokogiri", "~> 1.5" ]
15
15
 
16
- extra_dev_deps << [ "mocha", "~> 0.11" ]
16
+ extra_dev_deps << [ "mocha", "~> 1.1.0" ]
17
17
  extra_dev_deps << [ "vcr", "~> 2.2" ]
18
18
  extra_dev_deps << [ "webmock", "~> 1.8" ]
19
19
 
@@ -3,7 +3,7 @@ require 'digest/md5'
3
3
  require 'nokogiri'
4
4
 
5
5
  module Rets
6
- VERSION = '0.10.0'
6
+ VERSION = '0.11.0'
7
7
 
8
8
  HttpError = Class.new(StandardError)
9
9
  MalformedResponse = Class.new(ArgumentError)
@@ -13,6 +13,9 @@ module Rets
13
13
  end
14
14
 
15
15
  def clean_setup
16
+ if options.fetch(:login_after_error, true)
17
+ @capabilities = nil
18
+ end
16
19
  @metadata = nil
17
20
  @tries = nil
18
21
  @login_url = options[:login_url]
@@ -102,9 +105,11 @@ module Rets
102
105
  end
103
106
 
104
107
  def handle_find_failure(retries, opts, e)
105
- if retries < opts.fetch(:max_retries, 3)
108
+ max_retries = fetch_max_retries(opts)
109
+ if retries < max_retries
106
110
  retries += 1
107
- client_progress.find_with_retries_failed_a_retry(e, retries)
111
+ wait_before_next_request
112
+ client_progress.find_with_retries_failed_a_retry(e, retries, max_retries)
108
113
  clean_setup
109
114
  find_with_given_retry(retries, opts)
110
115
  else
@@ -113,6 +118,18 @@ module Rets
113
118
  end
114
119
  end
115
120
 
121
+ def fetch_max_retries(hash)
122
+ hash[:max_retries] || options.fetch(:max_retries, 3)
123
+ end
124
+
125
+ def wait_before_next_request
126
+ sleep_time = Float(options.fetch(:recoverable_error_wait_secs, 0))
127
+ if sleep_time > 0
128
+ logger.info "Waiting #{sleep_time} seconds before next attempt"
129
+ sleep sleep_time
130
+ end
131
+ end
132
+
116
133
  def find_every(opts)
117
134
  raise ArgumentError.new("missing option :search_type (provide the name of a RETS resource)") unless opts[:search_type]
118
135
  raise ArgumentError.new("missing option :class (provide the name of a RETS class)") unless opts[:class]
@@ -120,7 +137,6 @@ module Rets
120
137
  params = {
121
138
  "SearchType" => opts.fetch(:search_type),
122
139
  "Class" => opts.fetch(:class),
123
-
124
140
  "Count" => opts[:count],
125
141
  "Format" => opts.fetch(:format, "COMPACT"),
126
142
  "Limit" => opts[:limit],
@@ -132,13 +148,13 @@ module Rets
132
148
  "Query" => opts[:query],
133
149
  "QueryType" => opts.fetch(:query_type, "DMQL2"),
134
150
  }.reject { |k,v| v.nil? }
135
- res = http_post(capability_url("Search"), params)
151
+ res = clean_response(http_post(capability_url("Search"), params))
136
152
 
137
153
  if opts[:count] == COUNT.only
138
154
  Parser::Compact.get_count(res.body)
139
155
  else
140
156
  results = Parser::Compact.parse_document(
141
- res.body.encode("UTF-8", res.body.encoding, :invalid => :replace, :undef => :replace)
157
+ res.body
142
158
  )
143
159
  if opts[:resolve]
144
160
  rets_class = find_rets_class(opts[:search_type], opts[:class])
@@ -269,7 +285,7 @@ module Rets
269
285
  "Type" => "METADATA-#{type}",
270
286
  "ID" => "0"
271
287
  })
272
- res.body
288
+ clean_response(res).body
273
289
  end
274
290
 
275
291
  # The capabilies as provided by the RETS server during login.
@@ -330,7 +346,12 @@ module Rets
330
346
  end
331
347
 
332
348
  def http_get(url, params=nil, extra_headers={})
333
- @http_client.http_get(url, params, extra_headers)
349
+ clean_response(@http_client.http_get(url, params, extra_headers))
350
+ end
351
+
352
+ def clean_response(res)
353
+ res.body.encode!("UTF-8", res.body.encoding, :invalid => :replace, :undef => :replace)
354
+ res
334
355
  end
335
356
 
336
357
  def http_post(url, params, extra_headers = {})
@@ -18,10 +18,10 @@ module Rets
18
18
  @stats_prefix = stats_prefix
19
19
  end
20
20
 
21
- def find_with_retries_failed_a_retry(exception, retries)
21
+ def find_with_retries_failed_a_retry(exception, retries, max_retries)
22
22
  @stats.count("#{@stats_prefix}find_with_retries_failed_retry")
23
23
  @logger.warn("Rets::Client: Failed with message: #{exception.message}")
24
- @logger.info("Rets::Client: Retry #{retries}/3")
24
+ @logger.info("Rets::Client: Retry #{retries}/#{max_retries}")
25
25
  end
26
26
 
27
27
  def find_with_retries_exceeded_retry_count(exception)
@@ -22,7 +22,7 @@ module Rets
22
22
  #
23
23
  # [out] The file to print to. Defaults to $stdout.
24
24
  def print_tree(out = $stdout)
25
- out.puts " LookupTable: #{name}"
25
+ out.puts "### LookupTable: #{name}"
26
26
  out.puts " Resource: #{resource_id}"
27
27
  out.puts " Required: #{table_fragment['Required']}"
28
28
  out.puts " Searchable: #{ table_fragment["Searchable"] }"
@@ -30,7 +30,7 @@ module Rets
30
30
  out.puts " ShortName: #{ table_fragment["ShortName"] }"
31
31
  out.puts " LongName: #{ long_name }"
32
32
  out.puts " StandardName: #{ table_fragment["StandardName"] }"
33
- out.puts " Types:"
33
+ out.puts "#### Types:"
34
34
 
35
35
  lookup_types.each do |lookup_type|
36
36
  lookup_type.print_tree(out)
@@ -4,8 +4,8 @@ module Rets
4
4
  attr_reader :long_value, :value
5
5
 
6
6
  def initialize(lookup_type_fragment)
7
- @value = lookup_type_fragment["Value"]
8
- @long_value = lookup_type_fragment["LongValue"]
7
+ @value = lookup_type_fragment["Value"].strip
8
+ @long_value = lookup_type_fragment["LongValue"].strip
9
9
  end
10
10
 
11
11
  # Print the tree to a file
@@ -22,7 +22,7 @@ module Rets
22
22
  #
23
23
  # [out] The file to print to. Defaults to $stdout.
24
24
  def print_tree(out = $stdout)
25
- out.puts " MultiLookupTable: #{name}"
25
+ out.puts "### MultiLookupTable: #{name}"
26
26
  out.puts " Resource: #{resource_id}"
27
27
  out.puts " Required: #{table_fragment['Required']}"
28
28
  out.puts " Searchable: #{ table_fragment["Searchable"] }"
@@ -29,7 +29,12 @@ module Rets
29
29
  end
30
30
 
31
31
  def self.find_rets_objects(metadata, resource_id)
32
- metadata[:object].select { |object| object.resource == resource_id }.map(&:objects).flatten
32
+ objects = metadata[:object]
33
+ if objects
34
+ objects.select { |object| object.resource == resource_id }.map(&:objects).flatten
35
+ else
36
+ []
37
+ end
33
38
  end
34
39
 
35
40
  def self.build_lookup_tree(resource_id, metadata)
@@ -80,7 +85,7 @@ module Rets
80
85
  #
81
86
  # [out] The file to print to. Defaults to $stdout.
82
87
  def print_tree(out = $stdout)
83
- out.puts "Resource: #{id} (Key Field: #{key_field})"
88
+ out.puts "# Resource: #{id} (Key Field: #{key_field})"
84
89
  rets_classes.each do |rets_class|
85
90
  rets_class.print_tree(out)
86
91
  end
@@ -41,7 +41,7 @@ module Rets
41
41
  #
42
42
  # [out] The file to print to. Defaults to $stdout.
43
43
  def print_tree(out = $stdout)
44
- out.puts " Class: #{name}"
44
+ out.puts "## Class: #{name}"
45
45
  out.puts " Visible Name: #{visible_name}"
46
46
  out.puts " Description : #{description}"
47
47
  tables.each do |table|
@@ -1,24 +1,28 @@
1
1
  module Rets
2
2
  module Metadata
3
3
  class RetsObject
4
- attr_reader :name, :mime_type, :description
4
+ attr_reader :name, :mime_type, :description, :type
5
5
 
6
- def initialize(name, mime_type, description)
6
+ def initialize(type, name, mime_type, description)
7
7
  @name = name
8
8
  @mime_type = mime_type
9
9
  @description = description
10
+ @type = type
10
11
  end
11
12
 
12
13
  def self.build(rets_object_fragment)
13
- name = rets_object_fragment["VisibleName"]
14
- mime_type = rets_object_fragment["MIMEType"]
15
- description = rets_object_fragment["Description"]
16
- new(name, mime_type, description)
14
+ rets_object_fragment = downcase_hash_keys(rets_object_fragment)
15
+ name = rets_object_fragment["visiblename"]
16
+ mime_type = rets_object_fragment["mimetype"]
17
+ description = rets_object_fragment["description"]
18
+ type = rets_object_fragment['objecttype']
19
+ new(type, name, mime_type, description)
17
20
  end
18
21
 
19
22
  def print_tree(out = $stdout)
20
- out.puts " Object: #{name}"
21
- out.puts " MimeType: #{mime_type}"
23
+ out.puts " Object: #{type}"
24
+ out.puts " Visible Name: #{name}"
25
+ out.puts " Mime Type: #{mime_type}"
22
26
  out.puts " Description: #{description}"
23
27
  end
24
28
 
@@ -27,6 +31,11 @@ module Rets
27
31
  mime_type == other.mime_type &&
28
32
  description == other.description
29
33
  end
34
+
35
+ private
36
+ def self.downcase_hash_keys(hash)
37
+ Hash[hash.map { |k, v| [k.downcase, v] }]
38
+ end
30
39
  end
31
40
  end
32
41
  end
@@ -15,7 +15,7 @@ module Rets
15
15
  #
16
16
  # [out] The file to print to. Defaults to $stdout.
17
17
  def print_tree(out = $stdout)
18
- out.puts " Table: #{name}"
18
+ out.puts "### Table: #{name}"
19
19
  out.puts " Resource: #{resource_id}"
20
20
  out.puts " ShortName: #{ table_fragment["ShortName"] }"
21
21
  out.puts " LongName: #{ long_name }"
@@ -1,5 +1,6 @@
1
1
  # coding: utf-8
2
2
  require 'cgi'
3
+
3
4
  module Rets
4
5
  module Parser
5
6
  class Compact
@@ -76,7 +77,10 @@ module Rets
76
77
  end
77
78
 
78
79
  column_names = columns.split(delimiter)
79
- data_values = data.split(delimiter, INCLUDE_NULL_FIELDS).map { |x| CGI.unescapeHTML(x) }
80
+ data_values = data.split(delimiter, INCLUDE_NULL_FIELDS).map do |x|
81
+ safely_decode_character_references!(x)
82
+ CGI.unescape_html(x)
83
+ end
80
84
 
81
85
  zipped_key_values = column_names.zip(data_values).map { |k, v| [k.freeze, v.to_s] }
82
86
 
@@ -84,6 +88,21 @@ module Rets
84
88
  hash.reject { |key, value| key.empty? && value.to_s.empty? }
85
89
  end
86
90
 
91
+ def self.safely_decode_character_references!(string)
92
+ string.gsub!(/&#(x)?([\h]+);/) do
93
+ if $2
94
+ base = $1 == "x" ? 16 : 10
95
+ int = Integer($2, base)
96
+ begin
97
+ int.chr(Encoding::UTF_8)
98
+ rescue RangeError
99
+ ""
100
+ end
101
+ end
102
+ end
103
+ string
104
+ end
105
+
87
106
  def self.get_count(xml)
88
107
  doc = Nokogiri.parse(xml.to_s)
89
108
  if node = doc.at("//COUNT")
@@ -266,6 +266,14 @@ SAMPLE_COMPACT_WITH_SPECIAL_CHARS_2 = <<EOF
266
266
  </RETS>
267
267
  EOF
268
268
 
269
+ SAMPLE_COMPACT_WITH_DOUBLY_ENCODED_BAD_CHARACTER_REFERENCES = <<EOF
270
+ <RETS ReplyCode=\"0\" ReplyText=\"Operation Success.\">
271
+ <DELIMITER value=\"09\" />
272
+ <COLUMNS> PublicRemarksNew WindowCoverings YearBuilt Zoning ZoningCompatibleYN </COLUMNS>
273
+ <DATA> foo &amp;#56324; bar 1999 00 </DATA>
274
+ </RETS>
275
+ EOF
276
+
269
277
  SAMPLE_PROPERTY_WITH_LOTS_OF_COLUMNS = <<EOF
270
278
  <RETS ReplyCode=\"0\" ReplyText=\"Operation Success.\">
271
279
  <DELIMITER value=\"09\" />
@@ -309,7 +317,8 @@ Resource: Properties (Key Field: matrix_unique_key)
309
317
  Types:
310
318
  Quarterly -> Q
311
319
  Annually -> A
312
- Object: Photo
313
- MimeType: photo/jpg
320
+ Object: HiRes
321
+ Visible Name: Photo
322
+ Mime Type: photo/jpg
314
323
  Description: photo description
315
324
  EOF
@@ -97,7 +97,7 @@ class TestClient < MiniTest::Test
97
97
  logger.debug "foo"
98
98
  end
99
99
 
100
- def test_find_first_calls_find_every_with_limit_one
100
+ def test_find_every_raises_on_missing_required_arguments
101
101
  assert_raises ArgumentError do
102
102
  @client.find_every({})
103
103
  end
@@ -129,38 +129,27 @@ class TestClient < MiniTest::Test
129
129
 
130
130
  def test_response_text_encoding_from_ascii
131
131
  @client.stubs(:capability_url).with("Search").returns("search_url")
132
-
133
132
  response = mock
134
- response.stubs(:body).returns(("An ascii string").encode("binary", "UTF-8"))
135
- @client.stubs(:http_post).with("search_url", anything).returns(response)
133
+ response.stubs(:body).returns("An ascii string".encode("binary", "UTF-8"))
136
134
 
137
- Rets::Parser::Compact.expects(:parse_document).with("An ascii string")
138
-
139
- @client.find_every(:search_type => "Foo", :class => "Bar")
135
+ assert_equal @client.clean_response(response).body, "An ascii string"
140
136
  end
141
137
 
142
138
  def test_response_text_encoding_from_utf_8
143
139
  @client.stubs(:capability_url).with("Search").returns("search_url")
144
-
145
140
  response = mock
146
141
  response.stubs(:body).returns("Some string with non-ascii characters \u0119")
147
- @client.stubs(:http_post).with("search_url", anything).returns(response)
148
142
 
149
- Rets::Parser::Compact.expects(:parse_document).with("Some string with non-ascii characters \u0119")
143
+ assert_equal @client.clean_response(response).body, "Some string with non-ascii characters \u0119"
150
144
 
151
- @client.find_every(:search_type => "Foo", :class => "Bar")
152
145
  end
153
146
 
154
147
  def test_response_text_encoding_from_utf_16
155
148
  @client.stubs(:capability_url).with("Search").returns("search_url")
156
-
157
149
  response = mock
158
150
  response.stubs(:body).returns("Some string with non-utf-8 characters \xC2")
159
- @client.stubs(:http_post).with("search_url", anything).returns(response)
160
-
161
- Rets::Parser::Compact.expects(:parse_document).with("Some string with non-utf-8 characters \uFFFD")
162
151
 
163
- @client.find_every(:search_type => "Foo", :class => "Bar")
152
+ assert_equal @client.clean_response(response).body, "Some string with non-utf-8 characters \uFFFD"
164
153
  end
165
154
 
166
155
  def test_find_retries_when_receiving_no_records_found
@@ -187,6 +176,13 @@ class TestClient < MiniTest::Test
187
176
  @client.find(:all, :foo => :bar)
188
177
  end
189
178
 
179
+ def test_find_waits_configured_time_before_next_request
180
+ @client.options[:recoverable_error_wait_secs] = 3.14
181
+ @client.expects(:sleep).with(3.14).times(3)
182
+ @client.stubs(:find_every).raises(Rets::MiscellaneousSearchError.new(0, 'Foo'))
183
+ @client.find(:all, :foo => :bar) rescue nil
184
+ end
185
+
190
186
  def test_find_eventually_reraises_errors
191
187
  @client.stubs(:find_every).raises(Rets::AuthorizationFailure.new(401, 'Not Authorized'))
192
188
  @client.stubs(:login)
@@ -58,7 +58,7 @@ class TestMetadata < MiniTest::Test
58
58
  ]
59
59
 
60
60
  rets_objects = [
61
- Rets::Metadata::RetsObject.new("Photo", "photo/jpg", "photo description")
61
+ Rets::Metadata::RetsObject.new("HiRes", "Photo", "photo/jpg", "photo description")
62
62
  ]
63
63
 
64
64
  resource = Rets::Metadata::Resource.new(resource_id, "matrix_unique_key", rets_classes, rets_objects)
@@ -9,4 +9,13 @@ class TestMetadataLookupType < MiniTest::Test
9
9
  assert_equal('a', lookup_type.value)
10
10
  assert_equal('c', lookup_type.long_value)
11
11
  end
12
+
13
+ def test_lookup_type_ignores_trailing_whitespace
14
+ fragment = { "Value" => 'a ', "LongValue" => 'c ' }
15
+
16
+ lookup_type = Rets::Metadata::LookupType.new(fragment)
17
+
18
+ assert_equal('a', lookup_type.value)
19
+ assert_equal('c', lookup_type.long_value)
20
+ end
12
21
  end
@@ -5,16 +5,29 @@ class TestMetadataObject < MiniTest::Test
5
5
  name = "Name"
6
6
  mime_type = "mimetype"
7
7
  description = "description"
8
+ object_type = "type"
8
9
 
9
10
  object_fragment = {
10
- "VisibleName" => name,
11
- "MIMEType" => mime_type,
12
- "Description" => description,
11
+ "ObjectType" => object_type,
12
+ "VisibleName" => name,
13
+ "MIMEType" => mime_type,
14
+ "Description" => description,
13
15
  }
14
16
 
15
17
  assert_equal(
16
18
  Rets::Metadata::RetsObject.build(object_fragment),
17
- Rets::Metadata::RetsObject.new(name, mime_type, description)
19
+ Rets::Metadata::RetsObject.new(object_type, name, mime_type, description)
20
+ )
21
+ end
22
+
23
+ def test_rets_object_building_not_case_dependent
24
+ object_fragment = {
25
+ "MiMeTyPe" => "image/jpeg"
26
+ }
27
+
28
+ assert_equal(
29
+ Rets::Metadata::RetsObject.build(object_fragment).mime_type,
30
+ "image/jpeg"
18
31
  )
19
32
  end
20
33
  end
@@ -53,12 +53,20 @@ class TestMetadataResource < MiniTest::Test
53
53
  assert_equal([rets_object], objects)
54
54
  end
55
55
 
56
+ def test_resource_build_objects_when_objects_were_not_loaded
57
+ resource_id = "id"
58
+ metadata = {} # doesn't contain metadata for :object key
59
+
60
+ objects = Rets::Metadata::Resource.build_objects(resource_id, metadata)
61
+ assert_equal [], objects
62
+ end
63
+
56
64
  def test_resource_build
57
65
  fragment = { "ResourceID" => "test" }
58
66
 
59
67
  lookup_types = stub(:lookup_types)
60
68
  classes = stub(:classes)
61
- objects = stub(:classes)
69
+ objects = stub(:objects)
62
70
  metadata = stub(:metadata)
63
71
 
64
72
  Rets::Metadata::Resource.stubs(:build_lookup_tree => lookup_types)
@@ -86,9 +86,30 @@ class TestParserCompact < MiniTest::Test
86
86
  assert_equal "text with <tag>", rows.last["PublicRemarksNew"]
87
87
  end
88
88
 
89
+ def test_parse_doubly_encoded_bad_character_references
90
+ rows = Rets::Parser::Compact.parse_document(SAMPLE_COMPACT_WITH_DOUBLY_ENCODED_BAD_CHARACTER_REFERENCES)
91
+ assert_equal "foo bar", rows.last["PublicRemarksNew"]
92
+ end
93
+
89
94
  def test_parse_property_with_lots_of_columns
90
95
  row = Rets::Parser::Compact.parse_document(SAMPLE_PROPERTY_WITH_LOTS_OF_COLUMNS).first
91
96
  assert_equal 800, row.keys.size
92
97
  assert_equal 800.times.map { |x| "K%03d" % x }, row.keys
93
98
  end
99
+
100
+ def test_safely_decode_character_references!
101
+ assert_decoded "a", "&#97;"
102
+ assert_decoded "a", "&#097;"
103
+ assert_decoded "a", "&#x61;"
104
+ assert_decoded "a", "&#x061;"
105
+ assert_decoded "", "&#xDC04;"
106
+ assert_decoded "", "&#56324;"
107
+ assert_decoded "", "&#x2000FF;"
108
+ end
109
+
110
+
111
+ def assert_decoded(a, b)
112
+ recoded = Rets::Parser::Compact.safely_decode_character_references!(b)
113
+ assert_equal a, recoded
114
+ end
94
115
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rets
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Estately, Inc. Open Source
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-02-29 00:00:00.000000000 Z
11
+ date: 2017-01-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httpclient
@@ -72,14 +72,14 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0.11'
75
+ version: 1.1.0
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '0.11'
82
+ version: 1.1.0
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: vcr
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -114,18 +114,20 @@ dependencies:
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: '3.13'
117
+ version: '3.15'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: '3.13'
124
+ version: '3.15'
125
125
  description: |-
126
126
  [![Build Status](https://secure.travis-ci.org/estately/rets.png?branch=master)](http://travis-ci.org/estately/rets)
127
127
  A pure-ruby library for fetching data from [RETS] servers.
128
128
 
129
+ If you're looking for a slick CLI interface check out [retscli](https://github.com/summera/retscli), which is an awesome tool for exploring metadata or learning about RETS.
130
+
129
131
  [RETS]: http://www.rets.org
130
132
  email:
131
133
  - opensource@estately.com
@@ -137,7 +139,6 @@ extra_rdoc_files:
137
139
  - Manifest.txt
138
140
  - README.md
139
141
  files:
140
- - ".gemtest"
141
142
  - CHANGELOG.md
142
143
  - Manifest.txt
143
144
  - README.md
@@ -218,7 +219,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
218
219
  version: '0'
219
220
  requirements: []
220
221
  rubyforge_project:
221
- rubygems_version: 2.4.5.1
222
+ rubygems_version: 2.5.1
222
223
  signing_key:
223
224
  specification_version: 4
224
225
  summary: "[![Build Status](https://secure.travis-ci.org/estately/rets.png?branch=master)](http://travis-ci.org/estately/rets)
data/.gemtest DELETED
File without changes