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 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