gtfs 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +13 -2
  3. data/Gemfile +11 -8
  4. data/Gemfile.lock +41 -29
  5. data/LICENSE +21 -0
  6. data/README.md +15 -17
  7. data/Rakefile +2 -0
  8. data/gtfs.gemspec +5 -2
  9. data/lib/gtfs.rb +1 -0
  10. data/lib/gtfs/agency.rb +3 -3
  11. data/lib/gtfs/calendar.rb +2 -2
  12. data/lib/gtfs/calendar_date.rb +2 -2
  13. data/lib/gtfs/fare_attribute.rb +3 -3
  14. data/lib/gtfs/fare_rule.rb +2 -2
  15. data/lib/gtfs/feed_info.rb +19 -0
  16. data/lib/gtfs/frequency.rb +2 -2
  17. data/lib/gtfs/model.rb +28 -9
  18. data/lib/gtfs/route.rb +2 -2
  19. data/lib/gtfs/shape.rb +2 -2
  20. data/lib/gtfs/source.rb +19 -11
  21. data/lib/gtfs/stop.rb +3 -3
  22. data/lib/gtfs/stop_time.rb +3 -3
  23. data/lib/gtfs/transfer.rb +2 -2
  24. data/lib/gtfs/trip.rb +3 -3
  25. data/lib/gtfs/url_source.rb +1 -1
  26. data/lib/gtfs/version.rb +1 -2
  27. data/spec/fixtures/cassettes/invalid_gtfs_uri.yml +58 -65
  28. data/spec/fixtures/cassettes/valid_gtfs_uri.yml +55 -122
  29. data/spec/gtfs/agency_spec.rb +2 -2
  30. data/spec/gtfs/calendar_date_spec.rb +1 -1
  31. data/spec/gtfs/calendar_spec.rb +1 -1
  32. data/spec/gtfs/fare_attribute_spec.rb +14 -0
  33. data/spec/gtfs/fare_rule_spec.rb +13 -0
  34. data/spec/gtfs/feed_info_spec.rb +14 -0
  35. data/spec/gtfs/frequency_spec.rb +14 -0
  36. data/spec/gtfs/route_spec.rb +1 -1
  37. data/spec/gtfs/shape_spec.rb +1 -1
  38. data/spec/gtfs/source_spec.rb +23 -11
  39. data/spec/gtfs/stop_spec.rb +1 -1
  40. data/spec/gtfs/stop_time_spec.rb +2 -2
  41. data/spec/gtfs/transfer_spec.rb +14 -0
  42. data/spec/gtfs/trip_spec.rb +2 -2
  43. data/spec/gtfs/url_source_spec.rb +4 -4
  44. data/spec/spec_helper.rb +7 -3
  45. data/spec/support/model_shared_examples.rb +46 -22
  46. metadata +46 -52
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8df8ca58b1e0d2b4855443d0d7825c59f7eabfaf8be6c89fff0da03cd69ce6b0
4
+ data.tar.gz: e38f08d1959b969400f80353d26ad1ee12965df92fc4c0ebe405ee39d81555ea
5
+ SHA512:
6
+ metadata.gz: 689c542dead3e918da4d02dd6170ae31a134eeea4dfff6ba7bf1591fd71d297bcbd616b935790823a4247d8421aeb7fc9be9f8618ebd67f6a50388df57c27193
7
+ data.tar.gz: 4b09664e10ccd3c84dbdccbde25b66536395769dc3e789ce371c8f41ad6b77f68a2dbf70466ef88f508e717081eb7bfbf5be197d53a3fb80c6423d70d6495812
data/.travis.yml CHANGED
@@ -1,4 +1,15 @@
1
+ os: linux
2
+ dist: bionic
1
3
  language: ruby
2
4
  rvm:
3
- - 1.9.2
4
- - 1.9.3
5
+ - 2.5.7
6
+ - 2.6.5
7
+ - 2.7.0
8
+
9
+ before_install:
10
+ - gem install bundler
11
+
12
+ install: bundle install --retry=3
13
+
14
+ notifications:
15
+ email: false
data/Gemfile CHANGED
@@ -1,10 +1,13 @@
1
- source 'http://rubygems.org'
1
+ source 'https://rubygems.org'
2
2
 
3
- gem 'rake'
4
- gem 'rspec'
5
3
  gem 'multi_json', '1.0.4'
6
- gem 'simplecov', :require => false
7
- gem 'rubyzip'
8
- gem 'vcr'
9
- gem 'fakeweb'
10
- gem 'debugger'
4
+ gem 'rubyzip', '~> 1.1'
5
+
6
+ group :test, :development do
7
+ gem 'simplecov', :require => false
8
+ gem 'rake'
9
+ gem 'rspec-its'
10
+ gem 'rspec'
11
+ gem 'vcr'
12
+ gem 'webmock'
13
+ end
data/Gemfile.lock CHANGED
@@ -1,42 +1,54 @@
1
1
  GEM
2
- remote: http://rubygems.org/
2
+ remote: https://rubygems.org/
3
3
  specs:
4
- columnize (0.3.6)
5
- debugger (1.2.2)
6
- columnize (>= 0.3.1)
7
- debugger-linecache (~> 1.1.1)
8
- debugger-ruby_core_source (~> 1.1.5)
9
- debugger-linecache (1.1.2)
10
- debugger-ruby_core_source (>= 1.1.1)
11
- debugger-ruby_core_source (1.1.5)
12
- diff-lcs (1.1.3)
13
- fakeweb (1.3.0)
4
+ addressable (2.7.0)
5
+ public_suffix (>= 2.0.2, < 5.0)
6
+ crack (0.4.4)
7
+ diff-lcs (1.4.4)
8
+ docile (1.3.2)
9
+ hashdiff (1.0.1)
14
10
  multi_json (1.0.4)
15
- rake (0.9.2.2)
16
- rspec (2.8.0)
17
- rspec-core (~> 2.8.0)
18
- rspec-expectations (~> 2.8.0)
19
- rspec-mocks (~> 2.8.0)
20
- rspec-core (2.8.0)
21
- rspec-expectations (2.8.0)
22
- diff-lcs (~> 1.1.2)
23
- rspec-mocks (2.8.0)
24
- rubyzip (0.9.5)
25
- simplecov (0.6.1)
26
- multi_json (~> 1.0)
27
- simplecov-html (~> 0.5.3)
28
- simplecov-html (0.5.3)
29
- vcr (1.11.3)
11
+ public_suffix (4.0.6)
12
+ rake (13.0.1)
13
+ rspec (3.9.0)
14
+ rspec-core (~> 3.9.0)
15
+ rspec-expectations (~> 3.9.0)
16
+ rspec-mocks (~> 3.9.0)
17
+ rspec-core (3.9.3)
18
+ rspec-support (~> 3.9.3)
19
+ rspec-expectations (3.9.3)
20
+ diff-lcs (>= 1.2.0, < 2.0)
21
+ rspec-support (~> 3.9.0)
22
+ rspec-its (1.3.0)
23
+ rspec-core (>= 3.0.0)
24
+ rspec-expectations (>= 3.0.0)
25
+ rspec-mocks (3.9.1)
26
+ diff-lcs (>= 1.2.0, < 2.0)
27
+ rspec-support (~> 3.9.0)
28
+ rspec-support (3.9.4)
29
+ rubyzip (1.3.0)
30
+ simplecov (0.19.0)
31
+ docile (~> 1.1)
32
+ simplecov-html (~> 0.11)
33
+ simplecov-html (0.12.3)
34
+ vcr (6.0.0)
35
+ webmock (3.9.3)
36
+ addressable (>= 2.3.6)
37
+ crack (>= 0.3.2)
38
+ hashdiff (>= 0.4.0, < 2.0.0)
30
39
 
31
40
  PLATFORMS
32
41
  ruby
33
42
 
34
43
  DEPENDENCIES
35
- debugger
36
- fakeweb
37
44
  multi_json (= 1.0.4)
38
45
  rake
39
46
  rspec
40
- rubyzip
47
+ rspec-its
48
+ rubyzip (~> 1.1)
41
49
  simplecov
42
50
  vcr
51
+ webmock
52
+
53
+ BUNDLED WITH
54
+ 2.1.4
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2012 Ed Schmalzle
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![Build Status](https://travis-ci.org/nerdEd/gtfs.svg?branch=master)](https://travis-ci.org/nerdEd/gtfs)
2
+
1
3
  ### GTFS Ruby
2
4
 
3
5
  A Ruby wrapper for the [General Transit Feed Specification](https://developers.google.com/transit/gtfs/)
@@ -6,8 +8,12 @@ A Ruby wrapper for the [General Transit Feed Specification](https://developers.g
6
8
 
7
9
  Initialize a new GTFS source:
8
10
 
11
+ # Defaults to strict checking of required columns
9
12
  source = GTFS::Source.build(<URI or Path to GTFS zip file>)
10
-
13
+
14
+ # Relax the column checks, useful for sources that don't conform to standard
15
+ source = GTFS::Source.build(<URI or Path to GTFS zip file>, {strict: false})
16
+
11
17
  Accessing GTFS data from the source:
12
18
 
13
19
  source.agencies
@@ -22,24 +28,16 @@ Accessing GTFS data from the source:
22
28
  source.shapes
23
29
  source.frequencies
24
30
  source.transfers
31
+
32
+ Alternatively:
25
33
 
26
- ### License
34
+ source.each_agency {|agency| puts agency}
35
+ ...
36
+ source.each_transfer {|transfer| puts transfer}
27
37
 
28
- Copyright (C) 2012 Ed Schmalzle
29
38
 
30
- Permission is hereby granted, free of charge, to any person obtaining a
31
- copy of this software and associated documentation files (the "Software"), to
32
- deal in the Software without restriction, including without limitation the
33
- rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
34
- copies of the Software, and to permit persons to whom the Software is furnished
35
- to do so, subject to the following conditions:
39
+ ### License
36
40
 
37
- The above copyright notice and this permission notice shall be included in
38
- all copies or substantial portions of the Software.
41
+ This project is licensed under the terms of the MIT license.
39
42
 
40
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
41
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
42
- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
43
- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
44
- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
45
- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
43
+ See this license at [`LICENSE`](LICENSE).
data/Rakefile CHANGED
@@ -4,3 +4,5 @@ require 'rspec/core/rake_task'
4
4
  RSpec::Core::RakeTask.new do |t|
5
5
  t.pattern = './spec/**/*_spec.rb'
6
6
  end
7
+
8
+ task :default => [:spec]
data/gtfs.gemspec CHANGED
@@ -5,7 +5,6 @@ require './lib/gtfs/version'
5
5
  Gem::Specification.new do |gem|
6
6
  gem.name = 'gtfs'
7
7
  gem.version = GTFS::VERSION
8
- gem.date = Date.today.to_s
9
8
 
10
9
  gem.summary = 'Load and read GTFS data from zip bundles'
11
10
  gem.description = 'gtfs reads GTFS data from a google-compliant Zip bundle
@@ -15,9 +14,13 @@ Gem::Specification.new do |gem|
15
14
  gem.email = ['ed@nerded.net']
16
15
  gem.homepage = 'https://github.com/nerdEd/gtfs'
17
16
 
17
+ gem.license = 'MIT'
18
+
19
+ gem.required_ruby_version = '>= 1.9.2'
20
+
18
21
  gem.add_dependency 'rake'
19
22
  gem.add_dependency 'multi_json'
20
- gem.add_dependency 'rubyzip'
23
+ gem.add_dependency 'rubyzip', ['~> 1.1']
21
24
 
22
25
  gem.add_development_dependency 'rspec', ['>= 2.0.0']
23
26
  gem.add_development_dependency 'simplecov'
data/lib/gtfs.rb CHANGED
@@ -3,6 +3,7 @@ require 'gtfs/model'
3
3
  require 'gtfs/agency'
4
4
  require 'gtfs/calendar'
5
5
  require 'gtfs/calendar_date'
6
+ require 'gtfs/feed_info'
6
7
  require 'gtfs/route'
7
8
  require 'gtfs/shape'
8
9
  require 'gtfs/stop'
data/lib/gtfs/agency.rb CHANGED
@@ -3,7 +3,7 @@ module GTFS
3
3
  include GTFS::Model
4
4
 
5
5
  has_required_attrs :name, :url, :timezone
6
- has_optional_attrs :id, :lang, :phone, :fare_url
6
+ has_optional_attrs :id, :lang, :phone, :fare_url, :email
7
7
  attr_accessor *attrs
8
8
 
9
9
  column_prefix :agency_
@@ -12,8 +12,8 @@ module GTFS
12
12
  required_file true
13
13
  uses_filename 'agency.txt'
14
14
 
15
- def self.parse_agencies(data)
16
- return parse_models(data)
15
+ def self.parse_agencies(data, options={})
16
+ return parse_models(data, options)
17
17
  end
18
18
  end
19
19
  end
data/lib/gtfs/calendar.rb CHANGED
@@ -9,8 +9,8 @@ module GTFS
9
9
  required_file true
10
10
  uses_filename 'calendar.txt'
11
11
 
12
- def self.parse_calendars(data)
13
- return parse_models(data)
12
+ def self.parse_calendars(data, options={})
13
+ return parse_models(data, options)
14
14
  end
15
15
  end
16
16
  end
@@ -9,8 +9,8 @@ module GTFS
9
9
  required_file true
10
10
  uses_filename 'calendar_dates.txt'
11
11
 
12
- def self.parse_calendar_dates(data)
13
- return parse_models(data)
12
+ def self.parse_calendar_dates(data, options={})
13
+ return parse_models(data, options)
14
14
  end
15
15
  end
16
16
  end
@@ -3,15 +3,15 @@ module GTFS
3
3
  include GTFS::Model
4
4
 
5
5
  has_required_attrs :fare_id, :price, :currency_type, :payment_method, :transfers
6
- has_optional_attrs :transfer_duration
6
+ has_optional_attrs :transfer_duration
7
7
  attr_accessor *attrs
8
8
 
9
9
  collection_name :fare_attributes
10
10
  required_file false
11
11
  uses_filename 'fare_attributes.txt'
12
12
 
13
- def self.parse_fare_attributes(data)
14
- return parse_models(data)
13
+ def self.parse_fare_attributes(data, options={})
14
+ return parse_models(data, options)
15
15
  end
16
16
  end
17
17
  end
@@ -10,8 +10,8 @@ module GTFS
10
10
  required_file false
11
11
  uses_filename 'fare_rules.txt'
12
12
 
13
- def self.parse_fare_rules(data)
14
- return parse_models(data)
13
+ def self.parse_fare_rules(data, options={})
14
+ return parse_models(data, options)
15
15
  end
16
16
  end
17
17
  end
@@ -0,0 +1,19 @@
1
+ module GTFS
2
+ class FeedInfo
3
+ include GTFS::Model
4
+
5
+ has_required_attrs :publisher_name, :publisher_url, :lang
6
+ has_optional_attrs :start_date, :end_date, :version
7
+ attr_accessor *attrs
8
+
9
+ column_prefix :feed_
10
+
11
+ collection_name :feed_infos
12
+ required_file false
13
+ uses_filename 'feed_info.txt'
14
+
15
+ def self.parse_feed_infos(data, options={})
16
+ return parse_models(data, options)
17
+ end
18
+ end
19
+ end
@@ -10,8 +10,8 @@ module GTFS
10
10
  required_file false
11
11
  uses_filename 'frequencies.txt'
12
12
 
13
- def self.parse_frequencies(data)
14
- return parse_models(data)
13
+ def self.parse_frequencies(data, options={})
14
+ return parse_models(data, options)
15
15
  end
16
16
  end
17
17
  end
data/lib/gtfs/model.rb CHANGED
@@ -64,23 +64,42 @@ module GTFS
64
64
 
65
65
  def collection_name(collection_name)
66
66
  self.define_singleton_method(:name) {collection_name}
67
+
68
+ self.define_singleton_method(:singular_name) {
69
+ self.to_s.split('::').last.
70
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
71
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
72
+ tr("-", "_").downcase
73
+ }
67
74
  end
68
-
69
- def uses_filename(filename)
75
+
76
+ def uses_filename(filename)
70
77
  self.define_singleton_method(:filename) {filename}
71
78
  end
72
79
 
73
- def parse_models(data)
80
+ def each(filename)
81
+ CSV.foreach(filename, :headers => true) do |row|
82
+ yield parse_model(row.to_hash)
83
+ end
84
+ end
85
+
86
+ def parse_model(attr_hash, options={})
87
+ unprefixed_attr_hash = {}
88
+
89
+ attr_hash.each do |key, val|
90
+ unprefixed_attr_hash[key.gsub(/^#{prefix}/, '')] = val
91
+ end
92
+
93
+ model = self.new(unprefixed_attr_hash)
94
+ end
95
+
96
+ def parse_models(data, options={})
74
97
  return [] if data.nil? || data.empty?
75
98
 
76
99
  models = []
77
100
  CSV.parse(data, :headers => true) do |row|
78
- attr_hash = {}
79
- row.to_hash.each do |key, val|
80
- attr_hash[key.gsub(/^#{prefix}/, '')] = val
81
- end
82
- model = self.new(attr_hash)
83
- models << model if model.valid?
101
+ model = parse_model(row.to_hash, options)
102
+ models << model if options[:strict] == false || model.valid?
84
103
  end
85
104
  models
86
105
  end
data/lib/gtfs/route.rb CHANGED
@@ -12,8 +12,8 @@ module GTFS
12
12
  required_file true
13
13
  uses_filename 'routes.txt'
14
14
 
15
- def self.parse_routes(data)
16
- return parse_models(data)
15
+ def self.parse_routes(data, options={})
16
+ return parse_models(data, options)
17
17
  end
18
18
  end
19
19
  end
data/lib/gtfs/shape.rb CHANGED
@@ -12,8 +12,8 @@ module GTFS
12
12
 
13
13
  column_prefix :shape_
14
14
 
15
- def self.parse_shapes(data)
16
- return parse_models(data)
15
+ def self.parse_shapes(data, options={})
16
+ return parse_models(data, options)
17
17
  end
18
18
  end
19
19
  end
data/lib/gtfs/source.rb CHANGED
@@ -1,21 +1,23 @@
1
1
  require 'tmpdir'
2
2
  require 'fileutils'
3
- require 'zip/zip'
3
+ require 'zip'
4
4
 
5
5
  module GTFS
6
6
  class Source
7
7
 
8
- ENTITIES = [GTFS::Agency, GTFS::Stop, GTFS::Route, GTFS::Trip, GTFS::StopTime,
8
+ ENTITIES = [GTFS::Agency, GTFS::Stop, GTFS::Route, GTFS::Trip, GTFS::StopTime,
9
9
  GTFS::Calendar, GTFS::CalendarDate, GTFS::Shape, GTFS::FareAttribute,
10
- GTFS::FareRule, GTFS::Frequency, GTFS::Transfer]
10
+ GTFS::FareRule, GTFS::Frequency, GTFS::Transfer, GTFS::FeedInfo]
11
11
 
12
12
  REQUIRED_SOURCE_FILES = ENTITIES.select(&:required_file?).map(&:filename)
13
13
  OPTIONAL_SOURCE_FILES = ENTITIES.reject(&:required_file?).map(&:filename)
14
14
  SOURCE_FILES = ENTITIES.map(&:filename)
15
15
 
16
- attr_accessor :source, :archive
16
+ DEFAULT_OPTIONS = {strict: true}
17
17
 
18
- def initialize(source)
18
+ attr_accessor :source, :archive, :options
19
+
20
+ def initialize(source, opts={})
19
21
  raise 'Source cannot be nil' if source.nil?
20
22
 
21
23
  @tmp_dir = Dir.mktmpdir
@@ -23,6 +25,8 @@ module GTFS
23
25
 
24
26
  @source = source
25
27
  load_archive(@source)
28
+
29
+ @options = DEFAULT_OPTIONS.merge(opts)
26
30
  end
27
31
 
28
32
  def self.finalize(directory)
@@ -30,7 +34,7 @@ module GTFS
30
34
  end
31
35
 
32
36
  def extract_to_cache(source_path)
33
- Zip::ZipFile.open(source_path) do |zip|
37
+ Zip::File.open(source_path) do |zip|
34
38
  zip.entries.each do |entry|
35
39
  zip.extract(entry.name, File.join(@tmp_dir, '/', entry.name))
36
40
  end
@@ -41,11 +45,11 @@ module GTFS
41
45
  raise 'Cannot directly instantiate base GTFS::Source'
42
46
  end
43
47
 
44
- def self.build(data_root)
48
+ def self.build(data_root, opts={})
45
49
  if File.exists?(data_root)
46
- LocalSource.new(data_root)
50
+ src = LocalSource.new(data_root, opts)
47
51
  else
48
- URLSource.new(data_root)
52
+ src = URLSource.new(data_root, opts)
49
53
  end
50
54
  end
51
55
 
@@ -61,9 +65,13 @@ module GTFS
61
65
  ENTITIES.each do |entity|
62
66
  define_method entity.name.to_sym do
63
67
  parse_file entity.filename do |f|
64
- entity.send("parse_#{entity.name}".to_sym, f.read)
68
+ entity.send("parse_#{entity.name}".to_sym, f.read, options)
65
69
  end
66
70
  end
71
+
72
+ define_method "each_#{entity.singular_name}".to_sym do |&block|
73
+ entity.each(File.join(@tmp_dir, entity.filename)) { |model| block.call model }
74
+ end
67
75
  end
68
76
 
69
77
  def files
@@ -72,7 +80,7 @@ module GTFS
72
80
 
73
81
  def parse_file(filename)
74
82
  raise_if_missing_source filename
75
- open File.join(@tmp_dir, '/', filename) do |f|
83
+ open File.join(@tmp_dir, '/', filename), 'r:bom|utf-8' do |f|
76
84
  files[filename] ||= yield f
77
85
  end
78
86
  end