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 +7 -0
- data/Guardfile +10 -0
- data/README.md +1 -9
- data/lib/wasserstand.rb +7 -2
- data/lib/wasserstand/finders.rb +16 -0
- data/lib/wasserstand/hash_clod.rb +10 -0
- data/lib/wasserstand/heap_cache.rb +16 -0
- data/lib/wasserstand/level.rb +7 -15
- data/lib/wasserstand/provider/level_mapper.rb +13 -13
- data/lib/wasserstand/provider/pegel_online.rb +57 -0
- data/lib/wasserstand/provider/waterway_mapper.rb +16 -16
- data/lib/wasserstand/version.rb +1 -1
- data/lib/wasserstand/waterway.rb +6 -25
- data/test/helper.rb +1 -2
- data/test/unit/test_level.rb +0 -5
- data/test/unit/test_waterway.rb +0 -5
- data/wasserstand.gemspec +3 -0
- metadata +56 -3
- data/lib/wasserstand/provider/pegel-online.rb +0 -87
data/CONTRIBUTING.md
ADDED
data/Guardfile
ADDED
data/README.md
CHANGED
@@ -20,12 +20,4 @@ Or install it yourself as:
|
|
20
20
|
|
21
21
|
## Usage
|
22
22
|
|
23
|
-
|
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
|
-
|
13
|
-
|
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
|
data/lib/wasserstand/level.rb
CHANGED
@@ -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
|
5
|
+
class Level
|
6
6
|
class << self
|
7
|
-
|
8
|
-
provider[name]
|
9
|
-
end
|
7
|
+
include Finders
|
10
8
|
|
11
9
|
def all
|
12
|
-
provider.
|
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
|
2
|
+
module Provider
|
3
3
|
=begin
|
4
|
-
<item>
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
2
|
+
module Provider
|
3
3
|
=begin
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
data/lib/wasserstand/version.rb
CHANGED
data/lib/wasserstand/waterway.rb
CHANGED
@@ -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
|
-
|
16
|
-
provider[name]
|
17
|
-
end
|
4
|
+
include Wasserstand::Finders
|
18
5
|
|
19
6
|
def all
|
20
|
-
provider.
|
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.
|
10
|
-
Wasserstand.providers[PegelOnline::LevelProvider] = PegelOnline::LevelProvider.new(url)
|
9
|
+
Wasserstand.provider = Provider::PegelOnline.new(url)
|
11
10
|
end
|
12
11
|
end
|
data/test/unit/test_level.rb
CHANGED
@@ -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)
|
data/test/unit/test_waterway.rb
CHANGED
@@ -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.
|
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-
|
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/
|
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
|