net-ping 2.1.0-universal-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGES +28 -0
- data/CHANGES.out_of_date +354 -0
- data/CLAUDE.md +89 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +53 -0
- data/MANIFEST +25 -0
- data/README.md +65 -0
- data/Rakefile +183 -0
- data/doc/ping.txt +246 -0
- data/docs/superpowers/plans/2026-07-25-platform-specific-gems.md +317 -0
- data/docs/superpowers/specs/2026-07-25-platform-specific-gems-design.md +71 -0
- data/examples/example_pingexternal.rb +16 -0
- data/examples/example_pinghttp.rb +22 -0
- data/examples/example_pingtcp.rb +16 -0
- data/examples/example_pingudp.rb +12 -0
- data/lib/net/ping/external.rb +185 -0
- data/lib/net/ping/http.rb +189 -0
- data/lib/net/ping/icmp.rb +188 -0
- data/lib/net/ping/ping.rb +99 -0
- data/lib/net/ping/tcp.rb +110 -0
- data/lib/net/ping/udp.rb +117 -0
- data/lib/net/ping/version.rb +6 -0
- data/lib/net/ping/wmi.rb +118 -0
- data/lib/net/ping.rb +24 -0
- data/net-ping-universal-linux.gemspec +4 -0
- data/net-ping-universal-mingw-ucrt.gemspec +7 -0
- data/net-ping.gemspec +36 -0
- data/test/test_net_ping.rb +44 -0
- data/test/test_net_ping_external.rb +232 -0
- data/test/test_net_ping_gem_packaging.rb +333 -0
- data/test/test_net_ping_http.rb +264 -0
- data/test/test_net_ping_icmp.rb +199 -0
- data/test/test_net_ping_tcp.rb +106 -0
- data/test/test_net_ping_udp.rb +117 -0
- data/test/test_net_ping_wmi.rb +81 -0
- metadata +159 -0
data/Rakefile
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
require 'rake'
|
|
2
|
+
require 'rake/clean'
|
|
3
|
+
require 'rake/testtask'
|
|
4
|
+
include Object.const_defined?(:RbConfig) ? RbConfig : Config
|
|
5
|
+
|
|
6
|
+
CLEAN.include("**/*.gem", "**/*.rbc")
|
|
7
|
+
|
|
8
|
+
GEMSPEC_FILES = %w[
|
|
9
|
+
net-ping.gemspec
|
|
10
|
+
net-ping-universal-linux.gemspec
|
|
11
|
+
net-ping-universal-mingw-ucrt.gemspec
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
EXPECTED_GEM_METADATA = {
|
|
15
|
+
'net-ping.gemspec' => ['ruby', {}],
|
|
16
|
+
'net-ping-universal-linux.gemspec' => ['universal-linux', {'cap2' => '>= 0.2.2'}],
|
|
17
|
+
'net-ping-universal-mingw-ucrt.gemspec' => [
|
|
18
|
+
'universal-mingw-ucrt',
|
|
19
|
+
{'win32-security' => '>= 0.2.0', 'win32ole' => '>= 1.8.8'}
|
|
20
|
+
]
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
def load_gem_specifications
|
|
24
|
+
GEMSPEC_FILES.map do |path|
|
|
25
|
+
spec = Gem::Specification.load(path)
|
|
26
|
+
abort("Unable to load #{path}") unless spec
|
|
27
|
+
spec
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def with_unbundled_env
|
|
32
|
+
if defined?(Bundler) && Bundler.respond_to?(:with_unbundled_env)
|
|
33
|
+
Bundler.with_unbundled_env { yield }
|
|
34
|
+
elsif defined?(Bundler) && Bundler.respond_to?(:with_clean_env)
|
|
35
|
+
Bundler.with_clean_env { yield }
|
|
36
|
+
else
|
|
37
|
+
yield
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def platform_round_trips?(platform_string)
|
|
42
|
+
Gem::Platform.new(platform_string).to_s == platform_string
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def platform_installable?(spec)
|
|
46
|
+
if Gem::Platform.respond_to?(:installable?)
|
|
47
|
+
Gem::Platform.installable?(spec)
|
|
48
|
+
else
|
|
49
|
+
Gem::Platform.match(spec.platform)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def select_install_specification(specifications = load_gem_specifications)
|
|
54
|
+
specific = specifications.find do |spec|
|
|
55
|
+
spec.platform != Gem::Platform::RUBY && platform_installable?(spec)
|
|
56
|
+
end
|
|
57
|
+
spec = specific || specifications.find { |item| item.platform == Gem::Platform::RUBY }
|
|
58
|
+
abort('Unable to find a Ruby fallback gem specification') unless spec
|
|
59
|
+
spec
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
namespace 'gem' do
|
|
63
|
+
desc 'Create the net-ping gem artifacts (ruby, universal-linux, universal-mingw-ucrt)'
|
|
64
|
+
task :create => [:clean] do
|
|
65
|
+
specifications = load_gem_specifications
|
|
66
|
+
if Gem::VERSION.to_f < 2.0
|
|
67
|
+
specifications.each do |spec|
|
|
68
|
+
Gem::Builder.new(spec).build
|
|
69
|
+
end
|
|
70
|
+
else
|
|
71
|
+
require 'rubygems/package'
|
|
72
|
+
specifications.each do |spec|
|
|
73
|
+
Gem::Package.build(spec)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
desc 'Validate the net-ping gem artifacts'
|
|
79
|
+
task :check do
|
|
80
|
+
require 'rubygems/package'
|
|
81
|
+
|
|
82
|
+
EXPECTED_GEM_METADATA.each do |path, expectation|
|
|
83
|
+
expected_platform, expected_dependencies = expectation
|
|
84
|
+
artifact = Gem::Specification.load(path).file_name
|
|
85
|
+
packaged_spec = Gem::Package.new(artifact).spec
|
|
86
|
+
actual_dependencies = packaged_spec.runtime_dependencies.each_with_object({}) do |dependency, memo|
|
|
87
|
+
memo[dependency.name] = dependency.requirement.to_s
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
if platform_round_trips?(expected_platform) && packaged_spec.platform.to_s != expected_platform
|
|
91
|
+
abort("#{artifact}: expected platform #{expected_platform.inspect}, got #{packaged_spec.platform.to_s.inspect}")
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
if actual_dependencies != expected_dependencies
|
|
95
|
+
abort("#{artifact}: expected dependencies #{expected_dependencies.inspect}, got #{actual_dependencies.inspect}")
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
desc 'Install the net-ping gem'
|
|
101
|
+
task :install => [:create] do
|
|
102
|
+
spec = select_install_specification
|
|
103
|
+
|
|
104
|
+
install_command = if RUBY_PLATFORM == 'java'
|
|
105
|
+
['jruby', '-S', 'gem', 'install', '-l', spec.file_name]
|
|
106
|
+
else
|
|
107
|
+
['gem', 'install', '-l', spec.file_name]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
with_unbundled_env do
|
|
111
|
+
sh(*install_command)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
namespace 'example' do
|
|
117
|
+
desc 'Run the external ping example program'
|
|
118
|
+
task :external do
|
|
119
|
+
ruby '-Ilib examples/example_pingexternal.rb'
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
desc 'Run the http ping example program'
|
|
123
|
+
task :http do
|
|
124
|
+
ruby '-Ilib examples/example_pinghttp.rb'
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
desc 'Run the tcp ping example program'
|
|
128
|
+
task :tcp do
|
|
129
|
+
ruby '-Ilib examples/example_pingtcp.rb'
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
desc 'Run the udp ping example program'
|
|
133
|
+
task :udp do
|
|
134
|
+
ruby '-Ilib examples/example_pingudp.rb'
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
Rake::TestTask.new do |t|
|
|
139
|
+
t.libs << 'test'
|
|
140
|
+
t.warning = true
|
|
141
|
+
t.verbose = true
|
|
142
|
+
t.test_files = FileList['test/test_net_ping.rb']
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
namespace 'test' do
|
|
146
|
+
Rake::TestTask.new('external') do |t|
|
|
147
|
+
t.warning = true
|
|
148
|
+
t.verbose = true
|
|
149
|
+
t.test_files = FileList['test/test_net_ping_external.rb']
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
Rake::TestTask.new('http') do |t|
|
|
153
|
+
t.warning = true
|
|
154
|
+
t.verbose = true
|
|
155
|
+
t.test_files = FileList['test/test_net_ping_http.rb']
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
Rake::TestTask.new('icmp') do |t|
|
|
159
|
+
t.warning = true
|
|
160
|
+
t.verbose = true
|
|
161
|
+
t.test_files = FileList['test/test_net_ping_icmp.rb']
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
Rake::TestTask.new('tcp') do |t|
|
|
165
|
+
t.warning = true
|
|
166
|
+
t.verbose = true
|
|
167
|
+
t.test_files = FileList['test/test_net_ping_tcp.rb']
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
Rake::TestTask.new('udp') do |t|
|
|
171
|
+
t.warning = true
|
|
172
|
+
t.verbose = true
|
|
173
|
+
t.test_files = FileList['test/test_net_ping_udp.rb']
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
Rake::TestTask.new('wmi') do |t|
|
|
177
|
+
t.warning = true
|
|
178
|
+
t.verbose = true
|
|
179
|
+
t.test_files = FileList['test/test_net_ping_wmi.rb']
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
task :default => :test
|
data/doc/ping.txt
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
= Description
|
|
2
|
+
A simple Ruby interface to the 'ping' command.
|
|
3
|
+
|
|
4
|
+
= Synopsis
|
|
5
|
+
require 'net/ping'
|
|
6
|
+
include Net
|
|
7
|
+
|
|
8
|
+
Ping::TCP.service_check = true
|
|
9
|
+
|
|
10
|
+
pt = Net::Ping::TCP.new(host)
|
|
11
|
+
pu = Net::Ping::UDP.new(host)
|
|
12
|
+
pe = Net::Ping::External.new(host)
|
|
13
|
+
ph = Net::Ping::HTTP.new(uri)
|
|
14
|
+
|
|
15
|
+
if pt.ping
|
|
16
|
+
puts "TCP ping successful"
|
|
17
|
+
else
|
|
18
|
+
puts "TCP ping unsuccessful: " + pt.exception
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
if pu.ping
|
|
22
|
+
puts "UDP ping successful"
|
|
23
|
+
else
|
|
24
|
+
puts "UDP ping unsuccessful: " + pu.exception
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
if pe.ping
|
|
28
|
+
puts "External ping successful"
|
|
29
|
+
else
|
|
30
|
+
puts "External ping unsuccessful: " + pe.exception
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
if ph.ping?
|
|
34
|
+
puts "HTTP ping successful"
|
|
35
|
+
else
|
|
36
|
+
puts "HTTP ping unsuccessful: " + ph.exception
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
= Ping Classes
|
|
40
|
+
* Ping::TCP
|
|
41
|
+
* Ping::UDP
|
|
42
|
+
* Ping::External
|
|
43
|
+
* Ping::HTTP
|
|
44
|
+
* Ping::ICMP
|
|
45
|
+
* Ping::WMI
|
|
46
|
+
* Ping::LDAP
|
|
47
|
+
|
|
48
|
+
All Ping classes are children of the Ping parent class (which should
|
|
49
|
+
never be instantiated directly).
|
|
50
|
+
|
|
51
|
+
The Ping::ICMP class requires root/administrative privileges.
|
|
52
|
+
|
|
53
|
+
The Ping::WMI class only works on MS Windows.
|
|
54
|
+
|
|
55
|
+
== Net::Ping
|
|
56
|
+
Net::Ping.new(host=nil, port=7, timeout=5)
|
|
57
|
+
Creates and returns a new Ping object. If the host is not specified
|
|
58
|
+
in the constructor then it must be specified in the ping method.
|
|
59
|
+
|
|
60
|
+
== Net::Ping::TCP
|
|
61
|
+
Ping::TCP.service_check
|
|
62
|
+
Returns the setting for how ECONNREFUSED is handled. By default, this is
|
|
63
|
+
set to false, i.e. an ECONNREFUSED error is considered a failed ping.
|
|
64
|
+
|
|
65
|
+
Ping::TCP.service_check=(bool)
|
|
66
|
+
Sets the behavior for how ECONNREFUSED is handled. By default, this is
|
|
67
|
+
set to false, i.e. an ECONNREFUSED error is considered a failed ping.
|
|
68
|
+
|
|
69
|
+
Ping::TCP#ping(host=nil)
|
|
70
|
+
Attempts to open a connection using TCPSocket with a +host+ specified
|
|
71
|
+
either here or in the constructor. A successful open means the ping was
|
|
72
|
+
successful and true is returned. Otherwise, false is returned.
|
|
73
|
+
|
|
74
|
+
== Net::Ping::UDP
|
|
75
|
+
Ping::UDP#ping
|
|
76
|
+
Attempts to open a connection using UDPSocket and sends the value of
|
|
77
|
+
Ping::UDP#data as a string across the socket. If the return string matches,
|
|
78
|
+
then the ping was successful and true is returned. Otherwise, false is
|
|
79
|
+
returned.
|
|
80
|
+
|
|
81
|
+
Ping::UDP#data
|
|
82
|
+
Returns the string that is sent across the UDP socket.
|
|
83
|
+
|
|
84
|
+
Ping::UDP#data=(string)
|
|
85
|
+
Sets the string that is sent across the UDP socket. The default is "ping".
|
|
86
|
+
Note that the +string+ cannot be larger than MAX_DATA (64 characters).
|
|
87
|
+
|
|
88
|
+
== Net::Ping::External
|
|
89
|
+
Ping::External#ping
|
|
90
|
+
Uses the 'open3' module and calls your system's local 'ping' command with
|
|
91
|
+
various options, depending on platform. If nothing is sent to stderr, the
|
|
92
|
+
ping was successful and true is returned. Otherwise, false is returned.
|
|
93
|
+
|
|
94
|
+
The MS Windows platform requires the 'win32-open3' package.
|
|
95
|
+
|
|
96
|
+
== Ping::HTTP
|
|
97
|
+
Ping::HTTP.new(uri=nil, port=80, timeout=5)
|
|
98
|
+
Identical to Net::Ping.new except that, instead of a host, the first
|
|
99
|
+
argument is a URI.
|
|
100
|
+
|
|
101
|
+
Ping::HTTP#ping
|
|
102
|
+
Checks for a response against +uri+. As long as kind of Net::HTTPSuccess
|
|
103
|
+
response is returned, the ping is successful and true is returned.
|
|
104
|
+
Otherwise, false is returned and Ping::HTTP#exception is set to the error
|
|
105
|
+
message.
|
|
106
|
+
|
|
107
|
+
Note that redirects are automatically followed unless the
|
|
108
|
+
Ping::HTTP#follow_redirects method is set to false.
|
|
109
|
+
|
|
110
|
+
Ping::HTTP#follow_redirect
|
|
111
|
+
Indicates whether or not a redirect should be followed in a ping attempt.
|
|
112
|
+
By default this is set to true.
|
|
113
|
+
|
|
114
|
+
Ping::HTTP#follow_redirect=(bool)
|
|
115
|
+
Sets whether or not a redirect should be followed in a ping attempt. If
|
|
116
|
+
set to false, then any redirect is considered a failed ping.
|
|
117
|
+
|
|
118
|
+
Ping::HTTP#uri
|
|
119
|
+
An alias for Ping::HTTP#host.
|
|
120
|
+
|
|
121
|
+
Ping::HTTP#uri=(uri)
|
|
122
|
+
An alias for Ping::HTTP#host=.
|
|
123
|
+
|
|
124
|
+
== Ping::ICMP
|
|
125
|
+
Ping::ICMP#duration
|
|
126
|
+
The time it took to ping the host. Not a precise value but a good estimate.
|
|
127
|
+
|
|
128
|
+
== Ping::WMI
|
|
129
|
+
Ping::WMI#ping(host, options={})
|
|
130
|
+
Unlike other Ping classes, this method returns a PingStatus struct that
|
|
131
|
+
contains various bits of information about the ping itself. The PingStatus
|
|
132
|
+
struct is a wrapper for the Win32_PingStatus WMI class.
|
|
133
|
+
|
|
134
|
+
In addition, you can pass options that will be interpreted as WQL parameters.
|
|
135
|
+
|
|
136
|
+
Ping::WMI#ping?(host, options={})
|
|
137
|
+
Returns whether or not the ping succeeded.
|
|
138
|
+
|
|
139
|
+
= Common Instance Methods
|
|
140
|
+
Ping#exception
|
|
141
|
+
Returns the error string that was set if a ping call failed. If an exception
|
|
142
|
+
is raised, it is caught and stored in this attribute. It is not raised in
|
|
143
|
+
your code.
|
|
144
|
+
|
|
145
|
+
This should be nil if the ping succeeded.
|
|
146
|
+
|
|
147
|
+
Ping#host
|
|
148
|
+
Returns the host name that ping attempts will ping against.
|
|
149
|
+
|
|
150
|
+
Ping#host=(hostname)
|
|
151
|
+
Sets the host name that ping attempts will ping against.
|
|
152
|
+
|
|
153
|
+
Ping#port
|
|
154
|
+
Returns the port number that ping attempts will use.
|
|
155
|
+
|
|
156
|
+
Ping#port=(port)
|
|
157
|
+
Set the port number to open socket connections on. The default is 7 (or
|
|
158
|
+
whatever your 'echo' port is set to). Note that you can also specify a
|
|
159
|
+
string, such as "http".
|
|
160
|
+
|
|
161
|
+
Ping#timeout
|
|
162
|
+
Returns the amount of time before the timeout module raises a TimeoutError
|
|
163
|
+
during connection attempts. The default is 5 seconds.
|
|
164
|
+
|
|
165
|
+
Ping#timeout=(time)
|
|
166
|
+
Sets the amount of time before the timeout module raises a TimeoutError.
|
|
167
|
+
during connection attempts.
|
|
168
|
+
|
|
169
|
+
Ping#warning
|
|
170
|
+
Returns a warning string that was returned during the ping attempt. This
|
|
171
|
+
typically occurs only in the Ping::External class, or the Ping::HTTP class
|
|
172
|
+
if a redirect occurred.
|
|
173
|
+
|
|
174
|
+
== Notes
|
|
175
|
+
If a host is down *IT IS CONSIDERED A FAILED PING*, and the 'no answer from
|
|
176
|
+
+host+' text is assigned to the 'exception' attribute. You may disagree with
|
|
177
|
+
this behavior, in which case you need merely check the exception attribute
|
|
178
|
+
against a regex as a simple workaround.
|
|
179
|
+
|
|
180
|
+
== Pre-emptive FAQ
|
|
181
|
+
Q: "Why don't you return exceptions if a connection fails?"
|
|
182
|
+
|
|
183
|
+
A: Because ping is only meant to return one of two things - success or
|
|
184
|
+
failure. It's very simple. If you want to find out *why* the ping
|
|
185
|
+
failed, you can check the 'exception' attribute.
|
|
186
|
+
|
|
187
|
+
Q: "I know the host is alive, but a TCP or UDP ping tells me otherwise. What
|
|
188
|
+
gives?"
|
|
189
|
+
|
|
190
|
+
A: It's possible that the echo port has been disabled on the remote
|
|
191
|
+
host for security reasons. Your best best is to specify a different port
|
|
192
|
+
or to use Ping::ICMP or Ping::External instead.
|
|
193
|
+
|
|
194
|
+
In the case of UDP pings, they are often actively refused. It may be
|
|
195
|
+
more pragmatic to set Ping::UDP.service_check = false.
|
|
196
|
+
|
|
197
|
+
Q: "Why does a TCP ping return false when I know it should return true?"
|
|
198
|
+
|
|
199
|
+
A: By default ECONNREFUSED errors will return a value of false. This is
|
|
200
|
+
contrary to what most other folks do for TCP pings. The problem with
|
|
201
|
+
their philosophy is that you can get false positives if a firewall blocks
|
|
202
|
+
the route to the host. The problem with my philosophy is that you can
|
|
203
|
+
get false negatives if there is no firewall (or it's not blocking the
|
|
204
|
+
route). Given the alternatives I chose the latter.
|
|
205
|
+
|
|
206
|
+
You can always change the default behavior by using the +service_check+
|
|
207
|
+
class method.
|
|
208
|
+
|
|
209
|
+
A similar situation is true for UDP pings.
|
|
210
|
+
|
|
211
|
+
Q: "Couldn't you use traceroute information to tell for sure?"
|
|
212
|
+
|
|
213
|
+
A: I could but I won't so don't bug me about it. It's far more effort than
|
|
214
|
+
it's worth. If you want something like that, please port the
|
|
215
|
+
Net::Traceroute Perl module by Daniel Hagerty.
|
|
216
|
+
|
|
217
|
+
= Known Bugs
|
|
218
|
+
You may see a test failure from the test_net_ping_tcp test case. You can
|
|
219
|
+
ignore these.
|
|
220
|
+
|
|
221
|
+
UDP pings may not work with older versions of Ruby 1.9.x.
|
|
222
|
+
|
|
223
|
+
Please report any bugs on the project page at
|
|
224
|
+
https://github.com/djberg96/net-ping
|
|
225
|
+
|
|
226
|
+
= Acknowledgements
|
|
227
|
+
The Ping::ICMP#ping method is based largely on the identical method from
|
|
228
|
+
the Net::Ping Perl module by Rob Brown. Much of the code was ported by
|
|
229
|
+
Jos Backus on ruby-talk.
|
|
230
|
+
|
|
231
|
+
= Future Plans
|
|
232
|
+
Add support for syn pings.
|
|
233
|
+
|
|
234
|
+
= License
|
|
235
|
+
Artistic 2.0
|
|
236
|
+
|
|
237
|
+
= Copyright
|
|
238
|
+
(C) 2003-2014 Daniel J. Berger, All Rights Reserved
|
|
239
|
+
|
|
240
|
+
= Warranty
|
|
241
|
+
This package is provided "as is" and without any express or
|
|
242
|
+
implied warranties, including, without limitation, the implied
|
|
243
|
+
warranties of merchantability and fitness for a particular purpose.
|
|
244
|
+
|
|
245
|
+
= Author
|
|
246
|
+
Daniel J. Berger
|