whos_got_dirt 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](https://badge.fury.io/rb/whos_got_dirt)
|
|
4
|
+
[](https://travis-ci.org/influencemapping/whos_got_dirt-gem)
|
|
5
|
+
[](https://gemnasium.com/influencemapping/whos_got_dirt-gem)
|
|
6
|
+
[](https://coveralls.io/r/influencemapping/whos_got_dirt-gem)
|
|
7
|
+
[](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
|