dock_driver 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/History.txt +6 -0
- data/Ideas.txt +0 -1
- data/Manifest.txt +1 -1
- data/README.txt +81 -24
- data/bin/dock_driver +43 -10
- data/data/example.dock_driver.yml +100 -33
- data/lib/dock_driver.rb +6 -2
- data/lib/dock_driver/data_rate_poll.rb +14 -19
- data/lib/dock_driver/dock.rb +108 -66
- data/lib/dock_driver/hashutils.rb +79 -0
- data/lib/dock_driver/poll.rb +31 -13
- data/spec/dock_driver/dock_spec.rb +12 -16
- data/spec/dock_driver/poll_spec.rb +22 -28
- data/spec/dock_driver_spec.rb +2 -2
- metadata +5 -5
- metadata.gz.sig +0 -0
- data/lib/dock_driver/laika_hashutils.rb +0 -74
data.tar.gz.sig
CHANGED
Binary file
|
data/History.txt
CHANGED
data/Ideas.txt
CHANGED
data/Manifest.txt
CHANGED
data/README.txt
CHANGED
@@ -16,28 +16,42 @@ started. It's located in this gem's data directory, which is usually:
|
|
16
16
|
=== Features:
|
17
17
|
|
18
18
|
* easy, flexible configuration
|
19
|
+
* sholud the configuration change, the dock will be automatically restarted
|
19
20
|
* field extraction with +scan_regex+ (no grep or pipes necessary)
|
21
|
+
* automatic running average of data rates
|
22
|
+
|
23
|
+
See 'Ideas' for more info on what's planned for the future.
|
20
24
|
|
21
25
|
=== Problems:
|
22
26
|
|
23
27
|
* undiscovered bugs
|
24
|
-
*
|
28
|
+
* more RSpec examples needed
|
25
29
|
* cleanup
|
26
30
|
|
27
31
|
== SYNOPSIS:
|
28
32
|
|
29
33
|
There really isn't an API or interface other than the YAML config file.
|
30
34
|
|
35
|
+
See the +--help+ option for more information on how to use the
|
36
|
+
+dock_config+ binary.
|
37
|
+
|
31
38
|
== REQUIREMENTS:
|
32
39
|
|
33
|
-
* This gem proudly has zero dependencies
|
40
|
+
* This gem proudly has zero dependencies.
|
34
41
|
|
35
42
|
== INSTALL:
|
36
43
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
44
|
+
sudo gem install dock_driver
|
45
|
+
|
46
|
+
After that you might want to copy +example.dock_driver.yml+ from the
|
47
|
+
gem's data directory to ~/.dock_driver.yml and edit it to taste. You can
|
48
|
+
test your config by running +dock_driver+ directly without arguments.
|
49
|
+
|
50
|
+
Finally, you'll probably want run dock_driver from your .xsession or
|
51
|
+
~/.i3/config file. See the documentation for either on how to do that.
|
52
|
+
|
53
|
+
You'll find a log file called ~/.dock_driver.log that can give you
|
54
|
+
more information should something go horribly wrong.
|
41
55
|
|
42
56
|
== DEVELOPERS:
|
43
57
|
|
@@ -50,25 +64,68 @@ and generate the RDoc.
|
|
50
64
|
|
51
65
|
== LICENSE:
|
52
66
|
|
53
|
-
(The
|
67
|
+
(The BSD 2-Clause License)
|
68
|
+
|
69
|
+
Copyright (c) 2012, Michael B. Hix
|
70
|
+
All rights reserved.
|
71
|
+
|
72
|
+
Redistribution and use in source and binary forms, with or without
|
73
|
+
modification, are permitted provided that the following conditions are
|
74
|
+
met:
|
75
|
+
|
76
|
+
* Redistributions of source code must retain the above copyright notice,
|
77
|
+
this list of conditions and the following disclaimer.
|
78
|
+
|
79
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
80
|
+
this list of conditions and the following disclaimer in the documentation
|
81
|
+
and/or other materials provided with the distribution.
|
82
|
+
|
83
|
+
* Neither the name of the author/s, nor the names of the project's
|
84
|
+
contributors may be used to endorse or promote products derived from this
|
85
|
+
software without specific prior written permission.
|
86
|
+
|
87
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
88
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
89
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
90
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
91
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
92
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
93
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
94
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
95
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
96
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
97
|
+
|
98
|
+
This software includes one or more mixins under the following license:
|
99
|
+
|
100
|
+
Copyright (c) 2008-2011, Michael Granger and Mahlon E. Smith
|
101
|
+
All rights reserved.
|
102
|
+
|
103
|
+
Redistribution and use in source and binary forms, with or without
|
104
|
+
modification, are permitted provided that the following conditions are met:
|
105
|
+
|
106
|
+
* Redistributions of source code must retain the above copyright notice,
|
107
|
+
this list of conditions and the following disclaimer.
|
108
|
+
|
109
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
110
|
+
this list of conditions and the following disclaimer in the documentation
|
111
|
+
and/or other materials provided with the distribution.
|
112
|
+
|
113
|
+
* Neither the name of the author/s, nor the names of the project's
|
114
|
+
contributors may be used to endorse or promote products derived from this
|
115
|
+
software without specific prior written permission.
|
54
116
|
|
55
|
-
|
117
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
118
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
119
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
120
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
121
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
122
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
123
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
124
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
125
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
126
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
56
127
|
|
57
|
-
|
58
|
-
a copy of this software and associated documentation files (the
|
59
|
-
'Software'), to deal in the Software without restriction, including
|
60
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
61
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
62
|
-
permit persons to whom the Software is furnished to do so, subject to
|
63
|
-
the following conditions:
|
128
|
+
=== Authors
|
64
129
|
|
65
|
-
|
66
|
-
in all copies or substantial portions of the Software.
|
130
|
+
* Michael Hix <mike@musl.org>
|
67
131
|
|
68
|
-
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
69
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
70
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
71
|
-
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
72
|
-
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
73
|
-
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
74
|
-
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/bin/dock_driver
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# vim: set nosta noet ts=4 sw=4:
|
3
|
+
# encoding: utf-8
|
4
|
+
#
|
2
5
|
|
3
6
|
require 'optparse'
|
4
7
|
require 'dock_driver/dock'
|
@@ -11,11 +14,31 @@ parser = OptionParser.new do |p|
|
|
11
14
|
p.on(
|
12
15
|
'-c',
|
13
16
|
'--conf FILE',
|
14
|
-
|
17
|
+
"Specify an alternate config file. (Default: #{DockDriver::Dock::DEFAULT_CONFIG_FILE})"
|
15
18
|
) do |file|
|
19
|
+
unless File.exists? file
|
20
|
+
$stderr.puts "That config file doesn't exist." % file
|
21
|
+
$stderr.puts
|
22
|
+
$stderr.puts p
|
23
|
+
exit
|
24
|
+
end
|
16
25
|
opts[:conf] = file
|
17
26
|
end
|
18
27
|
|
28
|
+
p.on(
|
29
|
+
'-l',
|
30
|
+
'--log FILE',
|
31
|
+
"Specify an alternate log file. (Default: #{DockDriver::Dock::DEFAULT_LOG_FILE})"
|
32
|
+
) do |log|
|
33
|
+
if File.exists? log
|
34
|
+
$stderr.puts "That log file exists, and I'm cowardly refusing to overwrite it." % log
|
35
|
+
$stderr.puts
|
36
|
+
$stderr.puts p
|
37
|
+
exit
|
38
|
+
end
|
39
|
+
opts[:log] = log
|
40
|
+
end
|
41
|
+
|
19
42
|
p.on(
|
20
43
|
'-d',
|
21
44
|
'--dock COMMAND',
|
@@ -28,29 +51,39 @@ parser = OptionParser.new do |p|
|
|
28
51
|
|
29
52
|
p.on( '-h', '--help', 'Print this help text.' ) do
|
30
53
|
cmd = File.basename( $0 )
|
31
|
-
puts "\n%s\n%s\n\nVersion: %s\n%s\n\n" % [
|
54
|
+
$stderr.puts "\n%s\n%s\n\nVersion: %s\n%s\n\n" % [
|
32
55
|
cmd,
|
33
56
|
'=' * cmd.length,
|
34
57
|
DockDriver::VERSION,
|
35
58
|
DockDriver::REVISION,
|
36
59
|
]
|
37
|
-
puts p
|
38
|
-
puts "\nAuthors:\n\t%s\n\n" % [
|
60
|
+
$stderr.puts p
|
61
|
+
$stderr.puts "\nAuthors:\n\t%s\n\n" % [
|
39
62
|
DockDriver::AUTHORS.join( '\n\t' ),
|
40
63
|
]
|
41
64
|
exit
|
42
65
|
end
|
43
66
|
|
44
|
-
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Handle invalid options.
|
70
|
+
#
|
71
|
+
begin
|
72
|
+
parser.parse!
|
73
|
+
rescue => e
|
74
|
+
$stderr.puts "\n%s\n\n%s\n" % [e, parser]
|
75
|
+
exit
|
76
|
+
end
|
45
77
|
|
46
|
-
puts "Options: %p" % opts if $DEBUG
|
78
|
+
$stderr.puts "Options: %p" % opts if $DEBUG
|
47
79
|
|
48
|
-
dock = DockDriver::Dock.new( opts[:conf], opts[:dock] )
|
80
|
+
dock = DockDriver::Dock.new( opts[:conf], opts[:log], opts[:dock] )
|
49
81
|
|
82
|
+
# Respond to ^C.
|
83
|
+
#
|
50
84
|
trap 'INT' do
|
51
|
-
dock.
|
52
|
-
puts "\nKilled!" if $DEBUG
|
85
|
+
dock.stop
|
53
86
|
end
|
54
87
|
|
55
|
-
dock.
|
88
|
+
dock.start
|
56
89
|
|
@@ -19,62 +19,129 @@ time_format:
|
|
19
19
|
# Processed by +ERB+. +Poll+ objects as defined by the 'commands' section are
|
20
20
|
# available. Since +Poll+ objects respond to to_s intelligently, it's usually
|
21
21
|
# sufficient to just specify the object name. Since they're also subclasses of
|
22
|
-
# +OpenStruct+, you can also
|
22
|
+
# +OpenStruct+, you can also use any attribute defined on the poll here too.
|
23
23
|
#
|
24
24
|
# See the dzen2 documentation.
|
25
25
|
#
|
26
26
|
template:
|
27
|
-
|
28
|
-
|
29
|
-
^fg(white)
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
27
|
+
<%= time %>
|
28
|
+
<%= load %>
|
29
|
+
^fg(white)eth0
|
30
|
+
<%= eth0_rx %>^fg(gray30) <%= eth0_tx %>^fg(gray30) <%= eth0_rx.unit %>/s
|
31
|
+
<%= cpu0_temp %>
|
32
|
+
<%= cpu1_temp %>
|
33
|
+
<%= gpu_temp %>
|
34
|
+
^fg(black)
|
34
35
|
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
36
|
+
# Provides a list of commands to poll.
|
37
|
+
#
|
38
|
+
# The key commands needs to be a hash of hashes. The example below shows
|
39
|
+
# some interesting examples. Here's the basic structure:
|
40
|
+
#
|
41
|
+
# commands:
|
42
|
+
# name:
|
43
|
+
# cmd: 'whoami'
|
44
|
+
# frob:
|
45
|
+
# cmd: 'byte_frobber'
|
46
|
+
# type: 'rate'
|
47
|
+
# scan_regex: 'bytes_frobbed: (\d+)'
|
48
|
+
# format: '%0.3f bits per second'
|
49
|
+
#
|
50
|
+
# You'd refer to the above commands in your template like so:
|
51
|
+
#
|
52
|
+
# User '<%= name %>' is frobbing <%= frob %>!
|
53
|
+
#
|
54
|
+
# Which would yield something like the following on your dock.
|
55
|
+
#
|
56
|
+
# User 'mike' is frobbing 0.321 bits per second!
|
57
|
+
#
|
58
|
+
# Standard options for commands are as follows:
|
59
|
+
#
|
60
|
+
#
|
61
|
+
# cmd:
|
62
|
+
#
|
63
|
+
# A required string that will get executed by a shell. If you're comfortable enough with YAML,
|
64
|
+
# this can be a ruby Proc too.
|
65
|
+
#
|
66
|
+
#
|
67
|
+
# scan_regex:
|
68
|
+
#
|
69
|
+
# This optional key provides a way to extract values from command
|
70
|
+
# output. The value should be a regular expression without forward
|
71
|
+
# slashes, and you'll probably want to put it in single quotes. The best
|
72
|
+
# way to use it is to define one capture group with parenthasees. If you
|
73
|
+
# define more than one capture group, they'll be joined up with spaces and
|
74
|
+
# returned as a single string.
|
75
|
+
#
|
76
|
+
#
|
77
|
+
# format:
|
78
|
+
#
|
79
|
+
# A string that allows you to wrap the output from your command in
|
80
|
+
# another string. You can prepend a label, append a unit, convert the
|
81
|
+
# data type in a limited fashion, or whatever you like.
|
82
|
+
#
|
83
|
+
# See: Kernel#format
|
84
|
+
#
|
85
|
+
#
|
86
|
+
# delay:
|
87
|
+
#
|
88
|
+
# How long to cache the command output for. If delay is set to 60, even
|
89
|
+
# if the bar is refreshed every second, the command will only be run every
|
90
|
+
# 60 seconds or so. Timing isn't guaranteed, but it's good enough to
|
91
|
+
# estimate data rates.
|
92
|
+
#
|
93
|
+
#
|
94
|
+
# type:
|
95
|
+
#
|
96
|
+
# This optional key allows you to specify the type of poll to use. If
|
97
|
+
# you don't specify a type, the default 'text' is used. It simply runs the
|
98
|
+
# command, applies any scan_regex or format specified, and returns the
|
99
|
+
# resulting text.
|
100
|
+
#
|
101
|
+
# If you specify 'rate', the command and scan_regex are expected
|
102
|
+
# to return a number. This number will be interpereted as a total in
|
103
|
+
# bytes. For example the number of bits that your network adapter has
|
104
|
+
# sent. A history of how that value changes over time is kept, and the
|
105
|
+
# output from this poll is the average of that history.
|
106
|
+
#
|
107
|
+
#
|
108
|
+
# history_length:
|
109
|
+
#
|
110
|
+
# This only applies to the 'rate' type. The history length is the number
|
111
|
+
# of samples to keep and allows you to control how long of a running
|
112
|
+
# average to keep. The larger this number is, the more the average will
|
113
|
+
# lag behind the actual rate of change.
|
114
|
+
#
|
53
115
|
#
|
54
116
|
commands:
|
55
|
-
|
56
|
-
cmd: '
|
57
|
-
|
117
|
+
load:
|
118
|
+
cmd: 'uptime'
|
119
|
+
scan_regex: '(\d+\.\d+)$'
|
120
|
+
format: '^fg(white)load ^fg(gray60)%s^fg(gray30)/15'
|
121
|
+
delay: 60
|
58
122
|
cpu0_temp:
|
59
123
|
cmd: 'sensors'
|
60
124
|
scan_regex: 'Core 0:\s+\+(\d+\.\d+)'
|
125
|
+
format: '^fg(white)cpu0 ^fg(gray60)%s^fg(gray30)°C'
|
61
126
|
delay: 30
|
62
127
|
cpu1_temp:
|
63
128
|
cmd: 'sensors'
|
64
129
|
scan_regex: 'Core 1:\s+\+(\d+\.\d+)'
|
130
|
+
format: '^fg(white)cpu1 ^fg(gray60)%s^fg(gray30)°C'
|
65
131
|
delay: 30
|
66
132
|
gpu_temp:
|
67
133
|
cmd: 'nvidia-smi -q -d TEMPERATURE'
|
68
134
|
scan_regex: '(\d+) C'
|
135
|
+
format: '^fg(white)gpu ^fg(gray60)%s^fg(gray30)°C'
|
69
136
|
delay: 30
|
70
137
|
eth0_rx:
|
71
138
|
cmd: 'ifconfig eth0'
|
72
139
|
scan_regex: 'RX bytes:(\d+)'
|
73
|
-
|
74
|
-
|
140
|
+
format: '^fg(#208020)%0.3f'
|
141
|
+
type: 'rate'
|
75
142
|
eth0_tx:
|
76
143
|
cmd: 'ifconfig eth0'
|
77
144
|
scan_regex: 'TX bytes:(\d+)'
|
78
|
-
|
79
|
-
|
145
|
+
format: '^fg(#802020)%0.3f'
|
146
|
+
type: 'rate'
|
80
147
|
|
data/lib/dock_driver.rb
CHANGED
@@ -1,13 +1,17 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# vim: set nosta noet ts=4 sw=4:
|
3
|
+
# encoding: utf-8
|
4
|
+
#
|
1
5
|
|
2
6
|
# A namespace for the project.
|
3
7
|
#
|
4
8
|
module DockDriver
|
5
9
|
|
6
10
|
# Version Constant
|
7
|
-
VERSION = '0.1.
|
11
|
+
VERSION = '0.1.1'
|
8
12
|
|
9
13
|
# Revision Constant
|
10
|
-
REVISION = %q$Revision:
|
14
|
+
REVISION = %q$Revision: 196ad604d0de $
|
11
15
|
|
12
16
|
# Behold the author(s) responsible for this mess.
|
13
17
|
AUTHORS = [ 'Mike Hix <mike@musl.org>' ]
|
@@ -1,11 +1,14 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# vim: set nosta noet ts=4 sw=4:
|
3
|
+
# encoding: utf-8
|
1
4
|
|
2
5
|
require 'ostruct'
|
3
6
|
|
4
7
|
require 'dock_driver' unless defined? DockDriver
|
5
8
|
require 'dock_driver/poll'
|
6
9
|
|
7
|
-
# A class to provide
|
8
|
-
# running average
|
10
|
+
# A class to provide periodic command execution, caching, and a
|
11
|
+
# running average of changes in the output of the command.
|
9
12
|
#
|
10
13
|
class DockDriver::DataRatePoll < DockDriver::Poll
|
11
14
|
|
@@ -14,7 +17,7 @@ class DockDriver::DataRatePoll < DockDriver::Poll
|
|
14
17
|
DEFAULTS = {
|
15
18
|
:unit => :mb,
|
16
19
|
:history_size => 2,
|
17
|
-
:
|
20
|
+
:format => '%0.3f',
|
18
21
|
}.freeze
|
19
22
|
|
20
23
|
# Create a new DataRatePoll.
|
@@ -27,26 +30,18 @@ class DockDriver::DataRatePoll < DockDriver::Poll
|
|
27
30
|
#
|
28
31
|
# === Options:
|
29
32
|
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
# +:cmd+ - A string or block to execute.
|
33
|
-
#
|
34
|
-
# +:scan_regex+ - A bare string to be interpreted as a regex for use with +scan+. Any
|
35
|
-
# capture group matches will be joined with single spaces.
|
33
|
+
# Everything accepted by from Poll, plus:
|
36
34
|
#
|
37
35
|
# +:history_size+ - How many samples to keep
|
38
36
|
#
|
39
37
|
# +:unit+ - The unit to use. See the methods and aliases available.
|
40
38
|
# (b, kb, mb, gb, tb, pb, eb, zb, yb, and full names: bits, kibibits, mebibits, ...)
|
41
39
|
#
|
42
|
-
# +:number_format+ - The format used by +to_s+ to turn a number into a string. See: +printf+
|
43
|
-
#
|
44
40
|
def initialize( opts = {} )
|
45
41
|
super DEFAULTS.merge( opts )
|
46
42
|
|
47
43
|
@last_sample = nil
|
48
|
-
@history = Array.new(
|
49
|
-
@average = 0
|
44
|
+
@history = Array.new( self.history_size ) { 0 }
|
50
45
|
end
|
51
46
|
|
52
47
|
# Runs the command if enough time has elapsed since the last
|
@@ -56,13 +51,12 @@ class DockDriver::DataRatePoll < DockDriver::Poll
|
|
56
51
|
def poll
|
57
52
|
return false unless super
|
58
53
|
|
59
|
-
@sample = @sample.
|
54
|
+
@sample = @sample.to_f
|
60
55
|
@last_sample ||= @sample
|
61
56
|
sample = 8 * ( @sample - @last_sample )
|
62
|
-
@history.shift if @history.length >=
|
57
|
+
@history.shift if @history.length >= self.history_size
|
63
58
|
@history.push sample
|
64
59
|
@last_sample = @sample
|
65
|
-
@average = @table[:number_format] % send( @table[:unit] )
|
66
60
|
|
67
61
|
true
|
68
62
|
end
|
@@ -130,11 +124,12 @@ class DockDriver::DataRatePoll < DockDriver::Poll
|
|
130
124
|
end
|
131
125
|
alias :yb :yobibits
|
132
126
|
|
133
|
-
# Returns
|
134
|
-
# option :number_format.
|
127
|
+
# Returns a string representing the data rate this poll tracks.
|
135
128
|
#
|
136
129
|
def to_s
|
137
|
-
|
130
|
+
value = self.unit ? send( self.unit ) : bits
|
131
|
+
return value.to_s if self.format.nil?
|
132
|
+
self.format % value
|
138
133
|
end
|
139
134
|
|
140
135
|
end
|
data/lib/dock_driver/dock.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# vim: set nosta noet ts=4 sw=4:
|
3
|
+
# encoding: utf-8
|
1
4
|
|
2
5
|
require 'erb'
|
3
6
|
require 'yaml'
|
4
7
|
require 'ostruct'
|
8
|
+
require 'logger'
|
5
9
|
|
6
10
|
require 'dock_driver' unless defined? DockDriver
|
7
|
-
require 'dock_driver/
|
11
|
+
require 'dock_driver/hashutils'
|
8
12
|
require 'dock_driver/poll'
|
9
13
|
require 'dock_driver/data_rate_poll'
|
10
14
|
|
@@ -13,15 +17,27 @@ require 'dock_driver/data_rate_poll'
|
|
13
17
|
#
|
14
18
|
class DockDriver::Dock < OpenStruct
|
15
19
|
|
16
|
-
include
|
20
|
+
include HashUtilities
|
21
|
+
|
22
|
+
# The *normal* place people keep user-specific configuration data for this program.
|
23
|
+
DEFAULT_CONFIG_FILE = '~/.dock_driver.yml'.freeze
|
24
|
+
|
25
|
+
# The usual place to put user-specific diagnostic information pertaining to this program.
|
26
|
+
DEFAULT_LOG_FILE = '~/.dock_driver.log'.freeze
|
17
27
|
|
18
28
|
# Default values to populate this OpenStruct with.
|
19
29
|
#
|
20
30
|
DEFAULTS = {
|
21
31
|
:polls => {},
|
22
32
|
:time_format => '%Y-%m-%d %H:%M:%S %Z',
|
23
|
-
|
24
|
-
|
33
|
+
}.freeze
|
34
|
+
|
35
|
+
# A map of poll Classes/types to kewords for config.
|
36
|
+
#
|
37
|
+
POLL_TYPES = {
|
38
|
+
nil => DockDriver::Poll,
|
39
|
+
'text' => DockDriver::Poll,
|
40
|
+
'rate' => DockDriver::DataRatePoll,
|
25
41
|
}.freeze
|
26
42
|
|
27
43
|
# Create and start a new Dock.
|
@@ -33,94 +49,120 @@ class DockDriver::Dock < OpenStruct
|
|
33
49
|
#
|
34
50
|
# +:dock_command+ - Override the dock command from the config file.
|
35
51
|
#
|
36
|
-
def initialize( config_file = nil, dock_command = nil )
|
52
|
+
def initialize( config_file = nil, log_file = nil, dock_command = nil )
|
37
53
|
super DEFAULTS
|
38
|
-
@worker = nil
|
39
|
-
@table[:config_file] = config_file if config_file
|
40
|
-
load_config
|
41
|
-
@table[:dock_command] = dock_command if dock_command
|
42
|
-
end
|
43
54
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
@
|
55
|
+
@log_file = File.expand_path( log_file || DEFAULT_LOG_FILE )
|
56
|
+
@log = Logger.new( @log_file )
|
57
|
+
@log.level = $DEBUG ? Logger::DEBUG : Logger::WARN
|
58
|
+
|
59
|
+
@config_file_mtime = nil
|
60
|
+
@config_file = File.expand_path( config_file || DEFAULT_CONFIG_FILE )
|
49
61
|
|
50
|
-
|
51
|
-
|
52
|
-
@
|
62
|
+
load_config
|
63
|
+
|
64
|
+
@running = false
|
65
|
+
if dock_command
|
66
|
+
self.dock_command = dock_command
|
67
|
+
end
|
53
68
|
end
|
54
69
|
|
55
|
-
# Start
|
70
|
+
# Start the dock. Repeatedly calls Dock#drive until it's time to stop. See Dock#stop.
|
56
71
|
#
|
57
|
-
def
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
pipe.puts self
|
63
|
-
pipe.flush
|
64
|
-
sleep 1
|
65
|
-
break if load_config
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
72
|
+
def start
|
73
|
+
return if @running
|
74
|
+
@running = true
|
75
|
+
while @running
|
76
|
+
drive
|
69
77
|
end
|
70
|
-
@worker.run unless @worker.status
|
71
|
-
@worker.join
|
72
78
|
end
|
73
79
|
|
74
|
-
#
|
80
|
+
# Signal that it's time to stop the dock.
|
75
81
|
#
|
76
|
-
def
|
77
|
-
@
|
82
|
+
def stop
|
83
|
+
@running = false
|
78
84
|
end
|
79
85
|
|
80
86
|
#########
|
81
87
|
protected
|
82
88
|
#########
|
83
89
|
|
84
|
-
#
|
85
|
-
#
|
90
|
+
# Drives the dock program by IO#popen. This method provides a main
|
91
|
+
# loop that repeatedly checks the mtime of the config file, polls all of
|
92
|
+
# the polls, updates and delivers the template or any errors that
|
93
|
+
# happened along the way.
|
86
94
|
#
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
@table[:polls] = {}
|
95
|
+
def drive
|
96
|
+
pipe = File.popen( self.dock_command, 'w' )
|
97
|
+
pipe.sync = true
|
98
|
+
|
99
|
+
while true
|
100
|
+
|
101
|
+
begin
|
102
|
+
break if load_config
|
103
|
+
rescue Exception => e
|
104
|
+
@log.error e
|
105
|
+
pipe.puts "Couldn't load your config: %s " % e
|
106
|
+
sleep 10
|
107
|
+
next
|
108
|
+
end
|
103
109
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
klass = DockDriver::DataRatePoll
|
110
|
+
self.polls.each do |name,p|
|
111
|
+
begin
|
112
|
+
p.poll
|
113
|
+
rescue Exception => e
|
114
|
+
@log.error e
|
110
115
|
end
|
111
|
-
@table[:polls][name] = klass.new( hash )
|
112
116
|
end
|
117
|
+
|
118
|
+
begin
|
119
|
+
vbind = OpenStruct.new( self.polls ).send( :binding )
|
120
|
+
pipe.puts @template.result( vbind ).gsub( /\s+/, ' ' )
|
121
|
+
rescue SyntaxError => e
|
122
|
+
@log.error e
|
123
|
+
pipe.puts "ERB didn't like your template. Check #{@log_file} to see why."
|
124
|
+
sleep 30
|
125
|
+
rescue Exception
|
126
|
+
break
|
127
|
+
end
|
128
|
+
|
129
|
+
sleep 1
|
113
130
|
end
|
114
131
|
|
115
|
-
|
116
|
-
|
117
|
-
|
132
|
+
pipe.close
|
133
|
+
rescue => e
|
134
|
+
puts e
|
135
|
+
puts e.backtrace
|
136
|
+
exit
|
137
|
+
end
|
138
|
+
|
139
|
+
# Loads the configuration file defined by the option +:config_file+ if it has
|
140
|
+
# changed since this method was last called. If not, returns false.
|
141
|
+
#
|
142
|
+
def load_config
|
143
|
+
mtime = File.mtime @config_file
|
144
|
+
return false if mtime == @config_file_mtime
|
145
|
+
|
146
|
+
@table.merge! symbolify_keys YAML.load_file @config_file
|
147
|
+
@config_file_mtime = mtime
|
148
|
+
|
149
|
+
self.polls = {}
|
150
|
+
if self.commands and self.commands.is_a? Hash
|
151
|
+
self.commands.each do |name,hash|
|
152
|
+
hash[:name] = name
|
153
|
+
self.polls[name] = POLL_TYPES[hash[:type]].new( hash )
|
118
154
|
end
|
119
155
|
end
|
120
156
|
|
121
|
-
|
157
|
+
if self.polls[:time].nil?
|
158
|
+
self.polls[:time] = DockDriver::Poll.new( {
|
159
|
+
:cmd => Proc.new { Time.now.strftime( self.time_format ) },
|
160
|
+
} )
|
161
|
+
end
|
122
162
|
|
123
|
-
|
163
|
+
@template = ERB.new( self.template )
|
164
|
+
true
|
124
165
|
end
|
125
166
|
|
126
167
|
end
|
168
|
+
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# vim: set nosta noet ts=4 sw=4:
|
3
|
+
# encoding: utf-8
|
4
|
+
#
|
5
|
+
# Copyright (c) 2007-2012, Michael Granger and Mahlon E. Smith
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
|
9
|
+
### A collection of utilities for working with Hashes.
|
10
|
+
#
|
11
|
+
module HashUtilities
|
12
|
+
|
13
|
+
###############
|
14
|
+
module_function
|
15
|
+
###############
|
16
|
+
|
17
|
+
### Return a version of the given +hash+ with its keys transformed
|
18
|
+
### into Strings from whatever they were before.
|
19
|
+
def stringify_keys( hash )
|
20
|
+
newhash = {}
|
21
|
+
|
22
|
+
hash.each do |key,val|
|
23
|
+
if val.is_a?( Hash )
|
24
|
+
newhash[ key.to_s ] = stringify_keys( val )
|
25
|
+
else
|
26
|
+
newhash[ key.to_s ] = val
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
return newhash
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
### Return a duplicate of the given +hash+ with its identifier-like keys
|
35
|
+
### transformed into symbols from whatever they were before.
|
36
|
+
def symbolify_keys( hash )
|
37
|
+
newhash = {}
|
38
|
+
|
39
|
+
hash.each do |key,val|
|
40
|
+
keysym = key.to_s.dup.untaint.to_sym
|
41
|
+
|
42
|
+
if val.is_a?( Hash )
|
43
|
+
newhash[ keysym ] = symbolify_keys( val )
|
44
|
+
else
|
45
|
+
newhash[ keysym ] = val
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
return newhash
|
50
|
+
end
|
51
|
+
alias_method :internify_keys, :symbolify_keys
|
52
|
+
|
53
|
+
|
54
|
+
# Recursive hash-merge function
|
55
|
+
def merge_recursively( key, oldval, newval )
|
56
|
+
case oldval
|
57
|
+
when Hash
|
58
|
+
case newval
|
59
|
+
when Hash
|
60
|
+
oldval.merge( newval, &method(:merge_recursively) )
|
61
|
+
else
|
62
|
+
newval
|
63
|
+
end
|
64
|
+
|
65
|
+
when Array
|
66
|
+
case newval
|
67
|
+
when Array
|
68
|
+
oldval | newval
|
69
|
+
else
|
70
|
+
newval
|
71
|
+
end
|
72
|
+
|
73
|
+
else
|
74
|
+
newval
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end # HashUtilities
|
79
|
+
|
data/lib/dock_driver/poll.rb
CHANGED
@@ -1,9 +1,13 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# vim: set nosta noet ts=4 sw=4:
|
3
|
+
# encoding: utf-8
|
4
|
+
#
|
1
5
|
|
2
6
|
require 'ostruct'
|
3
7
|
|
4
8
|
require 'dock_driver' unless defined? DockDriver
|
5
9
|
|
6
|
-
# A class to provide
|
10
|
+
# A class to provide periodic command execution and caching.
|
7
11
|
#
|
8
12
|
class DockDriver::Poll < OpenStruct
|
9
13
|
|
@@ -30,42 +34,56 @@ class DockDriver::Poll < OpenStruct
|
|
30
34
|
# +:scan_regex+ - A bare string to be interpreted as a regex for use with +scan+. Any
|
31
35
|
# capture group matches will be joined with single spaces.
|
32
36
|
#
|
37
|
+
# +:format+ - A format string. See: Kernel#format
|
38
|
+
#
|
33
39
|
def initialize( opts = {}, &block )
|
34
40
|
super DEFAULTS.merge( opts )
|
41
|
+
|
35
42
|
@sample = nil
|
36
43
|
|
37
44
|
if block_given?
|
38
|
-
|
45
|
+
self.cmd = block
|
39
46
|
end
|
40
47
|
end
|
41
48
|
|
42
|
-
# Runs the command if enough time has elapsed since the last
|
43
|
-
# defined by the :delay option.
|
49
|
+
# Runs the command if enough time has elapsed since the last
|
50
|
+
# poll. "Enough Time" is defined by the :delay option. If the command
|
51
|
+
# fails to run, it is disabled and the format and output string are set
|
52
|
+
# to nil.
|
44
53
|
#
|
45
54
|
def poll
|
46
|
-
return false if @sample and (Time.now - (
|
55
|
+
return false if @sample and (Time.now - (self.last_poll || 0 )) < self.delay
|
47
56
|
|
48
|
-
|
57
|
+
self.last_poll = Time.now
|
49
58
|
|
50
|
-
case
|
59
|
+
case self.cmd
|
51
60
|
when Proc
|
52
|
-
@sample =
|
61
|
+
@sample = self.cmd.call.to_s.chomp
|
53
62
|
else
|
54
|
-
|
63
|
+
output = `#{self.cmd.to_s} 2>&1`.chomp
|
64
|
+
if $? == 0
|
65
|
+
@sample = output
|
66
|
+
else
|
67
|
+
self.format = nil
|
68
|
+
@sample = '[%s: Error!] ' % self.name
|
69
|
+
raise output
|
70
|
+
end
|
55
71
|
end
|
56
72
|
|
57
|
-
if
|
58
|
-
extract = @sample.scan( Regexp.new(
|
73
|
+
if self.scan_regex
|
74
|
+
extract = @sample.scan( Regexp.new( self.scan_regex ) ).flatten
|
59
75
|
@sample = extract.join( ' ' ) unless extract.empty?
|
60
76
|
end
|
61
77
|
|
62
78
|
true
|
63
79
|
end
|
64
80
|
|
65
|
-
# Returns the output of the command this Poll runs.
|
81
|
+
# Returns the output of the command this Poll runs. It will return a formatted string if
|
82
|
+
# the +:format+ option is correctly specified.
|
66
83
|
#
|
67
84
|
def to_s
|
68
|
-
@sample
|
85
|
+
return @sample.to_s if self.format.nil?
|
86
|
+
self.format % @sample
|
69
87
|
end
|
70
88
|
|
71
89
|
end
|
@@ -6,35 +6,31 @@ require 'dock_driver/dock'
|
|
6
6
|
describe DockDriver::Dock do
|
7
7
|
|
8
8
|
subject do
|
9
|
-
conf = Pathname( __FILE__ ).parent.parent.parent
|
10
|
-
conf += 'example.dock_driver.yml'
|
11
|
-
|
9
|
+
@conf = Pathname( __FILE__ ).parent.parent.parent
|
10
|
+
@conf += 'data/example.dock_driver.yml'
|
11
|
+
|
12
|
+
# Use very common binaries that are all but guaranteed to be in the
|
13
|
+
# user's path so that we can maintain a meaningful example config file.
|
14
|
+
#
|
15
|
+
dock = Dock.new( @conf.to_s, nil, 'cat' )
|
16
|
+
dock.polls.values.each { |p| p.cmd = 'echo test' }
|
12
17
|
dock
|
13
18
|
end
|
14
19
|
|
15
|
-
it 'should
|
20
|
+
it 'should load a config file' do
|
16
21
|
subject.dock_command.should_not be nil
|
17
22
|
subject.template.should_not be nil
|
18
23
|
subject.polls.should have_at_least( 2 ).things
|
19
24
|
subject.polls.values.each { |p| p.should be_a_kind_of Poll }
|
20
25
|
end
|
21
26
|
|
22
|
-
it 'should only
|
27
|
+
it 'should only reload a config file with a more recent mtime' do
|
23
28
|
subject.send( :load_config ).should be false
|
24
|
-
|
29
|
+
sleep 0.1
|
30
|
+
FileUtils.touch @conf
|
25
31
|
subject.send( :load_config ).should be true
|
26
32
|
subject.send( :load_config ).should be false
|
27
33
|
end
|
28
34
|
|
29
|
-
it 'should produce a string properly' do
|
30
|
-
|
31
|
-
# make sure we don't fail if a particular binary for an example doesn't exist.
|
32
|
-
subject.polls.values.each { |p| p.cmd = 'echo' }
|
33
|
-
|
34
|
-
string = subject.to_s
|
35
|
-
string.should be_a_kind_of String
|
36
|
-
string.should_not be ''
|
37
|
-
end
|
38
|
-
|
39
35
|
end
|
40
36
|
|
@@ -4,48 +4,42 @@ require 'dock_driver/data_rate_poll'
|
|
4
4
|
|
5
5
|
include DockDriver
|
6
6
|
|
7
|
-
shared_examples '
|
7
|
+
shared_examples 'a poll' do
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
lambda { p.poll }.should_not raise_error
|
9
|
+
subject do
|
10
|
+
described_class.new( :cmd => 'echo test' )
|
12
11
|
end
|
13
12
|
|
14
|
-
it 'should
|
15
|
-
|
16
|
-
|
13
|
+
it 'should execute shell commands' do
|
14
|
+
lambda do
|
15
|
+
subject.poll.should == true
|
16
|
+
end.should_not raise_error
|
17
17
|
end
|
18
18
|
|
19
|
-
it 'should
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
b = p.to_s
|
25
|
-
a.object_id.should == b.object_id
|
19
|
+
it 'should execute blocks' do
|
20
|
+
subject.cmd = lambda {}
|
21
|
+
lambda do
|
22
|
+
subject.poll.should == true
|
23
|
+
end.should_not raise_error
|
26
24
|
end
|
27
25
|
|
28
|
-
it 'should
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
26
|
+
it 'should cache command output' do
|
27
|
+
subject.delay = 10
|
28
|
+
subject.poll.should == true
|
29
|
+
subject.poll.should == false
|
30
|
+
|
31
|
+
subject.delay = 0
|
32
|
+
subject.poll.should == true
|
33
|
+
subject.poll.should == true
|
36
34
|
end
|
37
35
|
|
38
36
|
end
|
39
37
|
|
40
38
|
describe Poll do
|
41
|
-
|
42
|
-
include_examples 'Poll', Poll
|
43
|
-
|
39
|
+
it_behaves_like 'a poll'
|
44
40
|
end
|
45
41
|
|
46
42
|
describe DataRatePoll do
|
47
|
-
|
48
|
-
include_examples 'Poll', DataRatePoll
|
49
|
-
|
43
|
+
it_behaves_like 'a poll'
|
50
44
|
end
|
51
45
|
|
data/spec/dock_driver_spec.rb
CHANGED
@@ -3,12 +3,12 @@ require 'dock_driver'
|
|
3
3
|
|
4
4
|
describe DockDriver do
|
5
5
|
|
6
|
-
it 'should
|
6
|
+
it 'should define a standard version number' do
|
7
7
|
defined?( VERSION ).should == 'constant'
|
8
8
|
VERSION.should =~ /^\d+\.\d+\.\d+$/
|
9
9
|
end
|
10
10
|
|
11
|
-
it 'should
|
11
|
+
it 'should define a mercurial revision' do
|
12
12
|
defined?( REVISION ).should == 'constant'
|
13
13
|
REVISION.should =~ /^Revision: [[:xdigit:]]+ $/
|
14
14
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dock_driver
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Michael Hix
|
@@ -35,7 +35,7 @@ cert_chain:
|
|
35
35
|
Edc7ShBTME9+u6K/HFwVxTOH8WJVxkwGTmxKgCevr5mZzbINH/C6LbkmfEA=
|
36
36
|
-----END CERTIFICATE-----
|
37
37
|
|
38
|
-
date: 2012-05-
|
38
|
+
date: 2012-05-23 00:00:00 Z
|
39
39
|
dependencies:
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
41
|
name: hoe-mercurial
|
@@ -112,7 +112,7 @@ files:
|
|
112
112
|
- bin/dock_driver
|
113
113
|
- data/example.dock_driver.yml
|
114
114
|
- lib/dock_driver.rb
|
115
|
-
- lib/dock_driver/
|
115
|
+
- lib/dock_driver/hashutils.rb
|
116
116
|
- lib/dock_driver/poll.rb
|
117
117
|
- lib/dock_driver/data_rate_poll.rb
|
118
118
|
- lib/dock_driver/dock.rb
|
metadata.gz.sig
CHANGED
Binary file
|
@@ -1,74 +0,0 @@
|
|
1
|
-
# A module borrowed from my coworkers at LAIKA.
|
2
|
-
#
|
3
|
-
module LAIKA
|
4
|
-
|
5
|
-
### A collection of utilities for working with Hashes.
|
6
|
-
module HashUtilities
|
7
|
-
|
8
|
-
###############
|
9
|
-
module_function
|
10
|
-
###############
|
11
|
-
|
12
|
-
### Return a version of the given +hash+ with its keys transformed
|
13
|
-
### into Strings from whatever they were before.
|
14
|
-
def stringify_keys( hash )
|
15
|
-
newhash = {}
|
16
|
-
|
17
|
-
hash.each do |key,val|
|
18
|
-
if val.is_a?( Hash )
|
19
|
-
newhash[ key.to_s ] = stringify_keys( val )
|
20
|
-
else
|
21
|
-
newhash[ key.to_s ] = val
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
return newhash
|
26
|
-
end
|
27
|
-
|
28
|
-
|
29
|
-
### Return a duplicate of the given +hash+ with its identifier-like keys
|
30
|
-
### transformed into symbols from whatever they were before.
|
31
|
-
def symbolify_keys( hash )
|
32
|
-
newhash = {}
|
33
|
-
|
34
|
-
hash.each do |key,val|
|
35
|
-
keysym = key.to_s.dup.untaint.to_sym
|
36
|
-
|
37
|
-
if val.is_a?( Hash )
|
38
|
-
newhash[ keysym ] = symbolify_keys( val )
|
39
|
-
else
|
40
|
-
newhash[ keysym ] = val
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
return newhash
|
45
|
-
end
|
46
|
-
alias_method :internify_keys, :symbolify_keys
|
47
|
-
|
48
|
-
|
49
|
-
# Recursive hash-merge function
|
50
|
-
def merge_recursively( key, oldval, newval )
|
51
|
-
case oldval
|
52
|
-
when Hash
|
53
|
-
case newval
|
54
|
-
when Hash
|
55
|
-
oldval.merge( newval, &method(:merge_recursively) )
|
56
|
-
else
|
57
|
-
newval
|
58
|
-
end
|
59
|
-
|
60
|
-
when Array
|
61
|
-
case newval
|
62
|
-
when Array
|
63
|
-
oldval | newval
|
64
|
-
else
|
65
|
-
newval
|
66
|
-
end
|
67
|
-
|
68
|
-
else
|
69
|
-
newval
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
end # HashUtilities
|
74
|
-
end
|