bart_api 1.0.0

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 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: []