floripa-public-transit 0.0.1

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: eb2a054575bd9efd39c453934430bff1791d5846
4
+ data.tar.gz: 929429e8988ba3a3ecfdc2ec4edb0b3fd5fe0fd0
5
+ SHA512:
6
+ metadata.gz: 274957610cb4091355b5ccbff52fb161f048d850848c8ea832d117b21f4e7972041edd32de6dc1808fa241cabf2f0217e43f735a67348d1a67c8cbc813db1420
7
+ data.tar.gz: f4d37e3277ea024a9afb4737c6c294aeb23aa527f9b073c7f3cfaad321071cf5b796828fadfa0219ebfa51d39943e5ce95f10ac673588866a24d1bab949d5679
data/.bundle/config ADDED
@@ -0,0 +1,3 @@
1
+ ---
2
+ BUNDLE_PATH: vendor/bundle
3
+ BUNDLE_DISABLE_SHARED_GEMS: '1'
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ /vendor/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'nokogiri', '1.6.1'
4
+ gem 'rspec'
data/Gemfile.lock ADDED
@@ -0,0 +1,22 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.2.5)
5
+ mini_portile (0.5.3)
6
+ nokogiri (1.6.1)
7
+ mini_portile (~> 0.5.0)
8
+ rspec (2.14.1)
9
+ rspec-core (~> 2.14.0)
10
+ rspec-expectations (~> 2.14.0)
11
+ rspec-mocks (~> 2.14.0)
12
+ rspec-core (2.14.8)
13
+ rspec-expectations (2.14.5)
14
+ diff-lcs (>= 1.1.3, < 2.0)
15
+ rspec-mocks (2.14.6)
16
+
17
+ PLATFORMS
18
+ ruby
19
+
20
+ DEPENDENCIES
21
+ nokogiri (= 1.6.1)
22
+ rspec
data/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # floripa-public-transit
2
+
3
+ Ruby gem to fetch public transit information from the [Florianópolis Web Site](http://www.pmf.sc.gov.br/servicos/index.php?pagina=onibus&menu=2).
4
+
5
+ To fetch information of a specific line:
6
+
7
+ ```ruby
8
+ require 'floripa-public-transit'
9
+
10
+ FloripaPublicTransit.fetch_line '177'
11
+ ```
12
+
13
+ Where `177` in the number of the 'Santa Monica' bus line.
14
+
15
+ ## Authors
16
+
17
+ * [Felipe Munhoz](https://github.com/fnmunhoz)
18
+ * [Paulo Ragonha](https://github.com/pirelenito)
@@ -0,0 +1,19 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'floripa-public-transit'
3
+ s.version = '0.0.1'
4
+ s.date = '2014-04-14'
5
+ s.summary = 'Fetches public transit information from the Florianopolis city website'
6
+ s.description = 'Provides a simple API to fetch latest public transit information of the Florianopolis city'
7
+ s.authors = ['Paulo Ragonha', 'Felipe Munhoz']
8
+ s.email = ['paulo@ragonha.me', 'munhoz@gmail.com']
9
+
10
+ s.files = `git ls-files`.split("\n")
11
+ s.test_files = `git ls-files -- spec`.split("\n")
12
+ s.require_paths = ['lib']
13
+
14
+ s.homepage = 'https://github.com/floripa-mobi/floripa-public-transit'
15
+ s.license = 'MIT'
16
+
17
+ s.add_runtime_dependency 'nokogiri', '1.6.1'
18
+ s.add_development_dependency 'rspec', '2.14.1'
19
+ end
@@ -0,0 +1,27 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+
4
+
5
+ require 'rubygems'
6
+ require 'bundler/setup'
7
+
8
+
9
+ require 'floripa/bus_list_crawler'
10
+ require 'floripa/bus_crawler'
11
+
12
+
13
+ module FloripaPublicTransit
14
+ def self.fetch_all
15
+ FloripaPublicTransit::BusListCrawler.new.fetch.map do |line_number|
16
+ FloripaPublicTransit::BusCrawler.new(line_number).fetch
17
+ end
18
+ end
19
+
20
+ def self.fetch_line(line_number)
21
+ FloripaPublicTransit::BusCrawler.new(line_number).fetch
22
+ end
23
+
24
+ def self.fetch_line_numbers
25
+ FloripaPublicTransit::BusListCrawler.new.fetch
26
+ end
27
+ end
@@ -0,0 +1,104 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'nokogiri'
4
+ require "net/http"
5
+ require 'open-uri'
6
+ require "uri"
7
+ require 'json'
8
+
9
+
10
+ module FloripaPublicTransit
11
+ Bus = Struct.new(:number, :name, :operator, :itinerary, :schedules)
12
+ Schedule = Struct.new(:period, :hours, :direction, :origin, :destination)
13
+
14
+ class BusCrawler
15
+ PERIODS = ['weekday', 'saturday', 'sunday']
16
+ GOING = '1'
17
+ RETURNING = '2'
18
+
19
+ def initialize(bus_line_number)
20
+ @bus_line_number = bus_line_number
21
+ end
22
+
23
+ def fetch
24
+ name = fetch_name()
25
+ operator = fetch_operator()
26
+ itinerary = fetch_itinerary()
27
+ schedule = fetch_schedule(itinerary)
28
+
29
+ Bus.new(bus_line_number, name, operator, itinerary, schedule)
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :bus_line_number
35
+
36
+ def fetch_name
37
+ request_data('1', 'iso8859-1').css('#titulo_pagina').first.content.gsub(/\(.+\)/, '').strip
38
+ end
39
+
40
+ def fetch_operator
41
+ request_data('1', 'iso8859-1').css('#cabecalho_onibus_linha h4').first.content.gsub('Empresa:', '').strip
42
+ end
43
+
44
+ def fetch_itinerary
45
+ data = request_data '3'
46
+
47
+ data.css('#conteudo_itinerario li').map do |address|
48
+ address.content.gsub('» ', '').strip
49
+ end
50
+ end
51
+
52
+ def fetch_schedule(itinerary)
53
+ going_schedule = fetch_hours(GOING, itinerary)
54
+ returning_schedule = fetch_hours(RETURNING, itinerary)
55
+
56
+ [going_schedule, returning_schedule].flatten.select do |schedule|
57
+ schedule.hours.length > 0
58
+ end
59
+ end
60
+
61
+ def fetch_hours(direction, itinerary)
62
+ data = request_data direction, 'iso8859-1'
63
+
64
+ if direction == GOING
65
+ targets = data.css('#conteudo_horaida b:first-child').first.content.scan(/([\w|\s]+)/).flatten.map &:strip
66
+ @origin = targets[0]
67
+ @destination = targets[1]
68
+ end
69
+
70
+ if !@origin || @origin && @origin.empty?
71
+ @origin = itinerary[direction == GOING ? 0 : itinerary.length-1]
72
+ @destination = itinerary[direction == GOING ? itinerary.length-1 : 0]
73
+ end
74
+
75
+ origin = direction == GOING ? @origin : @destination
76
+ destination = direction == GOING ? @destination : @origin
77
+
78
+ data.css('.conteudo_abas_ext tr:last-child td').map.with_index do |period, index|
79
+ hours = period.to_s.split('<br>').map do |hour|
80
+ match = hour.match(/(\d\d:\d\d)/)
81
+ match && match[0]
82
+ end.compact
83
+
84
+ Schedule.new(PERIODS[index], hours, direction == GOING ? 'going' : 'returning', origin, destination)
85
+ end
86
+ end
87
+
88
+ def request_data(direction, encoding = 'utf-8')
89
+ url = "http://www.pmf.sc.gov.br/servicos/index.php?pagina=onibuslinha&idLinha=#{bus_line_number}"
90
+ uri = URI.parse(url)
91
+
92
+ http = Net::HTTP.new(uri.host, uri.port)
93
+
94
+ request = Net::HTTP::Post.new(uri.request_uri)
95
+ request.set_form_data({ 'passoGeral' => direction })
96
+
97
+ response = http.request(request)
98
+
99
+ Nokogiri::HTML(response.body, nil, encoding)
100
+ end
101
+
102
+ end
103
+
104
+ end
@@ -0,0 +1,44 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'nokogiri'
4
+ require "net/http"
5
+ require 'open-uri'
6
+ require "uri"
7
+ require 'json'
8
+
9
+
10
+ module FloripaPublicTransit
11
+ class BusListCrawler
12
+ def fetch
13
+ [
14
+ *fetch_lines('1'),
15
+ *fetch_lines('2'),
16
+ *fetch_lines('3'),
17
+ *fetch_lines('4'),
18
+ *fetch_lines('5'),
19
+ *fetch_lines('6'),
20
+ *fetch_lines('9'),
21
+ *fetch_lines('10')
22
+ ]
23
+ end
24
+
25
+ private
26
+
27
+ def fetch_lines(company)
28
+ uri = URI.parse('http://www.pmf.sc.gov.br/servicos/index.php?pagina=onibus&menu=2')
29
+
30
+ http = Net::HTTP.new(uri.host, uri.port)
31
+
32
+ request = Net::HTTP::Post.new(uri.request_uri)
33
+ request.set_form_data({ 'passoGeral' => '1', 'passoEmpresa' => '1', 'empresa' => company, 'opcao' => '1' })
34
+
35
+ response = http.request(request)
36
+
37
+ doc = Nokogiri::HTML(response.body, nil, 'utf-8')
38
+
39
+ doc.css('ul.listagem a').collect do |link|
40
+ link[:href].split("idLinha=").last.split("&menu=2").first.strip
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,96 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe "bus_crawler" do
6
+ describe "when fetching the data of a bus" do
7
+ subject { FloripaPublicTransit::BusCrawler.new('330').fetch }
8
+
9
+ it "should contain three different schedules" do
10
+ expect(subject.schedules.length).to eq 6
11
+ end
12
+
13
+ it "should have the periods" do
14
+ expect(subject.schedules[0].period).to eq 'weekday'
15
+ expect(subject.schedules[1].period).to eq 'saturday'
16
+ expect(subject.schedules[2].period).to eq 'sunday'
17
+ expect(subject.schedules[3].period).to eq 'weekday'
18
+ expect(subject.schedules[4].period).to eq 'saturday'
19
+ expect(subject.schedules[5].period).to eq 'sunday'
20
+ end
21
+
22
+ it "should have the hours" do
23
+ expect(subject.schedules[3].hours.length).to eq 47
24
+ expect(subject.schedules[4].hours.length).to eq 46
25
+ expect(subject.schedules[5].hours.length).to eq 35
26
+
27
+ expect(subject.schedules[0].hours[0]).to eq '05:32'
28
+ expect(subject.schedules[3].hours[0]).to eq '05:47'
29
+ end
30
+
31
+ it "should have the direction" do
32
+ expect(subject.schedules[0].direction).to eq 'going'
33
+ expect(subject.schedules[1].direction).to eq 'going'
34
+ expect(subject.schedules[2].direction).to eq 'going'
35
+ expect(subject.schedules[3].direction).to eq 'returning'
36
+ expect(subject.schedules[4].direction).to eq 'returning'
37
+ expect(subject.schedules[5].direction).to eq 'returning'
38
+ end
39
+
40
+ it "should have the origin and destination" do
41
+ expect(subject.schedules[0].origin).to eq 'TILAG'
42
+ expect(subject.schedules[0].destination).to eq 'TICEN'
43
+
44
+ expect(subject.schedules[3].origin).to eq 'TICEN'
45
+ expect(subject.schedules[3].destination).to eq 'TILAG'
46
+ end
47
+
48
+ it "should have the name of the bus" do
49
+ expect(subject.name).to eq('LAGOA DA CONCEIÇÃO')
50
+ end
51
+
52
+ it "should have the number" do
53
+ expect(subject.number).to eq('330')
54
+ end
55
+
56
+ it "should have the operator" do
57
+ expect(subject.operator).to eq('TRANSOL TRANSPORTE COLETIVO LTDA')
58
+ end
59
+
60
+ it "should have the itinerary" do
61
+ expected_itinerary = [
62
+ 'TICEN',
63
+ 'RUA PROCURADOR ABELARDO GOMES',
64
+ 'AVENIDA PAULO FONTES',
65
+ 'RUA DOUTOR ÁLVARO MILLEN DA SILVEIRA',
66
+ 'RUA DOUTOR JORGE DA LUZ FONTES',
67
+ 'RUA SILVA JARDIM',
68
+ 'AVENIDA MAURO RAMOS',
69
+ 'AVENIDA JORNALISTA RUBENS DE ARRUDA RAMOS',
70
+ 'RUA COMANDANTE CONSTANTINO NICOLAU SPYRIDES',
71
+ 'RUA DELMINDA SILVEIRA',
72
+ 'RUA IDALINA PEREIRA DOS SANTOS',
73
+ 'AVENIDA GOVERNADOR IRINEU BORNHAUSEN',
74
+ 'AVENIDA DA SAUDADE',
75
+ 'RODOVIA ADMAR GONZAGA',
76
+ 'AVENIDA AFONSO DELAMBERT NETO',
77
+ 'TILAG',
78
+ 'TILAG',
79
+ 'AVENIDA AFONSO DELAMBERT NETO',
80
+ 'RODOVIA SC 404',
81
+ 'ELEVADO DO ITACOROBI',
82
+ 'AVENIDA DA SAUDADE',
83
+ 'RUA DELMINDA SILVEIRA',
84
+ 'RUA RUI BARBOSA',
85
+ 'RUA FREI CANECA',
86
+ 'RUA HEITOR LUZ',
87
+ 'AVENIDA MAURO RAMOS',
88
+ 'RUA SILVA JARDIM',
89
+ 'AVENIDA GOVERNADOR GUSTAVO RICHARD',
90
+ 'TICEN'
91
+ ]
92
+
93
+ expect(subject.itinerary).to eq expected_itinerary
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe "bus_list_crawler" do
6
+ describe "when fetching the data of a lines" do
7
+ subject { FloripaPublicTransit::BusListCrawler.new.fetch }
8
+
9
+ it "should contain all bus line numbers" do
10
+ expect(subject.length).to eq 255
11
+ expect(subject).to include('330')
12
+ expect(subject).to include('320')
13
+ expect(subject).to include('D766')
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe "bus_crawler" do
6
+ describe "when fetching the data of the 360 line" do
7
+ subject { FloripaPublicTransit::BusCrawler.new('360').fetch }
8
+
9
+ it "should work (was breaking because of encoding issues)" do
10
+ expect(subject.name).to eq 'BARRA DA LAGOA'
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe "bus_crawler" do
6
+ describe "when fetching the data of the M230 line" do
7
+ subject { FloripaPublicTransit::BusCrawler.new('M230').fetch }
8
+
9
+ it "should work (was breaking because of encoding issues)" do
10
+ expect(subject.number).to eq 'M230'
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe "bus_crawler" do
6
+ describe "when fetching the data of a Cooperbarco operator" do
7
+ subject { FloripaPublicTransit::BusCrawler.new('10').fetch }
8
+
9
+ it "should work (was breaking because of encoding issues)" do
10
+ expect(subject.operator).to eq 'HIDROVIÁRIO COOPERBARCO'
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe "bus_crawler" do
6
+ describe "when fetching the data of the a bus where the origin/destination separator is a slash (like the 276)" do
7
+ subject { FloripaPublicTransit::BusCrawler.new('276').fetch }
8
+
9
+ it "should have the origin and destination taken from the itinerary" do
10
+ expect(subject.schedules[0].origin).to eq 'TICAN'
11
+ expect(subject.schedules[0].destination).to eq 'TICAN'
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe "bus_crawler" do
6
+ describe "when fetching the data of the a bus without defined origin or destination (like the 177)" do
7
+ subject { FloripaPublicTransit::BusCrawler.new('177').fetch }
8
+
9
+ it "should have the origin and destination taken from the itinerary" do
10
+ expect(subject.schedules[0].origin).to eq 'TITRI'
11
+ expect(subject.schedules[0].destination).to eq 'TITRI'
12
+ end
13
+
14
+ it "should only have schedules defined that have hours" do
15
+ subject.schedules.each do |schedule|
16
+ expect(schedule.hours.length).not_to eq 0
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,25 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+
4
+ require 'rubygems'
5
+ require 'bundler/setup'
6
+
7
+ require 'floripa-public-transit'
8
+
9
+ # This file was generated by the `rspec --init` command. Conventionally, all
10
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
11
+ # Require this file using `require "spec_helper"` to ensure that it is only
12
+ # loaded once.
13
+ #
14
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
15
+ RSpec.configure do |config|
16
+ config.treat_symbols_as_metadata_keys_with_true_values = true
17
+ config.run_all_when_everything_filtered = true
18
+ config.filter_run :focus
19
+
20
+ # Run specs in random order to surface order dependencies. If you find an
21
+ # order dependency and want to debug it, you can fix the order by providing
22
+ # the seed, which is printed after each run.
23
+ # --seed 1234
24
+ config.order = 'random'
25
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: floripa-public-transit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Paulo Ragonha
8
+ - Felipe Munhoz
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-04-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: nokogiri
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - '='
19
+ - !ruby/object:Gem::Version
20
+ version: 1.6.1
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - '='
26
+ - !ruby/object:Gem::Version
27
+ version: 1.6.1
28
+ - !ruby/object:Gem::Dependency
29
+ name: rspec
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - '='
33
+ - !ruby/object:Gem::Version
34
+ version: 2.14.1
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - '='
40
+ - !ruby/object:Gem::Version
41
+ version: 2.14.1
42
+ description: Provides a simple API to fetch latest public transit information of the
43
+ Florianopolis city
44
+ email:
45
+ - paulo@ragonha.me
46
+ - munhoz@gmail.com
47
+ executables: []
48
+ extensions: []
49
+ extra_rdoc_files: []
50
+ files:
51
+ - ".bundle/config"
52
+ - ".gitignore"
53
+ - ".rspec"
54
+ - Gemfile
55
+ - Gemfile.lock
56
+ - README.md
57
+ - floripa-public-transit.gemspec
58
+ - lib/floripa-public-transit.rb
59
+ - lib/floripa/bus_crawler.rb
60
+ - lib/floripa/bus_list_crawler.rb
61
+ - spec/floripa/bus_crawler_spec.rb
62
+ - spec/floripa/bus_line_crawler_spec.rb
63
+ - spec/floripa/edge_cases/bus_line_crawler_360_spec.rb
64
+ - spec/floripa/edge_cases/bus_m230_should_not_break.spec.rb
65
+ - spec/floripa/edge_cases/bus_operator_cooperbarco.spec.rb
66
+ - spec/floripa/edge_cases/bus_origin_and_destination_separated_with_a_slash.spec.rb
67
+ - spec/floripa/edge_cases/bus_without_defined_origin_and_destination.spec.rb
68
+ - spec/spec_helper.rb
69
+ homepage: https://github.com/floripa-mobi/floripa-public-transit
70
+ licenses:
71
+ - MIT
72
+ metadata: {}
73
+ post_install_message:
74
+ rdoc_options: []
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubyforge_project:
89
+ rubygems_version: 2.2.2
90
+ signing_key:
91
+ specification_version: 4
92
+ summary: Fetches public transit information from the Florianopolis city website
93
+ test_files:
94
+ - spec/floripa/bus_crawler_spec.rb
95
+ - spec/floripa/bus_line_crawler_spec.rb
96
+ - spec/floripa/edge_cases/bus_line_crawler_360_spec.rb
97
+ - spec/floripa/edge_cases/bus_m230_should_not_break.spec.rb
98
+ - spec/floripa/edge_cases/bus_operator_cooperbarco.spec.rb
99
+ - spec/floripa/edge_cases/bus_origin_and_destination_separated_with_a_slash.spec.rb
100
+ - spec/floripa/edge_cases/bus_without_defined_origin_and_destination.spec.rb
101
+ - spec/spec_helper.rb