dock_driver 0.1.0 → 0.1.1
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.
- 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
|