WLC and FJC output are now with 2 decimal precision. Added a comment on autopeak...
[hooke.git] / hooke_cli.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 '''
5 hooke_cli.py
6
7 Command line module of Hooke.
8
9 Copyright (C) 2006 Massimo Sandal (University of Bologna, Italy).
10
11 This program is released under the GNU General Public License version 2.
12 '''
13
14
15 from libhooke import * #FIXME
16 import libhookecurve as lhc
17
18 import libinput as linp
19 import liboutlet as lout
20
21 from libhooke import WX_GOOD
22 from libhooke import HOOKE_VERSION
23
24 import wxversion
25 wxversion.select(WX_GOOD)
26 import wx
27
28 from wx.lib.newevent import NewEvent
29 from matplotlib.numerix import * #FIXME
30
31 import xml.dom.minidom
32 import sys, os, os.path, glob, shutil
33 import Queue
34 import cmd
35 import time
36
37 global __version__
38 global __codename__
39 global __releasedate__
40 __version__ = HOOKE_VERSION[0]
41 __codename__ = HOOKE_VERSION[1]
42 __releasedate__ = HOOKE_VERSION[2]
43
44 from matplotlib import __version__ as mpl_version
45 from wx import __version__ as wx_version
46 from wxmpl import __version__ as wxmpl_version
47 from scipy import __version__ as scipy_version
48 from numpy import __version__ as numpy_version
49 from sys import version as python_version
50 import platform
51
52
53 class HookeCli(cmd.Cmd):
54     
55     def __init__(self,frame,list_of_events,events_from_gui,config,drivers):
56         cmd.Cmd.__init__(self)
57                        
58         self.prompt = 'hooke: '
59         
60         
61         self.current_list=[] #the playlist we're using
62         
63         self.current=None    #the current curve under analysis. 
64         self.plots=None
65         '''
66         The actual hierarchy of the "current curve" is a bit complex:
67         
68         self.current = the lhc.HookeCurve container object of the current curve
69         self.current.curve = the current "real" curve object as defined in the filetype driver class
70         self.current.curve.default_plots() = the default plots of the filetype driver.
71         
72         The plot objects obtained by mean of self.current.curve.default_plots() 
73         then undergoes modifications by the plotmanip
74         modifier functions. The modified plot is saved in self.plots and used if needed by other functions.       
75         '''
76         
77         
78         self.pointer=0       #a pointer to navigate the current list
79                         
80         #Things that come from outside
81         self.frame=frame                        #the wx frame we refer to
82         self.list_of_events=list_of_events      #a list of wx events we use to interact with the GUI
83         self.events_from_gui=events_from_gui    #the Queue object we use to have messages from the GUI
84         self.config=config                      #the configuration dictionary
85         self.drivers=drivers                    #the file format drivers
86         
87         #get plot manipulation functions
88         plotmanip_functions=[]
89         for object_name in dir(self):
90                 if object_name[0:9]=='plotmanip':
91                     plotmanip_functions.append(getattr(self,object_name))
92         #put plotmanips in order
93         self.plotmanip=[None for item in self.config['plotmanips']]
94         for item in plotmanip_functions:
95             namefunction=item.__name__[10:]
96             if namefunction in self.config['plotmanips']:
97                 nameindex=self.config['plotmanips'].index(namefunction) #index of function in plotmanips config
98                 self.plotmanip[nameindex] = item
99             else:
100                 pass
101            
102             
103         self.playlist_saved=0 #Did we save the playlist?
104         self.playlist_name='' #Name of playlist
105         self.notes_saved=1 #Did we save the notes?
106         self.notes_filename=None #Name of notes
107
108         #create outlet
109         self.outlet=lout.Outlet()
110         
111         #Data that must be saved in the playlist, related to the whole playlist (not individual curves)
112         self.playlist_generics={} 
113         
114         #make sure we execute _plug_init() for every command line plugin we import
115         for plugin_name in self.config['plugins']:
116             try:
117                 plugin=__import__(plugin_name)
118                 try:
119                     eval('plugin.'+plugin_name+'Commands._plug_init(self)')
120                 except AttributeError:
121                     pass
122             except ImportError:
123                 pass
124
125         #load default list, if possible
126         self.do_loadlist(self.config['defaultlist'])
127         
128 #HELPER FUNCTIONS
129 #Everything sending an event should be here
130     def _measure_N_points(self, N, whatset=1):
131         '''
132         general helper function for N-points measures
133         '''
134         wx.PostEvent(self.frame,self.list_of_events['measure_points'](num_of_points=N, set=whatset))
135         while 1:
136             try:
137                 points=self.frame.events_from_gui.get()
138                 break
139             except Empty:
140                 pass
141         return points
142         
143     def _get_displayed_plot(self,dest=0):
144         '''
145         returns the currently displayed plot.
146         '''
147         wx.PostEvent(self.frame, self.list_of_events['get_displayed_plot'](dest=dest))
148         while 1:
149             try:
150                 displayed_plot=self.events_from_gui.get()
151             except Empty:
152                 pass
153             if displayed_plot:
154                 break
155         return displayed_plot
156     
157     def _send_plot(self,plots):
158         '''
159         sends a plot to the GUI
160         '''
161         wx.PostEvent(self.frame, self.list_of_events['plot_graph'](plots=plots))
162         return
163         
164     def _find_plotmanip(self, name):
165         '''
166         returns a plot manipulator function from its name
167         '''
168         return self.plotmanip[self.config['plotmanips'].index(name)]
169     
170     def _clickize(self, xvector, yvector, index):
171         '''
172         returns a ClickedPoint() object from an index and vectors of x, y coordinates       
173         '''
174         point=ClickedPoint()
175         point.index=index
176         point.absolute_coords=xvector[index],yvector[index]
177         point.find_graph_coords(xvector,yvector)
178         return point
179     
180 #HERE COMMANDS BEGIN
181     
182     def help_set(self):
183         print '''
184 SET
185 Sets a local configuration variable
186 -------------
187 Syntax: set [variable] [value]
188         '''
189     def do_set(self,args):
190         #FIXME: some variables in self.config should be hidden or intelligently configurated...
191         args=args.split()
192         if len(args)==0:
193             print 'You must specify a variable and a value'
194             print 'Available variables:'
195             print self.config.keys()
196             return
197         if args[0] not in self.config.keys():
198             print 'This is not an internal Hooke variable!'
199             return
200         if len(args)==1:
201             #FIXME:we should reload the config file and reset the config value
202             print self.config[args[0]]
203             return
204         key=args[0]
205         try: #try to have a numeric value
206             value=float(args[1])
207         except ValueError: #if it cannot be converted to float, it's None, or a string...
208             value=args[1]
209             if value.lower()=='none':
210                 value=None
211             else:
212                 value=args[1]
213                 
214         self.config[key]=value
215         self.do_plot(0)
216         
217 #PLAYLIST MANAGEMENT AND NAVIGATION
218 #------------------------------------
219     
220     def help_loadlist(self):
221         print '''
222 LOADLIST
223 Loads a file playlist
224 -----------
225 Syntax: loadlist [playlist file]
226         '''
227     def do_loadlist(self, args):
228         #checking for args: if nothing is given as input, we warn and exit.
229         while len(args)==0:
230             args=linp.safeinput('File to load?')
231         
232         arglist=args.split()
233         play_to_load=arglist[0]
234         
235         #We assume a Hooke playlist has the extension .hkp
236         if play_to_load[-4:] != '.hkp':
237             play_to_load+='.hkp'
238         
239         try:            
240             playxml=PlaylistXML()
241             self.current_list, self.playlist_generics=playxml.load(play_to_load)
242             self.current_playxml=playxml
243         except IOError:
244             print 'File not found.'
245             return
246         
247         print 'Loaded %s curves' %len(self.current_list)
248         
249         if 'pointer' in self.playlist_generics.keys():
250             self.pointer=int(self.playlist_generics['pointer'])
251         else:
252             #if no pointer is found, set the current curve as the first curve of the loaded playlist
253             self.pointer=0
254         print 'Starting at curve ',self.pointer
255             
256         self.current=self.current_list[self.pointer]
257         
258         #resets saved/notes saved state
259         self.playlist_saved=0
260         self.playlist_name=''
261         self.notes_saved=0        
262     
263         self.do_plot(0)
264         
265         
266     def help_genlist(self):
267         print '''
268 GENLIST
269 Generates a file playlist.
270 Note it doesn't *save* it: see savelist for this.
271
272 If [input files] is a directory, it will use all files in the directory for playlist.
273 So:
274 genlist dir
275 genlist dir/
276 genlist dir/*.*
277
278 are all equivalent syntax.
279 ------------
280 Syntax: genlist [input files]
281         
282 '''
283     def do_genlist(self,args):
284         #args list is: input path, output name
285         if len(args)==0:
286             args=linp.safeinput('Input files?')
287                     
288         arglist=args.split()      
289         list_path=arglist[0]
290                   
291         #if it's a directory, is like /directory/*.*
292         #FIXME: probably a bit kludgy.
293         if os.path.isdir(list_path): 
294             if platform.system == 'Windows':
295                 SLASH="\\"
296             else:
297                 SLASH="/"
298             if list_path[-1] == SLASH:
299                 list_path=list_path+'*'
300             else:    
301                 list_path=list_path+SLASH+'*'
302         
303         #expanding correctly the input list with the glob module :)        
304         list_files=glob.glob(list_path)
305         list_files.sort()
306
307         self.current_list=[]
308         for item in list_files:
309             try:
310                 if os.path.isfile(item):
311                     self.current_list.append(lhc.HookeCurve(os.path.abspath(item))) 
312             except:
313                 pass
314             
315         self.pointer=0    
316         if len(self.current_list)>0:
317             self.current=self.current_list[self.pointer]
318         else:
319             print 'Empty list!'
320             return
321         
322         #resets saved/notes saved state
323         self.playlist_saved=0
324         self.playlist_name=''
325         self.notes_saved=0  
326         
327         self.do_plot(0)
328        
329         
330     def do_savelist(self,args):
331         '''
332         SAVELIST
333         Saves the current file playlist on disk.
334         ------------
335         Syntax: savelist [filename]
336         '''
337         while len(args)==0:
338             args=linp.safeinput('Output file?',['savedlist.txt'])
339     
340         output_filename=args
341         
342         self.playlist_generics['pointer']=self.pointer
343         
344         #autocomplete filename if not specified
345         if output_filename[-4:] != '.hkp':
346             output_filename+='.hkp'
347         
348         playxml=PlaylistXML()
349         playxml.export(self.current_list, self.playlist_generics)
350         playxml.save(output_filename)                  
351         
352         #remembers we have saved playlist
353         self.playlist_saved=1
354         
355     def help_addtolist(self):
356         print '''
357 ADDTOLIST
358 Adds a file to the current playlist
359 --------------
360 Syntax: addtolist [filename]
361 '''
362     def do_addtolist(self,args):
363         #args list is: input path
364         if len(args)==0:
365             print 'You must give the input filename you want to add'
366             self.help_addtolist()
367             return
368           
369         filenames=glob.glob(args)
370         
371         for filename in filenames:
372             self.current_list.append(lhc.HookeCurve(os.path.abspath(filename)))
373         #we need to save playlist
374         self.playlist_saved=0
375     
376     def help_printlist(self):
377         print '''
378 PRINTLIST
379 Prints the list of curves in the current playlist
380 -------------
381 Syntax: printlist
382 '''
383     def do_printlist(self,args):
384         for item in self.current_list:
385             print item.path
386             
387     
388     def help_jump(self):
389         print '''
390 JUMP
391 Jumps to a given curve.
392 ------
393 Syntax: jump {$curve}
394
395 If the curve is not in the current playlist, it politely asks if we want to add it.
396         '''  
397     def do_jump(self,filename):
398         '''
399         jumps to the curve with the given filename.
400         if the filename is not in the playlist, it asks if we must add it or not.
401         '''
402         
403         if filename=='':
404             filename=linp.safeinput('Jump to?')
405             
406         filepath=os.path.abspath(filename)
407         print filepath
408                 
409         c=0
410         item_not_found=1
411         while item_not_found:
412             try:
413                 
414                 if self.current_list[c].path == filepath:
415                     self.pointer=c
416                     self.current=self.current_list[self.pointer]
417                     item_not_found=0
418                     self.do_plot(0)
419                 else:
420                     c+=1  
421             except IndexError:
422                 #We've found the end of the list.
423                 answer=linp.safeinput('Curve not found in playlist. Add it to list?',['y'])
424                 if answer.lower()[0]=='y':
425                     try:
426                         self.do_addtolist(filepath)
427                     except:
428                         print 'Curve file not found.'
429                         return
430                     self.current=self.current_list[-1]
431                     self.pointer=(len(current_list)-1)
432                     self.do_plot(0)
433                     
434                 item_not_found=0
435     
436     
437     def do_index(self,args):
438         '''
439         INDEX
440         Prints the index of the current curve in the list
441         -----
442         Syntax: index
443         '''
444         print self.pointer+1, 'of', len(self.current_list) 
445     
446     
447     def help_next(self):
448         print '''
449 NEXT
450 Go the next curve in the playlist.
451 If we are at the last curve, we come back to the first.
452 -----
453 Syntax: next, n
454         '''
455     def do_next(self,args):
456         try:
457             self.current.curve.close_all()
458         except:
459             print 'No curve file loaded, currently!'
460             print 'This should not happen, report to http://code.google.com/p/hooke'
461             return
462         
463         if self.pointer == (len(self.current_list)-1):
464             self.pointer=0
465             print 'Playlist finished; back to first curve.'
466         else:
467             self.pointer+=1
468         
469         self.current=self.current_list[self.pointer]
470         self.do_plot(0)
471         
472     
473     def help_n(self):
474         self.help_next()
475     def do_n(self,args):
476         self.do_next(args)
477         
478     def help_previous(self,args):
479         print '''
480 PREVIOUS
481 Go to the previous curve in the playlist.
482 If we are at the first curve, we jump to the last.
483 -------
484 Syntax: previous, p
485     '''
486     def do_previous(self,args):
487         try:
488             self.current.curve.close_all()
489         except:
490             print 'No curve file loaded, currently!'
491             print 'This should not happen, report to http://code.google.com/p/hooke'
492             return
493         if self.pointer == 0:
494             self.pointer=(len(self.current_list)-1)
495             print 'Start of playlist; jump to last curve.' 
496         else:
497             self.pointer-=1
498             
499         self.current=self.current_list[self.pointer]
500         self.do_plot(args)
501         
502             
503     def help_p(self):
504         self.help_previous()
505     def do_p(self,args):
506         self.do_previous(args)
507
508         
509 #PLOT INTERACTION COMMANDS
510 #-------------------------------    
511     def help_plot(self):
512         print '''
513 PLOT
514 Plots the current force curve
515 -------
516 Syntax: plot
517         '''
518     def do_plot(self,args):
519         
520         self.current.identify(self.drivers)
521         self.plots=self.current.curve.default_plots()
522         try:
523             self.plots=self.current.curve.default_plots()
524         except Exception, e:
525             print 'Unexpected error occurred in do_plot().'
526             print e
527             return
528             
529         #apply the plotmanip functions eventually present
530         nplots=len(self.plots)
531         c=0
532         while c<nplots:
533             for function in self.plotmanip: #FIXME: something strange happens about self.plotmanip[0]
534                 self.plots[c]=function(self.plots[c], self.current)
535                 
536             self.plots[c].xaxes=self.config['xaxes'] #FIXME: in the future, xaxes and yaxes should be set per-plot
537             self.plots[c].yaxes=self.config['yaxes']
538                 
539             c+=1
540
541         self._send_plot(self.plots)
542         
543     def _delta(self, set=1):
544         '''
545         calculates the difference between two clicked points
546         '''
547         print 'Click two points'
548         points=self._measure_N_points(N=2, whatset=set)
549         dx=abs(points[0].graph_coords[0]-points[1].graph_coords[0])
550         dy=abs(points[0].graph_coords[1]-points[1].graph_coords[1])
551         unitx=self.plots[points[0].dest].units[0]
552         unity=self.plots[points[0].dest].units[1]
553         return dx,unitx,dy,unity
554         
555     def do_delta(self,args):
556         '''
557         DELTA
558         
559         Measures the delta X and delta Y between two points.
560         ----
561         Syntax: delta
562         '''
563         dx,unitx,dy,unity=self._delta()
564         print str(dx)+' '+unitx
565         print str(dy)+' '+unity
566     
567     def _point(self, set=1):
568         '''calculates the coordinates of a single clicked point'''
569
570         print 'Click one point'
571         point=self._measure_N_points(N=1, whatset=set)
572         
573         x=point[0].graph_coords[0]
574         y=point[0].graph_coords[1]
575         unitx=self.plots[point[0].dest].units[0]
576         unity=self.plots[point[0].dest].units[1]
577         return x,unitx,y,unity
578         
579     def do_point(self,args):
580         '''
581         POINT
582         
583         Returns the coordinates of a point on the graph.
584         ----
585         Syntax: point
586         '''
587         x,unitx,y,unity=self._point()
588         print str(x)+' '+unitx
589         print str(y)+' '+unity
590         to_dump='point '+self.current.path+' '+str(x)+' '+unitx+', '+str(y)+' '+unity
591         self.outlet.push(to_dump)    
592    
593         
594     def do_close(self,args=None):
595         '''
596         CLOSE
597         Closes one of the two plots. If no arguments are given, the bottom plot is closed.
598         ------
599         Syntax: close [top,bottom]
600         '''
601         if args=='top':
602             to_close=0
603         elif args=='bottom':
604             to_close=1
605         else:
606             to_close=1
607         
608         close_plot=self.list_of_events['close_plot']
609         wx.PostEvent(self.frame, close_plot(to_close=to_close))
610         
611     def do_show(self,args=None):
612         '''
613         SHOW
614         Shows both plots.
615         ''' 
616         show_plots=self.list_of_events['show_plots']
617         wx.PostEvent(self.frame, show_plots())
618        
619         
620     
621     #PLOT EXPORT AND MANIPULATION COMMANDS
622     def help_export(self):
623         print '''
624 EXPORT
625 Saves the current plot as an image file
626 ---------------
627 Syntax: export [filename] {plot to export}
628
629 The supported formats are PNG and EPS; the file extension of the filename is automatically recognized
630 and correctly exported. Resolution is (for now) fixed at 150 dpi.
631
632 If you have a multiple plot, the optional plot to export argument tells Hooke which plot you want to export. If 0, the top plot is exported. If 1, the bottom plot is exported (Exporting both plots is still to implement)
633         '''
634     def do_export(self,args):
635         #FIXME: the bottom plot doesn't have the title
636         
637         dest=0
638         
639         if len(args)==0:
640             #FIXME: We have to go into the libinput stuff and fix it, for now here's a dummy replacement...
641             #name=linp.safeinput('Filename?',[self.current.path+'.png'])
642             name=raw_input('Filename? ')
643         else:
644             args=args.split()
645             name=args[0]
646             if len(args) > 1:
647                 dest=int(args[1]) 
648                 
649         export_image=self.list_of_events['export_image']
650         wx.PostEvent(self.frame, export_image(name=name, dest=dest))
651         
652         
653     def help_txt(self):
654         print '''
655 TXT
656 Saves the current curve as a text file
657 Columns are, in order:
658 X1 , Y1 , X2 , Y2 , X3 , Y3 ...
659
660 -------------
661 Syntax: txt [filename] {plot to export} or
662         txt [filename] all
663         all  : To save all the curves in different windows in a single file.
664         '''
665     def do_txt(self,args):
666         
667         def transposed2(lists, defval=0):
668             '''
669             transposes a list of lists, i.e. from [[a,b,c],[x,y,z]] to [[a,x],[b,y],[c,z]] without losing
670             elements
671             (by Zoran Isailovski on the Python Cookbook online)
672             '''
673             if not lists: return []
674             return map(lambda *row: [elem or defval for elem in row], *lists)
675         
676         whichplot=0
677         args=args.split()
678         if len(args)==0:
679             filename=linp.safeinput('Filename?',[self.current.path+'.txt'])
680         else:
681             filename=linp.checkalphainput(args[0],self.current.path+'.txt',[])
682             try:
683                 if args[1]=="all":
684                   whichplot="all"
685                 else:
686                   whichplot=int(args[1])
687             except:
688                 pass
689         
690         if whichplot!="all":
691             try:
692                 outofplot=self.plots[whichplot].vectors
693             except:
694                 print "Plot index out of range."
695                 return 0
696             columns=[]     
697             for dataset in self.plots[whichplot].vectors:
698                 for i in range(0,len(dataset)): 
699                     columns.append([])
700                     for value in dataset[i]:
701                         #columns[-1].append(str(value*(10**9)))                   
702                         columns[-1].append(str(value))
703             rows=transposed2(columns, 'nan')
704             rows=[' , '.join(item) for item in rows]
705             text='\n'.join(rows)
706             
707             txtfile=open(filename,'w+')
708             #Save units of measure in header
709             txtfile.write('X:'+self.plots[whichplot].units[0]+'\n')
710             txtfile.write('Y:'+self.plots[whichplot].units[1]+'\n')
711             txtfile.write(text)
712             txtfile.close()
713
714         else:
715           columns=[]
716           for wp in range(len(self.plots)):     
717             for dataset in self.plots[wp].vectors:
718                 for i in range(0,len(dataset)): 
719                     columns.append([])
720                     for value in dataset[i]:
721                         #columns[-1].append(str(value*(10**9)))                   
722                         columns[-1].append(str(value))
723             rows=transposed2(columns, 'nan')
724             rows=[' , '.join(item) for item in rows]
725             text='\n'.join(rows)
726
727             txtfile=open(filename,'w+')
728             #Save units of measure in header
729             for i in range(len(self.plots)):
730               txtfile.write('X:'+self.plots[i].units[0]+'\n')
731               txtfile.write('Y:'+self.plots[i].units[1]+'\n')
732             txtfile.write(text)
733             txtfile.close()
734         
735     
736     #LOGGING, REPORTING, NOTETAKING
737     
738
739     def do_note_old(self,args):
740         '''
741         NOTE_OLD
742         **deprecated**: Use note instead. Will be removed in 0.9
743         
744         Writes or displays a note about the current curve.
745         If [anything] is empty, it displays the note, otherwise it adds a note.
746         The note is then saved in the playlist if you issue a savelist command
747         ---------------
748         Syntax: note_old [anything]        
749
750         '''
751         if args=='':
752             print self.current_list[self.pointer].notes
753         else:
754             #bypass UnicodeDecodeError troubles
755             try:
756                 args=args.decode('ascii')
757             except:
758                 args=args.decode('ascii','ignore')
759                 if len(args)==0:
760                     args='?'
761                     
762             self.current_list[self.pointer].notes=args
763         self.notes_saved=0
764             
765             
766     def do_note(self,args):
767         '''
768         NOTE
769         
770         Writes or displays a note about the current curve.
771         If [anything] is empty, it displays the note, otherwise it adds a note.
772         The note is then saved in the playlist if you issue a savelist command.
773         ---------------
774         Syntax: note_old [anything]        
775
776         '''
777         if args=='':
778             print self.current_list[self.pointer].notes
779         else:
780             if self.notes_filename == None:
781                 if not os.path.exists(os.path.realpath('output')):
782                     os.mkdir('output')
783                 self.notes_filename=raw_input('Notebook filename? ')
784                 self.notes_filename=os.path.join(os.path.realpath('output'),self.notes_filename)
785                 title_line='Notes taken at '+time.asctime()+'\n'
786                 f=open(self.notes_filename,'a')
787                 f.write(title_line)
788                 f.close()
789                 
790             #bypass UnicodeDecodeError troubles    
791             try:
792                args=args.decode('ascii')
793             except:
794                args=args.decode('ascii','ignore')
795                if len(args)==0:
796                    args='?'
797             self.current_list[self.pointer].notes=args
798             
799             f=open(self.notes_filename,'a+')
800             note_string=(self.current.path+'  |  '+self.current.notes+'\n')
801             f.write(note_string)
802             f.close()
803                            
804     def help_notelog(self):
805         print '''
806 NOTELOG
807 Writes a log of the notes taken during the session for the current
808 playlist
809 --------------        
810 Syntax notelog [filename]
811 '''        
812     def do_notelog(self,args):
813         
814         if len(args)==0:
815             args=linp.safeinput('Notelog filename?',['notelog.txt'])
816             
817         note_lines='Notes taken at '+time.asctime()+'\n'
818         for item in self.current_list:
819             if len(item.notes)>0:
820                 #FIXME: log should be justified
821                 #FIXME: file path should be truncated...
822                 note_string=(item.path+'  |  '+item.notes+'\n')
823                 note_lines+=note_string
824                 
825         try:
826             f=open(args,'a+')
827             f.write(note_lines)
828             f.close()
829         except IOError, (ErrorNumber, ErrorMessage):
830             print 'Error: notes cannot be saved. Catched exception:'
831             print ErrorMessage
832         
833         self.notes_saved=1
834
835     def help_copylog(self):
836         print '''
837 COPYLOG
838 Moves the annotated curves to another directory
839 -----------
840 Syntax copylog [directory]
841         '''
842     def do_copylog(self,args):
843         
844         if len(args)==0:
845             args=linp.safeinput('Destination directory?')  #TODO default
846         
847         mydir=os.path.abspath(args)
848         if not os.path.isdir(mydir):
849             print 'Destination is not a directory.'
850             return
851         
852         for item in self.current_list:
853             if len(item.notes)>0:
854                 try:
855                     shutil.copy(item.path, mydir)
856                 except (OSError, IOError):
857                     print 'Cannot copy file. '+item.path+' Perhaps you gave me a wrong directory?'
858
859 #OUTLET management
860 #-----------------
861     def do_outlet_show(self,args):
862         '''OUTLET_SHOW
863         ---------
864         Shows current content of outlet with index for reference
865         '''
866         self.outlet.printbuf()
867
868     def do_outlet_undo(self, args):
869         '''OUTLET_UNDO
870         ---------
871         Eliminates last entry in outlet
872         '''
873         print 'Erasing last entry'
874         self.outlet.pop()
875
876     def do_outlet_delete(self, args):
877         '''OUTLET_DELETE
878         Eliminates a particular entry from outlet
879         Syntax: outlet_delete n
880         '''
881         if len(args)==0:
882             print 'Index needed!, use outlet_show to know it'
883         else:
884             self.outlet.delete(args)
885
886 #OS INTERACTION COMMANDS
887 #-----------------    
888     def help_dir(self):
889         print '''
890 DIR, LS
891 Lists the files in the directory
892 ---------
893 Syntax: dir [path]
894           ls  [path]
895         '''
896     def do_dir(self,args):
897         
898         if len(args)==0:
899             args='*'
900         print glob.glob(args)
901         
902     def help_ls(self):
903         self.help_dir(self)
904     def do_ls(self,args):
905         self.do_dir(args)
906         
907     def help_pwd(self):
908         print '''
909 PWD
910 Gives the current working directory.
911 ------------
912 Syntax: pwd
913         '''
914     def do_pwd(self,args):
915         print os.getcwd()         
916     
917     def help_cd(self):
918         print '''
919 CD
920 Changes the current working directory
921 -----
922 Syntax: cd
923         '''
924     def do_cd(self,args):
925         mypath=os.path.abspath(args)
926         try:
927             os.chdir(mypath)
928         except OSError:
929             print 'I cannot access that directory.'
930     
931     
932     def help_system(self):
933         print '''
934 SYSTEM
935 Executes a system command line and reports the output
936 -----
937 Syntax system [command line]
938         '''
939         pass
940     def do_system(self,args):
941         waste=os.system(args)           
942             
943     def do_debug(self,args):
944         '''
945         this is a dummy command where I put debugging things
946         '''
947         print self.config['plotmanips']
948         pass
949             
950     def help_current(self):
951         print '''
952 CURRENT
953 Prints the current curve path.
954 ------
955 Syntax: current
956         '''
957     def do_current(self,args):
958         print self.current.path
959         
960     def do_info(self,args):
961         '''
962         INFO
963         ----
964         Returns informations about the current curve.
965         '''
966         print 'Path: ',self.current.path
967         print 'Experiment: ',self.current.curve.experiment
968         print 'Filetype: ',self.current.curve.filetype
969         for plot in self.current.curve.default_plots():
970             for set in plot.vectors:
971                 lengths=[len(item) for item in set]
972                 print 'Data set size: ',lengths
973         
974     def do_version(self,args):
975         '''
976         VERSION
977         ------
978         Prints the current version and codename, plus library version. Useful for debugging.
979         '''     
980         print 'Hooke '+__version__+' ('+__codename__+')'
981         print 'Released on: '+__releasedate__
982         print '---'
983         print 'Python version: '+python_version
984         print 'WxPython version: '+wx_version
985         print 'wxMPL version: '+wxmpl_version
986         print 'Matplotlib version: '+mpl_version
987         print 'SciPy version: '+scipy_version
988         print 'NumPy version: '+numpy_version
989         print '---'
990         print 'Platform: '+str(platform.uname())
991         print '---'
992         print 'Loaded plugins:',self.config['loaded_plugins']
993         
994     def help_exit(self):
995         print '''
996 EXIT, QUIT
997 Exits the program cleanly.
998 ------
999 Syntax: exit
1000 Syntax: quit
1001 '''    
1002     def do_exit(self,args):
1003         we_exit='N'
1004         
1005         if (not self.playlist_saved) or (not self.notes_saved):
1006             we_exit=linp.safeinput('You did not save your playlist and/or notes. Exit?',['n'])
1007         else:
1008             we_exit=linp.safeinput('Exit?',['y'])
1009         
1010         if we_exit[0].upper()=='Y':
1011             wx.CallAfter(self.frame.Close)
1012             sys.exit(0)
1013         else:
1014             return
1015     
1016     def help_quit(self):
1017         self.help_exit()
1018     def do_quit(self,args):
1019         self.do_exit(args)
1020
1021
1022
1023
1024
1025 if __name__ == '__main__':
1026     mycli=HookeCli(0)
1027     mycli.cmdloop()