transitland_client 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 51540a969bfd94f06f1f072888a906b0d5d42051
4
+ data.tar.gz: a3c660825b13e17aad68b4a61600e2d7649ad4ec
5
+ SHA512:
6
+ metadata.gz: e4e21ea7519d4469dca59cd01342daf406db62248834dbd576e3f83bd8e20d95824f342f1e2428e7b43feef7e0998807e576ebd27bf04e9a37b39f98a200fb57
7
+ data.tar.gz: 8ef9e2c092390fe1c60f32c14c0cbee798646d8d31543fa7816f1a52b7c83d4b4a8c9ceadc28d63444dfb6892029afaea1df2859da8f792411e20801029ccbcf
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Mapzen
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,78 @@
1
+ [![Circle CI](https://circleci.com/gh/transitland/transitland-ruby-client.svg?style=svg)](https://circleci.com/gh/transitland/transitland-ruby-client)
2
+ [![Gem Version](https://badge.fury.io/rb/transitland-client.svg)](http://badge.fury.io/rb/transitland-client)
3
+
4
+ # transitland-(ruby)-client
5
+
6
+ This Ruby library enables you to read from the [Transitland Feed Registry](https://github.com/transitland/transitland-feed-registry). In the future, it will also provide access to the [Transitland Datastore](https://github.com/transitland/transitland-datastore), and allow you to write to both.
7
+
8
+ ## Installation
9
+
10
+ These instructions assume you already have a Ruby 2.0+ interpreter available for local use.
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ ```ruby
15
+ gem 'transitland-client'
16
+ ```
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install transitland-client
25
+
26
+ ## Configuration
27
+
28
+ The following settings can be changed with environment variables:
29
+
30
+ Environment variable name | default value
31
+ -------------------------------- | -------------
32
+ `TRANSITLAND_FEED_REGISTRY_URL` | `git@github.com:transitland/transitland-feed-registry.git`
33
+ `TRANSITLAND_FEED_REGISTRY_PATH` | `./tmp/transitland-feed-registry`
34
+
35
+ ## Usage
36
+
37
+ This library is intended for use within other software and services. It also includes its own console, which lets developers try functionality locally.
38
+
39
+ To start the console:
40
+
41
+ bundle exec rake console
42
+
43
+ To load a local copy of the [Transitland Feed Registry](https://github.com/transitland/transitland-feed-registry):
44
+
45
+ [1] pry(main)> TransitlandClient::FeedRegistry.repo
46
+
47
+ To list all registries in the Feed Registry:
48
+
49
+ [2] pry(main)> TransitlandClient::Entities::Feed.all
50
+
51
+ That returns a standard Ruby array, so you can also perform operators like counting the number of feeds in the registry:
52
+
53
+ [3] pry(main)> TransitlandClient::Entities::Feed.all.count
54
+ => 20
55
+
56
+ To find a single feed by its [Onestop ID](https://github.com/transitland/onestop-id):
57
+
58
+ [4] pry(main)> TransitlandClient::Entities::Feed.find_by(onestop_id: 'f-9q9-actransit')
59
+
60
+ To find all feeds that include an operator by its [Onestop ID](https://github.com/transitland/onestop-id):
61
+
62
+ [5] pry(main)> TransitlandClient::Entities::Feed.find_by(onestop_id: 'o-dhw-miamidadetransit')
63
+
64
+ To find all feeds by a tag key and value:
65
+
66
+ [6] pry(main)> TransitlandClient::Entities::Feed.find_by(tag_key: 'license', tag_value: 'Creative Commons Attribution 3.0 Unported License')
67
+
68
+ To find all feeds that include an operator identifier:
69
+
70
+ [7] pry(main)> TransitlandClient::Entities::Feed.find_by(operator_identifier: 'usntd://4034')
71
+
72
+ To get the URL for a feed:
73
+
74
+ [8] pry(main)> TransitlandClient::Entities::Feed.find_by(onestop_id: 'f-9q9-bayarearapidtransit').url
75
+
76
+ To update the local copy of the Feed Registry (pulling any recent commits):
77
+
78
+ [9] pry(main)> TransitlandClient::FeedRegistry.repo(force_update: true)
@@ -0,0 +1,85 @@
1
+ require 'json'
2
+
3
+ module TransitlandClient
4
+ module Entities
5
+ class Feed
6
+ attr_accessor :onestop_id, :url, :feed_format, :tags, :operators_in_feed
7
+
8
+ def initialize(onestop_id: nil, json_blob: nil)
9
+ if onestop_id
10
+ begin
11
+ json_file = File.open(FeedRegistry.json_file_for_entity_with_name('feeds', onestop_id), 'r')
12
+ parsed_json = JSON.parse(json_file.read)
13
+ rescue Errno::ENOENT
14
+ raise ArgumentError.new("no JSON file found with a Onestop ID of #{onestop_id}")
15
+ end
16
+ elsif json_blob
17
+ parsed_json = JSON.parse(json_blob)
18
+ else
19
+ raise ArgumentError.new('provide a Onestop ID or a JSON blob')
20
+ end
21
+
22
+ map_from_json_properties_to_object_variables(parsed_json)
23
+
24
+ @parsed_json = parsed_json
25
+
26
+ @operators_in_feed = create_operators_in_feed(@parsed_json['operatorsInFeed'])
27
+
28
+ self
29
+ end
30
+
31
+ def self.find_by(onestop_id: nil, operator_identifier: nil, tag_key: nil, tag_value: nil)
32
+ if onestop_id && (onestop_id_object = OnestopId.new(string: onestop_id))
33
+ if onestop_id_object.entity_prefix == 'f'
34
+ self.new(onestop_id: onestop_id)
35
+ elsif onestop_id_object.entity_prefix == 'o'
36
+ all.find_all { |feed| feed.operators_in_feed.any? { |oif| oif.operator_onestop_id == onestop_id } }
37
+ end
38
+ elsif tag_key && tag_value
39
+ all.find_all { |feed| feed.tags[tag_key] == tag_value }
40
+ elsif operator_identifier
41
+ all.find_all { |feed| feed.operators_in_feed.any? { |oif| oif.identifiers.include?(operator_identifier) } }
42
+ else
43
+ raise ArgumentError.new('must specify a Onestop ID, a tag key and value, or an operator identifier.')
44
+ end
45
+ end
46
+
47
+ def self.all(force_reload: false)
48
+ if force_reload || !defined?(@entity_objects)
49
+ @entity_objects = []
50
+ files = FeedRegistry.json_files_for_entity('feeds')
51
+ files.each do |file_path|
52
+ json_file = File.open(file_path, 'r')
53
+ @entity_objects << new(json_blob: json_file.read)
54
+ end
55
+ end
56
+ @entity_objects
57
+ end
58
+
59
+ private
60
+
61
+ def create_operators_in_feed(parsed_json_array)
62
+ array = parsed_json_array.map do |parsed_json_object|
63
+ OperatorInFeed.new(
64
+ feed: self,
65
+ operator_onestop_id: parsed_json_object['onestopId'],
66
+ gtfs_agency_id: parsed_json_object['gtfsAgencyId'],
67
+ identifiers: parsed_json_object['identifiers']
68
+ )
69
+ end
70
+ array
71
+ end
72
+
73
+ def map_from_json_properties_to_object_variables(parsed_json)
74
+ [
75
+ ['@onestop_id', 'onestopId'],
76
+ ['@url', 'url'],
77
+ ['@feed_format', 'feedFormat'],
78
+ ['@tags', 'tags'],
79
+ ].each do |mapping|
80
+ instance_variable_set(mapping[0], parsed_json[mapping[1]])
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,26 @@
1
+ require_relative 'feed'
2
+ # require_relative 'operator'
3
+
4
+ module TransitlandClient
5
+ module Entities
6
+ class OperatorInFeed
7
+ attr_accessor :gtfs_agency_id, :operator_onestop_id, :feed_onestop_id, :feed, :identifiers
8
+
9
+ def initialize(gtfs_agency_id: nil, operator_onestop_id: nil, feed_onestop_id: nil, feed: nil, identifiers: nil)
10
+ if feed
11
+ @feed = feed
12
+ elsif feed_onestop_id
13
+ @feed = Feed.new(onestop_id: feed_onestop_id)
14
+ else
15
+ raise ArgumentError.new('a feed object or Onestop ID must be specified')
16
+ end
17
+
18
+ @gtfs_agency_id = gtfs_agency_id
19
+ @operator_onestop_id = operator_onestop_id
20
+ @identifiers = identifiers || []
21
+
22
+ self
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,37 @@
1
+ require 'git'
2
+ require 'singleton'
3
+ require 'fileutils'
4
+
5
+ module TransitlandClient
6
+ class FeedRegistry
7
+ include Singleton
8
+
9
+ def self.repo(force_update: false)
10
+ @remote_url ||= ENV['TRANSITLAND_FEED_REGISTRY_URL'] || 'git@github.com:transitland/transitland-feed-registry.git'
11
+ @local_path ||= ENV['TRANSITLAND_FEED_REGISTRY_PATH'] || File.join(__dir__, '..', '..','tmp', 'transitland-feed-registry')
12
+
13
+ if !defined?(@repo) || force_update
14
+ begin
15
+ FileUtils.mkdir_p(@local_path)
16
+
17
+ @repo = Git.open(@local_path)
18
+ @repo.pull if force_update
19
+ rescue ArgumentError => error
20
+ if error.message == 'path does not exist'
21
+ @repo = Git.clone(@remote_url, @local_path)
22
+ end
23
+ end
24
+ end
25
+
26
+ @repo
27
+ end
28
+
29
+ def self.json_files_for_entity(entity)
30
+ Dir[File.join(@local_path, entity, '**', '*.json')]
31
+ end
32
+
33
+ def self.json_file_for_entity_with_name(entity, name)
34
+ File.join(@local_path, entity, "#{name}.json")
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,99 @@
1
+ module TransitlandClient
2
+ class OnestopId
3
+ ENTITY_TO_PREFIX = {
4
+ 'stop' => 's',
5
+ 'operator' => 'o',
6
+ 'feed' => 'f',
7
+ 'route' => 'r'
8
+ }
9
+ PREFIX_TO_ENTITY = ENTITY_TO_PREFIX.invert
10
+ COMPONENT_SEPARATOR = '-'
11
+
12
+ attr_accessor :entity_prefix, :geohash, :name
13
+
14
+ def initialize(string: nil, entity_prefix: nil, geohash: nil, name: nil)
15
+ errors = []
16
+
17
+ if string && string.length > 0
18
+ is_a_valid_onestop_id, errors = OnestopId.validate_onestop_id_string(string)
19
+ if is_a_valid_onestop_id
20
+ @entity_prefix = string.split(COMPONENT_SEPARATOR)[0]
21
+ @geohash = string.split(COMPONENT_SEPARATOR)[1]
22
+ @name = string.split(COMPONENT_SEPARATOR)[2]
23
+ self
24
+ else
25
+ raise ArgumentError.new(errors.join(', '))
26
+ end
27
+ elsif entity_prefix && geohash && name
28
+ if OnestopId.valid_component?(:entity_prefix, entity_prefix)
29
+ @entity_prefix = entity_prefix
30
+ else
31
+ errors << 'invalid entity prefix'
32
+ end
33
+ if OnestopId.valid_component?(:geohash, geohash)
34
+ @geohash = geohash
35
+ else
36
+ errors << 'invalid geohash'
37
+ end
38
+ if OnestopId.valid_component?(:name, name)
39
+ @name = name
40
+ else
41
+ errors << 'invalid name'
42
+ end
43
+ else
44
+ errors << 'either a string or entity/geohash/name must be specified'
45
+ end
46
+
47
+ if errors.length > 0
48
+ raise ArgumentError.new(errors.join(', '))
49
+ else
50
+ self
51
+ end
52
+ end
53
+
54
+ def self.validate_onestop_id_string(onestop_id, expected_entity_type: nil)
55
+ errors = []
56
+ is_a_valid_onestop_id = true
57
+
58
+ if onestop_id.split(COMPONENT_SEPARATOR).length != 3
59
+ errors << 'must include 3 components separated by hyphens ("-")'
60
+ is_a_valid_onestop_id = false
61
+ end
62
+
63
+ if expected_entity_type && onestop_id.split(COMPONENT_SEPARATOR)[0] != ENTITY_TO_PREFIX[expected_entity_type]
64
+ errors << "must start with \"#{ENTITY_TO_PREFIX[expected_entity_type]}\" as its 1st component"
65
+ is_a_valid_onestop_id = false
66
+ elsif expected_entity_type == nil && !valid_component?(:entity_prefix, onestop_id.split(COMPONENT_SEPARATOR)[0])
67
+ errors << "must start with \"#{ENTITY_TO_PREFIX.values.join(' or ')}\" as its 1st component"
68
+ is_a_valid_onestop_id = false
69
+ end
70
+
71
+ if onestop_id.split(COMPONENT_SEPARATOR)[1].length == 0 || !valid_component?(:geohash, onestop_id.split(COMPONENT_SEPARATOR)[1])
72
+ errors << 'must include a valid geohash as its 2nd component'
73
+ is_a_valid_onestop_id = false
74
+ end
75
+
76
+ if !valid_component?(:name, onestop_id.split(COMPONENT_SEPARATOR)[2])
77
+ errors << 'must include only letters and digits in its abbreviated name (the 3rd component)'
78
+ is_a_valid_onestop_id = false
79
+ end
80
+ return is_a_valid_onestop_id, errors
81
+ end
82
+
83
+ private
84
+
85
+ def self.valid_component?(component, value)
86
+ return false if !value || value.length == 0
87
+ case component
88
+ when :entity_prefix
89
+ ENTITY_TO_PREFIX.values.include?(value)
90
+ when :geohash
91
+ !(value =~ /[^a-z\d]/)
92
+ when :name
93
+ !(value =~ /[^a-zA-Z\d]/)
94
+ else
95
+ false
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,3 @@
1
+ module TransitlandClient
2
+ VERSION = '0.0.5'
3
+ end
@@ -0,0 +1,4 @@
1
+ Gem.find_files("transitland_client/**/*.rb").each { |path| require path }
2
+
3
+ module TransitlandClient
4
+ end
metadata ADDED
@@ -0,0 +1,193 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: transitland_client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.5
5
+ platform: ruby
6
+ authors:
7
+ - Drew Dara-Abrams
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-05-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: virtus
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: git
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.2'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: vcr
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.9'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.9'
83
+ - !ruby/object:Gem::Dependency
84
+ name: webmock
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.20'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.20'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.9'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.9'
111
+ - !ruby/object:Gem::Dependency
112
+ name: pry
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.10'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.10'
125
+ - !ruby/object:Gem::Dependency
126
+ name: byebug
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '3.5'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '3.5'
139
+ - !ruby/object:Gem::Dependency
140
+ name: pry-byebug
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '3.0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '3.0'
153
+ description: ''
154
+ email:
155
+ - drew@mapzen.com
156
+ executables: []
157
+ extensions: []
158
+ extra_rdoc_files: []
159
+ files:
160
+ - LICENSE.txt
161
+ - README.md
162
+ - lib/transitland_client.rb
163
+ - lib/transitland_client/entities/feed.rb
164
+ - lib/transitland_client/entities/operator_in_feed.rb
165
+ - lib/transitland_client/feed_registry.rb
166
+ - lib/transitland_client/onestop_id.rb
167
+ - lib/transitland_client/version.rb
168
+ homepage: https://github.com/transitland/transitland-ruby-client
169
+ licenses:
170
+ - MIT
171
+ metadata: {}
172
+ post_install_message:
173
+ rdoc_options: []
174
+ require_paths:
175
+ - lib
176
+ required_ruby_version: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '2.0'
181
+ required_rubygems_version: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - ">="
184
+ - !ruby/object:Gem::Version
185
+ version: '0'
186
+ requirements: []
187
+ rubyforge_project:
188
+ rubygems_version: 2.2.2
189
+ signing_key:
190
+ specification_version: 4
191
+ summary: Read and write data from the Transitland Datastore and Feed Registry
192
+ test_files: []
193
+ has_rdoc: