Mike Hix avatar Mike Hix committed 8750bea

checkpoint: prep for rewrite

Comments (0)

Files changed (12)

-= Ideas
-
-== Without Dependencies
-
-* Graphing
-* Automatic Coloring (with definable syntax)
-
-== With Dependencies
-
-* Maildir support
-* SNMP Poller
-* gateway / internet latency
-
-== dzen2 Ideas
-
-* buttons
-* events
-* text window
-
-.autotest
-.hgignore
-History.txt
-Ideas.txt
-Manifest.txt
-README.txt
-Rakefile
-bin/dock_driver
-data/example.dock_driver.yml
-lib/dock_driver.rb
-lib/dock_driver/hashutils.rb
-lib/dock_driver/poll.rb
-lib/dock_driver/data_rate_poll.rb
-lib/dock_driver/dock.rb
-spec/dock_driver/dock_spec.rb
-spec/dock_driver/poll_spec.rb
-spec/dock_driver_spec.rb

bin/dock_driver

-#!/usr/bin/env ruby
-# vim: set nosta noet ts=4 sw=4:
-# encoding: utf-8
-#
-
-require 'optparse'
-require 'dock_driver/dock'
-
-include Signal
-
-opts = {}
-parser = OptionParser.new do |p|
-
-	p.on(
-		'-c',
-		'--conf FILE',
-		"Specify an alternate config file. (Default: #{DockDriver::Dock::DEFAULT_CONFIG_FILE})"
-	) do |file|
-		unless File.exists? file
-			$stderr.puts "That config file doesn't exist." % file
-			$stderr.puts
-			$stderr.puts p
-			exit
-		end
-		opts[:conf] = file
-	end
-
-	p.on(
-		'-l',
-		'--log FILE',
-		"Specify an alternate log file. (Default: #{DockDriver::Dock::DEFAULT_LOG_FILE})"
-	) do |log|
-		if File.exists? log
-			$stderr.puts "That log file exists, and I'm cowardly refusing to overwrite it." % log
-			$stderr.puts
-			$stderr.puts p
-			exit
-		end
-		opts[:log] = log
-	end
-
-	p.on(
-		'-d',
-		'--dock COMMAND',
-		'Override the dock command from the config file.'
-	) do |command|
-		opts[:dock] = command
-	end
-
-	p.on( '-e', '--debug', 'Enable $DEBUG' ) { |debug| $DEBUG = true }
-
-	p.on( '-h', '--help', 'Print this help text.' ) do
-		cmd = File.basename( $0 )
-		$stderr.puts "\n%s\n%s\n\nVersion: %s\n%s\n\n" % [
-			cmd,
-			'=' * cmd.length,
-			DockDriver::VERSION,
-			DockDriver::REVISION,
-		]
-		$stderr.puts p
-		$stderr.puts "\nAuthors:\n\t%s\n\n" % [
-			DockDriver::AUTHORS.join( '\n\t' ),
-		]
-		exit
-	end
-
-end
-
-# Handle invalid options.
-#
-begin
-	parser.parse!
-rescue => e
-	$stderr.puts "\n%s\n\n%s\n" % [e, parser]
-	exit
-end
-
-$stderr.puts "Options: %p" % opts if $DEBUG
-
-dock = DockDriver::Dock.new( opts[:conf], opts[:log], opts[:dock] )
-
-# Respond to ^C.
-#
-trap 'INT' do
-	dock.stop
-	exit
-end
-
-dock.start
-

data/example.dock_driver.yml

----
-# Specify a program to run here. This bit of code was initially meant to run
-# dzen2 but other docks are certainly appropriate, provided they accept commands
-# and data over standard input. You'll probably need to adjust your formats and
-# templates below if you use a different dock program.
-#
-dock_command: 
-    dzen2 -dock -ta r -x 0 -y 0 -w 1680 -h 24 -bg black -fg white 
-    -fn "-*-terminus-medium-r-*-*-12-*-*-*-*-*-*-*"
-    -e "button3="
-
-# See the manpages for strftime and dzen2.
-#
-time_format: 
-    ^fg(white)%A ^fg(gray80)%Y-%m-%d ^fg(gray60)%H:%M:%S ^fg(gray30)%Z
-
-# Determines the layout and styling of the dock.
-#
-# Processed by +ERB+. +Poll+ objects as defined by the 'commands' section are
-# available. Since +Poll+ objects respond to to_s intelligently, it's usually
-# sufficient to just specify the object name. Since they're also subclasses of
-# +OpenStruct+, you can also use any attribute defined on the poll here too.
-#
-# See the dzen2 documentation.
-#
-template:
-    <%= time %>
-    <%= load %>
-    ^fg(white)eth0
-    <%= eth0_rx %>^fg(gray30) <%= eth0_tx %>^fg(gray30) <%= eth0_rx.unit %>/s
-    <%= cpu0_temp %>
-    <%= cpu1_temp %>
-    <%= gpu_temp %>
-    ^fg(black)
-
-# Provides a list of commands to poll.
-#
-# The key commands needs to be a hash of hashes. The example below shows
-# some interesting examples. Here's the basic structure:
-#
-#   commands:
-#       name:
-#           cmd: 'whoami'
-#       frob:
-#           cmd: 'byte_frobber'
-#           type: 'rate'
-#           scan_regex: 'bytes_frobbed: (\d+)'
-#           format: '%0.3f bits per second'
-#
-# You'd refer to the above commands in your template like so:
-#
-#   User '<%= name %>' is frobbing <%= frob %>!
-#
-# Which would yield something like the following on your dock.
-#
-#   User 'mike' is frobbing 0.321 bits per second!
-#
-# Standard options for commands are as follows:
-#
-#
-# cmd:
-#
-# A required string that will get executed by a shell. If you're comfortable enough with YAML,
-# this can be a ruby Proc too.
-#
-#
-# scan_regex:
-#
-# This optional key provides a way to extract values from command
-# output. The value should be a regular expression without forward
-# slashes, and you'll probably want to put it in single quotes. The best
-# way to use it is to define one capture group with parenthasees. If you
-# define more than one capture group, they'll be joined up with spaces and
-# returned as a single string.
-#
-#
-# format:
-#
-# A string that allows you to wrap the output from your command in
-# another string. You can prepend a label, append a unit, convert the
-# data type in a limited fashion, or whatever you like.
-#
-# See: Kernel#format
-#
-#
-# delay:
-#
-# How long to cache the command output for. If delay is set to 60, even
-# if the bar is refreshed every second, the command will only be run every
-# 60 seconds or so. Timing isn't guaranteed, but it's good enough to
-# estimate data rates.
-#
-#
-# type:
-#
-# This optional key allows you to specify the type of poll to use. If
-# you don't specify a type, the default 'text' is used. It simply runs the
-# command, applies any scan_regex or format specified, and returns the
-# resulting text.
-#
-# If you specify 'rate', the command and scan_regex are expected
-# to return a number. This number will be interpereted as a total in
-# bytes. For example the number of bits that your network adapter has
-# sent. A history of how that value changes over time is kept, and the
-# output from this poll is the average of that history.
-#
-#
-# history_length:
-#
-# This only applies to the 'rate' type. The history length is the number
-# of samples to keep and allows you to control how long of a running
-# average to keep. The larger this number is, the more the average will
-# lag behind the actual rate of change.
-#
-#
-commands:
-    load:
-        cmd: 'uptime'
-        scan_regex: '(\d+\.\d+)$'
-        format: '^fg(white)load ^fg(gray60)%s^fg(gray30)/15'
-        delay: 60
-    cpu0_temp:
-        cmd: 'sensors'
-        scan_regex: 'Core 0:\s+\+(\d+\.\d+)'
-        format: '^fg(white)cpu0 ^fg(gray60)%s^fg(gray30)°C'
-        delay: 30
-    cpu1_temp:
-        cmd: 'sensors'
-        scan_regex: 'Core 1:\s+\+(\d+\.\d+)'
-        format: '^fg(white)cpu1 ^fg(gray60)%s^fg(gray30)°C'
-        delay: 30
-    gpu_temp:
-        cmd: 'nvidia-smi -q -d TEMPERATURE'
-        scan_regex: '(\d+) C'
-        format: '^fg(white)gpu ^fg(gray60)%s^fg(gray30)°C'
-        delay: 30    
-    eth0_rx:
-        cmd: 'ifconfig eth0'
-        scan_regex: 'RX bytes:(\d+)'
-        format: '^fg(#208020)%0.3f'
-        type: 'rate'
-    eth0_tx:
-        cmd: 'ifconfig eth0'
-        scan_regex: 'TX bytes:(\d+)'
-        format: '^fg(#802020)%0.3f'
-        type: 'rate'
-

lib/dock_driver.rb

-# -*- ruby -*-
-# vim: set nosta noet ts=4 sw=4:
-# encoding: utf-8
-#
-
-# A namespace for the project.
-#
-module DockDriver
-
-	# Version Constant
-	VERSION = '0.1.2'
-
-	# Revision Constant
-    REVISION = %q$Revision$
-
-	# Behold the author(s) responsible for this mess.
-	AUTHORS = [ 'Mike Hix <mike@musl.org>' ]
-
-end
-

lib/dock_driver/data_rate_poll.rb

-# -*- ruby -*-
-# vim: set nosta noet ts=4 sw=4:
-# encoding: utf-8
-
-require 'ostruct'
-
-require 'dock_driver' unless defined? DockDriver
-require 'dock_driver/poll'
-
-# A class to provide periodic command execution, caching, and a
-# running average of changes in the output of the command.
-#
-class DockDriver::DataRatePoll < DockDriver::Poll
-
-	# Default values to populate the OpenStruct with.
-	#
-	DEFAULTS = {
-		:unit => :mb,
-		:history_size => 2,
-		:format => '%0.3f',
-	}.freeze
-
-	# Create a new DataRatePoll.
-	#
-	# === Arguments:
-	#
-	# +opts+ - A hash containing options. Merged with DEFAULTS.
-	#
-	# +block+ - If a block is given, replace :cmd with that block.
-	#
-	# === Options:
-	#
-	# Everything accepted by from Poll, plus:
-	#
-	# +:history_size+ - How many samples to keep
-	#
-	# +:unit+ - The unit to use. See the methods and aliases available.
-	# (b, kb, mb, gb, tb, pb, eb, zb, yb, and full names: bits, kibibits, mebibits, ...)
-	#
-	def initialize( opts = {} )
-		super DEFAULTS.merge( opts )
-
-		@last_sample = nil
-		@history = Array.new( self.history_size ) { 0 }
-	end
-
-	# Runs the command if enough time has elapsed since the last
-	# poll. "Enough Time" is defined by the :delay option. Maintains the
-	# history of output, average and formatting.
-	#
-	def poll
-		return false unless super
-
-		@sample = @sample.to_f
-		@last_sample ||= @sample
-		sample = 8 * ( @sample - @last_sample )
-		@history.shift if @history.length >= self.history_size
-		@history.push sample
-		@last_sample = @sample
-
-		true
-	end
-
-	# Returns the average value in bits.
-	#
-	def bits
-		@history.inject( :+ ) / @history.count.to_f
-	end
-	alias :b :bits
-
-	# Returns the average value in kibibits.
-	#
-	def kibibits
-		bits / 1024.0
-	end
-	alias :kb :kibibits
-
-	# Returns the average value in mebibits.
-	#
-	def mebibits
-		kibibits / 1024.0
-	end
-	alias :mb :mebibits
-
-	# Returns the average value in gibibits.
-	#
-	def gibibits
-		mebibits / 1024.0
-	end
-	alias :gb :gibibits
-
-	# Returns the average value in tebibits.
-	#
-	def tebibits
-		gibibits / 1024.0
-	end
-	alias :tb :tebibits
-
-	# Returns the average value in pebibits.
-	#
-	def pebibits
-		tebibits / 1024.0
-	end
-	alias :pb :pebibits
-
-	# Returns the average value in exbibits.
-	#
-	def exbibits
-		pebibits / 1024.0
-	end
-	alias :eb :exbibits
-
-	# Returns the average value in zebibits.
-	#
-	def zebibits
-		exbibits / 1024.0
-	end
-	alias :zb :zebibits
-
-	# Returns the average value in yobibits.
-	#
-	def yobibits
-		zebibits / 1024.0
-	end
-	alias :yb :yobibits
-
-	# Returns a string representing the data rate this poll tracks.
-	#
-	def to_s
-		value = self.unit ? send( self.unit ) : bits
-		return value.to_s if self.format.nil?
-		self.format % value
-	end
-
-end
-

lib/dock_driver/dock.rb

-# -*- ruby -*-
-# vim: set nosta noet ts=4 sw=4:
-# encoding: utf-8
-
-require 'erb'
-require 'yaml'
-require 'ostruct'
-require 'logger'
-
-require 'dock_driver' unless defined? DockDriver
-require 'dock_driver/hashutils'
-require 'dock_driver/poll'
-require 'dock_driver/data_rate_poll'
-
-# An object that represents and drives a dock program, periodically
-# updating what it displays.
-#
-class DockDriver::Dock < OpenStruct
-
-	include HashUtilities
-
-	# The *normal* place people keep user-specific configuration data for this program.
-	DEFAULT_CONFIG_FILE = '~/.dock_driver.yml'.freeze
-
-	# The usual place to put user-specific diagnostic information pertaining to this program.
-	DEFAULT_LOG_FILE = '~/.dock_driver.log'.freeze
-
-	# Default values to populate this OpenStruct with.
-	#
-	DEFAULTS = {
-		:polls => {},
-		:time_format => '%Y-%m-%d %H:%M:%S %Z',
-	}.freeze
-
-	# A map of poll Classes/types to kewords for config.
-	#
-	POLL_TYPES = {
-		nil => DockDriver::Poll,
-		'text' => DockDriver::Poll,
-		'rate' => DockDriver::DataRatePoll,
-	}.freeze
-
-	# Create and start a new Dock.
-	#
-	# === Arguments:
-	#
-	# +:config_file+ - Provide a path to a config file different from
-	# the default: ~/.dock_driver.yml
-	#
-	# +:dock_command+ - Override the dock command from the config file.
-	#
-	def initialize( config_file = nil, log_file = nil, dock_command = nil )
-		super DEFAULTS
-
-		@log_file = File.expand_path( log_file || DEFAULT_LOG_FILE )
-		@log = Logger.new( @log_file )
-		@log.level = $DEBUG ? Logger::DEBUG : Logger::WARN
-
-		@config_file_mtime = nil
-		@config_file = File.expand_path( config_file || DEFAULT_CONFIG_FILE )
-
-		load_config
-
-		@running = false
-		if dock_command
-			self.dock_command = dock_command
-		end
-	end
-
-	# Start the dock. Repeatedly calls Dock#drive until it's time to stop. See Dock#stop.
-	#
-	def start
-		return if @running
-		@running = true
-		while @running
-			drive
-		end
-	end
-
-	# Signal that it's time to stop the dock.
-	#
-	def stop
-		@running = false
-	end
-
-	#########
-	protected
-	#########
-
-	# Drives the dock program by IO#popen. This method provides a main
-	# loop that repeatedly checks the mtime of the config file, polls all of
-	# the polls, updates and delivers the template or any errors that
-	# happened along the way.
-	#
-	def drive
-		pipe = File.popen( self.dock_command, 'w' )
-		pipe.sync = true
-
-		while true
-
-			begin
-				break if load_config
-			rescue Exception => e
-				@log.error e
-				pipe.puts "Couldn't load your config: %s " % e
-				sleep 10
-				next
-			end
-
-			self.polls.each do |name,p|
-				begin
-					p.poll
-				rescue Exception => e
-					@log.error e
-				end
-			end
-
-			begin
-				vbind = OpenStruct.new( self.polls ).send( :binding )
-				pipe.puts @template.result( vbind ).gsub( /\s+/, ' ' )
-			rescue SyntaxError => e
-				@log.error e
-				pipe.puts "ERB didn't like your template. Check #{@log_file} to see why."
-				sleep 30
-			rescue Exception
-				break
-			end
-
-			sleep 1
-		end
-
-		pipe.close
-	rescue => e
-		puts e
-		puts e.backtrace
-		exit
-	end
-
-	# Loads the configuration file defined by the option +:config_file+ if it has
-	# changed since this method was last called. If not, returns false.
-	#
-	def load_config
-		mtime = File.mtime @config_file
-		return false if mtime == @config_file_mtime
-
-		@table.merge! symbolify_keys YAML.load_file @config_file
-		@config_file_mtime = mtime
-
-		self.polls = {}
-		if self.commands and self.commands.is_a? Hash
-			self.commands.each do |name,hash|
-				hash[:name] = name
-				self.polls[name] = POLL_TYPES[hash[:type]].new( hash )
-			end
-		end
-
-		if self.polls[:time].nil?
-			self.polls[:time] = DockDriver::Poll.new( {
-				:cmd => Proc.new { Time.now.strftime( self.time_format ) },
-			} )
-		end
-
-		@template = ERB.new( self.template )
-		true
-	end
-
-end
-

lib/dock_driver/hashutils.rb

-# -*- ruby -*-
-# vim: set nosta noet ts=4 sw=4:
-# encoding: utf-8
-#
-# Copyright (c) 2007-2012, Michael Granger and Mahlon E. Smith
-# All rights reserved.
-#
-
-### A collection of utilities for working with Hashes.
-#
-module HashUtilities
-
-	###############
-	module_function
-	###############
-
-	### Return a version of the given +hash+ with its keys transformed
-	### into Strings from whatever they were before.
-	def stringify_keys( hash )
-		newhash = {}
-
-		hash.each do |key,val|
-			if val.is_a?( Hash )
-				newhash[ key.to_s ] = stringify_keys( val )
-			else
-				newhash[ key.to_s ] = val
-			end
-		end
-
-		return newhash
-	end
-
-
-	### Return a duplicate of the given +hash+ with its identifier-like keys
-	### transformed into symbols from whatever they were before.
-	def symbolify_keys( hash )
-		newhash = {}
-
-		hash.each do |key,val|
-			keysym = key.to_s.dup.untaint.to_sym
-
-			if val.is_a?( Hash )
-				newhash[ keysym ] = symbolify_keys( val )
-			else
-				newhash[ keysym ] = val
-			end
-		end
-
-		return newhash
-	end
-	alias_method :internify_keys, :symbolify_keys
-
-
-	# Recursive hash-merge function
-	def merge_recursively( key, oldval, newval )
-		case oldval
-		when Hash
-			case newval
-			when Hash
-				oldval.merge( newval, &method(:merge_recursively) )
-			else
-				newval
-			end
-
-		when Array
-			case newval
-			when Array
-				oldval | newval
-			else
-				newval
-			end
-
-		else
-			newval
-		end
-	end
-
-end # HashUtilities
-

lib/dock_driver/poll.rb

-# -*- ruby -*-
-# vim: set nosta noet ts=4 sw=4:
-# encoding: utf-8
-#
-
-require 'ostruct'
-
-require 'dock_driver' unless defined? DockDriver
-
-# A class to provide periodic command execution and caching.
-#
-class DockDriver::Poll < OpenStruct
-
-	# Default values to populate the OpenStruct with.
-	#
-	DEFAULTS = {
-		:delay => 1,
-	}.freeze
-
-	# Create a new Poll.
-	#
-	# === Arguments:
-	#
-	# +opts+ - A hash containing options. Merged with DEFAULTS.
-	#
-	# +block+ - If a block is given, replace :cmd with that block.
-	#
-	# === Options:
-	#
-	# +:delay+ - Determines the minimum amount of time to let elapse between executions.
-	#
-	# +:cmd+ - A string or block to execute.
-	#
-	# +:scan_regex+ - A bare string to be interpreted as a regex for use with +scan+. Any
-	# capture group matches will be joined with single spaces.
-	#
-	# +:format+ - A format string. See: Kernel#format
-	#
-	def initialize( opts = {}, &block )
-		super DEFAULTS.merge( opts )
-
-		@sample = nil
-
-		if block_given?
-			self.cmd = block
-		end
-	end
-
-	# Runs the command if enough time has elapsed since the last
-	# poll. "Enough Time" is defined by the :delay option. If the command
-	# fails to run, it is disabled and the format and output string are set
-	# to nil.
-	#
-	def poll
-		return false if @sample and (Time.now - (self.last_poll || 0 )) < self.delay
-
-		self.last_poll = Time.now
-
-		case self.cmd
-		when Proc
-			@sample = self.cmd.call.to_s.chomp
-		else
-			output = `#{self.cmd.to_s} 2>&1`.chomp
-			if $? == 0
-				@sample = output
-			else
-				self.format = nil
-				@sample = '[%s: Error!] ' % self.name
-				raise output
-			end
-		end
-
-		if self.scan_regex
-			extract = @sample.scan( Regexp.new( self.scan_regex ) ).flatten
-			@sample = extract.join( ' ' ) unless extract.empty?
-		end
-
-		true
-	end
-
-	# Returns the output of the command this Poll runs. It will return a formatted string if
-	# the +:format+ option is correctly specified.
-	#
-	def to_s
-		return @sample.to_s if self.format.nil?
-		self.format % @sample
-	end
-
-end
-

spec/dock_driver/dock_spec.rb

-
-require 'pathname'
-require 'fileutils'
-require 'dock_driver/dock'
-
-describe DockDriver::Dock do
-
-	subject do
-		@conf = Pathname( __FILE__ ).parent.parent.parent
-		@conf += 'data/example.dock_driver.yml'
-
-		# Use very common binaries that are all but guaranteed to be in the
-		# user's path so that we can maintain a meaningful example config file.
-		#
-		dock = Dock.new( @conf.to_s, nil, 'cat' )
-		dock.polls.values.each { |p| p.cmd = 'echo test' }
-		dock
-	end
-
-	it 'should load a config file' do
-		subject.dock_command.should_not be nil
-		subject.template.should_not be nil
-		subject.polls.should have_at_least( 2 ).things
-		subject.polls.values.each { |p| p.should be_a_kind_of Poll }
-	end
-
-	it 'should only reload a config file with a more recent mtime' do
-		subject.send( :load_config ).should be false
-		sleep 0.1
-		FileUtils.touch @conf
-		subject.send( :load_config ).should be true
-		subject.send( :load_config ).should be false
-	end
-
-end
-

spec/dock_driver/poll_spec.rb

-
-require 'dock_driver/poll'
-require 'dock_driver/data_rate_poll'
-
-include DockDriver
-
-shared_examples 'a poll' do
-
-	subject do
-		described_class.new( :cmd => 'echo test' )
-	end
-
-	it 'should execute shell commands' do
-		lambda do
-			subject.poll.should == true
-		end.should_not raise_error
-	end
-
-	it 'should execute blocks' do
-		subject.cmd = lambda {}
-		lambda do
-			subject.poll.should == true
-		end.should_not raise_error
-	end
-
-	it 'should cache command output' do
-		subject.delay = 10
-		subject.poll.should == true
-		subject.poll.should == false
-
-		subject.delay = 0
-		subject.poll.should == true
-		subject.poll.should == true
-	end
-
-end
-
-describe Poll do
-	it_behaves_like 'a poll'
-end
-
-describe DataRatePoll do
-	it_behaves_like 'a poll'
-end
-

spec/dock_driver_spec.rb

-
-require 'dock_driver'
-
-describe DockDriver do
-
-	it 'should define a standard version number' do
-		defined?( VERSION ).should == 'constant'
-		VERSION.should =~ /^\d+\.\d+\.\d+$/
-	end
-
-	it 'should define a mercurial revision' do
-		defined?( REVISION ).should == 'constant'
-		REVISION.should =~ /^Revision: [[:xdigit:]]+ $/
-	end
-
-end
-
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.