#!/usr/bin/env ruby # dwm-d.rb: a simple app that periodically emits text to standard output, controllable by # DRb. Suitable for use with dwm's stdin status bar. class DwmStatus STATUS_LINE_INTERPOLATION_REGEX = Regexp.new(/:([A-Za-z\-_]+)(\((.+?)\))?/) # The format of the status line is "blah blah :blah :time(%c) etc." where: # - any colon followed by [A-Za-z\-_] will be interpolated # - any parentheses following that chunk will be interpreted as wrapping arguments # - The contents of those parentheses will be split on semicolons and passed as arguments, if reasonable attr_accessor :status_line # Interpolation can be drawn from one of two sources: # - The status_items hash # Items in status_items are either a Proc or something with to_s. If the item is a Proc, it's eligible # to have arguments passed in. # You can set a Proc over DRb by using 'set_status_item_proc' and passing a string which evaluates to # a proc. That's the best I can do right now. # - Built-in methods # These are tried after trying the hash and are simply methods on DwmStatus. date(fmt) is the most obvious. attr_accessor :status_items # The DwmStatus will emit to this IO. All it has to do is support puts. attr_accessor :output # The DwmStatus will wait this many seconds between emitting. attr_accessor :timeout def initialize self.status_line = "STATUS :date(%-I:%M %p | %d %B %Y)" self.status_items = {} self.output = $stdout self.timeout = 1 end def interpolate_status @status_line.gsub(STATUS_LINE_INTERPOLATION_REGEX) do |match| interpolator = find_interpolator($1) if interpolator if interpolator.respond_to?(:call) if $3 interpolator.call(*($3.split(';'))) rescue '$ERROR$' else interpolator.call rescue '$ERROR$' end else interpolator end else match end end end def insert_date(fmt) Time.now.strftime(fmt) end def insert_exec(cmd, *args) `#{cmd} #{args.join(' ')}` end def insert_mpd_now_playing(trailer = ' |', leader = nil) mpd_info = `mpc status`.split("\n") if mpd_info.length == 3 leader.to_s + mpd_info[0].chomp + ' ' + (mpd_info[1].split(' ', 2).first) + trailer.to_s end end def find_interpolator(term) status_items.fetch(term) { self.method('insert_' + term) rescue nil } end def set_status_item_proc(term, proc_str) status_items[term] = eval(proc_str) end def set_status_item(term, item) status_items[term] = item end def emit self.output.puts(interpolate_status) self.output.flush end def emit_loop until defined? @time_to_die emit sleep self.timeout end end def quit @time_to_die = true end end def start_drb(status, uri = 'druby://localhost:12120') require 'drb/drb' dserv = DRb.start_service(uri, status) status.emit_loop dserv.stop_service end if ARGV.empty? start_drb(DwmStatus.new) else require 'drb' dobj = DRbObject.new_with_uri('druby://localhost:12120') meth = ARGV.shift puts dobj.send(meth, *ARGV) end