pricehubble 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +30 -0
- data/.gitignore +16 -0
- data/.rspec +3 -0
- data/.rubocop.yml +62 -0
- data/.simplecov +3 -0
- data/.travis.yml +27 -0
- data/.yardopts +6 -0
- data/Appraisals +25 -0
- data/CHANGELOG.md +9 -0
- data/Dockerfile +29 -0
- data/Envfile +6 -0
- data/Gemfile +8 -0
- data/LICENSE +21 -0
- data/Makefile +149 -0
- data/README.md +385 -0
- data/Rakefile +80 -0
- data/bin/console +16 -0
- data/bin/run +12 -0
- data/bin/setup +8 -0
- data/config/docker/.bash_profile +3 -0
- data/config/docker/.bashrc +48 -0
- data/config/docker/.inputrc +17 -0
- data/doc/assets/project.svg +68 -0
- data/doc/examples/authentication.rb +19 -0
- data/doc/examples/complex_property_valuations.rb +91 -0
- data/doc/examples/config.rb +30 -0
- data/doc/examples/property_valuations_errors.rb +30 -0
- data/doc/examples/simple_property_valuations.rb +69 -0
- data/docker-compose.yml +9 -0
- data/gemfiles/rails_4.2.gemfile +11 -0
- data/gemfiles/rails_5.0.gemfile +11 -0
- data/gemfiles/rails_5.1.gemfile +11 -0
- data/gemfiles/rails_5.2.gemfile +11 -0
- data/lib/price_hubble.rb +3 -0
- data/lib/pricehubble/client/authentication.rb +29 -0
- data/lib/pricehubble/client/base.rb +59 -0
- data/lib/pricehubble/client/request/data_sanitization.rb +28 -0
- data/lib/pricehubble/client/request/default_headers.rb +33 -0
- data/lib/pricehubble/client/response/data_sanitization.rb +29 -0
- data/lib/pricehubble/client/response/recursive_open_struct.rb +31 -0
- data/lib/pricehubble/client/utils/request.rb +41 -0
- data/lib/pricehubble/client/utils/response.rb +60 -0
- data/lib/pricehubble/client/valuation.rb +68 -0
- data/lib/pricehubble/client.rb +25 -0
- data/lib/pricehubble/configuration.rb +26 -0
- data/lib/pricehubble/configuration_handling.rb +50 -0
- data/lib/pricehubble/core_ext/hash.rb +52 -0
- data/lib/pricehubble/entity/address.rb +11 -0
- data/lib/pricehubble/entity/authentication.rb +34 -0
- data/lib/pricehubble/entity/base_entity.rb +63 -0
- data/lib/pricehubble/entity/concern/associations.rb +197 -0
- data/lib/pricehubble/entity/concern/attributes/date_array.rb +29 -0
- data/lib/pricehubble/entity/concern/attributes/enum.rb +57 -0
- data/lib/pricehubble/entity/concern/attributes/range.rb +32 -0
- data/lib/pricehubble/entity/concern/attributes/string_inquirer.rb +27 -0
- data/lib/pricehubble/entity/concern/attributes.rb +171 -0
- data/lib/pricehubble/entity/concern/callbacks.rb +19 -0
- data/lib/pricehubble/entity/concern/client.rb +31 -0
- data/lib/pricehubble/entity/coordinates.rb +11 -0
- data/lib/pricehubble/entity/location.rb +14 -0
- data/lib/pricehubble/entity/property.rb +32 -0
- data/lib/pricehubble/entity/property_conditions.rb +20 -0
- data/lib/pricehubble/entity/property_qualities.rb +20 -0
- data/lib/pricehubble/entity/property_type.rb +21 -0
- data/lib/pricehubble/entity/valuation.rb +48 -0
- data/lib/pricehubble/entity/valuation_request.rb +60 -0
- data/lib/pricehubble/entity/valuation_scores.rb +11 -0
- data/lib/pricehubble/errors.rb +60 -0
- data/lib/pricehubble/faraday.rb +12 -0
- data/lib/pricehubble/identity.rb +46 -0
- data/lib/pricehubble/railtie.rb +16 -0
- data/lib/pricehubble/utils/bangers.rb +44 -0
- data/lib/pricehubble/utils/decision.rb +97 -0
- data/lib/pricehubble/version.rb +6 -0
- data/lib/pricehubble.rb +103 -0
- data/pricehubble.gemspec +47 -0
- metadata +432 -0
data/README.md
ADDED
@@ -0,0 +1,385 @@
|
|
1
|
+
![PriceHubble](doc/assets/project.svg)
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.com/hausgold/pricehubble.svg?token=4XcyqxxmkyBSSV3wWRt7&branch=master)](https://travis-ci.com/hausgold/pricehubble)
|
4
|
+
[![Gem Version](https://badge.fury.io/rb/pricehubble.svg)](https://badge.fury.io/rb/pricehubble)
|
5
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/cd15f59fc84566e4b200/maintainability)](https://codeclimate.com/repos/5da572bd60163201b800c255/maintainability)
|
6
|
+
[![Test Coverage](https://api.codeclimate.com/v1/badges/cd15f59fc84566e4b200/test_coverage)](https://codeclimate.com/repos/5da572bd60163201b800c255/test_coverage)
|
7
|
+
[![API docs](https://img.shields.io/badge/docs-API-blue.svg)](https://www.rubydoc.info/gems/pricehubble)
|
8
|
+
|
9
|
+
This project is dedicated to build a client/API wrapper around the
|
10
|
+
[PriceHubble](https://pricehubble.com) REST API. It follows strictly the
|
11
|
+
[version 1 of the API specification](https://docs.pricehubble.com). Furthermore
|
12
|
+
this gem allows you to easily interact with common PriceHubble operations like
|
13
|
+
fetching property valuations. At the time of writing it supports not the full
|
14
|
+
API specification, but it should be easy to enhance the client functionality,
|
15
|
+
so feel free to send a pull request.
|
16
|
+
|
17
|
+
- [Installation](#installation)
|
18
|
+
- [Usage](#usage)
|
19
|
+
- [Configuration](#configuration)
|
20
|
+
- [Available environment variables](#available-environment-variables)
|
21
|
+
- [Authentication](#authentication)
|
22
|
+
- [Error Handling](#error-handling)
|
23
|
+
- [Property Valuations](#property-valuations)
|
24
|
+
- [Bare Minimum Request Example](#bare-minimum-request-example)
|
25
|
+
- [Use the PriceHubble::Valuation representation](#use-the-pricehubblevaluation-representation)
|
26
|
+
- [Error Handling](#error-handling-1)
|
27
|
+
- [Advanced Request Examples](#advanced-request-examples)
|
28
|
+
- [Development](#development)
|
29
|
+
- [Contributing](#contributing)
|
30
|
+
|
31
|
+
## Installation
|
32
|
+
|
33
|
+
Add this line to your application's Gemfile:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
gem 'pricehubble'
|
37
|
+
```
|
38
|
+
|
39
|
+
And then execute:
|
40
|
+
|
41
|
+
```bash
|
42
|
+
$ bundle
|
43
|
+
```
|
44
|
+
|
45
|
+
Or install it yourself as:
|
46
|
+
|
47
|
+
```bash
|
48
|
+
$ gem install pricehubble
|
49
|
+
```
|
50
|
+
|
51
|
+
## Usage
|
52
|
+
|
53
|
+
### Configuration
|
54
|
+
|
55
|
+
You can configure the pricehubble gem via an Rails initializer, by environment
|
56
|
+
variables or on demand. Here we show a common Rails initializer example:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
PriceHubble.configure do |conf|
|
60
|
+
# Configure the API authentication credentials
|
61
|
+
conf.username = 'your-username'
|
62
|
+
conf.password = 'your-password'
|
63
|
+
|
64
|
+
# The base URL of the API (there is no staging/canary
|
65
|
+
# endpoint we know about)
|
66
|
+
conf.base_url = 'https://api.pricehubble.com'
|
67
|
+
|
68
|
+
# Writes to stdout by default
|
69
|
+
conf.logger = Logger.new(IO::NULL)
|
70
|
+
end
|
71
|
+
```
|
72
|
+
|
73
|
+
The pricehubble gem comes with sensitive defaults as you can see. For most
|
74
|
+
users an extra configuration, beside the authorization credentials, is not
|
75
|
+
needed.
|
76
|
+
|
77
|
+
#### Available environment variables
|
78
|
+
|
79
|
+
The pricehubble gem can be configured hardly with its configuration code block
|
80
|
+
like shown before. Respecting the [twelve-factor app](https://12factor.net/)
|
81
|
+
concerns, the gem allows you to set almost all configurations (just the
|
82
|
+
relevant ones for runtime) via environment variables. Here comes a list of
|
83
|
+
available configuration options:
|
84
|
+
|
85
|
+
* **PRICEHUBBLE_USERNAME**: The API username to use for authentication.
|
86
|
+
* **PRICEHUBBLE_PASSWORD**: The API password to use for authentication.
|
87
|
+
* **PRICEHUBBLE_BASE_URL**: The base URL of the API. Defaults to the production one.
|
88
|
+
|
89
|
+
### Authentication
|
90
|
+
|
91
|
+
After configuring the pricehubble gem you can directly fetch an
|
92
|
+
authentication which is valid for two hours. All operational requests
|
93
|
+
require an authentication to be present and unexpired. But now comes
|
94
|
+
the good part: you don't ever need to take care about this, because
|
95
|
+
the gem handles the refreshing of the authentication transparently for
|
96
|
+
you and also takes care of passing the authentication to each request
|
97
|
+
you can do with the gem.
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
# Fetch the authentication/identity for the first time, subsequent calls
|
101
|
+
# to this method will return the cached authentication instance until it
|
102
|
+
# is expired (or near expiration, 5 minutes leeway) then a new
|
103
|
+
# authentication is fetched transparently
|
104
|
+
PriceHubble.identity
|
105
|
+
# => #<PriceHubble::Authentication access_token="...", ...>
|
106
|
+
|
107
|
+
# Check the expiration state (transparently always unexpired)
|
108
|
+
PriceHubble.identity.expired?
|
109
|
+
# => false
|
110
|
+
|
111
|
+
# Get the current authentication expiration date/time
|
112
|
+
PriceHubble.identity.expires_at
|
113
|
+
# => 2019-10-17 08:01:23 +0000
|
114
|
+
```
|
115
|
+
|
116
|
+
### Error Handling
|
117
|
+
|
118
|
+
The pricehubble gem allows you to decide how to react on errors on a
|
119
|
+
per-request basis. (except the transparent authentication) All request
|
120
|
+
performing methods are shiped in a bang and non-bang variant.
|
121
|
+
|
122
|
+
The bang variants (eg. `PriceHubble::ValuationRequest#perform!`, mind
|
123
|
+
the exclamation mark at the end) will raise an child instance of the
|
124
|
+
`PriceHubble::RequestError` or `PriceHubble::EntityError` class ([see
|
125
|
+
errors for more details](lib/pricehubble/errors.rb)) when errors
|
126
|
+
occur. This comes in handy on asynchronous jobs which are retried on
|
127
|
+
exceptions.
|
128
|
+
|
129
|
+
The non-bang variants (eg. `PriceHubble::ValuationRequest#perform`,
|
130
|
+
without the exclamation mark) wont raise and just return empty results
|
131
|
+
(eg. `false` or `[]`). This might me comfortable in complex control
|
132
|
+
flows or when you do not care if one out of 100 times the data is
|
133
|
+
missing. But watch out for bad/invalid requests you might mask with
|
134
|
+
this behaviour.
|
135
|
+
|
136
|
+
It's up to you to choose the correct flavor for your usecase.
|
137
|
+
|
138
|
+
### Property Valuations
|
139
|
+
|
140
|
+
The pricehubble gem allows you to request property valuations easily.
|
141
|
+
You can formulate your request elegantly with in-place attribute sets
|
142
|
+
or already built instances the same way. Furthermore the gem ships
|
143
|
+
some sensible defaults which makes it even more easy to get an
|
144
|
+
valuation for a property.
|
145
|
+
|
146
|
+
The `PriceHubble::ValuationRequest` allows to fetch valuations for one
|
147
|
+
or more (bulk) properties at once for one or several dates. The upper
|
148
|
+
limit for this is `properties.count * dates.count <= 50` from API
|
149
|
+
side.
|
150
|
+
|
151
|
+
**Gotcha!** The API allows to fetch property valuations with
|
152
|
+
per-property independent time series. The pricehubble gem does not
|
153
|
+
support this for the sake of ease.
|
154
|
+
|
155
|
+
#### Bare Minimum Request Example
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
# Fetch the valuations for a single property (sale) for today (defaults).
|
159
|
+
# This is the bare minimum variant, as a starting point.
|
160
|
+
valuations = PriceHubble::ValuationRequest.new(
|
161
|
+
property: {
|
162
|
+
location: {
|
163
|
+
address: {
|
164
|
+
post_code: '22769',
|
165
|
+
city: 'Hamburg',
|
166
|
+
street: 'Stresemannstr.',
|
167
|
+
house_number: '29'
|
168
|
+
}
|
169
|
+
},
|
170
|
+
property_type: { code: :apartment },
|
171
|
+
building_year: 1990,
|
172
|
+
living_area: 200
|
173
|
+
}
|
174
|
+
).perform!
|
175
|
+
# => [#<PriceHubble::Valuation ...>]
|
176
|
+
|
177
|
+
# Print all relevant valuation attributes
|
178
|
+
pp valuations.first.attributes.deep_compact
|
179
|
+
# => {"currency"=>"EUR",
|
180
|
+
# => "sale_price"=>1283400,
|
181
|
+
# => "sale_price_range"=>1180800..1386100,
|
182
|
+
# => "confidence"=>"good",
|
183
|
+
# => "deal_type"=>"sale",
|
184
|
+
# => "valuation_date"=>Thu, 17 Oct 2019,
|
185
|
+
# => "country_code"=>"DE",
|
186
|
+
# => "property"=>
|
187
|
+
# => {"location"=>
|
188
|
+
# => {"address"=>
|
189
|
+
# => {"post_code"=>"22769",
|
190
|
+
# => "city"=>"Hamburg",
|
191
|
+
# => "street"=>"Stresemannstr.",
|
192
|
+
# => "house_number"=>"29"}},
|
193
|
+
# => "property_type"=>{"code"=>:apartment},
|
194
|
+
# => "building_year"=>1990,
|
195
|
+
# => "living_area"=>200}}
|
196
|
+
```
|
197
|
+
|
198
|
+
#### Use the PriceHubble::Valuation representation
|
199
|
+
|
200
|
+
The [`PriceHubble::Valuation`](lib/pricehubble/entity/valuation.rb)
|
201
|
+
representation ships a lot utilities and helpers to process the data.
|
202
|
+
Here are some examples:
|
203
|
+
|
204
|
+
```ruby
|
205
|
+
# Fetch the valuations
|
206
|
+
valuations = PriceHubble::ValuationRequest.new(...).perform!
|
207
|
+
# We just want to work on the first valuation
|
208
|
+
valuation = valuations.first
|
209
|
+
|
210
|
+
# Get the deal type dependent value of the property in a generic way.
|
211
|
+
# (sale price if deal type is sale, and rent gross if deal type is rent)
|
212
|
+
valuation.value
|
213
|
+
# => 1283400
|
214
|
+
|
215
|
+
# Get the upper and lower value range of the property in a generic
|
216
|
+
# way. The deal type logic is equal to the
|
217
|
+
# +PriceHubble::Valuation#value+ method.
|
218
|
+
valuation.value_range
|
219
|
+
# => 1180800..1386100
|
220
|
+
|
221
|
+
# Query the valuation confidence in an elegant way
|
222
|
+
valuation.confidence.good?
|
223
|
+
# => true
|
224
|
+
|
225
|
+
# The +PriceHubble::Valuation+ entity is a self contained
|
226
|
+
# representation of the request and response. This means it includes
|
227
|
+
# the property, deal type, valuation date, country code, etc it was
|
228
|
+
# requested for. This makes it easy to process the data because
|
229
|
+
# everything related is in one place.
|
230
|
+
valuation.property.property_type.apartment?
|
231
|
+
# => true
|
232
|
+
```
|
233
|
+
|
234
|
+
#### Error Handling
|
235
|
+
|
236
|
+
The property valuation API is able to perform bulk operations which
|
237
|
+
may result in non-bang situations, even when you use the bang variant.
|
238
|
+
This is quite smart as a single property valuation might not break
|
239
|
+
others on the very same request. Watch for the
|
240
|
+
`PriceHubble::Valuation#status` hash, when it is not nil, then you got
|
241
|
+
an error for this valuation.
|
242
|
+
|
243
|
+
In case you request just one property valuation, then the API will
|
244
|
+
respond an error which is mapped when you use the bang variants.
|
245
|
+
|
246
|
+
```ruby
|
247
|
+
begin
|
248
|
+
# Fetch the valuations for a single property (sale) for today (defaults).
|
249
|
+
# This is the bare minimum variant, as a starting point.
|
250
|
+
PriceHubble::ValuationRequest.new(
|
251
|
+
property: {
|
252
|
+
location: {
|
253
|
+
address: {
|
254
|
+
post_code: '22769',
|
255
|
+
city: 'Hamburg',
|
256
|
+
street: 'Stresemannstr.',
|
257
|
+
house_number: '29'
|
258
|
+
}
|
259
|
+
},
|
260
|
+
property_type: { code: :apartment },
|
261
|
+
building_year: 2999,
|
262
|
+
living_area: 200
|
263
|
+
}
|
264
|
+
).perform!
|
265
|
+
rescue PriceHubble::EntityInvalid => e
|
266
|
+
# => #<PriceHubble::EntityInvalid: buildingYear: ...>
|
267
|
+
|
268
|
+
# The error message includes the detailed problem.
|
269
|
+
e.message
|
270
|
+
# => "buildingYear: Must be between 1850 and 2022."
|
271
|
+
end
|
272
|
+
```
|
273
|
+
|
274
|
+
#### Advanced Request Examples
|
275
|
+
|
276
|
+
Here comes a more complex example of a property valuations request
|
277
|
+
with multiple properties, for several valuations dates.
|
278
|
+
|
279
|
+
**Gotcha!** The API does not allow you to mix and match the deal type
|
280
|
+
per property. Therefore you need to set it once for the whole request.
|
281
|
+
|
282
|
+
```ruby
|
283
|
+
# Build a property with all relevant information for the valuation. This
|
284
|
+
# example reflects not the full list of supported fields, [see the API
|
285
|
+
# specification](https://docs.pricehubble.com/#types-property) for the full
|
286
|
+
# details.
|
287
|
+
apartment = PriceHubble::Property.new(
|
288
|
+
location: {
|
289
|
+
address: {
|
290
|
+
post_code: '22769',
|
291
|
+
city: 'Hamburg',
|
292
|
+
street: 'Stresemannstr.',
|
293
|
+
house_number: '29'
|
294
|
+
}
|
295
|
+
},
|
296
|
+
property_type: { code: :apartment },
|
297
|
+
building_year: 1990,
|
298
|
+
living_area: 200,
|
299
|
+
balcony_Area: 30,
|
300
|
+
floor_number: 5,
|
301
|
+
has_lift: true,
|
302
|
+
is_furnished: false,
|
303
|
+
is_new: false,
|
304
|
+
renovation_year: 2014,
|
305
|
+
condition: {
|
306
|
+
bathrooms: :well_maintained,
|
307
|
+
kitchen: :well_maintained,
|
308
|
+
flooring: :well_maintained,
|
309
|
+
windows: :well_maintained,
|
310
|
+
masonry: :well_maintained
|
311
|
+
},
|
312
|
+
quality: {
|
313
|
+
bathrooms: :normal,
|
314
|
+
kitchen: :normal,
|
315
|
+
flooring: :normal,
|
316
|
+
windows: :normal,
|
317
|
+
masonry: :normal
|
318
|
+
}
|
319
|
+
)
|
320
|
+
|
321
|
+
house = PriceHubble::Property.new(
|
322
|
+
location: {
|
323
|
+
address: {
|
324
|
+
post_code: '22769',
|
325
|
+
city: 'Hamburg',
|
326
|
+
street: 'Stresemannstr.',
|
327
|
+
house_number: '29'
|
328
|
+
}
|
329
|
+
},
|
330
|
+
property_type: { code: :house },
|
331
|
+
building_year: 1990,
|
332
|
+
land_area: 100,
|
333
|
+
living_area: 500,
|
334
|
+
number_of_floors_in_building: 5
|
335
|
+
)
|
336
|
+
|
337
|
+
# Fetch the property valuations for multiple properties, on multiple dates
|
338
|
+
request = PriceHubble::ValuationRequest.new(
|
339
|
+
deal_type: :sale,
|
340
|
+
properties: [apartment, house],
|
341
|
+
# The dates order is reflected on the valuations list
|
342
|
+
valuation_dates: [
|
343
|
+
1.year.ago,
|
344
|
+
Date.current,
|
345
|
+
1.year.from_now
|
346
|
+
]
|
347
|
+
)
|
348
|
+
valuations = request.perform!
|
349
|
+
|
350
|
+
# Print the valuations in a simple ASCII table. This is just a
|
351
|
+
# demonstration of how to use the valuations for a simple usecase.
|
352
|
+
require 'terminal-table'
|
353
|
+
table = Terminal::Table.new do |tab|
|
354
|
+
tab << ['Deal Type', 'Property Type', *request.valuation_dates.map(&:year)]
|
355
|
+
tab << :separator
|
356
|
+
# Group the valuations by the property they represent
|
357
|
+
valuations.group_by(&:property).each do |property, valuations|
|
358
|
+
tab << [request.deal_type, property.property_type.code,
|
359
|
+
*valuations.map { |val| "#{val.value} #{val.currency}" }]
|
360
|
+
end
|
361
|
+
end
|
362
|
+
# => +-----------+---------------+-------------+-------------+-------------+
|
363
|
+
# => | Deal Type | Property Type | 2018 | 2019 | 2020 |
|
364
|
+
# => +-----------+---------------+-------------+-------------+-------------+
|
365
|
+
# => | sale | apartment | 1282100 EUR | 1373100 EUR | 1420100 EUR |
|
366
|
+
# => | sale | house | 1824800 EUR | 1950900 EUR | 2016000 EUR |
|
367
|
+
# => +-----------+---------------+-------------+-------------+-------------+
|
368
|
+
```
|
369
|
+
|
370
|
+
## Development
|
371
|
+
|
372
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
373
|
+
`bundle exec rake spec` to run the tests. You can also run `bin/console` for an
|
374
|
+
interactive prompt that will allow you to experiment.
|
375
|
+
|
376
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To
|
377
|
+
release a new version, update the version number in `version.rb`, and then run
|
378
|
+
`bundle exec rake release`, which will create a git tag for the version, push
|
379
|
+
git commits and tags, and push the `.gem` file to
|
380
|
+
[rubygems.org](https://rubygems.org).
|
381
|
+
|
382
|
+
## Contributing
|
383
|
+
|
384
|
+
Bug reports and pull requests are welcome on GitHub at
|
385
|
+
https://github.com/hausgold/pricehubble.
|
data/Rakefile
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
require 'rails/code_statistics'
|
6
|
+
require 'pp'
|
7
|
+
|
8
|
+
RSpec::Core::RakeTask.new(:spec)
|
9
|
+
|
10
|
+
task default: :spec
|
11
|
+
|
12
|
+
# Load some railties tasks
|
13
|
+
load 'rails/tasks/statistics.rake'
|
14
|
+
load 'rails/tasks/annotations.rake'
|
15
|
+
|
16
|
+
# Clear the default statistics directory constant
|
17
|
+
#
|
18
|
+
# rubocop:disable Style/MutableConstant because we define it
|
19
|
+
Object.send(:remove_const, :STATS_DIRECTORIES)
|
20
|
+
::STATS_DIRECTORIES = []
|
21
|
+
# rubocop:enable Style/MutableConstant
|
22
|
+
|
23
|
+
# Monkey patch the Rails +CodeStatistics+ class to support configurable
|
24
|
+
# patterns per path. This is reuqired to support top-level only file matches.
|
25
|
+
class CodeStatistics
|
26
|
+
DEFAULT_PATTERN = /^(?!\.).*?\.(rb|js|coffee|rake)$/.freeze
|
27
|
+
|
28
|
+
# Pass the possible +pattern+ argument down to the
|
29
|
+
# +calculate_directory_statistics+ method call.
|
30
|
+
def calculate_statistics
|
31
|
+
Hash[@pairs.map do |pair|
|
32
|
+
[pair.first, calculate_directory_statistics(*pair[1..-1])]
|
33
|
+
end]
|
34
|
+
end
|
35
|
+
|
36
|
+
# Match the pattern against the individual file name and the relative file
|
37
|
+
# path. This allows top-level only matches.
|
38
|
+
def calculate_directory_statistics(directory, pattern = DEFAULT_PATTERN)
|
39
|
+
stats = CodeStatisticsCalculator.new
|
40
|
+
|
41
|
+
Dir.foreach(directory) do |file_name|
|
42
|
+
path = "#{directory}/#{file_name}"
|
43
|
+
|
44
|
+
if File.directory?(path) && (/^\./ !~ file_name)
|
45
|
+
stats.add(calculate_directory_statistics(path, pattern))
|
46
|
+
elsif file_name =~ pattern || path =~ pattern
|
47
|
+
stats.add_by_file_path(path)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
stats
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Configure all code statistics directories
|
56
|
+
vendors = [
|
57
|
+
[:unshift, 'Clients', 'lib/pricehubble/client'],
|
58
|
+
[:unshift, 'Entities', 'lib/pricehubble/entity'],
|
59
|
+
[:unshift, 'Utilities', 'lib/pricehubble/utils'],
|
60
|
+
[:unshift, 'Top-levels', 'lib', %r{lib(/pricehubble)?/[^/]+\.rb$}],
|
61
|
+
|
62
|
+
[:unshift, 'Clients specs', 'spec/client'],
|
63
|
+
[:unshift, 'Entities specs', 'spec/entity'],
|
64
|
+
[:unshift, 'Utilities specs', 'spec/utils'],
|
65
|
+
[:unshift, 'Top-levels specs', 'spec',
|
66
|
+
%r{spec/pricehubble(_spec\.rb|/[^/]+\.rb$)}]
|
67
|
+
].reverse
|
68
|
+
|
69
|
+
vendors.each do |method, type, dir, pattern|
|
70
|
+
::STATS_DIRECTORIES.send(method, [type, dir, pattern].compact)
|
71
|
+
::CodeStatistics::TEST_TYPES << type if type.include? 'specs'
|
72
|
+
end
|
73
|
+
|
74
|
+
# Setup annotations
|
75
|
+
ENV['SOURCE_ANNOTATION_DIRECTORIES'] = 'spec,doc'
|
76
|
+
|
77
|
+
desc 'Enumerate all annotations'
|
78
|
+
task :notes do
|
79
|
+
SourceAnnotationExtractor.enumerate '@?OPTIMIZE|@?FIXME|@?TODO', tag: true
|
80
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'price-hubble'
|
6
|
+
require 'pp'
|
7
|
+
|
8
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
9
|
+
# with your gem easier. You can also use a different console, if you like.
|
10
|
+
|
11
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
12
|
+
# require "pry"
|
13
|
+
# Pry.start
|
14
|
+
|
15
|
+
require 'irb'
|
16
|
+
IRB.start(__FILE__)
|
data/bin/run
ADDED
data/bin/setup
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# ~/.bashrc: executed by bash(1) for non-login shells.
|
2
|
+
# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
|
3
|
+
# for examples
|
4
|
+
|
5
|
+
_GEM_PATHS=$(ls -d1 ${HOME}/.gem/ruby/*/bin 2>/dev/null | paste -sd ':')
|
6
|
+
_APP_PATHS=$(ls -d1 /app/vendor/bundle/ruby/*/bin 2>/dev/null | paste -sd ':')
|
7
|
+
|
8
|
+
export PATH="${_GEM_PATHS}:${_APP_PATHS}:${PATH}"
|
9
|
+
export PATH="/app/node_modules/.bin:${HOME}/.bin:/app/bin:${PATH}"
|
10
|
+
export MAKE_ENV=baremetal
|
11
|
+
|
12
|
+
# Disable the autostart of all supervisord units
|
13
|
+
sudo sed -i 's/autostart=.*/autostart=false/g' /etc/supervisor/conf.d/*
|
14
|
+
|
15
|
+
# Start the supervisord (empty, no units)
|
16
|
+
sudo supervisord >/dev/null 2>&1 &
|
17
|
+
|
18
|
+
# Wait for supervisord
|
19
|
+
while ! supervisorctl status >/dev/null 2>&1; do sleep 1; done
|
20
|
+
|
21
|
+
# Boot the mDNS stack
|
22
|
+
echo '# Start the mDNS stack'
|
23
|
+
sudo supervisorctl start dbus avahi
|
24
|
+
echo
|
25
|
+
|
26
|
+
function watch-make-test()
|
27
|
+
{
|
28
|
+
while [ 1 ]; do
|
29
|
+
inotifywait --quiet -r `pwd` -e close_write --format '%e -> %w%f'
|
30
|
+
make test
|
31
|
+
done
|
32
|
+
}
|
33
|
+
|
34
|
+
function watch-make()
|
35
|
+
{
|
36
|
+
while [ 1 ]; do
|
37
|
+
inotifywait --quiet -r `pwd` -e close_write --format '%e -> %w%f'
|
38
|
+
make $@
|
39
|
+
done
|
40
|
+
}
|
41
|
+
|
42
|
+
function watch-run()
|
43
|
+
{
|
44
|
+
while [ 1 ]; do
|
45
|
+
inotifywait --quiet -r `pwd` -e close_write --format '%e -> %w%f'
|
46
|
+
bash -c "$@"
|
47
|
+
done
|
48
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# mappings for Ctrl-left-arrow and Ctrl-right-arrow for word moving
|
2
|
+
"\e[1;5C": forward-word
|
3
|
+
"\e[1;5D": backward-word
|
4
|
+
"\e[5C": forward-word
|
5
|
+
"\e[5D": backward-word
|
6
|
+
"\e\e[C": forward-word
|
7
|
+
"\e\e[D": backward-word
|
8
|
+
|
9
|
+
# handle common Home/End escape codes
|
10
|
+
"\e[1~": beginning-of-line
|
11
|
+
"\e[4~": end-of-line
|
12
|
+
"\e[7~": beginning-of-line
|
13
|
+
"\e[8~": end-of-line
|
14
|
+
"\eOH": beginning-of-line
|
15
|
+
"\eOF": end-of-line
|
16
|
+
"\e[H": beginning-of-line
|
17
|
+
"\e[F": end-of-line
|
@@ -0,0 +1,68 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
2
|
+
<svg
|
3
|
+
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
4
|
+
xmlns:cc="http://creativecommons.org/ns#"
|
5
|
+
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
6
|
+
xmlns:svg="http://www.w3.org/2000/svg"
|
7
|
+
xmlns="http://www.w3.org/2000/svg"
|
8
|
+
version="1.1"
|
9
|
+
id="Ebene_1"
|
10
|
+
x="0px"
|
11
|
+
y="0px"
|
12
|
+
viewBox="0 0 800 200"
|
13
|
+
xml:space="preserve"
|
14
|
+
width="800"
|
15
|
+
height="200"><metadata
|
16
|
+
id="metadata33"><rdf:RDF><cc:Work
|
17
|
+
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
18
|
+
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
19
|
+
id="defs31" />
|
20
|
+
<style
|
21
|
+
type="text/css"
|
22
|
+
id="style2">
|
23
|
+
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#E73E11;}
|
24
|
+
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#0371B9;}
|
25
|
+
.st2{fill:#132E48;}
|
26
|
+
.st3{font-family:'OpenSans-Bold';}
|
27
|
+
.st4{font-size:29.5168px;}
|
28
|
+
.st5{fill-rule:evenodd;clip-rule:evenodd;fill:none;}
|
29
|
+
.st6{opacity:0.5;fill:#132E48;}
|
30
|
+
.st7{font-family:'OpenSans';}
|
31
|
+
.st8{font-size:12px;}
|
32
|
+
</style>
|
33
|
+
<g
|
34
|
+
transform="translate(0,1.53584)"
|
35
|
+
id="g828"><g
|
36
|
+
transform="translate(35.93985,35.66416)"
|
37
|
+
id="g8">
|
38
|
+
<path
|
39
|
+
style="clip-rule:evenodd;fill:#e73e11;fill-rule:evenodd"
|
40
|
+
id="path4"
|
41
|
+
d="m -0.1,124.4 c 0,0 33.7,-123.2 66.7,-123.2 12.8,0 26.9,21.9 38.8,47.2 -23.6,27.9 -66.6,59.7 -94,76 -7.1,0 -11.5,0 -11.5,0 z"
|
42
|
+
class="st0" />
|
43
|
+
<path
|
44
|
+
style="clip-rule:evenodd;fill:#0371b9;fill-rule:evenodd"
|
45
|
+
id="path6"
|
46
|
+
d="m 88.1,101.8 c 13.5,-10.4 18.4,-16.2 27.1,-25.4 10,25.7 16.7,48 16.7,48 0,0 -41.4,0 -78,0 14.6,-7.9 18.7,-10.7 34.2,-22.6 z"
|
47
|
+
class="st1" />
|
48
|
+
</g><text
|
49
|
+
y="106.40316"
|
50
|
+
x="192.43155"
|
51
|
+
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:29.51733398px;font-family:'Open Sans', sans-serif;-inkscape-font-specification:'OpenSans-Bold, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#132e48"
|
52
|
+
id="text10"
|
53
|
+
class="st2 st3 st4">PriceHubble</text>
|
54
|
+
<rect
|
55
|
+
style="clip-rule:evenodd;fill:none;fill-rule:evenodd"
|
56
|
+
id="rect12"
|
57
|
+
height="24"
|
58
|
+
width="314.5"
|
59
|
+
class="st5"
|
60
|
+
y="118.06416"
|
61
|
+
x="194.23985" /><text
|
62
|
+
y="127.22146"
|
63
|
+
x="194.21715"
|
64
|
+
style="font-size:12px;font-family:'Open Sans', sans-serif;opacity:0.5;fill:#132e48;-inkscape-font-specification:'Open Sans, sans-serif, Normal';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;text-anchor:start;text-align:start;writing-mode:lr;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;"
|
65
|
+
id="text14"
|
66
|
+
class="st6 st7 st8">Ruby client for the PriceHubble REST API</text>
|
67
|
+
</g>
|
68
|
+
</svg>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative './config'
|
5
|
+
|
6
|
+
# Fetch the authentication/identity for the first time, subsequent calls
|
7
|
+
# to this method will return the cached authentication instance until it
|
8
|
+
# is expired (or near expiration, 5 minutes leeway) then a new
|
9
|
+
# authentication is fetched transparently
|
10
|
+
pp PriceHubble.identity
|
11
|
+
# => #<PriceHubble::Authentication access_token="...", ...>
|
12
|
+
|
13
|
+
# Check the expiration state (transparently always unexpired)
|
14
|
+
pp PriceHubble.identity.expired?
|
15
|
+
# => false
|
16
|
+
|
17
|
+
# Get the current authentication expiration date/time
|
18
|
+
pp PriceHubble.identity.expires_at
|
19
|
+
# => 2019-10-17 08:01:23 +0000
|