wasserstand 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,7 @@
1
+ # Contributing
2
+
3
+ 1. Fork it
4
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
5
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
6
+ 4. Push to the branch (`git push origin my-new-feature`)
7
+ 5. Create new Pull Request
data/Guardfile ADDED
@@ -0,0 +1,10 @@
1
+ guard 'bundler' do
2
+ watch('Gemfile')
3
+ end
4
+
5
+ guard 'minitest' do
6
+ watch(%r|^test/unit/test_(.*)\.rb|)
7
+ watch(%r|^lib/*\.rb|){'test'}
8
+ watch(%r{^lib/.*/([^/]+)\.rb$}){|m| "test/unit/test_#{m[1]}.rb"}
9
+ watch(%r|^test/helper\.rb|){'test'}
10
+ end
data/README.md CHANGED
@@ -20,12 +20,4 @@ Or install it yourself as:
20
20
 
21
21
  ## Usage
22
22
 
23
- TODO
24
-
25
- ## Contributing
26
-
27
- 1. Fork it
28
- 2. Create your feature branch (`git checkout -b my-new-feature`)
29
- 3. Commit your changes (`git commit -am 'Added some feature'`)
30
- 4. Push to the branch (`git push origin my-new-feature`)
31
- 5. Create new Pull Request
23
+ see tests
data/lib/wasserstand.rb CHANGED
@@ -3,14 +3,19 @@ require 'open-uri'
3
3
  require 'tzinfo'
4
4
  require 'unicode_utils/upcase'
5
5
  require 'unicode_utils/downcase'
6
+ require 'forwardable'
6
7
 
7
8
  require 'require_all'
8
9
  require_rel 'wasserstand'
9
10
 
10
11
  module Wasserstand
12
+ AmbigousNameError = Class.new(StandardError)
13
+
11
14
  class << self
12
- def providers
13
- @providers ||= Hash.new{|hash, k| hash[k] = k.new}
15
+ attr_writer :provider
16
+
17
+ def provider
18
+ @provider ||= Provider::PegelOnline.new
14
19
  end
15
20
  end
16
21
  end
@@ -0,0 +1,16 @@
1
+ module Wasserstand
2
+ module Finders
3
+ def [](name)
4
+ results = all.select{|named| UnicodeUtils.upcase(name) == named.name}
5
+
6
+ case results.size
7
+ when 0
8
+ nil # loookup returns nil if not found. This is a lookup, not find_all.
9
+ when 1
10
+ results.first
11
+ else
12
+ raise AmbigousNameError "Name '#{name}' is not unique. Found #{results.size} results."
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ # http://stackoverflow.com/questions/2030336/how-do-i-create-a-hash-in-ruby-that-compares-strings-ignoring-case
2
+ class HashClod < Hash
3
+ def [](key)
4
+ key.respond_to?(:upcase) ? super(UnicodeUtils.upcase(key)) : super(key)
5
+ end
6
+
7
+ def []=(key, value)
8
+ key.respond_to?(:upcase) ? super(UnicodeUtils.upcase(key), value) : super(key, value)
9
+ end
10
+ end
@@ -0,0 +1,16 @@
1
+ class HeapCache
2
+ def initialize
3
+ @backend = {}
4
+ end
5
+
6
+ def get(name)
7
+ result = @backend[name]
8
+ STDERR.puts "#{self.class.name} GET #{result ? 'HIT' : 'MISS'} #{name}"
9
+ result
10
+ end
11
+
12
+ def set(name, value)
13
+ STDERR.puts "#{self.class.name} SET #{name} => #{value.inspect}"
14
+ @backend[name] = value
15
+ end
16
+ end
@@ -2,24 +2,12 @@ module Wasserstand
2
2
  #
3
3
  # see http://www.pegelonline.wsv.de/gast/hilfe#hilfe_pegelparameter
4
4
  #
5
- class Level # Pegel
5
+ class Level
6
6
  class << self
7
- def [](name)
8
- provider[name]
9
- end
7
+ include Finders
10
8
 
11
9
  def all
12
- provider.all
13
- end
14
-
15
- def find_by_name(regex)
16
- provider.find_by_name(regex)
17
- end
18
-
19
- private
20
-
21
- def provider
22
- Wasserstand.providers[PegelOnline::LevelProvider]
10
+ Wasserstand.provider.levels
23
11
  end
24
12
  end
25
13
 
@@ -43,5 +31,9 @@ module Wasserstand
43
31
  def to_s
44
32
  name
45
33
  end
34
+
35
+ def inspect
36
+ "#<#{self.class.name}: #{name} (#{measurements.size} measurements)>"
37
+ end
46
38
  end
47
39
  end
@@ -1,18 +1,18 @@
1
1
  module Wasserstand
2
- module PegelOnline
2
+ module Provider
3
3
  =begin
4
- <item>
5
- <no>8</no>
6
- <psmgr>320</psmgr>
7
- <pegelname>KONSTANZ</pegelname>
8
- <messwert>380,7</messwert>
9
- <km>0</km>
10
- <pnp>391,89</pnp>
11
- <tendenz>Gleich</tendenz>
12
- <datum>13.09.2012</datum>
13
- <uhrzeit>20:00:00</uhrzeit>
14
- <pegelnummer>0906</pegelnummer>
15
- </item>
4
+ <item>
5
+ <no>8</no>
6
+ <psmgr>320</psmgr>
7
+ <pegelname>KONSTANZ</pegelname>
8
+ <messwert>380,7</messwert>
9
+ <km>0</km>
10
+ <pnp>391,89</pnp>
11
+ <tendenz>Gleich</tendenz>
12
+ <datum>13.09.2012</datum>
13
+ <uhrzeit>20:00:00</uhrzeit>
14
+ <pegelnummer>0906</pegelnummer>
15
+ </item>
16
16
  =end
17
17
  class LevelMapper
18
18
  class << self
@@ -0,0 +1,57 @@
1
+ module Wasserstand
2
+ module Provider
3
+ class PegelOnline
4
+ attr_writer :cache
5
+
6
+ include Enumerable
7
+ extend Forwardable
8
+ def_delegator :all, :each
9
+
10
+ def initialize(url = 'http://www.pegelonline.wsv.de/svgz/pegelstaende_neu.xml')
11
+ @url = url
12
+ @names = []
13
+ end
14
+
15
+ # Cache entries may expire, and simply iterating over the list of current cache entries would render an incomplete picture. Therefore we maintain the knowledge of what waterways exist in a single list, which is the authoritative source of what waterways exist.
16
+ def waterways
17
+ replenish if @names.empty?
18
+ @names.map do |name|
19
+ ww = cache.get(name) # no fetch with block in Dalli, so we cannot use it here either ...
20
+
21
+ # Not finding the Waterway for a name means it was removed from the cache, but it may or may not exist in the backend. Therefore we need to replenish our cache including our knowledge about the name.
22
+ if ww.nil?
23
+ replenish
24
+ ww = cache.get(name)
25
+ # Still nil? Does not matter. Replenish has auto-removed an outdated name.
26
+ end
27
+
28
+ ww
29
+ end.compact # don't return nil entries
30
+ end
31
+
32
+ def levels
33
+ waterways.inject([]){|result, ww| result.concat(ww.levels.values)}
34
+ end
35
+
36
+ def cache
37
+ @cache ||= HeapCache.new
38
+ end
39
+
40
+ private
41
+
42
+ #
43
+ # Replenish the cache and names index
44
+ #
45
+ def replenish
46
+ @names.clear
47
+ STDERR.puts "FETCH #{@url}"
48
+ doc = Nokogiri::HTML(open(@url).read, nil, 'ISO-8859-1')
49
+ doc.xpath("//data/table/gewaesser").each do |node|
50
+ ww = WaterwayMapper.map(node)
51
+ @names << ww.name
52
+ cache.set(ww.name, ww)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -1,21 +1,21 @@
1
1
  module Wasserstand
2
- module PegelOnline
2
+ module Provider
3
3
  =begin
4
- <gewaesser>
5
- <name>BODENSEE</name>
6
- <item>
7
- <no>8</no>
8
- <psmgr>320</psmgr>
9
- <pegelname>KONSTANZ</pegelname>
10
- <messwert>380,7</messwert>
11
- <km>0</km>
12
- <pnp>391,89</pnp>
13
- <tendenz>Gleich</tendenz>
14
- <datum>13.09.2012</datum>
15
- <uhrzeit>20:00:00</uhrzeit>
16
- <pegelnummer>0906</pegelnummer>
17
- </item>
18
- </gewaesser>
4
+ <gewaesser>
5
+ <name>BODENSEE</name>
6
+ <item>
7
+ <no>8</no>
8
+ <psmgr>320</psmgr>
9
+ <pegelname>KONSTANZ</pegelname>
10
+ <messwert>380,7</messwert>
11
+ <km>0</km>
12
+ <pnp>391,89</pnp>
13
+ <tendenz>Gleich</tendenz>
14
+ <datum>13.09.2012</datum>
15
+ <uhrzeit>20:00:00</uhrzeit>
16
+ <pegelnummer>0906</pegelnummer>
17
+ </item>
18
+ </gewaesser>
19
19
  =end
20
20
  class WaterwayMapper
21
21
  class << self
@@ -1,3 +1,3 @@
1
1
  module Wasserstand
2
- VERSION = "0.0.8"
2
+ VERSION = "0.0.9"
3
3
  end
@@ -1,33 +1,10 @@
1
1
  module Wasserstand
2
- # http://stackoverflow.com/questions/2030336/how-do-i-create-a-hash-in-ruby-that-compares-strings-ignoring-case
3
- class HashClod < Hash
4
- def [](key)
5
- key.respond_to?(:upcase) ? super(UnicodeUtils.upcase(key)) : super(key)
6
- end
7
-
8
- def []=(key, value)
9
- key.respond_to?(:upcase) ? super(UnicodeUtils.upcase(key), value) : super(key, value)
10
- end
11
- end
12
-
13
2
  class Waterway
14
3
  class << self
15
- def [](name)
16
- provider[name]
17
- end
4
+ include Wasserstand::Finders
18
5
 
19
6
  def all
20
- provider.all
21
- end
22
-
23
- def find_by_name(regex)
24
- provider.find_by_name(regex)
25
- end
26
-
27
- private
28
-
29
- def provider
30
- Wasserstand.providers[PegelOnline::WaterwayProvider]
7
+ Wasserstand.provider.waterways
31
8
  end
32
9
  end
33
10
 
@@ -41,5 +18,9 @@ module Wasserstand
41
18
  def to_s
42
19
  name
43
20
  end
21
+
22
+ def inspect
23
+ "#<#{self.class.name}: #{name} (#{levels.size} levels)>"
24
+ end
44
25
  end
45
26
  end
data/test/helper.rb CHANGED
@@ -6,7 +6,6 @@ class WasserstandTestCase < MiniTest::Unit::TestCase
6
6
 
7
7
  def setup
8
8
  url = File.join(File.dirname(__FILE__), 'fixtures', 'pegelstaende_neu.xml')
9
- Wasserstand.providers[PegelOnline::WaterwayProvider] = PegelOnline::WaterwayProvider.new(url)
10
- Wasserstand.providers[PegelOnline::LevelProvider] = PegelOnline::LevelProvider.new(url)
9
+ Wasserstand.provider = Provider::PegelOnline.new(url)
11
10
  end
12
11
  end
@@ -12,11 +12,6 @@ class TestLevel < WasserstandTestCase
12
12
  assert_level({:name => 'PIRNA', :km => 34.67, :measurements_size => 1}, elbe_levels['PIRNA'])
13
13
  end
14
14
 
15
- def test_finder
16
- assert_equal(['HEIDELBERG UW', 'HAVELBERG EP', 'HAVELBERG STADT', 'HAVELBERG UP'], Level.find_by_name('ELBERG').map{|w| w.name})
17
- assert_equal(21, Level.find_by_name('^E').size)
18
- end
19
-
20
15
  def test_lookup
21
16
  pirna = Level['Pirna']
22
17
  assert(pirna)
@@ -2,11 +2,6 @@
2
2
  require 'helper'
3
3
 
4
4
  class TestWaterway < WasserstandTestCase
5
- def test_finder
6
- assert_equal(['ELBE', 'ELBE-HAVEL-KANAL', 'ELBESEITENKANAL'], Waterway.find_by_name('ELB.*').map{|w| w.name})
7
- assert_equal(7, Waterway.find_by_name('^E').size)
8
- end
9
-
10
5
  def test_size
11
6
  assert_equal(77, Waterway.all.size)
12
7
  end
data/wasserstand.gemspec CHANGED
@@ -21,4 +21,7 @@ Gem::Specification.new do |gem|
21
21
  gem.add_runtime_dependency 'unicode_utils'
22
22
  gem.add_development_dependency 'minitest'
23
23
  gem.add_development_dependency 'rake'
24
+ gem.add_development_dependency 'pry'
25
+ gem.add_development_dependency 'guard'
26
+ gem.add_development_dependency 'rb-fsevent'
24
27
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wasserstand
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.8
4
+ version: 0.0.9
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-19 00:00:00.000000000 Z
12
+ date: 2012-09-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: require_all
@@ -107,6 +107,54 @@ dependencies:
107
107
  - - ! '>='
108
108
  - !ruby/object:Gem::Version
109
109
  version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: pry
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: guard
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ - !ruby/object:Gem::Dependency
143
+ name: rb-fsevent
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ! '>='
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
110
158
  description: Unofficial Ruby wrapper for Pegel Online
111
159
  email:
112
160
  - nerab@gmx.net
@@ -117,16 +165,21 @@ extra_rdoc_files: []
117
165
  files:
118
166
  - .gitignore
119
167
  - .travis.yml
168
+ - CONTRIBUTING.md
120
169
  - Gemfile
170
+ - Guardfile
121
171
  - LICENSE
122
172
  - README.md
123
173
  - Rakefile
124
174
  - bin/wasserstand
125
175
  - lib/wasserstand.rb
176
+ - lib/wasserstand/finders.rb
177
+ - lib/wasserstand/hash_clod.rb
178
+ - lib/wasserstand/heap_cache.rb
126
179
  - lib/wasserstand/level.rb
127
180
  - lib/wasserstand/measurement.rb
128
181
  - lib/wasserstand/provider/level_mapper.rb
129
- - lib/wasserstand/provider/pegel-online.rb
182
+ - lib/wasserstand/provider/pegel_online.rb
130
183
  - lib/wasserstand/provider/waterway_mapper.rb
131
184
  - lib/wasserstand/trend.rb
132
185
  - lib/wasserstand/version.rb
@@ -1,87 +0,0 @@
1
- module Wasserstand
2
- module PegelOnline
3
- class Provider
4
- def initialize(url = 'http://www.pegelonline.wsv.de/svgz/pegelstaende_neu.xml')
5
- @url = url
6
- end
7
-
8
- def [](name)
9
- results = doc.xpath(xpath_lookup(name))
10
-
11
- case results.size
12
- when 0
13
- return nil # loookup returns nil if not found. This is a lookup, not find_all.
14
- when 1
15
- return mapper.map(results.first)
16
- else
17
- raise "Name is not unique. Found #{results.size} results for #{name}."
18
- end
19
- end
20
-
21
- def all
22
- doc.xpath(xpath_all).map{|o| mapper.map(o)}
23
- end
24
-
25
- def find_by_name(name_expression)
26
- # Not the best performing way, but it gives us the ability to use the XPath 2.0 'matches' function
27
- # which isn't supported in Nokogiri (yet).
28
- doc.xpath(xpath_finder(name_expression), Class.new{
29
- def matches(node_set, regex)
30
- node_set.find_all do |node|
31
- node.to_s.match(%r{#{regex}})
32
- end
33
- end
34
- }.new).map{|o| mapper.map(o)}
35
- end
36
-
37
- protected
38
-
39
- def xpath_lookup(name)
40
- "#{xpath_all}[#{name_attribute}/text() = '#{UnicodeUtils.upcase(name)}']"
41
- end
42
-
43
- def xpath_finder(regex)
44
- "#{xpath_all}[matches(#{name_attribute}/text(), '#{regex}')]"
45
- end
46
-
47
- def fetch(url)
48
- STDERR.puts "FETCH #{url}"
49
- open(url).read
50
- end
51
-
52
- def name_attribute
53
- 'name'
54
- end
55
-
56
- private
57
-
58
- def doc
59
- Nokogiri::HTML(fetch(@url), nil, 'ISO-8859-1')
60
- end
61
- end
62
-
63
- class WaterwayProvider < Provider
64
- def xpath_all
65
- "//data/table/gewaesser"
66
- end
67
-
68
- def mapper
69
- Wasserstand::PegelOnline::WaterwayMapper
70
- end
71
- end
72
-
73
- class LevelProvider < Provider
74
- def xpath_all
75
- "//data/table/gewaesser/item"
76
- end
77
-
78
- def mapper
79
- Wasserstand::PegelOnline::LevelMapper
80
- end
81
-
82
- def name_attribute
83
- 'pegelname'
84
- end
85
- end
86
- end
87
- end