whos_got_dirt 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.rspec +2 -0
- data/.travis.yml +20 -0
- data/.yardopts +3 -0
- data/Gemfile +7 -0
- data/LICENSE +20 -0
- data/README.md +61 -0
- data/Rakefile +69 -0
- data/lib/whos_got_dirt/renderer.rb +66 -0
- data/lib/whos_got_dirt/request.rb +218 -0
- data/lib/whos_got_dirt/requests/entity/corp_watch.rb +123 -0
- data/lib/whos_got_dirt/requests/entity/little_sis.rb +52 -0
- data/lib/whos_got_dirt/requests/entity/open_corporates.rb +104 -0
- data/lib/whos_got_dirt/requests/entity/open_duka.rb +33 -0
- data/lib/whos_got_dirt/requests/entity/poderopedia.rb +48 -0
- data/lib/whos_got_dirt/requests/entity.rb +54 -0
- data/lib/whos_got_dirt/requests/list/little_sis.rb +37 -0
- data/lib/whos_got_dirt/requests/list/open_corporates.rb +36 -0
- data/lib/whos_got_dirt/requests/list.rb +16 -0
- data/lib/whos_got_dirt/requests/relation/open_corporates.rb +72 -0
- data/lib/whos_got_dirt/requests/relation/open_oil.rb +49 -0
- data/lib/whos_got_dirt/requests/relation.rb +51 -0
- data/lib/whos_got_dirt/response.rb +79 -0
- data/lib/whos_got_dirt/responses/entity/corp_watch.rb +72 -0
- data/lib/whos_got_dirt/responses/entity/little_sis.rb +66 -0
- data/lib/whos_got_dirt/responses/entity/open_corporates.rb +61 -0
- data/lib/whos_got_dirt/responses/entity/open_duka.rb +42 -0
- data/lib/whos_got_dirt/responses/entity/poderopedia.rb +44 -0
- data/lib/whos_got_dirt/responses/entity.rb +7 -0
- data/lib/whos_got_dirt/responses/helpers/little_sis.rb +34 -0
- data/lib/whos_got_dirt/responses/helpers/open_corporates.rb +28 -0
- data/lib/whos_got_dirt/responses/list/little_sis.rb +42 -0
- data/lib/whos_got_dirt/responses/list/open_corporates.rb +34 -0
- data/lib/whos_got_dirt/responses/list.rb +7 -0
- data/lib/whos_got_dirt/responses/relation/open_corporates.rb +66 -0
- data/lib/whos_got_dirt/responses/relation/open_oil.rb +70 -0
- data/lib/whos_got_dirt/responses/relation.rb +7 -0
- data/lib/whos_got_dirt/result.rb +106 -0
- data/lib/whos_got_dirt/validator.rb +52 -0
- data/lib/whos_got_dirt/version.rb +3 -0
- data/lib/whos_got_dirt.rb +43 -0
- data/schemas/popolo.json +1619 -0
- data/spec/cassettes/corp_watch_entity.yml +35 -0
- data/spec/cassettes/little_sis_entity.yml +98 -0
- data/spec/cassettes/little_sis_list.yml +159 -0
- data/spec/cassettes/open_corporates_entity.yml +137 -0
- data/spec/cassettes/open_corporates_list.yml +60 -0
- data/spec/cassettes/open_corporates_relation.yml +121 -0
- data/spec/cassettes/open_duka_entity.yml +1149 -0
- data/spec/cassettes/open_oil_relation.yml +640 -0
- data/spec/cassettes/poderopedia_entity.yml +43 -0
- data/spec/renderer_spec.rb +48 -0
- data/spec/request_spec.rb +205 -0
- data/spec/requests/entity/corp_watch_spec.rb +99 -0
- data/spec/requests/entity/little_sis_spec.rb +33 -0
- data/spec/requests/entity/open_corporates_spec.rb +81 -0
- data/spec/requests/entity/open_duka_spec.rb +21 -0
- data/spec/requests/entity/poderopedia_spec.rb +21 -0
- data/spec/requests/list/little_sis_spec.rb +25 -0
- data/spec/requests/list/open_corporates_spec.rb +33 -0
- data/spec/requests/relation/open_corporates_spec.rb +53 -0
- data/spec/requests/relation/open_oil_spec.rb +37 -0
- data/spec/response_spec.rb +119 -0
- data/spec/responses/entity/corp_watch_spec.rb +31 -0
- data/spec/responses/entity/little_sis_spec.rb +31 -0
- data/spec/responses/entity/open_corporates_spec.rb +31 -0
- data/spec/responses/entity/open_duka_spec.rb +25 -0
- data/spec/responses/entity/poderopedia_spec.rb +25 -0
- data/spec/responses/list/little_sis_spec.rb +31 -0
- data/spec/responses/list/open_corporates_spec.rb +31 -0
- data/spec/responses/relation/open_corporates_spec.rb +31 -0
- data/spec/responses/relation/open_oil_spec.rb +31 -0
- data/spec/result_spec.rb +90 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/shared_examples_for_requests.rb +155 -0
- data/spec/validator_spec.rb +43 -0
- data/whos_got_dirt.gemspec +28 -0
- metadata +281 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 98d4adb0f91ffac1ff9ceabe8232a3861e2f900a
|
4
|
+
data.tar.gz: 627276f14cba96da225df90590eb71267ef13c1a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d9e8cdc8e2076c071af133ac8abdcf97d0676c4edf6712c9591ab661ade771f5729abde4cef9766834fb49c282e6a6f1b134ab7daed572aaa55a9427b912184c
|
7
|
+
data.tar.gz: 1ff4a4d9c4afb312c5550afb83c48b503ecb82186fcb106240b6db7ca05187458d9fe067f1cef806e5c6f02841692150e0fb88ea21194e2560ed37f4b0010b89
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
sudo: false
|
2
|
+
language: ruby
|
3
|
+
cache: bundler
|
4
|
+
rvm:
|
5
|
+
- 1.9.3
|
6
|
+
- 2.0.0
|
7
|
+
- 2.1.0
|
8
|
+
- 2.2.0
|
9
|
+
env:
|
10
|
+
global:
|
11
|
+
# CORP_WATCH_API_KEY
|
12
|
+
- secure: dLqYYE/SMgjM7t4760RyWo9JC5gyGQ4QeBFDiiznEV1th1YQjXgBZxAKzwH2msjOL93z1F9U0AGmkSwm7uX6y6l6/Oom4zyjSJv2j/GTsNCB6dKLSKLDjL01Veb9+HjLtOgp/b8cCptzs/V0k79So3ku8MPGkZR8xOTu3C+8tjQEolUFIPTAxPTYYQAz2EirUgxlvXKTPXT6cSJMFoq5LOpzMhFpRwcAL5C0dg7WLObESCqZr8ZBPmAGMFbzI9toktDPFbgp6M7MpF/9qfU3PJNnGISp3T6/YQRXOcOacKrznE8BH6LKDApA02kB2u4YeBLpkM10APwi5/4leYEB1AY+LrVrCzyWibtIDLRYJfwuiiX8rd9w1aIhFs0WEnnvcSpb/sbW6PPuc9f3p6z+AqGek5fg3UhLGJPwP9Yz6eY34bwe+pD2JwL/iksORawHhtiyP3VXsaxIQnQTV/A3qv/mTkMPsTltgtBVLZobxkgz+QTcqaZPin82EnyaD4RKAYF8pfBidGStiG/hsBkMM6vnkF0usKAR6r9A7aZdRDs9v4E1A/RY0weonJPD3caOKX5rQm5rTrd8oS8J1bMyMKDNuOo0rvpPGM0KmZ/ePRFLN4Soo0EDFmGSoZjam8QI9EN2Gu7OlcvjrT3vRp4E5KbLtRGt/kZD+Z2u23ttRg0=
|
13
|
+
# LITTLE_SIS_API_KEY
|
14
|
+
- secure: P+76fE58oTzuM+1EVJI8WhzqE8QxxgyxOhtiAudTNt6tPA7VuQvbSbe8MAkiOvSnWQ/wAiJkhGZLE93lKSjOuBTLBbCcwLtMMQ+6U/WkLSVPUxD2p+HgMynZaV1zBbGbrBpOnTj4WyeM1Jz+zJkrI5YCOO1XS39mUNn9RZh6Bepg3mzRlRmsAy47bgzhprbv21tWmR9C2LZvhzSfWu+2GCKCVrnGdBDQ1WoKhnwCEboxdjDb0sdD/GlmebLMMfiS1MRpoXw0Z4AdLm2/eBAI4t/USeGLMan23U53BZ2yBAhy5Ns42+f18zr1iOHoX05oyevlqJkKEdEhnoJ1CyxByKQVLe0oWIgW38SgWvTTl+JaSSPyiNxlK6Asr3VKcMfui+nKziHfMSZTtwm9lq6JzSaOM2L/+Qe90BHN0raWH2uvR3ACCmR/PP7tl7ZV+8nNQ4nmzt2H9DpQEnYsil/4eQ3hLrny9fTMylYJpiN1yNhaVnWMXw2hknBnUwBq42Xu3zsv1dMYGzi1C/nJWw/3imIvftiJxArOlSFcZ+dB3iBWou5sm9pJpRtUyLcwhah6zc+GEroMdv6d27r8ANabtOAMgjxR29lFpNThMBgO84DWD05CViyzCBZQf8eNWvVRrmjAC+wn8L71ZSqOgauH7Urw0qJda2uvLZ7biJBCnvs=
|
15
|
+
# OPEN_DUKA_API_KEY
|
16
|
+
- secure: CYftDtbj2dqO0YQpct+r1SKkqKGbUeQCo4O0muBLQb6l87C9kCCKfNbgfMWzpuoFQXWD0CYRQvt22S8ulQl5pA6RYV0L0pAhiijRNWs090+1aEnqbmSWz0thhrdX7YGjiaEmd8KN7iCygDGxMV/az57LhqfETKQOJdI9Q0Ozynw5LuawW1kUsrzA6wy0uWxIz9g4Whtqh3ZGFgkJjFkMff2z5ZP4XRljNjxs6h/AFGiKWKOYtGTGI4JmlbZu5ibfqZ9G+ipS3Sg5tOVWjU43hUXmrMgbXNXB2Q/mkLpxDqRYLFfyj6KWTNaeVwqbURagswZ3u57ZhntUGsqL8tB8wVwM8WbVCx9neY1RRvSIl9Rhiobe4L1qEGxG9nEVHLlAh7S6JEt/iekzkribJnwdk2IaTdJ4rjkaaYLoYW59TLsMnTAxSoEjvPRU8E0WPrlHu1xYBW0/JieuoJVPXOD+z6wAMKM7BjswtRNmCmQ53C6M8AJvgg/HbKqA+5INBQomnyHZape3aCk7DX8FnL6DuBsYNFonMFN+IjfL/eP9gx9l1rY6hmyYVCIbqZ91OTdvMHDdJsYVkBMXX9Xrp6zQQDVapo4FArWji8BRfZwyOeplmUqx4ERP99+RKgVC5mRXqygGTRzYd6rHfThPZNQNQvzrrwIohEPWC4E6u2OKi1s=
|
17
|
+
# OPEN_OIL_API_KEY
|
18
|
+
- secure: bdGjQmbZ6oYVaM/UEqyGK6fZF/LTJLqWdmzP+HpVax5c6ogsTKhcruTScmkH4IbZRD3UCRc8WAh0Y4Wcr2xoxWwcdJS0XY2QQfsENrc8k0GMBr8ijf8nrSLwon1hUdxhmvFRpsDBIU7N2qhAvcYUVvFGKp8e1FsNzxFJZRsHC2cY1UxQyiWKMkLaGgdhki56/CJefH4TRuNFkuEIYyGiOiQX+QdtX9PhIYF1ycu2ucAF1qUBh9fMLyLv8YH+Hiudy8ko8SmHskhOTHtqvD542dbSzYznrvdVo+K1+4TcBajZ7jR8smoqvIzMxUpJXjLv/UpPmz0YIor8EeVSiKslXmCcpM5b5KVT+aAkMxk/0RTOEDJ1G8MuCwypUo9CiSqtg5quaOPRK4Yu4fsqGAO7oYD1rfyZj1b1lSy91yuS55ZlIsskJhDBLk4UEcugjtqjy4d0/JDfl97WmMX9QqvLw4K9vbSp+uBAIJbulvelB5ya1VaE7k2eGxiw4T5AommhltXvn64tahMf609bc42LiV8+FsW/LO/1GPQbHIM2ojQ24Rq3SdaMQxXnJTILFyQQX672ECAf/ME9QRwY3S7T9SoKEvWeB92Flo9NzFX+wodHME+yn0av5Em8st0MaBNJlHFrc2zAYKaSIdSpvW2c+15Ip3UEBQ/YKsu8ydouVTk=
|
19
|
+
# PODEROPEDIA_API_KEY
|
20
|
+
- secure: AfDhdNDjaio3W804DXKAaL6hyThHjM5670eZSF/2kKExyI1mxGSjS7uuB8102zhh8mRyZzs1iYr2SeWUvTeOCtbIo2Vo6apctQQnVjUGJ0WGWYCJD1Y+9sWknw3PxTq1ca8x9B3/jMJeppFJpJglqI+yYWrWYS5jXJ68H6BsNXK8zTJCuGQF62dxYds36r2CLVQmvs27zqox2HpwAJQ3dzS5aM+SxOQv3TemqjhHJ+nJ3F3dISQytEfBkSXIdyEdN8tT3QkppDbMfTilAuvezdI5QuY0kxWcrQXDBCSByaKb2PAxC5JXa3MBO/EiqZ8WAv1MDfJW/0DhfJTE0+Iznm2IUBY7hLMo2up046KnkfTygBTu25F6B5eFz5geqJH4RIf0bqtxwIp1h0cVOtV3QoWILDjl88dOqMuoUNiE/36ikwkesqXGbtLKrpqCgJm2f/HDWMtLjzcsdYiyzoGsoC4yoUPQNMHis/R0uRSGTx1BEFXL0RBYOCwrztWl4OPDfQUwuLHBp+5gcGCzz1RBeuBmQHqZ2amhxSMwSzl1ehME4yF2t6Rm50ROM1QUYz1ee5fGerSpDjNFyR0aFUBpbPPrSLdIX0UKDU3W1mXVZ+wKvSZAk+5/tS89YCbgwMSl4cJwATG8uBkM/OR/G8hq3qJTP55AfdFOxIeAhXjPT7M=
|
data/.yardopts
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2015 James McKinney
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# Who's got dirt? A federated search API for entities and relations
|
2
|
+
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/whos_got_dirt.svg)](https://badge.fury.io/rb/whos_got_dirt)
|
4
|
+
[![Build Status](https://secure.travis-ci.org/influencemapping/whos_got_dirt-gem.png)](https://travis-ci.org/influencemapping/whos_got_dirt-gem)
|
5
|
+
[![Dependency Status](https://gemnasium.com/influencemapping/whos_got_dirt-gem.png)](https://gemnasium.com/influencemapping/whos_got_dirt-gem)
|
6
|
+
[![Coverage Status](https://coveralls.io/repos/influencemapping/whos_got_dirt-gem/badge.svg)](https://coveralls.io/r/influencemapping/whos_got_dirt-gem)
|
7
|
+
[![Code Climate](https://codeclimate.com/github/influencemapping/whos_got_dirt-gem.png)](https://codeclimate.com/github/influencemapping/whos_got_dirt-gem)
|
8
|
+
|
9
|
+
## Usage
|
10
|
+
|
11
|
+
This gem provides a common API to multiple APIs. See the [server](https://github.com/influencemapping/whos_got_dirt-server) for a deployment.
|
12
|
+
|
13
|
+
To add support for new APIs, see the documentation for [Request](http://www.rubydoc.info/gems/whos_got_dirt/WhosGotDirt/Request) and [Response](http://www.rubydoc.info/gems/whos_got_dirt/WhosGotDirt/Response).
|
14
|
+
|
15
|
+
In this example, we convert generic API parameters to an OpenCorporates API URL, and request the URL with [Faraday](https://github.com/lostisland/faraday). Then, we convert the OpenCorporates API response to a generic API response.
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
require 'whos_got_dirt'
|
19
|
+
require 'faraday'
|
20
|
+
|
21
|
+
input = {
|
22
|
+
'subject' => [{
|
23
|
+
'name~=' => 'John Smith',
|
24
|
+
}],
|
25
|
+
'jurisdiction_code|=' => ['gb', 'ie'],
|
26
|
+
'role' => 'director',
|
27
|
+
'inactive' => false,
|
28
|
+
}
|
29
|
+
|
30
|
+
url = WhosGotDirt::Requests::Relation::OpenCorporates.new(input).to_s
|
31
|
+
#=> "https://api.opencorporates.com/officers/search?q=John+Smith&jurisdiction_code=gb%7Cie&position=director&inactive=false&order=score"
|
32
|
+
|
33
|
+
response = Faraday.get(url)
|
34
|
+
|
35
|
+
results = WhosGotDirt::Responses::Relation::OpenCorporates.new(response).to_a
|
36
|
+
#=> [{"@type"=>"Relation",
|
37
|
+
# "subject"=>
|
38
|
+
# {"name"=>"JOHN SMITH",
|
39
|
+
# "contact_details"=>[],
|
40
|
+
# "occupation"=>"CONTRACTS DIRECTORS"},
|
41
|
+
# "object"=>
|
42
|
+
# {"name"=>"IMPERIAL DUCTWORK SERVICES HOLDINGS LIMITED",
|
43
|
+
# "identifiers"=>[{"identifier"=>"08484366", "scheme"=>"Company Register"}],
|
44
|
+
# "links"=>[{"url"=>"https://opencorporates.com/companies/gb/08484366", "note"=>"OpenCorporates URL"}],
|
45
|
+
# "jurisdiction_code"=>"gb"},
|
46
|
+
# "start_date"=>"2013-04-30",
|
47
|
+
# "identifiers"=>[{"identifier"=>"71863990", "scheme"=>"OpenCorporates"}],
|
48
|
+
# "links"=>[{"url"=>"https://opencorporates.com/officers/71863990", "note"=>"OpenCorporates URL"}],
|
49
|
+
# "updated_at"=>"2014-10-13T13:57:58+00:00",
|
50
|
+
# "current_status"=>"CURRENT",
|
51
|
+
# "jurisdiction_code"=>"gb",
|
52
|
+
# "role"=>"director",
|
53
|
+
# "sources"=>[{"url"=>"https://api.opencorporates.com/officers/search?inactive=false&jurisdiction_code=gb%7Cie&order=score&position=director&q=John+Smith", "note"=>"OpenCorporates"}]},
|
54
|
+
# ...]
|
55
|
+
```
|
56
|
+
|
57
|
+
## Acknowledgements
|
58
|
+
|
59
|
+
Most terms are from [Popolo](http://www.popoloproject.com/). The request and response formats are inspired from the [Metaweb Query Language](http://mql.freebaseapps.com/index.html) and the [OpenRefine Reconciliation Service API](https://github.com/OpenRefine/OpenRefine/wiki/Reconciliation-Service-API).
|
60
|
+
|
61
|
+
Copyright (c) 2015 James McKinney, released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
|
7
|
+
task :default => :spec
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'yard'
|
11
|
+
YARD::Rake::YardocTask.new
|
12
|
+
rescue LoadError
|
13
|
+
task :yard do
|
14
|
+
abort 'YARD is not available. In order to run yard, you must: gem install yard'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
desc 'Fetch schemas, rewrite references, and store locally'
|
19
|
+
task :schemas do
|
20
|
+
require 'json'
|
21
|
+
require 'open-uri'
|
22
|
+
|
23
|
+
require 'json-schema'
|
24
|
+
|
25
|
+
def process_value(value, definitions)
|
26
|
+
url = value['$ref']
|
27
|
+
if url
|
28
|
+
name = url.rpartition('/')[2].chomp('.json#')
|
29
|
+
value['$ref'] = "#/definitions/#{name}"
|
30
|
+
define(name, url, definitions)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def process_schema(url, definitions)
|
35
|
+
schema = JSON.load(open(url).read)
|
36
|
+
schema['properties'].each do |_,value|
|
37
|
+
process_value(value, definitions)
|
38
|
+
if value.key?('items')
|
39
|
+
process_value(value['items'], definitions)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
schema
|
43
|
+
end
|
44
|
+
|
45
|
+
def define(name, url, definitions)
|
46
|
+
unless definitions.key?(name)
|
47
|
+
definitions[name] = {} # to avoid recursion
|
48
|
+
definitions[name] = process_schema(url, definitions)
|
49
|
+
definitions[name].delete('id')
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
definitions = {} # passed by reference
|
54
|
+
|
55
|
+
%w(organization person).each do |name|
|
56
|
+
define(name, "http://www.popoloproject.com/schemas/#{name}.json#", definitions)
|
57
|
+
end
|
58
|
+
|
59
|
+
schema = {
|
60
|
+
'$schema' => 'http://json-schema.org/draft-03/schema#',
|
61
|
+
'definitions' => definitions,
|
62
|
+
}
|
63
|
+
|
64
|
+
JSON::Validator.validate!(schema, {}, validate_schema: true)
|
65
|
+
|
66
|
+
File.open(File.expand_path(File.join('..', 'schemas', 'popolo.json'), __FILE__), 'w') do |f|
|
67
|
+
f.write(JSON.pretty_generate(schema))
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module WhosGotDirt
|
2
|
+
# Accepts a JSON template, which is a hash in which some values are
|
3
|
+
# [JSON Pointers](http://tools.ietf.org/html/rfc6901). The template is
|
4
|
+
# rendered by evaluating its JSON Pointers against JSON data.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# renderer = WhosGotDirt::Renderer.new('name' => '/fn')
|
8
|
+
# data = {'fn' => 'John Smith'}
|
9
|
+
# renderer.result(data)
|
10
|
+
# #=> {'name' => 'John Smith'}
|
11
|
+
#
|
12
|
+
# @see http://ruby-doc.org/stdlib-2.2.3/libdoc/erb/rdoc/ERB.html
|
13
|
+
class Renderer
|
14
|
+
# @!attribute [r] template
|
15
|
+
# @return [Hash] the template
|
16
|
+
attr_reader :template
|
17
|
+
|
18
|
+
# Sets the template.
|
19
|
+
#
|
20
|
+
# @param [Object] template a template
|
21
|
+
def initialize(template)
|
22
|
+
@template = template
|
23
|
+
end
|
24
|
+
|
25
|
+
# Renders the template by evaluating its JSON Pointers against JSON data.
|
26
|
+
#
|
27
|
+
# @param [Object] data the JSON data
|
28
|
+
# @return [Object] the rendered template
|
29
|
+
def result(data)
|
30
|
+
walk(template, data)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# @see https://github.com/pudo/jsonmapping/blob/master/jsonmapping/mapper.py
|
36
|
+
def walk(node, data)
|
37
|
+
case node
|
38
|
+
when Hash
|
39
|
+
hash = {}
|
40
|
+
node.each do |key,value|
|
41
|
+
if value.respond_to?(:call)
|
42
|
+
k, v = value.call(data)
|
43
|
+
else
|
44
|
+
v = walk(value, data)
|
45
|
+
end
|
46
|
+
if v
|
47
|
+
hash[k || key] = v
|
48
|
+
end
|
49
|
+
end
|
50
|
+
hash
|
51
|
+
when Array
|
52
|
+
node.map do |child|
|
53
|
+
walk(child, data)
|
54
|
+
end
|
55
|
+
when String
|
56
|
+
if node.start_with?('/')
|
57
|
+
JsonPointer.new(data, node).value
|
58
|
+
else
|
59
|
+
node
|
60
|
+
end
|
61
|
+
else
|
62
|
+
node
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
module WhosGotDirt
|
2
|
+
# Accepts MQL parameters and return URLs to request.
|
3
|
+
#
|
4
|
+
# @example Create a new class for transforming parameters to URLs.
|
5
|
+
# class MyAPIRequest < WhosGotDirt::Request
|
6
|
+
# @base_url = 'https://api.example.com'
|
7
|
+
#
|
8
|
+
# def to_s
|
9
|
+
# "#{base_url}/endpoint?#{to_query(input)}"
|
10
|
+
# end
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# @example Use the class in requesting a URL.
|
14
|
+
# url = MyAPIRequest.new(name: 'John Smith').to_s
|
15
|
+
# response = Faraday.get(url)
|
16
|
+
# #=> "https://api.example.com/endpoint?name=John+Smith"
|
17
|
+
class Request
|
18
|
+
class << self
|
19
|
+
# @!attribute [r] base_url
|
20
|
+
# @return [String] the base URL to be used in the request
|
21
|
+
attr_reader :base_url
|
22
|
+
|
23
|
+
# Transforms a query string from a hash to a string.
|
24
|
+
#
|
25
|
+
# @param [Hash] params query string parameters
|
26
|
+
# @return [String] a query string
|
27
|
+
def to_query(params)
|
28
|
+
params.map do |key,value|
|
29
|
+
"#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
|
30
|
+
end * '&'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# @!attribute input
|
35
|
+
# @return [Hash] the MQL parameters
|
36
|
+
attr_accessor :input
|
37
|
+
|
38
|
+
# @!attribute :output
|
39
|
+
# @return [Hash] the API-specific parameters
|
40
|
+
attr_reader :output
|
41
|
+
|
42
|
+
# Sets the MQL parameters.
|
43
|
+
#
|
44
|
+
# @param [Hash] input the MQL parameters
|
45
|
+
def initialize(input = {})
|
46
|
+
@input = ActiveSupport::HashWithIndifferentAccess.new(input)
|
47
|
+
@output = {}
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns the base URL to be used in the request.
|
51
|
+
#
|
52
|
+
# @return [String] the base URL to be used in the request
|
53
|
+
def base_url
|
54
|
+
self.class.base_url
|
55
|
+
end
|
56
|
+
|
57
|
+
# Helper method to map a parameter that supports the MQL equality operator.
|
58
|
+
#
|
59
|
+
# @param [String] target the API-specific parameter name
|
60
|
+
# @param [String] source the request parameter name
|
61
|
+
# @param [Hash] opts options
|
62
|
+
# @option opts [String] :input substitute MQL parameters
|
63
|
+
# @option opts [String] :transform a transformation to apply to the value
|
64
|
+
# @option opts [String] :default the default value
|
65
|
+
# @option opts [Set,Array] :valid a list of valid values
|
66
|
+
# @return [Hash] the API-specific parameters
|
67
|
+
def equal(target, source, opts = {})
|
68
|
+
params = parameters(opts)
|
69
|
+
|
70
|
+
if opts.key?(:valid)
|
71
|
+
if opts[:valid].include?(params[source])
|
72
|
+
output[target] = transform(params[source], opts)
|
73
|
+
end
|
74
|
+
else
|
75
|
+
if params[source]
|
76
|
+
output[target] = transform(params[source], opts)
|
77
|
+
elsif opts[:default]
|
78
|
+
output[target] = opts[:default]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
output
|
83
|
+
end
|
84
|
+
|
85
|
+
# Helper method to map a parameter that supports the MQL `~=` operator.
|
86
|
+
#
|
87
|
+
# @param [String] target the API-specific parameter name
|
88
|
+
# @param [String] source the request parameter name
|
89
|
+
# @param [Hash] opts options
|
90
|
+
# @option opts [String] :input substitute MQL parameters
|
91
|
+
# @option opts [String] :transform a transformation to apply to the value
|
92
|
+
# @return [Hash] the API-specific parameters
|
93
|
+
def match(target, source, opts = {})
|
94
|
+
params = parameters(opts)
|
95
|
+
|
96
|
+
if params[source]
|
97
|
+
equal(target, source, opts)
|
98
|
+
elsif params["#{source}~="]
|
99
|
+
output[target] = transform(params["#{source}~="], opts)
|
100
|
+
end
|
101
|
+
|
102
|
+
output
|
103
|
+
end
|
104
|
+
|
105
|
+
# Helper method to map a parameter that supports the MQL `|=` operator.
|
106
|
+
#
|
107
|
+
# @param [String] target the API-specific parameter name
|
108
|
+
# @param [String] source the request parameter name
|
109
|
+
# @param [Hash] opts options
|
110
|
+
# @option opts [String] :input substitute MQL parameters
|
111
|
+
# @option opts [String] :transform a transformation to apply to the value
|
112
|
+
# @return [Hash] the API-specific parameters
|
113
|
+
def one_of(target, source, opts = {})
|
114
|
+
params = parameters(opts)
|
115
|
+
|
116
|
+
if params[source]
|
117
|
+
equal(target, source, opts)
|
118
|
+
elsif params["#{source}|="]
|
119
|
+
output[target] = params["#{source}|="].map{|v| transform(v, opts)}.join(or_operator)
|
120
|
+
end
|
121
|
+
|
122
|
+
output
|
123
|
+
end
|
124
|
+
|
125
|
+
# Helper method to map a parameter that supports MQL `AND`-like constraints.
|
126
|
+
#
|
127
|
+
# @param [String] target the API-specific parameter name
|
128
|
+
# @param [String] source the request parameter name
|
129
|
+
# @param [Hash] opts options
|
130
|
+
# @option opts [String] :input substitute MQL parameters
|
131
|
+
# @option opts [String] :transform a transformation to apply to the value
|
132
|
+
# @option opts [String] :transform a transformation to apply to the value
|
133
|
+
# @return [Hash] the API-specific parameters
|
134
|
+
def all_of(target, source, opts = {})
|
135
|
+
params = parameters(opts)
|
136
|
+
|
137
|
+
if params[source]
|
138
|
+
equal(target, source, opts)
|
139
|
+
else
|
140
|
+
values = []
|
141
|
+
params.each do |key,value|
|
142
|
+
if key[/:([a-z_]+)$/, 1] == source
|
143
|
+
values << value
|
144
|
+
end
|
145
|
+
end
|
146
|
+
if values.empty?
|
147
|
+
if opts.key?(:backup)
|
148
|
+
send(opts[:backup], target, source, opts)
|
149
|
+
end
|
150
|
+
else
|
151
|
+
output[target] = values.map{|v| transform(v, opts)}.join(and_operator)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
output
|
156
|
+
end
|
157
|
+
|
158
|
+
# Helper method to map a date parameter that supports comparisons.
|
159
|
+
#
|
160
|
+
# @param [String] target the API-specific parameter name
|
161
|
+
# @param [String] source the request parameter name
|
162
|
+
# @param [Hash] opts options
|
163
|
+
# @option opts [String] :input substitute MQL parameters
|
164
|
+
# @return [Hash] the API-specific parameters
|
165
|
+
def date_range(target, source, opts = {})
|
166
|
+
params = parameters(opts)
|
167
|
+
|
168
|
+
# @note OpenCorporates date range format.
|
169
|
+
if params[source]
|
170
|
+
output[target] = "#{params[source]}:#{params[source]}"
|
171
|
+
elsif params["#{source}>="] || params["#{source}>"] || params["#{source}<="] || params["#{source}<"]
|
172
|
+
output[target] = "#{params["#{source}>="] || params["#{source}>"]}:#{params["#{source}<="] || params["#{source}<"]}"
|
173
|
+
end
|
174
|
+
|
175
|
+
output
|
176
|
+
end
|
177
|
+
|
178
|
+
# @abstract Subclass and override {#to_s} to return the URL from which to
|
179
|
+
# `GET` the results
|
180
|
+
def to_s
|
181
|
+
raise NotImplementedError
|
182
|
+
end
|
183
|
+
|
184
|
+
# @abstract Subclass and override {#and_operator} to return the "AND"
|
185
|
+
# operator's serialization
|
186
|
+
def and_operator
|
187
|
+
raise NotImplementedError
|
188
|
+
end
|
189
|
+
|
190
|
+
# @abstract Subclass and override {#or_operator} to return the "OR"
|
191
|
+
# operator's serialization
|
192
|
+
def or_operator
|
193
|
+
raise NotImplementedError
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
|
198
|
+
def to_query(params)
|
199
|
+
self.class.to_query(params)
|
200
|
+
end
|
201
|
+
|
202
|
+
def parameters(opts)
|
203
|
+
if opts.key?(:input)
|
204
|
+
opts[:input]
|
205
|
+
else
|
206
|
+
input
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def transform(value, opts)
|
211
|
+
if opts.key?(:transform)
|
212
|
+
opts[:transform].call(value)
|
213
|
+
else
|
214
|
+
value
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module WhosGotDirt
|
2
|
+
module Requests
|
3
|
+
module Entity
|
4
|
+
# Requests for companies from the CorpWatch API.
|
5
|
+
#
|
6
|
+
# By default, name and address queries will match complete words only in
|
7
|
+
# the same order as in the query, tokens less than three characters long
|
8
|
+
# will be ignored, and MySQL's [stopwords](http://dev.mysql.com/doc/refman/5.0/en/fulltext-stopwords.html)
|
9
|
+
# will be ignored. If `substring_match` is `1`, queries will match within
|
10
|
+
# words as well as complete words, at a severe performance penalty.
|
11
|
+
#
|
12
|
+
# @example Supply an API key.
|
13
|
+
# "corp_watch_api_key": "..."
|
14
|
+
#
|
15
|
+
# @example Find entities by IRS Employer Identification Number (EIN).
|
16
|
+
# "identifiers": [{
|
17
|
+
# "identifier": "911653725",
|
18
|
+
# "scheme": "IRS Employer Identification Number"
|
19
|
+
# }]
|
20
|
+
#
|
21
|
+
# @example Find entities by SEC Central Index Key (CIK).
|
22
|
+
# "identifiers": [{
|
23
|
+
# "identifier": "37996",
|
24
|
+
# "scheme": "SEC Central Index Key"
|
25
|
+
# }]
|
26
|
+
#
|
27
|
+
# @example Find companies by SIC code.
|
28
|
+
# "industry_code": "2011"
|
29
|
+
#
|
30
|
+
# @example Find companies by SIC sector.
|
31
|
+
# "sector_code": "4100"
|
32
|
+
#
|
33
|
+
# @example Match within words on name and address queries.
|
34
|
+
# "substring_match": 1
|
35
|
+
#
|
36
|
+
# @example Find companies by country code.
|
37
|
+
# "country_code": "US"
|
38
|
+
#
|
39
|
+
# @example Find companies by country subdivision code.
|
40
|
+
# "subdiv_code": "OR"
|
41
|
+
#
|
42
|
+
# @example Find companies with SEC filings in a given year.
|
43
|
+
# "year": 2005
|
44
|
+
#
|
45
|
+
# @example Find companies with SEC filings in or before a given year.
|
46
|
+
# "year>=": 2003
|
47
|
+
#
|
48
|
+
# @example Find companies with SEC filings in or after a given year.
|
49
|
+
# "year<=": 2007
|
50
|
+
#
|
51
|
+
# @example Find companies that appear as "filers" in SEC filings or as
|
52
|
+
# subsidiaries ("relationships") only.
|
53
|
+
# "source_type": "relationships"
|
54
|
+
#
|
55
|
+
# @example Find companies with three direct descendants in a hierarchy.
|
56
|
+
# "num_children": 3
|
57
|
+
#
|
58
|
+
# @example Find companies with two direct ancestors in a hierarchy.
|
59
|
+
# "num_parents": 2
|
60
|
+
#
|
61
|
+
# @example Find companies within the hierarchy of another company.
|
62
|
+
# "top_parent_id": "cw_7324"
|
63
|
+
class CorpWatch < Request
|
64
|
+
@base_url = 'http://api.corpwatch.org/%<year>s/companies.json'
|
65
|
+
|
66
|
+
# Returns the URL to request.
|
67
|
+
#
|
68
|
+
# @return [String] the URL to request
|
69
|
+
def to_s
|
70
|
+
params = convert
|
71
|
+
if params.key?(:year)
|
72
|
+
"#{base_url % params.slice(:year).symbolize_keys}?#{to_query(params.except(:year))}"
|
73
|
+
else
|
74
|
+
"http://api.corpwatch.org/companies.json?#{to_query(params)}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Converts the MQL parameters to API-specific parameters.
|
79
|
+
#
|
80
|
+
# @return [Hash] API-specific parameters
|
81
|
+
# @see http://api.corpwatch.org/documentation/api_examples.html#A17
|
82
|
+
def convert
|
83
|
+
match('company_name', 'name')
|
84
|
+
equal('limit', 'limit')
|
85
|
+
|
86
|
+
input['identifiers'] && input['identifiers'].each do |identifier|
|
87
|
+
case identifier['scheme']
|
88
|
+
when 'IRS Employer Identification Number'
|
89
|
+
equal('irs_number', 'identifier', input: identifier)
|
90
|
+
when 'SEC Central Index Key'
|
91
|
+
equal('cik', 'identifier', input: identifier)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
input['contact_details'] && input['contact_details'].each do |contact_detail|
|
96
|
+
if contact_detail['type'] == 'address' && (contact_detail['value'] || contact_detail['value~='])
|
97
|
+
output['raw_address'] = contact_detail['value'] || contact_detail['value~=']
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# API-specific parameters.
|
102
|
+
equal('key', 'corp_watch_api_key')
|
103
|
+
# http://api.corpwatch.org/documentation/api_examples.html#A35
|
104
|
+
equal('sic_code', 'industry_code')
|
105
|
+
equal('sic_sector', 'sector_code')
|
106
|
+
equal('substring_match', 'substring_match', valid: [1])
|
107
|
+
# http://api.corpwatch.org/documentation/api_examples.html#A36
|
108
|
+
equal('country_code', 'country_code', transform: lambda{|v| v.upcase})
|
109
|
+
equal('subdiv_code', 'subdiv_code', transform: lambda{|v| v.upcase})
|
110
|
+
equal('year', 'year')
|
111
|
+
equal('min_year', 'year>=')
|
112
|
+
equal('max_year', 'year<=')
|
113
|
+
equal('source_type', 'source_type')
|
114
|
+
equal('num_children', 'num_children')
|
115
|
+
equal('num_parents', 'num_parents')
|
116
|
+
equal('top_parent_id', 'top_parent_id')
|
117
|
+
|
118
|
+
output
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module WhosGotDirt
|
2
|
+
module Requests
|
3
|
+
module Entity
|
4
|
+
# Requests for entities from the LittleSis API.
|
5
|
+
#
|
6
|
+
# Tokens less than two characters long will be ignored by LittleSis' `q`
|
7
|
+
# filter. The `q` parameter matches names and aliases. If `search_all` is
|
8
|
+
# `1`, it also matches descriptions and summaries.
|
9
|
+
#
|
10
|
+
# @example Supply an API key.
|
11
|
+
# "little_sis_api_key": "..."
|
12
|
+
#
|
13
|
+
# @example Match descriptions and summaries on `name~=` queries.
|
14
|
+
# "search_all": 1
|
15
|
+
class LittleSis < Request
|
16
|
+
# The JSON response has less metadata, e.g. number of results.
|
17
|
+
@base_url = 'https://api.littlesis.org/entities.xml'
|
18
|
+
|
19
|
+
# Returns the URL to request.
|
20
|
+
#
|
21
|
+
# @return [String] the URL to request
|
22
|
+
def to_s
|
23
|
+
"#{base_url}?#{to_query(convert)}"
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the "OR" operator's serialization.
|
27
|
+
#
|
28
|
+
# @return [String] the "OR" operator's serialization
|
29
|
+
def or_operator
|
30
|
+
','
|
31
|
+
end
|
32
|
+
|
33
|
+
# Converts the MQL parameters to API-specific parameters.
|
34
|
+
#
|
35
|
+
# @return [Hash] API-specific parameters
|
36
|
+
# @see http://api.littlesis.org/documentation#entities
|
37
|
+
# @see http://api.littlesis.org/entities/types.json
|
38
|
+
def convert
|
39
|
+
match('q', 'name')
|
40
|
+
one_of('type_ids', 'classification')
|
41
|
+
equal('num', 'limit')
|
42
|
+
|
43
|
+
# API-specific parameters.
|
44
|
+
equal('_key', 'little_sis_api_key')
|
45
|
+
equal('search_all', 'search_all', valid: [1])
|
46
|
+
|
47
|
+
output
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|