3 Sample command line application
11 from optparse
import OptionParser
13 from oscopy
.readers
.reader
import ReadError
15 from matplotlib
.backends
.backend_gtkagg
import FigureCanvasGTKAgg
as FigureCanvas
16 from matplotlib
.backends
.backend_gtkagg
import NavigationToolbar2GTKAgg
as NavigationToolbar
18 class OscopyApp(object):
19 """ Analyse command arguments and call function from Cmd
23 self
.cmds
= oscopy
.Context()
27 self
.hist_file
= ".oscopy_hist"
29 # Readline configuration
30 if not os
.path
.exists(self
.hist_file
):
31 f
= open(self
.hist_file
, "w")
34 readline
.read_history_file(self
.hist_file
)
36 # Parse command line arguments
38 # -b : batch mode, read commands from file
39 # -i : interactive mode, do not quit at the end of batch file
40 # -q : do not display startup message
41 parser
= OptionParser()
42 parser
.add_option("-b", "--batch", dest
="fn",\
43 help="Execute commands in FILE then exit",\
45 parser
.add_option("-i", "--interactive", action
="store_true",\
47 help="Go to interactive mode after executing batch file")
48 parser
.add_option("-q", "--quiet", action
="store_true",\
50 help="Do not display startup message")
51 (options
, args
) = parser
.parse_args()
52 if options
.fn
is None:
56 f
= open(options
.fn
, 'r')
58 print "Unable to access batch file:", e
60 if options
.inter
== True:
66 if options
.quiet
is None:
67 print "This is oscopy, a program to view electrical simulation results\n\
68 Copyright (C) 2009 Arnaud Gardelein.\n\
69 This is free software, you are invited to redistribute it \n\
70 under certain conditions.\n\
71 There is ABSOLUTELY NO WARRANTY; not even for MERCHANTIBILITY or\n\
72 FITNESS FOR A PARTICULAR PURPOSE."
74 # Current graph and current figure
75 self
._current
_figure
= None
76 self
._current
_graph
= None
80 self
.loop(p
, f
, batch
)
82 def create(self
, args
):
84 print "Usage : create [SIG [, SIG [, SIG]...]]"
85 print " Create a new figure, set_ it as current, add the signals"
87 self
.cmds
.create(self
.get_signames(args
))
88 self
._current
_figure
= self
.cmds
.figures
[len(self
.cmds
.figures
) - 1]
89 if self
._current
_figure
.graphs
:
90 self
._current
_graph
=\
91 self
._current
_figure
.graphs
[len(\
92 self
._current
_figure
.graphs
) - 1]
94 self
._current
_graph
= None
96 def destroy(self
, args
):
98 print "Usage : destroy FIG#"
99 print " Destroy a figure"
101 self
.cmds
.destroy(eval(args
))
102 # Go back to the first graph of the first figure or None
103 if len(self
.cmds
.figures
):
104 self
._current
_figure
= self
.cmds
.figures
[0]
105 if self
._current
_figure
.graphs
:
106 self
._current
_graph
= self
._current
_figure
.graphs
[0]
108 self
._current
_graph
= None
110 self
._current
_figure
= None
111 self
._current
_graph
= None
113 def select(self
, args
):
115 print "Usage: select FIG#-GRAPH#"
116 print " Select the current figure and the current graph"
123 print "Usage: select FIG#-GRAPH#"
125 # self.cmds.current = num, gn
126 self
._current
_figure
= self
.cmds
.figures
[num
- 1]
127 self
._current
_graph
= self
._current
_figure
.graphs
[gn
- 1]
129 def layout(self
, args
):
131 print "Usage : layout horiz|vert|quad"
132 print " Define the layout of the current figure"
134 if self
._current
_figure
is not None:
135 self
._current
_figure
.layout
= args
137 def figlist(self
, args
):
139 print "Usage : figlist"
140 print " Print the list of figures"
144 for fn
, f
in enumerate(self
.cmds
.figures
):
145 print "%s Figure %d: %s" %\
146 ([" ", "*"][f
== self
._current
_figure
],\
148 for gn
, g
in enumerate(f
.graphs
):
149 print " %s Graph %d : (%s) %s" %\
150 ([" ","*"][g
== self
._current
_graph
],\
152 SEPARATOR
.join(g
.signals
.keys()))
154 def plot(self
, args
):
157 print " Draw and show the figures"
159 if self
._figcount
== len(self
.cmds
.figures
):
160 self
._main
_loop
.run()
162 # Create a window for each figure, add navigation toolbar
164 for i
, f
in enumerate(self
.cmds
.figures
):
165 # fig = plt.figure(i + 1)
168 w
.set_title('Figure %d' % self
._figcount
)
171 canvas
= FigureCanvas(f
)
172 canvas
.connect('destroy', self
._window
_destroy
)
173 vbox
.pack_start(canvas
)
174 toolbar
= NavigationToolbar(canvas
, w
)
175 vbox
.pack_start(toolbar
, False, False)
178 self
._main
_loop
= gobject
.MainLoop()
179 self
._main
_loop
.run()
181 def _window_destroy(self
, arg
):
182 self
._figcount
= self
._figcount
- 1
183 if not self
._figcount
:
184 self
._main
_loop
.quit()
187 def read(self
, args
):
189 print "Usage : load DATAFILE"
190 print " Load signal file"
196 print "Failed to read %s:" % fn
, e
198 except NotImplementedError:
199 print "File format not supported"
203 for signal
in self
.cmds
.signals
:
204 if signal
["reader"] == fn
:
205 print '%s / %s %s' % (signal
['name'],\
206 signal
['reference'],\
209 def write(self
, args
):
211 print "Usage: write format [(OPTIONS)] FILE SIG [, SIG [, SIG]...]"
212 print " Write signals to file"
214 # Extract format, options and signal list
215 tmp
= re
.search(r
'(?P<fmt>\w+)\s*(?P<opts>\([^\)]*\))?\s+(?P<fn>[\w\./]+)\s+(?P<sigs>\w+(\s*,\s*\w+)*)', args
)
218 print "What format ? Where ? Which signals ?"
220 fmt
= tmp
.group('fmt')
222 opt
= tmp
.group('opts')
223 sns
= self
.get_signames(tmp
.group('sigs'))
226 for on
in opt
.strip('()').split(','):
227 tmp
= on
.split(':', 1)
229 opts
[tmp
[0]] = tmp
[1]
231 self
.cmds
.write(fn
, fmt
, sns
, opts
)
232 except WriteError
, e
:
233 print "Write error:", e
235 except NotImplementedError:
236 print "File format not supported"
239 def update(self
, args
):
241 print "Usage: update"
242 print " Reread data files"
248 print "Usage : add SIG [, SIG [, SIG]...]"
249 print " Add a graph to the current figure"
251 if self
._current
_figure
is not None:
252 self
._current
_figure
.add(self
.cmds
.names_to_signals(\
253 self
.get_signames(args
)))
254 self
._current
_graph
=\
255 self
._current
_figure
.graphs
[len(\
256 self
._current
_figure
.graphs
) - 1]
258 self
.cmds
.create(self
.get_signames(args
))
259 if self
.cmds
.figures
:
260 self
._current
_figure
= self
.cmds
.figures
[0]
262 print "Create failed"
264 def delete(self
, args
):
266 print "Usage : delete GRAPH#"
267 print " Delete a graph from the current figure"
269 if self
._current
_figure
is not None:
270 self
._current
_figure
.delete(args
)
271 if self
._current
_figure
.graphs
:
272 self
._current
_graph
= self
._current
_figure
.graphs
[0]
274 self
._current
_graph
= None
276 def mode(self
, args
):
278 print "Usage: mode MODE"
279 print " Set the type of the current graph of the current figure"
280 print "Available modes :\n\
282 # fft Fast Fourier Transform (FFT) of signals\n\
283 # ifft Inverse FFT of signals"
285 if self
._current
_graph
is None:
287 idx
= self
._current
_figure
.graphs
.index(self
._current
_graph
)
288 self
._current
_figure
.mode
= self
._current
_graph
, mode
289 self
._current
_graph
= self
._current
_figure
.graphs
[idx
]
291 def scale(self
, args
):
293 print "Usage: scale [lin|logx|logy|loglog]"
294 print " Set the axis scale"
296 if self
._current
_graph
is None:
298 self
._current
_graph
.scale
= args
300 def range(self
, args
):
302 print "Usage: range [x|y min max]|[xmin xmax ymin ymax]|[reset]"
303 print " Set the axis range of the current graph of the current figure"
305 if self
._current
_graph
is None:
310 if range[0] == "reset":
311 self
._current
_graph
.range = range[0]
312 elif len(range) == 3:
313 if range[0] == 'x' or range[0] == 'y':
314 self
._current
_graph
.range = range[0],\
315 [float(range[1]), float(range[2])]
316 elif len(range) == 4:
317 self
._current
_graph
.range = [float(range[0]), float(range[1]),\
318 float(range[2]), float(range[3])]
320 def unit(self
, args
):
322 print "Usage: unit [XUNIT,] YUNIT"
323 print " Set the unit to be displayed on graph axis"
326 units
= args
.split(",", 1)
327 if len(units
) < 1 or self
._current
_graph
is None:
329 elif len(units
) == 1:
330 self
._current
_graph
.unit
= units
[0].strip(),
331 elif len(units
) == 2:
332 self
._current
_graph
.unit
= units
[0].strip(), units
[1].strip()
336 def insert(self
, args
):
338 print "Usage: insert SIG [, SIG [, SIG]...]"
339 print " Insert a list of signals into the current graph"
341 if self
._current
_graph
is None:
343 self
._current
_graph
.insert(self
.cmds
.names_to_signals(\
344 self
.get_signames(args
)))
346 def remove(self
, args
):
348 print "Usage: remove SIG [, SIG [, SIG]...]"
349 print " Delete a list of signals into from current graph"
351 if self
._current
_graph
is None:
353 self
._current
_graph
.remove(self
.cmds
.names_to_signals(\
354 self
.get_signames(args
)))
356 def freeze(self
, args
):
358 print "Usage: freeze SIG [, SIG [, SIG]...]"
359 print " Do not consider signal for subsequent updates"
360 self
.cmds
.freeze(self
.get_signames(args
))
362 def unfreeze(self
, args
):
364 print "Usage: unfreeze SIG [, SIG [, SIG]...]"
365 print " Consider signal for subsequent updates"
366 self
.cmds
.unfreeze(self
.get_signames(args
))
368 def siglist(self
, args
):
370 print "Usage : siglist"
371 print " List loaded signals"
374 HEADER
=["Name", "Unit", "Ref", "Reader"]
375 print SEPARATOR
.join(HEADER
)
376 for reader_name
, reader
in self
.cmds
.readers
.iteritems():
377 for signal_name
, signal
in reader
.signals
.iteritems():
378 print SEPARATOR
.join((signal_name
, \
385 print "Usage: destsig=mathexpr"
386 print " Define a new signal destsig using mathematical expression"
391 print "Error creating signal from math expression", e
394 def get_signames(self
, args
):
395 """ Return the signal names list extracted from the commandline
396 The list must be a coma separated list of signal names.
397 If no signals are loaded of no signal are found, return []
403 for sn
in args
.split(","):
404 sns
.append(sn
.strip())
407 def help(self
, args
):
408 """ Display help messages
412 Commands related to figures:\n\
413 create create a new figure\n\
414 destroy delete a figure\n\
415 select define the current figure and the current graph\n\
416 layout set_ the layout (either horiz, vert or quad)\n\
417 figlist list the existing figures\n\
418 plot draw and show the figures\n\
419 Commands related to graphs:\n\
420 add add a graph to the current figure\n\
421 delete delete a graph from the current figure\n\
422 mode set_ the mode of the current graph of the current figure\n\
423 unit set_ the units of the current graph of the current figure\n\
424 scale set_ the scale of the current graph of the current figure\n\
425 range set_ the axis range of the current graph of the current figure\n\
426 Commands related to signals:\n\
427 read read signals from file\n\
428 write write signals to file\n\
429 update reread signals from file(s)\n\
430 insert add a signal to the current graph of the current figure\n\
431 remove delete a signal from the current graph of the current figure\n\
432 (un)freeze toggle signal update\n\
433 siglist list the signals\n\
435 echo print a message\n\
436 pause wait for the user to press enter\n\
437 quit, exit exit the program\n\
438 help display this help message\n\
441 1, 2 Toggle first and second vertical cursors\n\
442 3, 4 Toggle first and second horizontal cursors\n\
444 SIG = EXPR Compute a signal from mathematical expression\n\
446 Compute Fast Fourier Transform (FFT) or inverse FFT from SIG\n\
448 Help for individual command can be obtained with 'help COMMAND'\
451 if args
in dir(cmds
):
452 eval("self." + args
+ "(\"help\")")
454 print "Unknown command", args
456 def echo(self
, args
):
458 print "Usage: echo [TEXT]"
463 def pause(self
, args
):
466 print " Wait for the user to press enter"
468 inp
= raw_input("Press enter")
470 def loop(self
, p
, f
= None, batch
= False):
478 inp
= f
.readline().rstrip("\n")
481 # Batch mode, exit at the end of script
484 # Interactive mode, continue with command line
488 print "Script error:", e
491 # Check if line is a comment
492 if inp
.lstrip().startswith("#"):
494 # Check if command is assignment
495 if inp
.find("=") >= 0:
499 # Separate command from args
500 if inp
.find(" ") >= 0:
501 st
= inp
.lstrip().split(' ', 1)
504 # print "cmd:", cmd, "args:", args
510 if cmd
== "exit" or cmd
== "quit":
513 # Evaluate the command
515 eval("self." + cmd
+ "(args)")
517 print cmd
, "not supported"
523 # except AttributeError, e:
524 # print "Unknown command:", e.message
527 # except NameError, e:
528 # print "Unknown command", e.message
531 except SyntaxError, e
:
532 print "Syntax Error", e
.message
536 print "Error in read :", e
538 readline
.write_history_file(self
.hist_file
)
540 if __name__
== "__main__":