]>
Commit | Line | Data |
---|---|---|
8b6154e9 I |
1 | #!/usr/bin/env ruby |
2 | ||
3 | require "time" | |
4 | require "ncurses" | |
5 | require "inifile" | |
6 | require "open3" | |
cfddfd90 I |
7 | require 'optparse' |
8 | require 'ostruct' | |
8b6154e9 I |
9 | |
10 | class IO | |
11 | def readline_nonblock | |
12 | buffer = "" | |
13 | buffer << read_nonblock(1) while buffer[-1] != "\n" | |
14 | buffer | |
15 | rescue IO::WaitReadable => blocking | |
16 | if (not buffer.empty?) | |
17 | ungetc(buffer) | |
18 | end | |
19 | raise blocking | |
20 | end | |
21 | end | |
22 | ||
cfddfd90 I |
23 | class OptParse |
24 | def parse(args) | |
25 | ||
26 | options = OpenStruct.new() | |
27 | options.inifile = nil | |
28 | ||
29 | opt_parser = OptionParser.new() do |opts| | |
30 | ||
31 | opts.banner = "Usage: monitor [options]" | |
32 | opts.separator "" | |
33 | ||
34 | opts.on( '-f', "-f ini_file", "chose a different initialization file") do |ini| | |
35 | options.inifile = ini | |
36 | end | |
37 | end | |
38 | opt_parser.parse!(args) | |
39 | return options | |
40 | end | |
41 | end | |
42 | ||
8b6154e9 I |
43 | class List_Win |
44 | def initialize(inifile) | |
45 | @params = inifile | |
46 | @win = Ncurses::WINDOW.new(0, Ncurses.COLS()/4, 0, 0) | |
47 | @win.border(*([0]*8)) | |
48 | @win.keypad(true) | |
49 | @win.timeout(1000) | |
50 | @win.refresh() | |
51 | @entry = 0 | |
52 | end | |
53 | ||
54 | def getch() | |
55 | @win.getch() | |
56 | end | |
57 | def clear() | |
58 | @win.clear() | |
59 | end | |
60 | def resize(x,y) | |
61 | @win.resize(x,y) | |
62 | end | |
63 | def move(x,y) | |
64 | @win.move(x,y) | |
65 | end | |
66 | def refresh() | |
67 | @win.refresh() | |
68 | end | |
69 | ||
70 | def print_list(entry=nil) | |
71 | if(not entry.nil?) | |
72 | @entry = entry | |
73 | end | |
74 | i = 0 | |
75 | @params.each_section do |section| | |
76 | if(@entry == i) | |
77 | @win.attron(Ncurses::A_REVERSE) | |
78 | end | |
79 | @win.move(i+1,1) | |
80 | print_line(@win,@params[section]['Name']) | |
81 | if(@entry == i) | |
82 | @win.attroff(Ncurses::A_REVERSE) | |
83 | end | |
84 | i = i+1 | |
85 | end | |
86 | @win.border(*([0]*8)) | |
87 | @win.move(0,3) | |
88 | @win.addstr("Menu") | |
89 | @win.refresh() | |
90 | end | |
91 | end | |
92 | ||
93 | class Buff_Win | |
94 | def initialize(winsize,winpos,params) | |
95 | @params = params | |
96 | @win = Ncurses::WINDOW.new(0, winsize, 0, winpos) | |
97 | @panel = Ncurses::Panel::PANEL.new(@win) | |
98 | if(@params['Type'] == 'oneshot') | |
99 | @buffer = Buffer.new(0) | |
100 | else | |
101 | @buffer = Buffer.new(@params['Buffer'].to_i) | |
102 | end | |
103 | @proc = nil | |
104 | @curr_offset = 0 | |
105 | @hscroll = 0 | |
106 | update_date() | |
107 | ||
108 | spawn_proc() | |
109 | print_buffer() | |
110 | end | |
111 | ||
112 | def move_resize(winsize,winpos) | |
113 | newwin = Ncurses::WINDOW.new(0, winsize, 0, winpos) | |
114 | Ncurses::Panel.replace_panel(@panel, newwin) | |
115 | Ncurses.delwin(@win) | |
116 | @win = newwin | |
117 | print_buffer() | |
118 | end | |
119 | def update_date() | |
120 | @last_update = Time.now | |
121 | @date = @last_update.strftime("%F %R:%S") | |
122 | end | |
123 | ||
124 | def win_border() | |
125 | @win.border(*([0]*8)) | |
126 | win_header() | |
127 | end | |
128 | def win_header() | |
129 | @win.move(0,3) | |
130 | @win.addnstr(@params['Name'],@win.getmaxx-@date.length-10) | |
131 | @win.move(0,@win.getmaxx-@date.length-2) | |
132 | @win.addstr(@date) | |
133 | end | |
134 | def refresh() | |
135 | @win.refresh() | |
136 | end | |
137 | def clear() | |
138 | @win.clear() | |
139 | end | |
140 | ||
141 | def show_win() | |
142 | Ncurses::Panel.top_panel(@panel) | |
143 | Ncurses::Panel.update_panels | |
144 | end | |
145 | ||
146 | def hscroll(scroll=0) | |
147 | @hscroll += scroll | |
148 | if(@hscroll < 0) | |
149 | @hscroll = 0 | |
150 | end | |
151 | if(@hscroll > @buffer.maxlen()-@win.getmaxx+3) | |
152 | @hscroll = @buffer.maxlen()-@win.getmaxx+3 | |
153 | end | |
154 | print_buffer() | |
155 | refresh() | |
156 | end | |
157 | def scroll(scroll=0,goto=nil,fact=nil) | |
158 | if (not fact.nil?) | |
159 | scroll = (fact * @win.getmaxy.to_f).to_i | |
160 | elsif (not goto.nil?) | |
161 | @curr_offset = (goto * @buffer.size()).to_i | |
162 | scroll = 0 | |
163 | end | |
164 | #@curr_offset -= @win.getmaxy/2 | |
165 | @curr_offset += scroll | |
166 | if(@curr_offset < 0) | |
167 | @curr_offset = 0 | |
168 | end | |
169 | if(@curr_offset > @buffer.size()-@win.getmaxy+2) | |
170 | @curr_offset = @buffer.size()-@win.getmaxy+2 | |
171 | end | |
172 | print_buffer() | |
173 | refresh() | |
174 | end | |
175 | ||
176 | def print_buffer() | |
9b96f7cb | 177 | #clear() |
8b6154e9 I |
178 | win_border() |
179 | j = 1 | |
180 | @buffer.yield(@win.getmaxy-2,@curr_offset) { |l,type| | |
181 | @win.move(j,1) | |
182 | if(type == 1) then @win.attron(Ncurses.COLOR_PAIR(1)) end | |
183 | print_line(@win,l,hscroll=@hscroll) | |
184 | if(type == 1) then @win.attroff(Ncurses.COLOR_PAIR(1)) end | |
185 | j = j+1 | |
186 | } | |
187 | if(@buffer.has_before?) | |
188 | @win.move(2,@win.getmaxx-1) | |
189 | @win.attron(Ncurses::A_REVERSE) | |
190 | @win.addstr("↑") | |
191 | @win.attroff(Ncurses::A_REVERSE) | |
192 | end | |
193 | if(@buffer.has_after?) | |
194 | @win.move(@win.getmaxy-2,@win.getmaxx-1) | |
195 | @win.attron(Ncurses::A_REVERSE) | |
196 | @win.addstr("↓") | |
197 | @win.attroff(Ncurses::A_REVERSE) | |
198 | end | |
199 | end | |
200 | ||
201 | def proc_readlines() | |
202 | begin | |
203 | while true | |
204 | @buffer.push(@proc.readline_nonblock) | |
205 | update_date() | |
206 | end | |
207 | rescue IO::WaitReadable | |
208 | end | |
209 | end | |
210 | ||
9b96f7cb | 211 | def update(force=false) |
8b6154e9 I |
212 | if(@params['Type'] == 'continuous') |
213 | proc_readlines() | |
214 | end | |
215 | if(@params['Type'] == 'oneshot') | |
9b96f7cb | 216 | if(force or (Time.now - @last_update > @params['Periodic'].to_i)) |
8b6154e9 I |
217 | @buffer.clear() |
218 | spawn_proc() | |
cfddfd90 | 219 | clear() |
8b6154e9 I |
220 | end |
221 | end | |
222 | print_buffer() | |
223 | end | |
224 | ||
225 | def spawn_proc() | |
226 | if(@params['Type'] == 'oneshot') | |
227 | update_date() | |
228 | Open3.popen3(@params["Command"]) { |i,o,e,t| | |
229 | while ((not o.eof?) or (not e.eof?)) | |
230 | rs = IO.select([o,e],nil)[0] | |
231 | r = (rs[0].eof?)? rs[1] : rs[0] | |
232 | ||
233 | if r.fileno == o.fileno | |
234 | @buffer.push(r.readline) | |
235 | elsif r.fileno == e.fileno | |
236 | @buffer.push(r.readline,type=1) | |
237 | end | |
238 | end | |
239 | } | |
240 | elsif(@params['Type'] == 'continuous') | |
241 | @proc = IO.popen(@params["Command"]) | |
242 | proc_readlines() | |
243 | end | |
244 | end | |
245 | end | |
246 | ||
247 | class Buffer | |
248 | def initialize(size) | |
249 | @size = size | |
250 | @buff = [] | |
251 | @buff_type = [] | |
252 | @current = 0 | |
253 | @wrap = false | |
254 | @before = false | |
255 | @after = false | |
256 | end | |
257 | def size() | |
258 | return @buff.length | |
259 | end | |
260 | def maxlen() | |
261 | maxlen = 0 | |
262 | @buff.each do |string| | |
263 | if string.length > maxlen | |
264 | maxlen = string.length | |
265 | end | |
266 | end | |
267 | return maxlen | |
268 | end | |
269 | ||
270 | def push(string,type=0) | |
271 | if(string.chomp.empty?) then string = " " end | |
272 | string.split( /\r?\n/ ).each do |line| | |
273 | @buff[@current] = line | |
274 | @buff_type[@current] = type | |
275 | if(@size > 0) | |
276 | @current = (1+@current) % @size | |
277 | else | |
278 | @current = 1+@current | |
279 | end | |
280 | if(@current == 0) then @wrap = true end | |
281 | end | |
282 | end | |
283 | def yield(size,offset=0,&block) | |
9b96f7cb | 284 | if(size < 0) then size = 0 end |
8b6154e9 I |
285 | range = Range.new(0,@current-1).to_a |
286 | if(@wrap) | |
287 | range = Range.new(@current,@size-1).to_a + range | |
288 | end | |
289 | range = range.last(size+offset)[0,size] | |
290 | @before = (size+offset < @buff.length) | |
291 | @after = (offset != 0 and size < @buff.length) | |
292 | if(block) | |
293 | range.each do |i| | |
294 | yield [@buff[i],@buff_type[i]] | |
295 | end | |
296 | else | |
297 | return range.collect{|r| [@buff[r],@buff_type[r]]} | |
298 | end | |
299 | end | |
300 | def has_after?() | |
301 | return @after | |
302 | end | |
303 | def has_before?() | |
304 | return @before | |
305 | end | |
306 | def clear() | |
307 | @current = 0 | |
308 | @buff = [] | |
309 | @buff_type = [] | |
310 | end | |
311 | end | |
312 | ||
cfddfd90 I |
313 | def find_ini(option_inifile) |
314 | if(not option_inifile.nil?) | |
315 | inifile = option_inifile | |
316 | elsif(ENV.has_key?('MONITOR_RC')) | |
317 | inifile = ENV['MONITOR_RC'] | |
318 | elsif(Process.uid == 0) | |
319 | inifile = "/etc/monitor.rc" | |
320 | else | |
321 | inifile = ENV['HOME']+"/.monitorrc" | |
322 | end | |
323 | return inifile | |
324 | end | |
325 | ||
326 | def read_ini(ini) | |
327 | inifile = IniFile.load(ini) | |
328 | if(inifile.nil?) | |
329 | puts "Initialization file not found or not readable" | |
330 | exit | |
331 | end | |
8b6154e9 I |
332 | return inifile |
333 | end | |
334 | ||
335 | def print_line(win, str, hscroll=0) | |
9b96f7cb I |
336 | revert_color = false |
337 | str[0,5].match(/\033\[3(.)m/) { |c| #Line starts with an escape sequence. We handle that `a la xterm` | |
338 | Ncurses.init_pair(10, c[1].to_i, Ncurses::COLOR_BLACK) | |
339 | win.attron(Ncurses.COLOR_PAIR(10)) | |
340 | revert_color = true | |
341 | str = str[5,str.length] | |
342 | } | |
343 | str = str.gsub("\011"," ") | |
344 | #Any other control char is ignored and escaped | |
345 | str = str.gsub(/[[:cntrl:]]/) { |m| | |
346 | "^"+(m.ord + 64).chr | |
347 | } | |
8b6154e9 I |
348 | if(hscroll > 0) |
349 | strcut = str[hscroll,str.length] | |
350 | if(strcut.nil? or strcut.empty?) | |
351 | str = "" | |
352 | else | |
353 | str = "…"+strcut | |
354 | end | |
355 | end | |
356 | strlen = str.length | |
357 | winlen = win.getmaxx-win.getcurx-1 | |
358 | if(strlen <= winlen) | |
359 | win.addstr(str + " "*(winlen-strlen)) | |
360 | else | |
361 | win.addstr(str[0,winlen-1]+"…") | |
362 | end | |
9b96f7cb I |
363 | if(revert_color) |
364 | win.attroff(Ncurses.COLOR_PAIR(10)) | |
365 | end | |
8b6154e9 I |
366 | end |
367 | ||
368 | def make_bufwins(inifile) | |
369 | bufwins = [] | |
370 | inifile.each_section do |section| | |
371 | bufwin = Buff_Win.new(Ncurses.COLS()-Ncurses.COLS()/4, | |
372 | Ncurses.COLS()/4, | |
373 | inifile[section]) | |
374 | bufwins.push(bufwin) | |
375 | end | |
376 | return bufwins | |
377 | end | |
378 | ||
379 | def update_buffers(bufwins) | |
380 | bufwins.each do |bufwin| | |
381 | bufwin.update() | |
382 | end | |
383 | end | |
384 | ||
385 | def redraw_all(list,bufwins,curr_bufwin) | |
386 | bufwins.each do |bufwin| | |
387 | bufwin.move_resize(Ncurses.COLS()-Ncurses.COLS()/4,Ncurses.COLS()/4) | |
388 | end | |
389 | list.resize(Ncurses.LINES(), Ncurses.COLS()/4) | |
390 | list.clear() | |
391 | list.print_list() | |
392 | list.refresh() | |
393 | curr_bufwin.refresh() | |
394 | end | |
cfddfd90 I |
395 | |
396 | ||
397 | options = OptParse.new().parse(ARGV) | |
398 | inifile = read_ini(find_ini(options.inifile)) | |
8b6154e9 I |
399 | begin |
400 | # initialize ncurses | |
401 | Ncurses.initscr | |
402 | Ncurses.start_color | |
403 | Ncurses.cbreak # provide unbuffered input | |
404 | Ncurses.noecho # turn off input echoing | |
405 | #Ncurses.nonl # turn off newline translation | |
406 | #Ncurses.stdscr.intrflush(false) # turn off flush-on-interrupt | |
407 | Ncurses.stdscr.keypad(true) # turn on keypad mode | |
408 | Ncurses.init_pair(1, Ncurses::COLOR_RED, Ncurses::COLOR_BLACK) | |
9b96f7cb | 409 | Ncurses.init_pair(10, Ncurses::COLOR_WHITE, Ncurses::COLOR_BLACK) |
8b6154e9 I |
410 | |
411 | ||
8b6154e9 I |
412 | list = List_Win.new(inifile) |
413 | bufwins = make_bufwins(inifile) | |
414 | entry = 0 | |
415 | cur_bufwin = bufwins[entry] | |
416 | cur_bufwin.show_win() | |
417 | list.print_list() | |
418 | while(ch = list.getch()) do | |
419 | case(ch) | |
420 | when "n".ord | |
421 | entry = (entry +1) % bufwins.length | |
422 | cur_bufwin = bufwins[entry] | |
423 | cur_bufwin.show_win() | |
424 | list.print_list(entry=entry) | |
425 | when "p".ord | |
426 | entry = (entry -1) % bufwins.length | |
427 | cur_bufwin = bufwins[entry] | |
428 | cur_bufwin.show_win() | |
429 | list.print_list(entry=entry) | |
430 | when 12 #ctrl+L | |
431 | redraw_all(list,bufwins,cur_bufwin) | |
9b96f7cb I |
432 | when 18 #ctrl+R |
433 | cur_bufwin.update(force=true) | |
8b6154e9 I |
434 | when Ncurses::KEY_RESIZE |
435 | redraw_all(list,bufwins,cur_bufwin) | |
436 | when Ncurses::KEY_LEFT | |
437 | cur_bufwin.hscroll(scroll=-1) | |
438 | when Ncurses::KEY_RIGHT | |
439 | cur_bufwin.hscroll(scroll=1) | |
440 | when Ncurses::KEY_DOWN | |
441 | cur_bufwin.scroll(scroll=-1) | |
442 | when Ncurses::KEY_UP | |
443 | cur_bufwin.scroll(scroll=1) | |
444 | when Ncurses::KEY_NPAGE | |
445 | cur_bufwin.scroll(scroll=0,goto=nil,fact=-0.75) | |
446 | when Ncurses::KEY_PPAGE | |
447 | cur_bufwin.scroll(scroll=0,goto=nil,fact=0.75) | |
448 | when Ncurses::KEY_HOME | |
449 | cur_bufwin.scroll(scroll=0,goto=1.0) | |
450 | when Ncurses::KEY_END | |
451 | cur_bufwin.scroll(scroll=0,goto=0.0) | |
452 | when Ncurses::ERR | |
453 | update_buffers(bufwins) | |
454 | cur_bufwin.show_win() | |
455 | when "q".ord | |
456 | break | |
457 | else | |
458 | next | |
459 | end | |
460 | end | |
461 | ||
462 | ensure | |
463 | Ncurses.echo | |
464 | Ncurses.nocbreak | |
465 | Ncurses.nl | |
466 | Ncurses.endwin | |
467 | end |