#!/usr/bin/env ruby
require "time"
require "ncurses"
require "inifile"
require "open3"
require 'optparse'
require 'ostruct'
class IO
def readline_nonblock
buffer = ""
buffer << read_nonblock(1) while buffer[-1] != "\n"
buffer
rescue IO::WaitReadable => blocking
if (not buffer.empty?)
ungetc(buffer)
end
raise blocking
end
end
class OptParse
def parse(args)
options = OpenStruct.new()
options.inifile = nil
opt_parser = OptionParser.new() do |opts|
opts.banner = "Usage: monitor [options]"
opts.separator ""
opts.on( '-f', "-f ini_file", "chose a different initialization file") do |ini|
options.inifile = ini
end
end
opt_parser.parse!(args)
return options
end
end
class List_Win
def initialize(inifile)
@params = inifile
@win = Ncurses::WINDOW.new(0, Ncurses.COLS()/4, 0, 0)
@win.border(*([0]*8))
@win.keypad(true)
@win.timeout(1000)
@win.refresh()
@entry = 0
end
def getch()
@win.getch()
end
def clear()
@win.clear()
end
def resize(x,y)
@win.resize(x,y)
end
def move(x,y)
@win.move(x,y)
end
def refresh()
@win.refresh()
end
def print_list(entry=nil)
if(not entry.nil?)
@entry = entry
end
i = 0
@params.each_section do |section|
if(@entry == i)
@win.attron(Ncurses::A_REVERSE)
end
@win.move(i+1,1)
print_line(@win,@params[section]['Name'])
if(@entry == i)
@win.attroff(Ncurses::A_REVERSE)
end
i = i+1
end
@win.border(*([0]*8))
@win.move(0,3)
@win.addstr("Menu")
@win.refresh()
end
end
class Buff_Win
def initialize(winsize,winpos,params)
@params = params
@win = Ncurses::WINDOW.new(0, winsize, 0, winpos)
@panel = Ncurses::Panel::PANEL.new(@win)
if(@params['Type'] == 'oneshot')
@buffer = Buffer.new(0)
else
@buffer = Buffer.new(@params['Buffer'].to_i)
end
@proc = nil
@curr_offset = 0
@hscroll = 0
update_date()
spawn_proc()
print_buffer()
end
def move_resize(winsize,winpos)
newwin = Ncurses::WINDOW.new(0, winsize, 0, winpos)
Ncurses::Panel.replace_panel(@panel, newwin)
Ncurses.delwin(@win)
@win = newwin
print_buffer()
end
def update_date()
@last_update = Time.now
@date = @last_update.strftime("%F %R:%S")
end
def win_border()
@win.border(*([0]*8))
win_header()
end
def win_header()
@win.move(0,3)
@win.addnstr(@params['Name'],@win.getmaxx-@date.length-10)
@win.move(0,@win.getmaxx-@date.length-2)
@win.addstr(@date)
end
def refresh()
@win.refresh()
end
def clear()
@win.clear()
end
def show_win()
Ncurses::Panel.top_panel(@panel)
Ncurses::Panel.update_panels
end
def hscroll(scroll=0)
@hscroll += scroll
if(@hscroll < 0)
@hscroll = 0
end
if(@hscroll > @buffer.maxlen()-@win.getmaxx+3)
@hscroll = @buffer.maxlen()-@win.getmaxx+3
end
print_buffer()
refresh()
end
def scroll(scroll=0,goto=nil,fact=nil)
if (not fact.nil?)
scroll = (fact * @win.getmaxy.to_f).to_i
elsif (not goto.nil?)
@curr_offset = (goto * @buffer.size()).to_i
scroll = 0
end
#@curr_offset -= @win.getmaxy/2
@curr_offset += scroll
if(@curr_offset < 0)
@curr_offset = 0
end
if(@curr_offset > @buffer.size()-@win.getmaxy+2)
@curr_offset = @buffer.size()-@win.getmaxy+2
end
print_buffer()
refresh()
end
def print_buffer()
#clear()
win_border()
j = 1
@buffer.yield(@win.getmaxy-2,@curr_offset) { |l,type|
@win.move(j,1)
if(type == 1) then @win.attron(Ncurses.COLOR_PAIR(1)) end
print_line(@win,l,hscroll=@hscroll)
if(type == 1) then @win.attroff(Ncurses.COLOR_PAIR(1)) end
j = j+1
}
if(@buffer.has_before?)
@win.move(2,@win.getmaxx-1)
@win.attron(Ncurses::A_REVERSE)
@win.addstr("↑")
@win.attroff(Ncurses::A_REVERSE)
end
if(@buffer.has_after?)
@win.move(@win.getmaxy-2,@win.getmaxx-1)
@win.attron(Ncurses::A_REVERSE)
@win.addstr("↓")
@win.attroff(Ncurses::A_REVERSE)
end
end
def proc_readlines()
begin
while true
@buffer.push(@proc.readline_nonblock)
update_date()
end
rescue IO::WaitReadable
end
end
def update(force=false)
if(@params['Type'] == 'continuous')
proc_readlines()
end
if(@params['Type'] == 'oneshot')
if(force or (Time.now - @last_update > @params['Periodic'].to_i))
@buffer.clear()
spawn_proc()
clear()
end
end
print_buffer()
end
def spawn_proc()
if(@params['Type'] == 'oneshot')
update_date()
Open3.popen3(@params["Command"]) { |i,o,e,t|
while ((not o.eof?) or (not e.eof?))
rs = IO.select([o,e],nil)[0]
r = (rs[0].eof?)? rs[1] : rs[0]
if r.fileno == o.fileno
@buffer.push(r.readline)
elsif r.fileno == e.fileno
@buffer.push(r.readline,type=1)
end
end
}
elsif(@params['Type'] == 'continuous')
@proc = IO.popen(@params["Command"])
proc_readlines()
end
end
end
class Buffer
def initialize(size)
@size = size
@buff = []
@buff_type = []
@current = 0
@wrap = false
@before = false
@after = false
end
def size()
return @buff.length
end
def maxlen()
maxlen = 0
@buff.each do |string|
if string.length > maxlen
maxlen = string.length
end
end
return maxlen
end
def push(string,type=0)
if(string.chomp.empty?) then string = " " end
string.split( /\r?\n/ ).each do |line|
@buff[@current] = line
@buff_type[@current] = type
if(@size > 0)
@current = (1+@current) % @size
else
@current = 1+@current
end
if(@current == 0) then @wrap = true end
end
end
def yield(size,offset=0,&block)
if(size < 0) then size = 0 end
range = Range.new(0,@current-1).to_a
if(@wrap)
range = Range.new(@current,@size-1).to_a + range
end
range = range.last(size+offset)[0,size]
@before = (size+offset < @buff.length)
@after = (offset != 0 and size < @buff.length)
if(block)
range.each do |i|
yield [@buff[i],@buff_type[i]]
end
else
return range.collect{|r| [@buff[r],@buff_type[r]]}
end
end
def has_after?()
return @after
end
def has_before?()
return @before
end
def clear()
@current = 0
@buff = []
@buff_type = []
end
end
def find_ini(option_inifile)
if(not option_inifile.nil?)
inifile = option_inifile
elsif(ENV.has_key?('MONITOR_RC'))
inifile = ENV['MONITOR_RC']
elsif(Process.uid == 0)
inifile = "/etc/monitor.rc"
else
inifile = ENV['HOME']+"/.monitorrc"
end
return inifile
end
def read_ini(ini)
inifile = IniFile.load(ini)
if(inifile.nil?)
puts "Initialization file not found or not readable"
exit
end
return inifile
end
def print_line(win, str, hscroll=0)
revert_color = false
str[0,5].match(/\033\[3(.)m/) { |c| #Line starts with an escape sequence. We handle that `a la xterm`
Ncurses.init_pair(10, c[1].to_i, Ncurses::COLOR_BLACK)
win.attron(Ncurses.COLOR_PAIR(10))
revert_color = true
str = str[5,str.length]
}
str = str.gsub("\011"," ")
#Any other control char is ignored and escaped
str = str.gsub(/[[:cntrl:]]/) { |m|
"^"+(m.ord + 64).chr
}
if(hscroll > 0)
strcut = str[hscroll,str.length]
if(strcut.nil? or strcut.empty?)
str = ""
else
str = "…"+strcut
end
end
strlen = str.length
winlen = win.getmaxx-win.getcurx-1
if(strlen <= winlen)
win.addstr(str + " "*(winlen-strlen))
else
win.addstr(str[0,winlen-1]+"…")
end
if(revert_color)
win.attroff(Ncurses.COLOR_PAIR(10))
end
end
def make_bufwins(inifile)
bufwins = []
inifile.each_section do |section|
bufwin = Buff_Win.new(Ncurses.COLS()-Ncurses.COLS()/4,
Ncurses.COLS()/4,
inifile[section])
bufwins.push(bufwin)
end
return bufwins
end
def update_buffers(bufwins)
bufwins.each do |bufwin|
bufwin.update()
end
end
def redraw_all(list,bufwins,curr_bufwin)
bufwins.each do |bufwin|
bufwin.move_resize(Ncurses.COLS()-Ncurses.COLS()/4,Ncurses.COLS()/4)
end
list.resize(Ncurses.LINES(), Ncurses.COLS()/4)
list.clear()
list.print_list()
list.refresh()
curr_bufwin.refresh()
end
options = OptParse.new().parse(ARGV)
inifile = read_ini(find_ini(options.inifile))
begin
# initialize ncurses
Ncurses.initscr
Ncurses.start_color
Ncurses.cbreak # provide unbuffered input
Ncurses.noecho # turn off input echoing
#Ncurses.nonl # turn off newline translation
#Ncurses.stdscr.intrflush(false) # turn off flush-on-interrupt
Ncurses.stdscr.keypad(true) # turn on keypad mode
Ncurses.init_pair(1, Ncurses::COLOR_RED, Ncurses::COLOR_BLACK)
Ncurses.init_pair(10, Ncurses::COLOR_WHITE, Ncurses::COLOR_BLACK)
list = List_Win.new(inifile)
bufwins = make_bufwins(inifile)
entry = 0
cur_bufwin = bufwins[entry]
cur_bufwin.show_win()
list.print_list()
while(ch = list.getch()) do
case(ch)
when "n".ord
entry = (entry +1) % bufwins.length
cur_bufwin = bufwins[entry]
cur_bufwin.show_win()
list.print_list(entry=entry)
when "p".ord
entry = (entry -1) % bufwins.length
cur_bufwin = bufwins[entry]
cur_bufwin.show_win()
list.print_list(entry=entry)
when 12 #ctrl+L
redraw_all(list,bufwins,cur_bufwin)
when 18 #ctrl+R
cur_bufwin.update(force=true)
when Ncurses::KEY_RESIZE
redraw_all(list,bufwins,cur_bufwin)
when Ncurses::KEY_LEFT
cur_bufwin.hscroll(scroll=-1)
when Ncurses::KEY_RIGHT
cur_bufwin.hscroll(scroll=1)
when Ncurses::KEY_DOWN
cur_bufwin.scroll(scroll=-1)
when Ncurses::KEY_UP
cur_bufwin.scroll(scroll=1)
when Ncurses::KEY_NPAGE
cur_bufwin.scroll(scroll=0,goto=nil,fact=-0.75)
when Ncurses::KEY_PPAGE
cur_bufwin.scroll(scroll=0,goto=nil,fact=0.75)
when Ncurses::KEY_HOME
cur_bufwin.scroll(scroll=0,goto=1.0)
when Ncurses::KEY_END
cur_bufwin.scroll(scroll=0,goto=0.0)
when Ncurses::ERR
update_buffers(bufwins)
cur_bufwin.show_win()
when "q".ord
break
else
next
end
end
ensure
Ncurses.echo
Ncurses.nocbreak
Ncurses.nl
Ncurses.endwin
end