atco 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.mdown ADDED
@@ -0,0 +1,27 @@
1
+ ## ATCO-CIF
2
+
3
+ ATCO-CIF is the format of choice for UK public transport authorities. This is a ruby library that reads **.cif** files and gives you JSON back.
4
+
5
+ * **Official spec:** [http://www.pti.org.uk/CIF/atco-cif-spec.pdf](http://www.pti.org.uk/CIF/atco-cif-spec.pdf)
6
+
7
+ ### USAGE
8
+
9
+ Currently this library is under-development and has several things left to do before it is perfect (see the [todo.mdown](http://github.com/davidjrice/atco/blob/master/todo.mdown) list ).
10
+
11
+ * clone this library
12
+ * start an irb session
13
+ * put the cif file in ./data (needs to change from being hardcoded)
14
+
15
+ Code example, for more detailed internal api usage see the spec files.
16
+
17
+ require 'lib/atco'
18
+
19
+ result = Atco.parse('filename.cif')
20
+ result = Atco.parse('SVRTMAO009A-20091005.cif) # an example data file
21
+
22
+ => {
23
+ :header => {...},
24
+ :locations => [...],
25
+ :journies => {...}
26
+ }
27
+
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |s|
4
+ s.name = "atco"
5
+ s.summary = "Simple and opinionated library for parsing ATCO-CIF files with Ruby."
6
+ s.email = "me@davidjrice.co.uk"
7
+ s.homepage = "http://github.com/davidjrice/atco"
8
+ s.description = "Simple and opinionated library for parsing ATCO-CIF files with Ruby."
9
+ s.authors = ["David Rice"]
10
+ s.files = FileList["[A-Z]*", "{bin,generators,lib,test}/**/*", 'lib/jeweler/templates/.gitignore']
11
+ end
12
+ rescue LoadError
13
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install jeweler"
14
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/lib/atco.rb ADDED
@@ -0,0 +1,180 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'open3'
5
+ require 'tempfile'
6
+ require 'location'
7
+
8
+ module Atco
9
+ VERSION = '0.0.1'
10
+
11
+ class << self
12
+
13
+ @path = nil
14
+ @@methods = {
15
+ :bank_holiday => 'QH',
16
+ :operator => 'QP',
17
+ :additional_location_info => 'QB',
18
+ :location => 'QL',
19
+ :destination => 'QT',
20
+ :intermediate => 'QI',
21
+ :origin => 'QO',
22
+ :journey_header => 'QS'
23
+ }
24
+
25
+ def parse(file)
26
+ @path = File.expand_path(file)
27
+ data = File.readlines(@path)
28
+
29
+ objects = []
30
+ current_journey = nil
31
+ current_location = nil
32
+ locations = []
33
+ journeys = {}
34
+ header = nil
35
+
36
+ data.each do |line|
37
+ if line == data.first
38
+ header = parse_header(line)
39
+ next
40
+ end
41
+ @@methods.each do |method,identifier|
42
+ object = self.send("parse_#{method}", line)
43
+ if object[:record_identity] && object[:record_identity] == identifier
44
+ current_journey = object if object[:record_identity] && object[:record_identity] == @@methods[:journey_header]
45
+ if object[:record_identity] && ( object[:record_identity] == @@methods[:location] || object[:record_identity] == @@methods[:additional_location_info] )
46
+ if object[:record_identity] == @@methods[:location]
47
+ current_location = object
48
+ else
49
+ locations << Location.new(current_location, object)
50
+ end
51
+ end
52
+
53
+ if current_journey
54
+ if journeys[current_journey[:unique_journey_identifier]]
55
+ journeys[current_journey[:unique_journey_identifier]] << object
56
+ else
57
+ journeys[current_journey[:unique_journey_identifier]] = [object]
58
+ end
59
+ end
60
+ objects << object
61
+ end
62
+ end
63
+ end
64
+ return {:header => header, :locations => locations, :journeys => journeys}
65
+ end
66
+
67
+ def parse_header(string)
68
+ {
69
+ :file_type => string[0,8],
70
+ :version => "#{string[8,2].to_i}.#{string[10,2].to_i}",
71
+ :file_originator => string[12,32].strip!,
72
+ :source_product => string[44,16].strip!,
73
+ :production_datetime => string[60,14]
74
+ }
75
+ end
76
+
77
+ def parse_bank_holiday(string)
78
+ {
79
+ :record_identity => string[0,2],
80
+ :transaction_type => string[2,1],
81
+ :date_of_bank_holiday => string[3,8]
82
+ }
83
+ end
84
+
85
+ def parse_operator(string)
86
+ {
87
+ :record_identity => string[0,2],
88
+ :transaction_type => string[2,1],
89
+ :operator => parse_value(string[3,4]),
90
+ :operator_short_form => parse_value(string[7,24]),
91
+ :operator_legal_name => parse_value(string[31,48])
92
+ }
93
+ end
94
+
95
+ def parse_additional_location_info(string)
96
+ {
97
+ :record_identity => string[0,2],
98
+ :transaction_type => string[2,1],
99
+ :location => string[3,12].strip,
100
+ :grid_reference_easting => parse_value(string[15,8]),
101
+ :grid_reference_northing => parse_value(string[23,8])
102
+ }
103
+ end
104
+
105
+ def parse_location(string)
106
+ {
107
+ :record_identity => string[0,2],
108
+ :transaction_type => string[2,1],
109
+ :location => parse_value(string[3,12]),
110
+ :full_location => parse_value(string[15,48]),
111
+ :gazetteer_code => string[63,1]
112
+ }
113
+ end
114
+
115
+ def parse_destination(string)
116
+ {
117
+ :record_identity => string[0,2],
118
+ :location => string[2,12],
119
+ :published_arrival_time => string[14,4],
120
+ :bay_number => parse_value(string[18,3]),
121
+ :timing_point_indicator => string[21,2],
122
+ :fare_stage_indicator => string[23,2]
123
+ }
124
+ end
125
+
126
+ def parse_intermediate(string)
127
+ {
128
+ :record_identity => string[0,2],
129
+ :location => string[2,12],
130
+ :published_arrival_time => string[14,4],
131
+ :published_departure_time => string[18,4],
132
+ :activity_flag => string[22,1],
133
+ :bay_number => parse_value(string[23,3]),
134
+ :timing_point_indicator => string[26,2],
135
+ :fare_stage_indicator => string[28,2]
136
+ }
137
+ end
138
+
139
+ def parse_origin(string)
140
+ {
141
+ :record_identity => string[0,2],
142
+ :location => string[2,12],
143
+ :published_departure_time => string[14,4],
144
+ :bay_number => parse_value(string[18,3]),
145
+ :timing_point_indicator => string[21,2],
146
+ :fare_stage_indicator => string[23,2]
147
+ }
148
+ end
149
+
150
+ def parse_journey_header(string)
151
+ {
152
+ :record_identity => string[0,2],
153
+ :transaction_type => string[2,1],
154
+ :operator => string[3,4].strip,
155
+ :unique_journey_identifier => string[7,6],
156
+ :first_date_of_operation => parse_value(string[13,8]),
157
+ :last_date_of_operation => parse_value(string[21,8]),
158
+ :operates_on_mondays => string[29,1],
159
+ :operates_on_tuesdays => string[30,1],
160
+ :operates_on_wednesdays => string[31,1],
161
+ :operates_on_thursdays => string[32,1],
162
+ :operates_on_fridays => string[33,1],
163
+ :operates_on_saturdays => string[34,1],
164
+ :operates_on_sundays => string[35,1],
165
+ :school_term_time => parse_value(string[36,1]),
166
+ :bank_holidays => parse_value(string[37,1]),
167
+ :route_number => parse_value(string[38,4]),
168
+ :running_board => parse_value(string[42,6]),
169
+ :vehicle_type => parse_value(string[48,8]),
170
+ :registration_number => parse_value(string[56,8]),
171
+ :route_direction => string[64,1]
172
+ }
173
+ end
174
+
175
+ def parse_value(value)
176
+ return value.strip if value
177
+ end
178
+ end
179
+
180
+ end
data/lib/location.rb ADDED
@@ -0,0 +1,23 @@
1
+ class Location
2
+
3
+ attr_accessor :name, :identifier, :easting, :northing, :gazeteer_code
4
+
5
+ def initialize(location_header, additional_location_information)
6
+ @name = location_header[:full_location]
7
+ @identifier = location_header[:record_identity]
8
+ @easting = additional_location_information[:grid_reference_easting]
9
+ @northing = additional_location_information[:grid_reference_northing]
10
+ @gazeteer_code = location_header[:gazetteer_code]
11
+ end
12
+
13
+ def to_json(*a)
14
+ {
15
+ :name => @name,
16
+ :identifier => @identifier,
17
+ :easting => @easting,
18
+ :northing => @northing,
19
+ :gazeteer_code => @gazeteer_code
20
+ }.to_json(*a)
21
+ end
22
+
23
+ end
data/spec/atco_spec.rb ADDED
@@ -0,0 +1,150 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require 'json'
3
+
4
+ describe Atco do
5
+
6
+ before(:all) do
7
+
8
+ end
9
+
10
+ it "should output file for debugging!" do
11
+ result = Atco.parse('spec/fixtures/example.cif')
12
+ File.open('test.output', 'w+') do |f|
13
+ f.flush
14
+ f.write(JSON.pretty_generate(result))
15
+ end
16
+ #fixture = JSON.parse(File.read('spec/fixtures/example.json'))
17
+ end
18
+
19
+ it "should parse header from fixture" do
20
+ result = Atco.parse('spec/fixtures/example.cif')
21
+ result[:header].should == {
22
+ :file_type => "ATCO-CIF",
23
+ :file_originator => "Electronic Registration",
24
+ :source_product => "MIA 4.20.18",
25
+ :version => "5.0",
26
+ :production_datetime => "20090915113809"
27
+ }
28
+ end
29
+
30
+ it "should parse locations from fixture" do
31
+ result = Atco.parse('spec/fixtures/example.cif')
32
+ result[:header].should == {
33
+ :file_type => "ATCO-CIF",
34
+ :file_originator => "Electronic Registration",
35
+ :source_product => "MIA 4.20.18",
36
+ :version => "5.0",
37
+ :production_datetime => "20090915113809"
38
+ }
39
+ end
40
+
41
+ it "should parse header" do
42
+ Atco.parse_header("ATCO-CIF0500Electronic Registration MIA 4.20.18 20090915113809\r\n").should == {
43
+ :file_type => 'ATCO-CIF',
44
+ :version => '5.0',
45
+ :file_originator => 'Electronic Registration',
46
+ :source_product => 'MIA 4.20.18',
47
+ :production_datetime => '20090915113809'
48
+ }
49
+ end
50
+
51
+ it "should parse bank holiday" do
52
+ Atco.parse_bank_holiday("QHN20061225").should == {
53
+ :record_identity => 'QH',
54
+ :transaction_type => 'N',
55
+ :date_of_bank_holiday => '20061225'
56
+ }
57
+ end
58
+
59
+ it "should parse operator" do
60
+ Atco.parse_operator("QPNTM Translink Metro Translink Metro \r\n").should == {
61
+ :record_identity => 'QP',
62
+ :transaction_type => 'N',
63
+ :operator => 'TM',
64
+ :operator_short_form => 'Translink Metro',
65
+ :operator_legal_name => 'Translink Metro'
66
+ }
67
+ end
68
+
69
+ it "should parse additional location information" do
70
+ Atco.parse_additional_location_info("QBN700000001252 328622 367433 \r\n").should == {
71
+ :record_identity => 'QB',
72
+ :transaction_type => 'N',
73
+ :location => '700000001252',
74
+ :grid_reference_easting => '328622',
75
+ :grid_reference_northing => '367433'
76
+ }
77
+ end
78
+
79
+ it "should parse location" do
80
+ Atco.parse_location("QLN700000001252Conway (River Rd) 1\r\n").should == {
81
+ :record_identity => 'QL',
82
+ :transaction_type => 'N',
83
+ :location => '700000001252',
84
+ :full_location => 'Conway (River Rd)',
85
+ :gazetteer_code => '1'
86
+ }
87
+ end
88
+
89
+ # QT7000000012520605 T1F0
90
+ it "should parse destination" do
91
+ Atco.parse_destination("QT7000000012520605 T1F0\r\n").should == {
92
+ :record_identity => 'QT',
93
+ :location => '700000001252',
94
+ :published_arrival_time => '0605',
95
+ :bay_number => "",
96
+ :timing_point_indicator => 'T1',
97
+ :fare_stage_indicator => 'F0'
98
+ }
99
+ end
100
+
101
+ it "should parse intermediate" do
102
+ Atco.parse_intermediate("QI70000000125607120712B T1F0\r\n").should == {
103
+ :record_identity => 'QI',
104
+ :location => '700000001256',
105
+ :published_arrival_time => '0712',
106
+ :published_departure_time => '0712',
107
+ :activity_flag => "B",
108
+ :bay_number => "",
109
+ :timing_point_indicator => 'T1',
110
+ :fare_stage_indicator => 'F0'
111
+ }
112
+ end
113
+
114
+ it "should parse origin" do
115
+ Atco.parse_origin("QO7000000012520730 T1F0\r\n").should == {
116
+ :record_identity => 'QO',
117
+ :location => '700000001252',
118
+ :published_departure_time => '0730',
119
+ :bay_number => "",
120
+ :timing_point_indicator => 'T1',
121
+ :fare_stage_indicator => 'F0'
122
+ }
123
+ end
124
+
125
+ it "should parse journey header" do
126
+ Atco.parse_journey_header("QSNTM 13986520091005 1111100 9A 9018 0 I\r\n").should == {
127
+ :record_identity => 'QS',
128
+ :transaction_type => 'N',
129
+ :operator => 'TM',
130
+ :unique_journey_identifier => '139865',
131
+ :first_date_of_operation => '20091005',
132
+ :last_date_of_operation => '',
133
+ :operates_on_mondays => '1',
134
+ :operates_on_tuesdays => '1',
135
+ :operates_on_wednesdays => '1',
136
+ :operates_on_thursdays => '1',
137
+ :operates_on_fridays => '1',
138
+ :operates_on_saturdays => '0',
139
+ :operates_on_sundays => '0',
140
+ :school_term_time => '',
141
+ :bank_holidays => '',
142
+ :route_number => '9A',
143
+ :running_board => '9018',
144
+ :vehicle_type => '0',
145
+ :registration_number => '',
146
+ :route_direction => 'I'
147
+ }
148
+ end
149
+
150
+ end
@@ -0,0 +1,2 @@
1
+ require 'rubygems'
2
+ require File.dirname(__FILE__) + '/../lib/atco'
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: atco
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - David Rice
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-02-22 00:00:00 +00:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: Simple and opinionated library for parsing ATCO-CIF files with Ruby.
22
+ email: me@davidjrice.co.uk
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files:
28
+ - README.mdown
29
+ files:
30
+ - README.mdown
31
+ - Rakefile
32
+ - VERSION
33
+ - lib/atco.rb
34
+ - lib/location.rb
35
+ has_rdoc: true
36
+ homepage: http://github.com/davidjrice/atco
37
+ licenses: []
38
+
39
+ post_install_message:
40
+ rdoc_options:
41
+ - --charset=UTF-8
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ segments:
49
+ - 0
50
+ version: "0"
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ segments:
56
+ - 0
57
+ version: "0"
58
+ requirements: []
59
+
60
+ rubyforge_project:
61
+ rubygems_version: 1.3.6
62
+ signing_key:
63
+ specification_version: 3
64
+ summary: Simple and opinionated library for parsing ATCO-CIF files with Ruby.
65
+ test_files:
66
+ - spec/atco_spec.rb
67
+ - spec/spec_helper.rb