rets 0.10.0 → 0.11.0

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.
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