bart_api 1.0.0

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: acf48065dee6a232f3d2696dd6ce3de6e6a28f95
4
+ data.tar.gz: 1d4aee5fe2c759ff55845523aa4acddd1b0344c9
5
+ SHA512:
6
+ metadata.gz: 8229a8062b76851b6fd7a677a5f544d930a55667b5d8819ae399a181bc71d79593cf0d2299a959298de762838a3683cb2ae16d3c99693e0443e4bda8a6b37955
7
+ data.tar.gz: 5d0c905ef5ba65d12804295e19f3a3dbf2af8d00e0392ddb0635ebd966b0c3d2f60d7236c7967bd43043914494106c6011fce9a86615fa68ba38b3cb36cf7bb3
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2016 Jon Egeland
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.
@@ -0,0 +1,52 @@
1
+ require 'memoist'
2
+
3
+ module Bart
4
+ # A base class implementing common API operations
5
+ class API
6
+ include Connection
7
+
8
+ class << self
9
+ extend Memoist
10
+
11
+ # Include another API's functionality via a new method on this API.
12
+ # For example, `include_api :routes` would include the "Routes" API into
13
+ # the "Client" API, accessible as `Client#routes`.
14
+ def include_api name
15
+ klass = self.const_get(name.to_s.constantize)
16
+ define_method(name) do
17
+ klass.new
18
+ end
19
+ self.memoize name
20
+ end
21
+
22
+ # Require all the files given in `names` that exist in the given folder
23
+ def require_all folder, *libs
24
+ libs.each do |lib|
25
+ require_relative "#{File.join(folder, lib)}"
26
+ end
27
+ end
28
+
29
+ # Return a singleton instance of this API
30
+ def singleton
31
+ @singleton ||= self.new
32
+ end
33
+ end
34
+
35
+
36
+ # Perform a GET request over the connection to the given endpoint.
37
+ def get_request endpoint, opts={}, &block
38
+ connection.get endpoint, opts, &block
39
+ end
40
+
41
+ # Perform a POST request over the connection to the given endpoint.
42
+ def post_request endpoint, opts={}, &block
43
+ connection.post endpoint, opts, &block
44
+ end
45
+
46
+ # For APIs that extend Memoist, allow the user to call `refresh` as an
47
+ # alias for `flush_cache`.
48
+ def refresh
49
+ send(:flush_cache) if respond_to?(:flush_cache)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,38 @@
1
+ module Bart
2
+ class Client::Estimates < API
3
+ RESOLUTION = 30 # seconds
4
+
5
+ def key stop_id, platform, direction
6
+ [stop_id, platform, direction]
7
+ end
8
+
9
+ # We need to memoize but never for longer than `RESOLUTION`, so rather than
10
+ # use Memoist this API maintains its own memo.
11
+ def get stop_id, platform: nil, direction: nil
12
+ @memo ||= {}
13
+ model, stored_at = @memo[[stop_id, platform, direction]]
14
+ return model if stored_at &&
15
+ Time.now - Bart.configuration.refresh_time < stored_at
16
+
17
+ parsed = get_request '/api/etd.aspx', query: {
18
+ cmd: :etd,
19
+ orig: stop_id,
20
+ plat: platform,
21
+ dir: direction
22
+ }.select { |_,v| v != nil }
23
+
24
+ parsed['root']['station'].tap do |station|
25
+ station['etd'] = [station['etd']].flatten
26
+ station['etd'].each do |etd|
27
+ etd['estimate'] = [etd['estimate']].flatten
28
+ end
29
+ end
30
+
31
+ model = Stop.new parsed['root']['station']
32
+ @memo[[stop_id, platform, direction]] = [model, Time.now]
33
+ model
34
+ end
35
+ alias_method :find, :get
36
+
37
+ end
38
+ end
@@ -0,0 +1,21 @@
1
+ module Bart
2
+ class Client::Routes < API
3
+ extend Memoist
4
+
5
+ # Return a list of all routes on the system.
6
+ def list
7
+ parsed = get_request '/api/route.aspx', query: { cmd: :routes }
8
+ parsed['root']['routes']['route'].map { |route| Route.new route }
9
+ end
10
+ memoize :list
11
+ alias_method :all, :list
12
+
13
+ # Return the route whose id matches the given id
14
+ def get id
15
+ parsed = get_request '/api/route.aspx', query: { cmd: :routeinfo, route: id }
16
+ Route.new parsed['root']['routes']['route']
17
+ end
18
+ memoize :get
19
+ alias_method :find, :get
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module Bart
2
+ class Client::Stops < API
3
+ extend Memoist
4
+
5
+ # Return a list of all stops on the system.
6
+ def list
7
+ parsed = get_request '/api/stn.aspx', query: { cmd: :stns }
8
+ parsed['root']['stations']['station'].map { |stop| Stop.new stop }
9
+ end
10
+ memoize :list
11
+ alias_method :all, :list
12
+
13
+ # Return the route whose id matches the given id
14
+ def get id
15
+ parsed = get_request '/api/stn.aspx', query: { cmd: :stninfo, orig: id }
16
+ Stop.new parsed['root']['stations']['station']
17
+ end
18
+ memoize :get
19
+ alias_method :find, :get
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ require 'memoist'
2
+
3
+ module Bart
4
+ class Client < API
5
+ extend Memoist
6
+
7
+ require_all 'client', 'routes', 'stops', 'estimates'
8
+
9
+ include_api :routes
10
+ include_api :stops
11
+ include_api :estimates
12
+ end
13
+ end
14
+
@@ -0,0 +1,49 @@
1
+ module Bart
2
+ class Configuration
3
+ # The version of the BART system
4
+ attr_accessor :version
5
+ # The base URL of the BART system
6
+ attr_accessor :base_uri
7
+ # The adapter to use for network communication
8
+ attr_accessor :adapter
9
+ # The output stream to which debug information should be written
10
+ attr_accessor :debug_output
11
+ # The key used to make requests, defaulting to BART's public key.
12
+ attr_accessor :api_key
13
+ # The number of seconds real-time results are cached for.
14
+ attr_accessor :refresh_time
15
+
16
+ # The defaults to use for any configuration options that are not provided
17
+ DEFAULT_CONFIGURATION = {
18
+ adapter: :httparty,
19
+ debug_output: false,
20
+ base_uri: 'http://api.bart.gov',
21
+ refresh_time: 30,
22
+ api_key: 'MW9S-E7SL-26DU-VV8V'
23
+ }
24
+
25
+ # The options required when configuring a BART instance
26
+ REQUIRED_CONFIGURATION = [
27
+ :base_uri,
28
+ :api_key,
29
+ :refresh_time
30
+ ]
31
+
32
+ def initialize
33
+ # Apply the default set of configurations before anything else to ensure
34
+ # all options are initialized.
35
+ DEFAULT_CONFIGURATION.each do |name, value|
36
+ send("#{name}=", value)
37
+ end
38
+ end
39
+
40
+ # Ensure that all required configurations have been given a value. Returns
41
+ # true if all required configuration options have been set.
42
+ def validate!
43
+ REQUIRED_CONFIGURATION.each do |name|
44
+ raise "`#{name}` is a required configuration option, but was not given a value." if send("#{name}").nil?
45
+ end
46
+ true
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,23 @@
1
+ module Bart
2
+ module Connection
3
+ extend self
4
+
5
+ # Return the connection adapter instance
6
+ def connection
7
+ @connection ||= adapter.new(Bart.configuration)
8
+ end
9
+
10
+ # Return the class of the adapter to use for the connection
11
+ def adapter
12
+ @@adapters[Bart.configuration.adapter]
13
+ end
14
+
15
+ # Register a new class that can be used as a connection adapter
16
+ def register_adapter name, klass
17
+ (@@adapters ||= {})[name] = klass
18
+ end
19
+ end
20
+ end
21
+
22
+ # Include the adapters that come packaged with the gem
23
+ require_relative 'connection_adapters/httparty_adapter.rb'
@@ -0,0 +1,35 @@
1
+ require 'httparty'
2
+ require 'json'
3
+
4
+ # A Connection adapter using HTTParty as the network transport
5
+ module Bart
6
+ module Connection
7
+ class HTTPartyAdapter
8
+ include HTTParty
9
+
10
+ def initialize config
11
+ self.class.base_uri config.base_uri
12
+ # Write debug information to the configured output stream
13
+ self.class.debug_output config.debug_output
14
+ @config = config
15
+ end
16
+
17
+ def defaults
18
+ {
19
+ query: { key: @config.api_key }
20
+ }
21
+ end
22
+
23
+ def get endpoint, opts={}, &block
24
+ self.class.get(endpoint, defaults.deep_merge(opts), &block).parsed_response
25
+ end
26
+
27
+ def post endpoint, opts={}, &block
28
+ self.class.post(endpoint, defaults.deep_merge(opts), &block).parsed_response
29
+ end
30
+ end
31
+
32
+ register_adapter :httparty, HTTPartyAdapter
33
+ end
34
+ end
35
+
@@ -0,0 +1,5 @@
1
+ class Float
2
+ def self.new numeric
3
+ numeric.to_f if numeric.respond_to? :to_f
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ class Hash
2
+ def deep_merge hash
3
+ self.merge hash do |k, a, b|
4
+ if a.respond_to? :merge and b.respond_to? :merge
5
+ a.merge b
6
+ else
7
+ b
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ class Integer
2
+ def self.new numeric
3
+ numeric.to_i if numeric.respond_to? :to_i
4
+ end
5
+ end
@@ -0,0 +1,21 @@
1
+ class String
2
+ def underscore
3
+ self.dup.underscore!
4
+ end unless method_defined? :underscore
5
+
6
+ def underscore!
7
+ self.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
8
+ self.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
9
+ self.tr_s!('- ', '_')
10
+ self.downcase!
11
+ self
12
+ end unless method_defined? :underscore!
13
+
14
+ def titleize
15
+ self.tr_s('- ', '_').split('_').map(&:capitalize).join(' ')
16
+ end unless method_defined? :titleize
17
+
18
+ def constantize
19
+ self.tr_s('- ', '_').split('_').map(&:capitalize).join
20
+ end unless method_defined? :constantize
21
+ end
@@ -0,0 +1,13 @@
1
+ module Bart
2
+ class Arrival < Model
3
+ attribute :minutes
4
+ attribute :platform, type: Integer
5
+ attribute :direction
6
+ attribute :length, type: Integer
7
+ alias_method :train_length, :length
8
+ attribute :color
9
+ attribute :hexcolor
10
+ attribute :bikeflag, type: Integer
11
+ attribute :delay, type: Integer
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module Bart
2
+ class Destination < Model
3
+ attribute :destination
4
+ alias_method :name, :destination
5
+ attribute :abbreviation
6
+ alias_method :abbr, :abbreviation
7
+
8
+ attribute :limited, type: Integer
9
+ attribute :estimate, type: Bart::Arrival, array: true
10
+ alias_method :arrivals, :estimate
11
+
12
+ primary_attribute :abbr
13
+ end
14
+ end
15
+
@@ -0,0 +1,21 @@
1
+ module Bart
2
+ class Route < Model
3
+ attribute :name
4
+ attribute :abbr
5
+ attribute :route_id
6
+ attribute :number, type: Integer
7
+ alias_method :id, :number
8
+ attribute :hexcolor
9
+ attribute :color
10
+
11
+ attribute :origin
12
+ attribute :destination
13
+ attribute :holidays
14
+ attribute :num_stns
15
+ attribute :direction
16
+
17
+ attribute :config
18
+
19
+ primary_attribute :id
20
+ end
21
+ end
@@ -0,0 +1,40 @@
1
+ module Bart
2
+ class Stop < Model
3
+ # Identity
4
+ attribute :name
5
+ attribute :abbr
6
+ attribute :link
7
+
8
+ # Location
9
+ attribute :gtfs_latitude, type: Float
10
+ alias_method :latitude, :gtfs_latitude
11
+ attribute :gtfs_longitude, type: Float
12
+ alias_method :longitude, :gtfs_longitude
13
+ attribute :address
14
+ attribute :city
15
+ attribute :county
16
+ attribute :state
17
+ attribute :zipcode
18
+
19
+ # Associations
20
+ attribute :north_routes
21
+ attribute :south_routes
22
+ attribute :north_platforms
23
+ attribute :south_platforms
24
+
25
+ # Prose metadata
26
+ attribute :platform_info
27
+ attribute :intro
28
+ attribute :cross_street
29
+ attribute :food
30
+ attribute :shopping
31
+ attribute :attraction
32
+
33
+ # Estimates from the `etd` endpoint
34
+ attribute :etd, type: Bart::Destination, array: true
35
+ alias_method :estimates, :etd
36
+
37
+ primary_attribute :abbr
38
+ alias_method :id, :abbr
39
+ end
40
+ end
@@ -0,0 +1,89 @@
1
+ require 'set'
2
+
3
+ module Bart
4
+ class Model
5
+ extend Forwardable
6
+
7
+ class << self
8
+ # Define a new attribute of the model.
9
+ # If `type` is given, a new instance of `type` will be created whenever
10
+ # this attribute is assigned a value. This allows creation of nested
11
+ # objects from a simple Hash.
12
+ # If `type` is given and `array` is true, the value given to this
13
+ # attribute will be interpreted as an array and a new instance of `type`
14
+ # will be created for each entry in the array
15
+ # It `type` is given, and the value given to this attribute is nil, no
16
+ # new instance of `type` will be created. Instead, the value will remain
17
+ # nil, as an instance of NilClass.
18
+ def attribute name, type: nil, array: false
19
+ attributes << [name, type]
20
+ attr_reader name
21
+ # Use a custom writer method to allow typed attributes to be
22
+ # instantiated properly.
23
+ define_method "#{name}=" do |value|
24
+ # Only do type conversion if the type is specified and the value is
25
+ # not nil.
26
+ if type and !value.nil?
27
+ # Lookup is done on Bart to ensure that Model subclasses are
28
+ # searched first, falling back to other types (Numeric, Hash, etc.)
29
+ # if no Model subclass is found.
30
+ klass = Bart.const_get(type.to_s)
31
+ value = array ? value.map{ |v| klass.new(v) } : klass.new(value)
32
+ end
33
+ instance_variable_set("@#{name}", value)
34
+ end
35
+ end
36
+
37
+ # The list of attributes defined on the model
38
+ def attributes
39
+ @attributes ||= Set.new
40
+ end
41
+
42
+ # The attribute of the model that can be used to uniquely identify an
43
+ # instance from any other. The primary attribute should also be set
44
+ # with `attribute <name>`.
45
+ attr_accessor :identifier
46
+ def primary_attribute name
47
+ @identifier = name
48
+ end
49
+
50
+ # Define one or more delegated methods on the model, passing them to the
51
+ # given attribute.
52
+ def delegate *names, to:
53
+ names.each do |name|
54
+ def_delegator to, name
55
+ end
56
+ end
57
+ end
58
+
59
+ # Initialize a model instance with any given attributes assigned
60
+ def initialize args={}
61
+ assign(args)
62
+ end
63
+
64
+ # Mass assign a group of attributes. Attribute names will be automatically
65
+ # be converted to snake_case for consistency.
66
+ def assign args={}
67
+ args.each do |_name, value|
68
+ public_send("#{_name.underscore}=", value) if respond_to? "#{_name.underscore}="
69
+ end
70
+ end
71
+
72
+ # The value of the primary attribute on this model
73
+ def identifier
74
+ send(self.class.identifier)
75
+ end
76
+
77
+ # Assume that two Model objects are the same if their primary attributes
78
+ # have the same value
79
+ def == o
80
+ identifier == o.identifier
81
+ end
82
+ end
83
+ end
84
+
85
+ # Include all model subclasses
86
+ require_relative 'models/route'
87
+ require_relative 'models/arrival'
88
+ require_relative 'models/destination'
89
+ require_relative 'models/stop'
@@ -0,0 +1,3 @@
1
+ module Bart
2
+ VERSION = "1.0.0"
3
+ end
data/lib/bart_api.rb ADDED
@@ -0,0 +1,35 @@
1
+ require_relative 'bart_api/core_ext/string'
2
+ require_relative 'bart_api/core_ext/hash'
3
+ require_relative 'bart_api/core_ext/float'
4
+ require_relative 'bart_api/core_ext/integer'
5
+
6
+ require_relative 'bart_api/version'
7
+ require_relative 'bart_api/configuration'
8
+ require_relative 'bart_api/connection'
9
+ require_relative 'bart_api/models'
10
+ require_relative 'bart_api/api'
11
+ require_relative 'bart_api/client'
12
+
13
+ module Bart
14
+ class << self
15
+ # Alias for `Bart::Client.new`
16
+ def new
17
+ Client.new
18
+ end
19
+
20
+ # The current client configuration
21
+ def configuration
22
+ @configuration ||= Configuration.new
23
+ end
24
+
25
+ # Allow users to set configuration options via a block. By default, the
26
+ # configuration will be validated after the block returns. This will raise
27
+ # an exception if any required configurations are not provided. This
28
+ # behavior can be skipped by passing `validate: false` as a parameter.
29
+ def configure validate: true
30
+ yield configuration
31
+ configuration.validate! if validate
32
+ configuration
33
+ end
34
+ end
35
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bart_api
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Elliott Williams
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-09-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: httparty
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.13'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.13'
27
+ - !ruby/object:Gem::Dependency
28
+ name: memoist
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.14'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.14'
41
+ description: A Ruby client for the BART real-time transit API.
42
+ email: code@elliottwillia.ms
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files:
46
+ - LICENSE
47
+ files:
48
+ - LICENSE
49
+ - lib/bart_api.rb
50
+ - lib/bart_api/api.rb
51
+ - lib/bart_api/client.rb
52
+ - lib/bart_api/client/estimates.rb
53
+ - lib/bart_api/client/routes.rb
54
+ - lib/bart_api/client/stops.rb
55
+ - lib/bart_api/configuration.rb
56
+ - lib/bart_api/connection.rb
57
+ - lib/bart_api/connection_adapters/httparty_adapter.rb
58
+ - lib/bart_api/core_ext/float.rb
59
+ - lib/bart_api/core_ext/hash.rb
60
+ - lib/bart_api/core_ext/integer.rb
61
+ - lib/bart_api/core_ext/string.rb
62
+ - lib/bart_api/models.rb
63
+ - lib/bart_api/models/arrival.rb
64
+ - lib/bart_api/models/destination.rb
65
+ - lib/bart_api/models/route.rb
66
+ - lib/bart_api/models/stop.rb
67
+ - lib/bart_api/version.rb
68
+ homepage: http://github.com/propershark/bart_api
69
+ licenses:
70
+ - MIT
71
+ metadata: {}
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: 2.2.0
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubyforge_project:
88
+ rubygems_version: 2.6.9
89
+ signing_key:
90
+ specification_version: 4
91
+ summary: A Ruby client for the BART real-time transit API.
92
+ test_files: []