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