viaggiatreno 1.0.2 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +25 -0
  3. data/.gitignore +5 -1
  4. data/.rubocop.yml +46 -0
  5. data/.travis.yml +17 -0
  6. data/Gemfile +10 -1
  7. data/Guardfile +12 -0
  8. data/LICENSE +21 -0
  9. data/README.md +8 -2
  10. data/Rakefile +2 -1
  11. data/lib/viaggia_treno.rb +1 -0
  12. data/lib/viaggiatreno.rb +2 -3
  13. data/lib/viaggiatreno/regex_match_info.rb +11 -0
  14. data/lib/viaggiatreno/scraper.rb +94 -0
  15. data/lib/viaggiatreno/string_utils.rb +6 -0
  16. data/lib/viaggiatreno/train.rb +93 -0
  17. data/lib/viaggiatreno/train_state.rb +6 -0
  18. data/lib/viaggiatreno/train_stop.rb +26 -0
  19. data/lib/viaggiatreno/train_stop_state.rb +5 -0
  20. data/lib/viaggiatreno/version.rb +1 -2
  21. data/lib/viaggiatreno/viaggiatreno_urls.rb +14 -0
  22. data/lib/viaggiatreno/xpath_match_info.rb +9 -0
  23. data/spec/spec_helper.rb +17 -0
  24. data/spec/vcr_cassettes/Arrived_train_delay_0_.yml +538 -0
  25. data/spec/vcr_cassettes/Arrived_train_delay_negative_.yml +621 -0
  26. data/spec/vcr_cassettes/Arrived_train_delay_positive_.yml +680 -0
  27. data/spec/vcr_cassettes/Not_departed_train.yml +438 -0
  28. data/spec/vcr_cassettes/Running_train_delay_positive_.yml +468 -0
  29. data/spec/viaggiatreno/train_spec.rb +190 -0
  30. data/spec/viaggiatreno_spec.rb +9 -0
  31. data/viaggiatreno.gemspec +9 -8
  32. metadata +57 -25
  33. data/LICENSE.txt +0 -22
  34. data/lib/viaggiatreno/RegExpMatchInfo.rb +0 -23
  35. data/lib/viaggiatreno/Scraper.rb +0 -87
  36. data/lib/viaggiatreno/StopState.rb +0 -8
  37. data/lib/viaggiatreno/StringUtils.rb +0 -7
  38. data/lib/viaggiatreno/Train.rb +0 -92
  39. data/lib/viaggiatreno/TrainState.rb +0 -11
  40. data/lib/viaggiatreno/TrainStop.rb +0 -24
  41. data/lib/viaggiatreno/ViaggiatrenoURLs.rb +0 -17
  42. data/lib/viaggiatreno/XPathMatchInfo.rb +0 -19
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5cf3f766e8e7a062b46e264817834fe98d21f02c
4
+ data.tar.gz: 91de43e8d50a9b01e92e59984995e01467f73149
5
+ SHA512:
6
+ metadata.gz: b8cc778203d61d1c5ced80dd05759b07c81a2278f2831160b0495b258636678ce90741a1d9fedb6acb926998879802a731d4378d6da6e2767283ac398b943d61
7
+ data.tar.gz: a0a8181f56b43948d01953abf2b2c1306e392013b7bf0790e46f51761f0c863dfc1ab06c19083de010527b4f078380f9e0417079f2e1003ef920f87e68268d09
@@ -0,0 +1,25 @@
1
+ ---
2
+ engines:
3
+ duplication:
4
+ enabled: true
5
+ config:
6
+ languages:
7
+ - ruby
8
+ - javascript
9
+ - python
10
+ - php
11
+ fixme:
12
+ enabled: true
13
+ rubocop:
14
+ enabled: true
15
+ ratings:
16
+ paths:
17
+ - "**.inc"
18
+ - "**.js"
19
+ - "**.jsx"
20
+ - "**.module"
21
+ - "**.php"
22
+ - "**.py"
23
+ - "**.rb"
24
+ exclude_paths:
25
+ - spec/**/*
data/.gitignore CHANGED
@@ -1,6 +1,5 @@
1
1
  *.gem
2
2
  *.rbc
3
- .bundle
4
3
  .config
5
4
  .yardoc
6
5
  Gemfile.lock
@@ -15,3 +14,8 @@ spec/reports
15
14
  test/tmp
16
15
  test/version_tmp
17
16
  tmp
17
+
18
+ # Ignore bundle configuration
19
+ /bin
20
+ .bundle
21
+ /vendor/bundle
@@ -0,0 +1,46 @@
1
+ AllCops:
2
+ Include:
3
+ - '**/Rakefile'
4
+ - '**/Gemfile'
5
+
6
+ AndOr:
7
+ Enabled: false
8
+
9
+ AsciiComments:
10
+ Enabled: false
11
+
12
+ Blocks:
13
+ Enabled: false
14
+
15
+ BracesAroundHashParameters:
16
+ Enabled: false
17
+
18
+ ClassAndModuleChildren:
19
+ Enabled: false
20
+
21
+ ClassLength:
22
+ Enabled: false
23
+
24
+ ConstantName:
25
+ Enabled: false
26
+
27
+ Documentation:
28
+ Enabled: false
29
+
30
+ Encoding:
31
+ Enabled: false
32
+
33
+ LineLength:
34
+ Max: 100
35
+
36
+ MethodLength:
37
+ Max: 12
38
+
39
+ RegexpLiteral:
40
+ Enabled: false
41
+
42
+ SignalException:
43
+ EnforcedStyle: only_raise
44
+
45
+ Validation:
46
+ Enabled: false
@@ -0,0 +1,17 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 2.1.8
5
+ - 2.2.4
6
+ - 2.3.0
7
+ - ruby-head
8
+ script:
9
+ - 'bundle exec rspec --color --format documentation'
10
+ notifications:
11
+ recipients:
12
+ - michele.bologna@gmail.com
13
+ - marius.colacioiu@gmail.com
14
+ sudo: false
15
+ addons:
16
+ code_climate:
17
+ repo_token: 31c466aad9acda9cfe23ab74ca7a9c50a2243f2dd473d972a70c795c2affdab0
data/Gemfile CHANGED
@@ -1,4 +1,13 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in viaggiatreno.gemspec
4
3
  gemspec
4
+
5
+ group :test do
6
+ gem 'codeclimate-test-reporter', require: nil
7
+ gem 'guard-rspec', require: false
8
+ gem 'guard-rubocop', require: false
9
+ gem 'rspec'
10
+ gem 'simplecov', require: false
11
+ gem 'vcr'
12
+ gem 'webmock'
13
+ end
@@ -0,0 +1,12 @@
1
+ guard :rspec, cmd: 'rspec' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
6
+
7
+ guard :rubocop, all_on_start: false, cli: ['--format', 'clang'] do
8
+ watch(%r{^bin/flash$})
9
+ watch(%r{^lib/.+\.rb$})
10
+ watch(%r{^spec/.+\.rb$})
11
+ watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
12
+ end
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Michele Bologna
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,6 +1,12 @@
1
1
  # Viaggiatreno
2
2
 
3
- TODO: Write a gem description
3
+ [![Build Status](https://travis-ci.org/mbologna/viaggiatreno.svg)](https://travis-ci.org/mbologna/viaggiatreno)
4
+ [![Code Climate](https://codeclimate.com/repos/569a5314b23bff7a6c011fb0/badges/7a1d250d86b806acee6c/gpa.svg)](https://codeclimate.com/repos/569a5314b23bff7a6c011fb0/feed)
5
+ [![Test Coverage](https://codeclimate.com/repos/569a5314b23bff7a6c011fb0/badges/7a1d250d86b806acee6c/coverage.svg)](https://codeclimate.com/repos/569a5314b23bff7a6c011fb0/coverage)
6
+ [![Issue Count](https://codeclimate.com/repos/569a5314b23bff7a6c011fb0/badges/7a1d250d86b806acee6c/issue_count.svg)](https://codeclimate.com/repos/569a5314b23bff7a6c011fb0/feed)
7
+
8
+ This gem parses Italian railway real-time status for trains (viaggiatreno.it).
9
+ Use this API to fetch online information about Italian trains.
4
10
 
5
11
  ## Installation
6
12
 
@@ -18,7 +24,7 @@ Or install it yourself as:
18
24
 
19
25
  ## Usage
20
26
 
21
- TODO: Write usage instructions here
27
+ See examples at http://michelebologna.net
22
28
 
23
29
  ## Contributing
24
30
 
data/Rakefile CHANGED
@@ -1 +1,2 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
2
+ task default: 'build'
@@ -0,0 +1 @@
1
+ require 'viaggiatreno'
@@ -1,6 +1,5 @@
1
- require "viaggiatreno/version"
2
- require "viaggiatreno/Train"
3
-
1
+ require 'viaggiatreno/version'
2
+ require 'viaggiatreno/train'
4
3
 
5
4
  module Viaggiatreno
6
5
  end
@@ -0,0 +1,11 @@
1
+ class RegExpMatchInfo
2
+ # regex to match train status (string)
3
+ REGEXP_STATE_TRAVELING = /(Il treno viaggia.*)(Ultimo rilevamento a)(.*)/
4
+ REGEXP_STATE_NOT_DEPARTED = /Il treno non e' ancora partito/
5
+ REGEXP_STATE_ARRIVED = /Il treno e' arrivato.*/
6
+ REGEXP_DELAY_STR = /con (\d+) minuti di ([anticipo|ritardo]+)/
7
+ REGEXP_NODELAY_STR = /Il treno .* in orario.*/
8
+ REGEXP_STOP_ALREADY_DONE = /giaeffettuate/
9
+ STR_DELAY_STR = 'ritardo'.freeze
10
+ STR_TRAIN_NUMBER_URL_REPLACE = 'TRAINNUMBER'.freeze
11
+ end
@@ -0,0 +1,94 @@
1
+ require 'open-uri'
2
+ require 'nokogiri'
3
+ require_relative 'train_stop'
4
+ require_relative 'train_state'
5
+ require_relative 'string_utils'
6
+ require_relative 'regex_match_info'
7
+ require_relative 'train_stop_state'
8
+ require_relative 'xpath_match_info'
9
+ require_relative 'viaggiatreno_urls'
10
+
11
+ class Scraper
12
+ def initialize(train_number, train)
13
+ @site_info_main = ViaggiatrenoURLs::SITE_INFO_MAIN.gsub(
14
+ RegExpMatchInfo::STR_TRAIN_NUMBER_URL_REPLACE, train_number)
15
+ @site_info_details = ViaggiatrenoURLs::SITE_INFO_DETAILS.gsub(
16
+ RegExpMatchInfo::STR_TRAIN_NUMBER_URL_REPLACE, train_number)
17
+ @train = train
18
+ end
19
+
20
+ # fetch and parse basic train information (status, train)name, details)
21
+ def update_train
22
+ doc = Nokogiri::HTML(open(@site_info_main))
23
+ @train.status = StringUtils.remove_newlines_tabs_and_spaces(
24
+ doc.xpath(XPathMatchInfo::XPATH_STATUS).first)
25
+ @train.train_name = doc.xpath(XPathMatchInfo::XPATH_TRAIN_NAME).first.content
26
+ update_train_status(@train)
27
+ @train.delay = fetch_train_delay(@train.status)
28
+ end
29
+
30
+ def update_train_status(train)
31
+ case
32
+ when train.status =~ RegExpMatchInfo::REGEXP_STATE_NOT_DEPARTED
33
+ train.state = TrainState::NOT_DEPARTED
34
+ when train.status =~ RegExpMatchInfo::REGEXP_STATE_ARRIVED
35
+ train.state = TrainState::ARRIVED
36
+ when train.status =~ RegExpMatchInfo::REGEXP_STATE_TRAVELING
37
+ train.state = TrainState::TRAVELING
38
+ regex_match = train.status.match(
39
+ RegExpMatchInfo::REGEXP_STATE_TRAVELING)
40
+ train.last_update = regex_match[3].strip
41
+ train.status = regex_match[1].rstrip
42
+ end
43
+ end
44
+
45
+ def fetch_train_delay(status)
46
+ return nil if @train.state == TrainState::NOT_DEPARTED
47
+ if status =~ RegExpMatchInfo::REGEXP_NODELAY_STR
48
+ delay = 0
49
+ else
50
+ delay = status.match(RegExpMatchInfo::REGEXP_DELAY_STR)[1].to_i
51
+ if status.match(RegExpMatchInfo::REGEXP_DELAY_STR)[2] != RegExpMatchInfo::STR_DELAY_STR
52
+ delay *= -1 # train is ahead of schedule, delay is negative
53
+ end
54
+ end
55
+ delay
56
+ end
57
+
58
+ # fetch and parse train details (departing and arriving station,
59
+ # intermediate stops)
60
+ def update_train_details
61
+ doc = Nokogiri::HTML(open(@site_info_details))
62
+ doc.xpath(XPathMatchInfo::XPATH_DETAILS_GENERIC).each do |x|
63
+ @station_name = x.xpath(XPathMatchInfo::XPATH_DETAILS_STATION_NAME).first.to_s
64
+ arrival_time = fetch_trainstop_arrival_time(x)
65
+ @scheduled_arrival_time = arrival_time['scheduled_arrival_time']
66
+ @actual_arrival_time = arrival_time['actual_arrival_time']
67
+ @status = update_trainstop_status(x, @train, @status)
68
+ @train.add_stop(TrainStop.new(
69
+ @station_name, @scheduled_arrival_time,
70
+ @actual_arrival_time, @status))
71
+ end
72
+ end
73
+
74
+ def update_trainstop_status(x, train, status)
75
+ status = if x.attributes['class'].to_s =~ RegExpMatchInfo::REGEXP_STOP_ALREADY_DONE && \
76
+ train.state != TrainState::NOT_DEPARTED
77
+ TrainStopState::DONE
78
+ else
79
+ TrainStopState::TO_BE_DONE
80
+ end
81
+ status
82
+ end
83
+
84
+ def fetch_trainstop_arrival_time(xpath)
85
+ scheduled_arrival_time = StringUtils.remove_newlines_tabs_and_spaces(
86
+ xpath.xpath(XPathMatchInfo::XPATH_DETAILS_SCHEDULED_STOP_TIME).first).to_s
87
+ actual_arrival_time = StringUtils.remove_newlines_tabs_and_spaces(
88
+ xpath.xpath(XPathMatchInfo::XPATH_DETAILS_ACTUAL_STOP_TIME).first).to_s
89
+ {
90
+ 'scheduled_arrival_time' => scheduled_arrival_time,
91
+ 'actual_arrival_time' => actual_arrival_time
92
+ }
93
+ end
94
+ end
@@ -0,0 +1,6 @@
1
+ class StringUtils
2
+ # utility method
3
+ def self.remove_newlines_tabs_and_spaces(str)
4
+ str.content.delete("\r").delete("\n").tr("\t", ' ').gsub(/ +/, ' ').strip
5
+ end
6
+ end
@@ -0,0 +1,93 @@
1
+ require_relative 'scraper.rb'
2
+ require_relative 'train_state.rb'
3
+ require_relative 'train_stop.rb'
4
+
5
+ class Train
6
+ attr_accessor :train_number, :train_name, :delay, :status, :last_update, :state
7
+
8
+ def initialize(train_number)
9
+ @train_number = train_number
10
+ @scraper = Scraper.new(train_number.to_s, self)
11
+ update
12
+ end
13
+
14
+ def update
15
+ @scraper.update_train
16
+ end
17
+
18
+ def update_details
19
+ @train_stops = []
20
+ @scraper.update_train_details
21
+ end
22
+
23
+ def to_s
24
+ "#{@train_number} #{@train_name}: #{@status} state: #{@state}, \
25
+ delay: #{@delay}, last_update: #{@last_update}"
26
+ end
27
+
28
+ def add_stop(train_stop)
29
+ @train_stops << train_stop
30
+ end
31
+
32
+ def train_stops
33
+ update_details if @train_stops.nil?
34
+ @train_stops
35
+ end
36
+
37
+ def departing_station
38
+ train_stops.first.train_station.to_s
39
+ end
40
+
41
+ def arriving_station
42
+ train_stops.last.train_station.to_s
43
+ end
44
+
45
+ def scheduled_departing_time
46
+ train_stops.first.scheduled_stop_time.to_s
47
+ end
48
+
49
+ def scheduled_arriving_time
50
+ train_stops.last.scheduled_stop_time.to_s
51
+ end
52
+
53
+ def actual_departing_time
54
+ train_stops.first.actual_stop_time.to_s
55
+ end
56
+
57
+ def actual_arriving_time
58
+ train_stops.last.actual_stop_time.to_s
59
+ end
60
+
61
+ def scheduled_stop_time(station_name)
62
+ find_stop_time(station_name)['scheduled']
63
+ end
64
+
65
+ def actual_stop_time(station_name)
66
+ find_stop_time(station_name)['actual']
67
+ end
68
+
69
+ def last_stop
70
+ return train_stops.last.to_s if @state == TrainState::ARRIVED
71
+ train_stops.each_with_index do |train_stop, index|
72
+ if train_stop.status == TrainStopState::DONE && \
73
+ train_stops[index + 1].status == TrainStopState::TO_BE_DONE
74
+ return train_stop.to_s
75
+ end
76
+ end
77
+ nil
78
+ end
79
+
80
+ private
81
+
82
+ def find_stop_time(station_name)
83
+ train_stops.each do |train_stop|
84
+ if train_stop.train_station.to_s == station_name
85
+ return \
86
+ {
87
+ 'actual' => "#{train_stop.actual_stop_time} [#{train_stop.status}]",
88
+ 'scheduled' => "#{train_stop.scheduled_stop_time} [#{train_stop.status}]"
89
+ }
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,6 @@
1
+ class TrainState
2
+ # Train status
3
+ TRAVELING = 'TRAVELING'.freeze
4
+ ARRIVED = 'ARRIVED'.freeze
5
+ NOT_DEPARTED = 'NOT DEPARTED'.freeze
6
+ end
@@ -0,0 +1,26 @@
1
+ require_relative 'train_stop_state'
2
+
3
+ class TrainStop
4
+ attr_accessor :train_station, :scheduled_stop_time, :actual_stop_time, :status
5
+
6
+ def initialize(train_station, scheduled_stop_time, actual_stop_time, status)
7
+ @train_station = train_station
8
+ @scheduled_stop_time = scheduled_stop_time
9
+ @actual_stop_time = actual_stop_time
10
+ @status = status
11
+ end
12
+
13
+ def to_s
14
+ retstr = ''
15
+ if @status == TrainStopState::DONE
16
+ done = 'X'
17
+ actual_or_expected = 'ACTUAL'
18
+ elsif @status == TrainStopState::TO_BE_DONE
19
+ done = ' '
20
+ actual_or_expected = 'EXPECTED'
21
+ end
22
+ retstr += "[#{done}] #{train_station} = SCHEDULED: #{scheduled_stop_time}"\
23
+ " #{actual_or_expected}: #{actual_stop_time}"
24
+ retstr
25
+ end
26
+ end