wasserstand 0.0.10 → 0.0.11
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +3 -0
- data/TODO +0 -1
- data/bin/wasserstand +36 -30
- data/lib/wasserstand.rb +9 -3
- data/lib/wasserstand/commandline.rb +76 -0
- data/lib/wasserstand/finders.rb +1 -1
- data/lib/wasserstand/heap_cache.rb +3 -0
- data/lib/wasserstand/measurement.rb +1 -1
- data/lib/wasserstand/null_cache.rb +7 -0
- data/lib/wasserstand/provider/pegel_online.rb +27 -12
- data/lib/wasserstand/version.rb +1 -1
- data/test/unit/test_app.rb +76 -0
- data/test/unit/test_level.rb +6 -0
- metadata +6 -2
data/Gemfile
CHANGED
data/TODO
CHANGED
data/bin/wasserstand
CHANGED
@@ -6,12 +6,27 @@ Bundler.require
|
|
6
6
|
require 'wasserstand'
|
7
7
|
require 'optparse'
|
8
8
|
|
9
|
-
|
9
|
+
options = {}
|
10
|
+
|
11
|
+
params = OptionParser.new do |opts|
|
10
12
|
opts.banner = <<HERE
|
11
|
-
|
13
|
+
Shows water level information as provided by PegelOnline.
|
12
14
|
|
13
15
|
Usage:
|
14
|
-
#{File.basename($0)} [options] [WATERWAY | LEVEL]
|
16
|
+
#{File.basename($0)} [options] [WATERWAY | LEVEL | WATERWAY LEVEL]
|
17
|
+
|
18
|
+
Examples:
|
19
|
+
# all waterways
|
20
|
+
#{File.basename($0)}
|
21
|
+
|
22
|
+
# single waterway
|
23
|
+
#{File.basename($0)} Rhein
|
24
|
+
|
25
|
+
# single level
|
26
|
+
#{File.basename($0)} Koblenz
|
27
|
+
|
28
|
+
# single level that happens to have the same name as a waterway
|
29
|
+
#{File.basename($0)} Ilm Ilmenau
|
15
30
|
|
16
31
|
Author:
|
17
32
|
Nicolas E. Rabenau nerab@gmx.at
|
@@ -33,34 +48,25 @@ HERE
|
|
33
48
|
end
|
34
49
|
|
35
50
|
opts.on("-u", "--url URL", "Use URL to fetch levels") do |url|
|
36
|
-
|
37
|
-
Wasserstand.provider = Wasserstand::Provider::PegelOnline.new(url)
|
51
|
+
options[:url] = url
|
38
52
|
end
|
39
|
-
end.parse!
|
40
53
|
|
41
|
-
|
42
|
-
|
43
|
-
if query.nil?
|
44
|
-
Wasserstand::Waterway.all.each{|ww| puts "#{ww.name} (#{ww.levels.size} levels)"}
|
45
|
-
else
|
46
|
-
begin
|
47
|
-
waterway = Wasserstand::Waterway[query]
|
48
|
-
|
49
|
-
if waterway
|
50
|
-
puts "#{waterway.name} has #{waterway.levels.size} levels:"
|
51
|
-
waterway.levels.sort_by{|name, level| level.km}.each{|name, level| puts "#{name} (km #{level.km}): #{level.measurements.last}"}
|
52
|
-
else
|
53
|
-
level = Wasserstand::Level[query]
|
54
|
-
|
55
|
-
if level.nil?
|
56
|
-
STDERR.puts "#{File.basename($0)}: No waterway nor level found that matches '#{query}."
|
57
|
-
else
|
58
|
-
puts "Level #{level.name} (#{level.waterway}, km #{level.km}):"
|
59
|
-
level.measurements.each{|measurement| puts measurement}
|
60
|
-
end
|
61
|
-
end
|
62
|
-
rescue
|
63
|
-
STDERR.puts $!
|
64
|
-
$!.backtrace.each{|msg| Wasserstand.logger.debug(msg)}
|
54
|
+
opts.on("-c", "--cache [URL]", "Use memcached at URL instead of heap. URL defaults to 127.0.0.1:11211.") do |url|
|
55
|
+
options[:cache] = url || '127.0.0.1:11211'
|
65
56
|
end
|
57
|
+
end.parse!
|
58
|
+
|
59
|
+
Wasserstand.provider = Wasserstand::Provider::PegelOnline.new(options[:url])
|
60
|
+
|
61
|
+
if options[:cache]
|
62
|
+
Bundler.require(:default, :dalli)
|
63
|
+
Wasserstand.logger.info("Using memcached at #{options[:cache]}")
|
64
|
+
Wasserstand.provider.cache = Dalli::Client.new(options[:cache], :expires_in => 60 * 60)
|
65
|
+
end
|
66
|
+
|
67
|
+
begin
|
68
|
+
Wasserstand::Commandline.get(params)
|
69
|
+
rescue
|
70
|
+
STDERR.puts $!
|
71
|
+
$!.backtrace.each{|msg| Wasserstand.logger.debug(msg)}
|
66
72
|
end
|
data/lib/wasserstand.rb
CHANGED
@@ -13,10 +13,16 @@ module Wasserstand
|
|
13
13
|
AmbigousNameError = Class.new(StandardError)
|
14
14
|
|
15
15
|
class << self
|
16
|
-
attr_writer :provider
|
17
|
-
|
18
16
|
def provider
|
19
|
-
@provider
|
17
|
+
if @provider.nil?
|
18
|
+
self.provider = Provider::PegelOnline.new # go through attribute writer in order to log
|
19
|
+
end
|
20
|
+
@provider
|
21
|
+
end
|
22
|
+
|
23
|
+
def provider=(p)
|
24
|
+
Wasserstand.logger.info "Using provider #{p}"
|
25
|
+
@provider = p
|
20
26
|
end
|
21
27
|
|
22
28
|
def logger
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Wasserstand
|
2
|
+
module Commandline
|
3
|
+
def self.get(params = [])
|
4
|
+
case params.size
|
5
|
+
when 0
|
6
|
+
present_waterway(Wasserstand::Waterway.all)
|
7
|
+
when 1 # "Elbe" or "Dresden"
|
8
|
+
waterway = Wasserstand::Waterway[params.first]
|
9
|
+
|
10
|
+
if waterway
|
11
|
+
present_waterway(waterway)
|
12
|
+
else
|
13
|
+
# not a waterway, treat it as level
|
14
|
+
present_level(Wasserstand::Level[params.first])
|
15
|
+
end
|
16
|
+
when 2 # "Elbe Dresden" or "Ilm Ilmenau"
|
17
|
+
if waterway = Wasserstand::Waterway[params.first]
|
18
|
+
present_level(waterway.levels[params.last])
|
19
|
+
else
|
20
|
+
STDERR.puts "No matching waterway found."
|
21
|
+
end
|
22
|
+
else
|
23
|
+
raise "Unexpected number of parameters."
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def self.present_waterway(waterway, dig = true)
|
30
|
+
if waterway.nil?
|
31
|
+
STDERR.puts "No matching waterway found."
|
32
|
+
else
|
33
|
+
if waterway.respond_to?(:each)
|
34
|
+
STDERR.puts "The following waterways are available:"
|
35
|
+
waterway.each{|w| present_waterway(w, false)}
|
36
|
+
else
|
37
|
+
if !dig
|
38
|
+
puts waterway.name
|
39
|
+
else
|
40
|
+
STDERR.puts "#{waterway.name} has #{waterway.levels.size} levels:"
|
41
|
+
present_level(waterway.levels.values)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.present_level(level, dig = true)
|
48
|
+
if level.nil?
|
49
|
+
STDERR.puts "No match found."
|
50
|
+
else
|
51
|
+
if level.respond_to?(:each)
|
52
|
+
level.each{|l| present_level(l, false)}
|
53
|
+
else
|
54
|
+
if !dig
|
55
|
+
puts level.name
|
56
|
+
else
|
57
|
+
STDERR.puts "#{level.name} (#{level.waterway}, km #{level.km}):"
|
58
|
+
present_measurement(level.measurements)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.present_measurement(measurement)
|
65
|
+
if measurement.nil?
|
66
|
+
STDERR.puts "No measurement found."
|
67
|
+
else
|
68
|
+
if measurement.respond_to?(:each)
|
69
|
+
measurement.each{|l| present_measurement(l)}
|
70
|
+
else
|
71
|
+
puts measurement
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/wasserstand/finders.rb
CHANGED
@@ -1,24 +1,22 @@
|
|
1
1
|
module Wasserstand
|
2
2
|
module Provider
|
3
3
|
class PegelOnline
|
4
|
-
attr_writer :cache
|
5
|
-
|
6
4
|
include Enumerable
|
7
5
|
extend Forwardable
|
8
6
|
def_delegator :all, :each
|
9
7
|
|
10
|
-
def initialize(url =
|
11
|
-
@url = url
|
12
|
-
@names = []
|
8
|
+
def initialize(url = nil)
|
9
|
+
@url = url || 'http://www.pegelonline.wsv.de/svgz/pegelstaende_neu.xml'
|
13
10
|
end
|
14
11
|
|
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
|
12
|
+
# 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. If that one is gone, it is gone as a whole and everything will need to be refreshed.
|
16
13
|
def waterways
|
17
|
-
|
18
|
-
|
14
|
+
names = cache.get(KEY_NAMES) || replenish
|
15
|
+
|
16
|
+
names.map do |name|
|
19
17
|
ww = cache.get(name) # no fetch with block in Dalli, so we cannot use it here either ...
|
20
18
|
|
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
|
19
|
+
# 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 names.
|
22
20
|
if ww.nil?
|
23
21
|
replenish
|
24
22
|
ww = cache.get(name)
|
@@ -34,23 +32,40 @@ module Wasserstand
|
|
34
32
|
end
|
35
33
|
|
36
34
|
def cache
|
37
|
-
@cache
|
35
|
+
if @cache.nil?
|
36
|
+
self.cache = HeapCache.new
|
37
|
+
end
|
38
|
+
@cache
|
39
|
+
end
|
40
|
+
|
41
|
+
def cache=(c)
|
42
|
+
Wasserstand.logger.info "Using cache #{c}"
|
43
|
+
@cache = c
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_s
|
47
|
+
"#<#{self.class.name}:#{@url}>"
|
38
48
|
end
|
39
49
|
|
40
50
|
private
|
41
51
|
|
52
|
+
KEY_NAMES = "#{self.name}#names"
|
53
|
+
|
42
54
|
#
|
43
55
|
# Replenish the cache and names index
|
44
56
|
#
|
45
57
|
def replenish
|
46
|
-
@names.clear
|
47
58
|
Wasserstand.logger.info "Fetching #{@url}"
|
48
59
|
doc = Nokogiri::HTML(open(@url).read, nil, 'ISO-8859-1')
|
60
|
+
|
61
|
+
names = []
|
49
62
|
doc.xpath("//data/table/gewaesser").each do |node|
|
50
63
|
ww = WaterwayMapper.map(node)
|
51
|
-
@names << ww.name
|
52
64
|
cache.set(ww.name, ww)
|
65
|
+
names << ww.name
|
53
66
|
end
|
67
|
+
cache.set(KEY_NAMES, names)
|
68
|
+
names
|
54
69
|
end
|
55
70
|
end
|
56
71
|
end
|
data/lib/wasserstand/version.rb
CHANGED
@@ -0,0 +1,76 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'helper'
|
3
|
+
require 'open3'
|
4
|
+
|
5
|
+
#
|
6
|
+
# End-to-end test
|
7
|
+
#
|
8
|
+
class TestApp < MiniTest::Unit::TestCase
|
9
|
+
APP_SCRIPT = 'ruby bin/wasserstand'
|
10
|
+
DEFAULT_OPTIONS = {
|
11
|
+
'url' => File.join(File.dirname(__FILE__), '..', 'fixtures', 'pegelstaende_neu.xml')
|
12
|
+
}
|
13
|
+
|
14
|
+
def test_all_waterways
|
15
|
+
stdout, stderr = execute
|
16
|
+
assert_equal(77, stdout.size)
|
17
|
+
assert_equal(["ILM\n", "ILMENAU\n", "ITTER ZUR DIEMEL\n", "ITTER ZUR EDER\n"], stdout.select{|name| name =~ /^I/i})
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_waterway
|
21
|
+
stdout, stderr = execute('Diemel')
|
22
|
+
assert_equal(3, stdout.size)
|
23
|
+
assert_equal(["DIEMELTALSPERRE\n", "HELMINGHAUSEN\n", "WILHELMSBRÜCKE\n"], stdout)
|
24
|
+
assert_equal(["DIEMEL has 3 levels:\n"], stderr)
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_unknown_waterway
|
28
|
+
stdout, stderr = execute("Donaukanal")
|
29
|
+
assert_equal(0, stdout.size)
|
30
|
+
assert_equal(["No match found.\n"], stderr)
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_level
|
34
|
+
stdout, stderr = execute(['Cochem'])
|
35
|
+
assert_equal(["2012-09-13 18:15:00 +0000: 221.0 cm, Trend fallend\n"], stdout)
|
36
|
+
assert_equal(["COCHEM (MOSEL, km 51.6):\n"], stderr)
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_level_with_waterway
|
40
|
+
stdout, stderr = execute(['Ems', 'Knock'])
|
41
|
+
assert_equal(["2012-09-13 20:31:00 +0000: 626.6 cm, Trend fallend\n"], stdout)
|
42
|
+
assert_equal(["KNOCK (EMS, km 50.848):\n"], stderr)
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_unknown_level
|
46
|
+
stdout, stderr = execute("Altstadt")
|
47
|
+
assert_equal(0, stdout.size)
|
48
|
+
assert_equal(["No match found.\n"], stderr)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def execute(params = [], options = {})
|
54
|
+
line = []
|
55
|
+
line << "TZ='UTC'"
|
56
|
+
line << APP_SCRIPT
|
57
|
+
line.concat(Array(params))
|
58
|
+
line << DEFAULT_OPTIONS.merge(options).map do |k,v|
|
59
|
+
if v.nil? || v.to_s.empty?
|
60
|
+
"#{k}"
|
61
|
+
else
|
62
|
+
"--#{k}=#{v}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
out = ''
|
67
|
+
err = ''
|
68
|
+
|
69
|
+
Open3.popen3(line.join(' ')) do |stdin, stdout, stderr|
|
70
|
+
out << stdout.read
|
71
|
+
err << stderr.read
|
72
|
+
end
|
73
|
+
|
74
|
+
return [out.lines.to_a, err.lines.to_a]
|
75
|
+
end
|
76
|
+
end
|
data/test/unit/test_level.rb
CHANGED
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.11
|
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-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: require_all
|
@@ -190,11 +190,13 @@ files:
|
|
190
190
|
- TODO
|
191
191
|
- bin/wasserstand
|
192
192
|
- lib/wasserstand.rb
|
193
|
+
- lib/wasserstand/commandline.rb
|
193
194
|
- lib/wasserstand/finders.rb
|
194
195
|
- lib/wasserstand/hash_clod.rb
|
195
196
|
- lib/wasserstand/heap_cache.rb
|
196
197
|
- lib/wasserstand/level.rb
|
197
198
|
- lib/wasserstand/measurement.rb
|
199
|
+
- lib/wasserstand/null_cache.rb
|
198
200
|
- lib/wasserstand/provider/level_mapper.rb
|
199
201
|
- lib/wasserstand/provider/pegel_online.rb
|
200
202
|
- lib/wasserstand/provider/waterway_mapper.rb
|
@@ -203,6 +205,7 @@ files:
|
|
203
205
|
- lib/wasserstand/waterway.rb
|
204
206
|
- test/fixtures/pegelstaende_neu.xml
|
205
207
|
- test/helper.rb
|
208
|
+
- test/unit/test_app.rb
|
206
209
|
- test/unit/test_level.rb
|
207
210
|
- test/unit/test_measurement.rb
|
208
211
|
- test/unit/test_trend.rb
|
@@ -235,6 +238,7 @@ summary: Wasserstand auf deutschen Flüssen
|
|
235
238
|
test_files:
|
236
239
|
- test/fixtures/pegelstaende_neu.xml
|
237
240
|
- test/helper.rb
|
241
|
+
- test/unit/test_app.rb
|
238
242
|
- test/unit/test_level.rb
|
239
243
|
- test/unit/test_measurement.rb
|
240
244
|
- test/unit/test_trend.rb
|