devinci 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ .bundle
2
+ coverage
3
+ Gemfile.lock
4
+ pkg
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in devinci.gemspec
4
+ gemspec
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Paul Guelpa
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,19 @@
1
+ = devinci
2
+
3
+ Parses the data files supplied by a number of bike share systems
4
+
5
+ == Contributing to devinci
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
9
+ * Fork the project
10
+ * Start a feature/bugfix branch
11
+ * Commit and push until you are happy with your contribution
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2011 Paul Guelpa. See LICENSE.txt for
18
+ further details.
19
+
@@ -0,0 +1,24 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ begin
5
+ Bundler.setup(:default, :development)
6
+ rescue Bundler::BundlerError => e
7
+ $stderr.puts e.message
8
+ $stderr.puts "Run `bundle install` to install missing gems"
9
+ exit e.status_code
10
+ end
11
+
12
+ require 'rspec/core'
13
+ require 'rspec/core/rake_task'
14
+ RSpec::Core::RakeTask.new(:spec) do |spec|
15
+ spec.pattern = FileList['spec/**/*_spec.rb']
16
+ end
17
+
18
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
19
+ spec.rcov_opts = ['--exclude', 'gems']
20
+ spec.pattern = 'spec/**/*_spec.rb'
21
+ spec.rcov = true
22
+ end
23
+
24
+ task :default => :spec
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "devinci/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "devinci"
7
+ s.version = Devinci::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Paul Guelpa"]
10
+ s.email = ["paul.guelpa@gmail.com"]
11
+ s.homepage = "http://github.com/pguelpa/devinci"
12
+ s.summary = %q{Parser for various bike share data files}
13
+ s.description = %q{Parses the data files supplied by a number of bike share systems}
14
+
15
+ s.rubyforge_project = "devinci"
16
+
17
+ s.add_dependency "libxml-ruby", ">= 1.1.3"
18
+ s.add_dependency "nokogiri", ">= 1.5.0"
19
+
20
+ s.add_development_dependency "bundler", ">= 1.0.0"
21
+ s.add_development_dependency "rake", ">= 0.9.0"
22
+ s.add_development_dependency "rcov", ">= 0.9.10"
23
+ s.add_development_dependency "rspec", ">= 2.3.0"
24
+
25
+ s.files = `git ls-files`.split("\n")
26
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
27
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
28
+ s.require_paths = ["lib"]
29
+ end
@@ -0,0 +1,5 @@
1
+ require 'libxml'
2
+ require 'nokogiri'
3
+
4
+ require 'devinci/base'
5
+ require 'devinci/bixi'
@@ -0,0 +1,33 @@
1
+ module Devinci
2
+ class ParseError < RuntimeError; end
3
+
4
+ class Base
5
+ attr_reader :filename, :options
6
+
7
+ def initialize(filename, options = {})
8
+ @filename = filename
9
+ @options = options
10
+ end
11
+
12
+ def io
13
+ @io ||= case @options[:compression]
14
+ when :gzip
15
+ Zlib::GzipReader.open(@filename)
16
+ else
17
+ File.open(@filename)
18
+ end
19
+ end
20
+
21
+ def downloaded_on
22
+ match = filename.match(/(\d{4})-(\d{2})-(\d{2})..(\d{2}):(\d{2}):(\d{2})/)
23
+ if match
24
+ numbers = (1..6).map { |n| match[n].to_i }
25
+ Time.utc(*numbers)
26
+ end
27
+ end
28
+
29
+ def parse
30
+ raise NotImplementedError, "Each parser must define their own parse method"
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,52 @@
1
+ module Devinci
2
+ class Bixi < Base
3
+ attr_reader :document
4
+
5
+ def parse
6
+ @document = Nokogiri::XML(io)
7
+
8
+ @document.xpath('/stations/station').map do |node|
9
+ parse_station(node)
10
+ end
11
+ rescue Exception => e
12
+ raise Devinci::ParseError, "Error parsing the input from XML: #{e.message}", e.backtrace
13
+ ensure
14
+ io.close
15
+ end
16
+
17
+ private
18
+ def parse_station(node)
19
+ station = {}
20
+ node.children.each do |c|
21
+ case c.name
22
+ when 'id'
23
+ station[:id] = c.content
24
+ when 'name'
25
+ station[:name] = c.content
26
+ when 'terminalName'
27
+ station[:terminal_name] = c.content
28
+ when 'lat'
29
+ station[:latitude] = c.content.to_f
30
+ when 'long'
31
+ station[:longitude] = c.content.to_f
32
+ when 'installed'
33
+ station[:installed] = (c.content == 'true')
34
+ when 'locked'
35
+ station[:locked] = (c.content == 'true')
36
+ when 'installDate'
37
+ station[:installed_on] = c.content.empty? ? nil : Time.at(c.content.to_i / 1000)
38
+ when 'removalDate'
39
+ station[:removed_on] = c.content.empty? ? nil : Time.at(c.content.to_i / 1000)
40
+ when 'temporary'
41
+ station[:temporary] = (c.content == 'true')
42
+ when 'nbBikes'
43
+ station[:bikes] = c.content.to_i
44
+ when 'nbEmptyDocks'
45
+ station[:empty_docks] = c.content.to_i
46
+ end
47
+ end
48
+
49
+ station
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,3 @@
1
+ module Devinci
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe Devinci::Base do
4
+ describe ".new" do
5
+ it "should take a filename" do
6
+ filename = "/root/fooname"
7
+ Devinci::Base.new(filename).filename.should == filename
8
+ end
9
+ end
10
+
11
+ describe "#io" do
12
+ it "should return a regular file object when there is no compression" do
13
+ File.should_receive(:open).with('/root/fooname').and_return(:file_io)
14
+ Devinci::Base.new('/root/fooname').io.should == :file_io
15
+ end
16
+
17
+ it "should memoize the io object for later use" do
18
+ file = mock(File)
19
+ File.should_receive(:open).once.and_return(file)
20
+
21
+ parser = Devinci::Base.new(anything)
22
+ 2.times { parser.io }
23
+ end
24
+
25
+ it "should build an Gzip IO object when :compression => :gzip" do
26
+ Zlib::GzipReader.should_receive(:open).with('/root/fooname').and_return(:gzip_file_io)
27
+ Devinci::Base.new('/root/fooname', :compression => :gzip).io.should == :gzip_file_io
28
+ end
29
+ end
30
+
31
+ describe "#downloaded_on" do
32
+ it "should parse the time that the data was downloaded on from the filename as UTC" do
33
+ at = Devinci::Base.new('someStation-2011-05-14::14:00:01.xml.gz').downloaded_on
34
+ at.should == Time.utc(2011, 5, 14, 14, 0, 1)
35
+ end
36
+ end
37
+
38
+ describe "#parse" do
39
+ it "should raise a NotImplementedError" do
40
+ lambda {
41
+ Devinci::Base.new(anything).parse
42
+ }.should raise_error NotImplementedError
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+
3
+ describe Devinci::Bixi do
4
+ describe "#parse" do
5
+ before do
6
+ @io = mock(IO, :close => nil)
7
+ @document = mock(Nokogiri::XML::Document, :xpath => [])
8
+
9
+ @parser = Devinci::Bixi.new(anything)
10
+ @parser.stub!(:io).and_return(@io)
11
+ end
12
+
13
+ it "should use the inherited io method and pass the result to Nokogiri" do
14
+ Nokogiri.should_receive(:XML).with(@io).and_return(@document)
15
+ @parser.parse
16
+ end
17
+
18
+ it "should search for all the stations" do
19
+ Nokogiri.stub!(:XML).and_return(@document)
20
+ @document.should_receive(:xpath).with('/stations/station').and_return([])
21
+
22
+ @parser.parse
23
+ end
24
+
25
+ it "should close the io object after successful parsing" do
26
+ Nokogiri.stub!(:XML).and_return(@document)
27
+
28
+ @io.should_receive(:close)
29
+ @parser.parse
30
+ end
31
+
32
+ it "should close the io object after failed parsing" do
33
+ Nokogiri.stub!(:XML).and_raise
34
+
35
+ @io.should_receive(:close)
36
+ lambda { @parser.parse }.should raise_error
37
+ end
38
+
39
+ it "should raise a ParseError if the file fails to be parsed" do
40
+ Nokogiri.stub!(:XML).and_raise ArgumentError.new("Foo Error")
41
+
42
+ lambda {
43
+ @parser.parse
44
+ }.should raise_error Devinci::ParseError, /Foo Error/
45
+ end
46
+ end
47
+
48
+ describe "#parse (station details)" do
49
+ before :all do
50
+ @station = Devinci::Bixi.new('spec/fixtures/devinci/bixi_station.xml').parse.first
51
+ end
52
+
53
+ {
54
+ :id => '1',
55
+ :name => 'Notre Dame / Place Jacques Cartier',
56
+ :terminal_name => '6001',
57
+ :latitude => 45.508183,
58
+ :longitude => -73.554094,
59
+ :installed => true,
60
+ :locked => false,
61
+ :installed_on => Time.utc(2010, 6, 8, 16, 02, 00),
62
+ :removed_on => Time.utc(2010, 7, 9, 16, 02, 00),
63
+ :temporary => false,
64
+ :bikes => 13,
65
+ :empty_docks => 17,
66
+ }.each do |attribute, value|
67
+ it "should parse #{attribute} from the node" do
68
+ @station[attribute].should == value
69
+ end
70
+ end
71
+ end
72
+
73
+ describe "#parse (missing dates)" do
74
+ before :all do
75
+ @station = Devinci::Bixi.new('spec/fixtures/devinci/bixi_station_no_dates.xml').parse.first
76
+ end
77
+
78
+ it "should return nil for install_date if it has no value" do
79
+ @station[:installed_on].should be_nil
80
+ end
81
+
82
+ it "should return nil for removed_on if it has no value" do
83
+ @station[:removed_on].should be_nil
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,17 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <stations lastUpdate="1305354525205" version="2.0">
3
+ <station>
4
+ <id>1</id>
5
+ <name>Notre Dame / Place Jacques Cartier</name>
6
+ <terminalName>6001</terminalName>
7
+ <lat>45.508183</lat>
8
+ <long>-73.554094</long>
9
+ <installed>true</installed>
10
+ <locked>false</locked>
11
+ <installDate>1276012920000</installDate>
12
+ <removalDate>1278691320000</removalDate>
13
+ <temporary>false</temporary>
14
+ <nbBikes>13</nbBikes>
15
+ <nbEmptyDocks>17</nbEmptyDocks>
16
+ </station>
17
+ </stations>
@@ -0,0 +1,17 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <stations>
3
+ <station>
4
+ <id>1</id>
5
+ <name>Notre Dame / Place Jacques Cartier</name>
6
+ <terminalName>6001</terminalName>
7
+ <lat>45.508183</lat>
8
+ <long>-73.554094</long>
9
+ <installed>true</installed>
10
+ <locked>false</locked>
11
+ <installDate/>
12
+ <removalDate/>
13
+ <temporary>false</temporary>
14
+ <nbBikes>13</nbBikes>
15
+ <nbEmptyDocks>17</nbEmptyDocks>
16
+ </station>
17
+ </stations>
@@ -0,0 +1,2 @@
1
+ require 'rspec'
2
+ require 'devinci'
metadata ADDED
@@ -0,0 +1,182 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: devinci
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Paul Guelpa
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-09-19 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ type: :runtime
23
+ name: libxml-ruby
24
+ version_requirements: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 21
30
+ segments:
31
+ - 1
32
+ - 1
33
+ - 3
34
+ version: 1.1.3
35
+ prerelease: false
36
+ requirement: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ type: :runtime
39
+ name: nokogiri
40
+ version_requirements: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 3
46
+ segments:
47
+ - 1
48
+ - 5
49
+ - 0
50
+ version: 1.5.0
51
+ prerelease: false
52
+ requirement: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ type: :development
55
+ name: bundler
56
+ version_requirements: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 23
62
+ segments:
63
+ - 1
64
+ - 0
65
+ - 0
66
+ version: 1.0.0
67
+ prerelease: false
68
+ requirement: *id003
69
+ - !ruby/object:Gem::Dependency
70
+ type: :development
71
+ name: rake
72
+ version_requirements: &id004 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ hash: 59
78
+ segments:
79
+ - 0
80
+ - 9
81
+ - 0
82
+ version: 0.9.0
83
+ prerelease: false
84
+ requirement: *id004
85
+ - !ruby/object:Gem::Dependency
86
+ type: :development
87
+ name: rcov
88
+ version_requirements: &id005 !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ hash: 47
94
+ segments:
95
+ - 0
96
+ - 9
97
+ - 10
98
+ version: 0.9.10
99
+ prerelease: false
100
+ requirement: *id005
101
+ - !ruby/object:Gem::Dependency
102
+ type: :development
103
+ name: rspec
104
+ version_requirements: &id006 !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ hash: 3
110
+ segments:
111
+ - 2
112
+ - 3
113
+ - 0
114
+ version: 2.3.0
115
+ prerelease: false
116
+ requirement: *id006
117
+ description: Parses the data files supplied by a number of bike share systems
118
+ email:
119
+ - paul.guelpa@gmail.com
120
+ executables: []
121
+
122
+ extensions: []
123
+
124
+ extra_rdoc_files: []
125
+
126
+ files:
127
+ - .gitignore
128
+ - .rspec
129
+ - Gemfile
130
+ - LICENSE.txt
131
+ - README.rdoc
132
+ - Rakefile
133
+ - devinci.gemspec
134
+ - lib/devinci.rb
135
+ - lib/devinci/base.rb
136
+ - lib/devinci/bixi.rb
137
+ - lib/devinci/version.rb
138
+ - spec/devinci/base_spec.rb
139
+ - spec/devinci/bixi_spec.rb
140
+ - spec/fixtures/devinci/bixi_station.xml
141
+ - spec/fixtures/devinci/bixi_station_no_dates.xml
142
+ - spec/spec_helper.rb
143
+ has_rdoc: true
144
+ homepage: http://github.com/pguelpa/devinci
145
+ licenses: []
146
+
147
+ post_install_message:
148
+ rdoc_options: []
149
+
150
+ require_paths:
151
+ - lib
152
+ required_ruby_version: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ hash: 3
158
+ segments:
159
+ - 0
160
+ version: "0"
161
+ required_rubygems_version: !ruby/object:Gem::Requirement
162
+ none: false
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ hash: 3
167
+ segments:
168
+ - 0
169
+ version: "0"
170
+ requirements: []
171
+
172
+ rubyforge_project: devinci
173
+ rubygems_version: 1.4.2
174
+ signing_key:
175
+ specification_version: 3
176
+ summary: Parser for various bike share data files
177
+ test_files:
178
+ - spec/devinci/base_spec.rb
179
+ - spec/devinci/bixi_spec.rb
180
+ - spec/fixtures/devinci/bixi_station.xml
181
+ - spec/fixtures/devinci/bixi_station_no_dates.xml
182
+ - spec/spec_helper.rb