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 CHANGED
Binary file
data/History.txt CHANGED
@@ -1,3 +1,9 @@
1
+ === 0.1.1 / 2012-05-20
2
+
3
+ * 2 minor enhancements
4
+ * logging
5
+ * bug fixes
6
+
1
7
  === 0.1.0 / 2012-05-15
2
8
 
3
9
  * 3 minor enhancements
data/Ideas.txt CHANGED
@@ -4,7 +4,6 @@
4
4
 
5
5
  * Graphing
6
6
  * Automatic Coloring (with definable syntax)
7
- * Logging
8
7
 
9
8
  == With Dependencies
10
9
 
data/Manifest.txt CHANGED
@@ -8,7 +8,7 @@ Rakefile
8
8
  bin/dock_driver
9
9
  data/example.dock_driver.yml
10
10
  lib/dock_driver.rb
11
- lib/dock_driver/laika_hashutils.rb
11
+ lib/dock_driver/hashutils.rb
12
12
  lib/dock_driver/poll.rb
13
13
  lib/dock_driver/data_rate_poll.rb
14
14
  lib/dock_driver/dock.rb
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
- * unwritten test cases
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 besides ruby and rubygems.
40
+ * This gem proudly has zero dependencies.
34
41
 
35
42
  == INSTALL:
36
43
 
37
- * sudo gem install dock_driver
38
- * copy <gemdir>/etc/example.dock_driver.yml to ~/.dock_driver.yml
39
- * edit ~/.dock_driver.yml to taste
40
- * run dock_driver from your xsession or ~/.i3/config file.
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 MIT License)
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
- Copyright (c) 2012 Michael Hix
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
- Permission is hereby granted, free of charge, to any person obtaining
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
- The above copyright notice and this permission notice shall be included
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
- 'Specify an alternate config file. (Default: ~/.dock_driver.yml)'
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.parse!
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.kill
52
- puts "\nKilled!" if $DEBUG
85
+ dock.stop
53
86
  end
54
87
 
55
- dock.run
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 reference any method or member here.
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
- ^fg(white)rx ^fg(gray60)<%= eth0_rx %> ^fg(gray30)<%= eth0_rx.unit %> ·
28
- ^fg(white)tx ^fg(gray60)<%= eth0_tx %> ^fg(gray30)<%= eth0_tx.unit %> ·
29
- ^fg(white)cpu0 ^fg(gray60)<%= cpu0_temp %>^fg(gray30) C ·
30
- ^fg(white)cpu1 ^fg(gray60)<%= cpu1_temp %>^fg(gray30) C ·
31
- ^fg(white)gpu ^fg(gray60)<%= gpu_temp %>^fg(gray30) C ·
32
- ^fg(white)mail <%= mail %>^fg(gray30) ·
33
- ^fg(gray60)<%= time %>^fg(gray30) ·
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
- # Commands to poll. Each command will be polled every second, but specifying a
36
- # +delay+ greater than one will cache the value and only run the command every
37
- # +delay+ seconds. This defaults to evaluating a string with a shell, but if
38
- # your YAML fu is strong enough, you can specify a Proc here too.
39
- #
40
- # To narrow down the output, you may use +scan_regex+. A single-quoted regex
41
- # without forward slashes works great. Since scan returns an array, using one
42
- # or more capture groups will allow you to pull multiple values from a single
43
- # command's output - the values returned will be joined with single spaces.
44
- #
45
- # To change the class used to poll the command, you can specify a +type+.
46
- # Right now, only 'rate' is recognized. It uses a separate class that expects
47
- # a string containing a single number. Best to use +scan_regex+ here. It
48
- # calculates a running average based on the changes in the value over time.
49
- # A unit of measurement (b, kb, mb, gb, tb, pb, eb, zb, yb) or its full name
50
- # (bits, kibibits, mebibits, ...) is also useful here. You may also specify a
51
- # +number_format+ here if you don't like the default. See +sprintf+ for details
52
- # on the format string.
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
- mail:
56
- cmd: '/home/joeuser/bin/dockmail'
57
- delay: 5
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
- type: rate
74
- unit: mb
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
- type: rate
79
- unit: mb
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.0'
11
+ VERSION = '0.1.1'
8
12
 
9
13
  # Revision Constant
10
- REVISION = %q$Revision: d17549778789 $
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 simple, periodic command execution, caching, and a
8
- # running average in changes in a value.
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
- :number_format => '%0.3f',
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
- # +:delay+ - Determines the minimum amount of time to let elapse between executions.
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( @table[:history_size] ) { 0 }
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.to_s.to_f unless @sample.is_a? Float
54
+ @sample = @sample.to_f
60
55
  @last_sample ||= @sample
61
56
  sample = 8 * ( @sample - @last_sample )
62
- @history.shift if @history.length >= @table[:history_size]
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 the output of the command this Poll runs, formated as per the
134
- # option :number_format.
127
+ # Returns a string representing the data rate this poll tracks.
135
128
  #
136
129
  def to_s
137
- @average
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
@@ -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/laika_hashutils'
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 LAIKA::HashUtilities
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
- :config_file => File.expand_path( '~/.dock_driver.yml' ),
24
- :template => '',
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
- # Returns the result of the template.
45
- #
46
- def to_s()
47
- # Poll everything.
48
- @table[:polls].values.map( &:poll )
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
- # Prepare a binding and render the ERB template.
51
- vbind = OpenStruct.new( @table[:polls] ).send( :binding )
52
- @table[:template].result( vbind ).gsub( /\s+/, ' ' )
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 thiis Dock if it isn't already running, does nothing otherwise.
70
+ # Start the dock. Repeatedly calls Dock#drive until it's time to stop. See Dock#stop.
56
71
  #
57
- def run
58
- @worker ||= Thread.new do
59
- while true
60
- File.popen( @table[:dock_command], 'w' ) do |pipe|
61
- while true
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
- # Forcibly stops this Dock if it is running, does nothing otherwise.
80
+ # Signal that it's time to stop the dock.
75
81
  #
76
- def kill
77
- @worker.kill if @worker.status
82
+ def stop
83
+ @running = false
78
84
  end
79
85
 
80
86
  #########
81
87
  protected
82
88
  #########
83
89
 
84
- # Loads the configuration file defined by the option +:config_file+ if it has
85
- # changed since this method was last called. If not, returns false.
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
- # === Arguments:
88
- #
89
- # +force+ - If true, force a reload, even if the config file hasn't changed.
90
- #
91
- def load_config( force = false )
92
-
93
- mtime = File.mtime @table[:config_file]
94
- return false if not force and mtime == @table[:config_mtime]
95
-
96
- @table.delete :template
97
- hash = symbolify_keys( YAML.load_file( @table[:config_file] ) )
98
- hash.delete :config_file
99
- @table.merge! hash
100
- @table[:config_mtime] = mtime
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
- if @table[:commands] and @table[:commands].is_a? Hash
105
- @table[:commands].each do |name,hash|
106
- klass = DockDriver::Poll
107
- case hash[:type]
108
- when /rate/i
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
- if @table[:polls]['time'].nil?
116
- @table[:polls]['time'] = DockDriver::Poll.new( { :delay => 1 } ) do
117
- Time.now.strftime( @table[:time_format] )
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
- @table[:template] = ERB.new( @table[:template] )
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
- return true
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
+
@@ -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 simple, periodic command execution and caching.
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
- @table[:cmd] = block
45
+ self.cmd = block
39
46
  end
40
47
  end
41
48
 
42
- # Runs the command if enough time has elapsed since the last poll. "Enough Time" is
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 - (@table[:last_poll] || 0 )) < @table[:delay]
55
+ return false if @sample and (Time.now - (self.last_poll || 0 )) < self.delay
47
56
 
48
- @table[:last_poll] = Time.now
57
+ self.last_poll = Time.now
49
58
 
50
- case @table[:cmd]
59
+ case self.cmd
51
60
  when Proc
52
- @sample = @table[:cmd].call
61
+ @sample = self.cmd.call.to_s.chomp
53
62
  else
54
- @sample = `#{@table[:cmd].to_s}`
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 @table[:scan_regex]
58
- extract = @sample.scan( Regexp.new( @table[:scan_regex] ) ).flatten
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 + 'data'
10
- conf += 'example.dock_driver.yml'
11
- dock = Dock.new conf, 'cat'
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 be able to load a config file properly' do
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 attempt to reload a config file with a more recent mtime' do
27
+ it 'should only reload a config file with a more recent mtime' do
23
28
  subject.send( :load_config ).should be false
24
- FileUtils.touch subject.config_file
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 'Poll' do |klass|
7
+ shared_examples 'a poll' do
8
8
 
9
- it 'should be able to execute shell commands' do
10
- p = Poll.new( :cmd => "echo" )
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 be able to execute blocks' do
15
- p = Poll.new do; end
16
- lambda { p.poll }.should_not raise_error
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 return the same object if polled quickly' do
20
- p = Poll.new( :cmd => "echo" )
21
- p.poll
22
- a = p.to_s
23
- p.poll
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 return a different object if polled slowly' do
29
- p = Poll.new( :cmd => "echo", :delay => 0.1 )
30
- p.poll
31
- a = p.to_s
32
- sleep 0.5
33
- p.poll
34
- b = p.to_s
35
- a.object_id.should_not == b.object_id
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
 
@@ -3,12 +3,12 @@ require 'dock_driver'
3
3
 
4
4
  describe DockDriver do
5
5
 
6
- it 'should have a standard version number' do
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 have a mercurial revision' do
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: 27
4
+ hash: 25
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 0
10
- version: 0.1.0
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-16 00:00:00 Z
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/laika_hashutils.rb
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