# -*- coding: utf-8 -*- # coding=UTF-8 # Relational # Copyright (C) 2010 Salvo "LtWorf" Tomaselli # # Relational is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # author Salvo "LtWorf" Tomaselli # Initial readline code from http://www.doughellmann.com/PyMOTW/readline/index.html import readline import logging import os.path import os import sys from relational import relation, parser, rtypes from xtermcolor import colorize PROMPT_COLOR = 0xffff00 ERROR_COLOR = 0xff0000 class SimpleCompleter(object): '''Handles completion''' def __init__(self, options): '''Takes a list of valid completion options''' self.options = sorted(options) return def add_completion(self,option): '''Adds one string to the list of the valid completion options''' if option not in self.options: self.options.append(option) self.options.sort() def remove_completion(self,option): '''Removes one completion from the list of the valid completion options''' if option in self.options: self.options.remove(option) pass def complete(self, text, state): response = None if state == 0: # This is the first time for this text, so build a match list. if text: self.matches =[s for s in self.options if s and s.startswith(text)] #Add the completion for files here try: d=os.path.dirname(text) listf=os.listdir(d) d+="/" except: d="" listf=os.listdir('.') for i in listf: i=(d+i).replace('//','/') if i.startswith(text): if os.path.isdir(i): i=i+"/" self.matches.append(i) logging.debug('%s matches: %s', repr(text), self.matches) else: self.matches = self.options[:] logging.debug('(empty input) matches: %s', self.matches) # Return the state'th item from the match list, # if we have that many. try: response = self.matches[state] except IndexError: response = None logging.debug('complete(%s, %s) => %s', repr(text), state, repr(response)) return response relations={} completer=SimpleCompleter(['SURVEY','LIST','LOAD ','UNLOAD ','HELP ','QUIT','SAVE ','_PRODUCT ','_UNION ','_INTERSECTION ','_DIFFERENCE ','_JOIN ','_LJOIN ','_RJOIN ','_FJOIN ','_PROJECTION ','_RENAME_TO ','_SELECTION ','_RENAME ','_DIVISION ']) def load_relation(filename,defname=None): if not os.path.isfile(filename): print >> sys.stderr, colorize("%s is not a file" % filename,ERROR_COLOR) return None f=filename.split('/') if defname==None: defname=f[len(f)-1].lower() if defname.endswith(".csv"): #removes the extension defname=defname[:-4] if not rtypes.is_valid_relation_name(defname): print >> sys.stderr, colorize("%s is not a valid relation name" % defname,ERROR_COLOR) return try: relations[defname]=relation.relation(filename) completer.add_completion(defname) print colorize("Loaded relation %s"% defname,0x00ff00) return defname except Exception, e: print >>sys.stderr,colorize(e,ERROR_COLOR) return None def survey(): '''performs a survey''' from relational import maintenance post= {'software':'Relational algebra (cli)','version':version} fields=('System','Country','School','Age','How did you find','email (only if you want a reply)','Comments') for i in fields: a=raw_input('%s: '%i) post[i]=a maintenance.send_survey(post) def help(command): '''Prints help on the various functions''' p=command.split(' ',1) if len(p)==1: print 'HELP command' print 'To execute a query:\n[relation =] query\nIf the 1st part is omitted, the result will be stored in the relation last_.' print 'To prevent from printing the relation, append a ; to the end of the query.' print 'To insert relational operators, type _OPNAME, they will be internally replaced with the correct symbol.' print 'Rember: the tab key is enabled and can be very helpful if you can\'t remember something.' return cmd=p[1] if cmd=='QUIT': print 'Quits the program' elif cmd=='LIST': print "Lists the relations loaded" elif cmd=='LOAD': print "LOAD filename [relationame]" print "Loads a relation into memory" elif cmd=='UNLOAD': print "UNLOAD relationame" print "Unloads a relation from memory" elif cmd=='SAVE': print "SAVE filename relationame" print "Saves a relation in a file" elif cmd=='HELP': print "Prints the help on a command" elif cmd=='SURVEY': print "Fill and send a survey" else: print "Unknown command: %s" %cmd def exec_line(command): command=command.strip() if command.startswith(';'): return elif command=='QUIT': sys.exit(0) elif command.startswith('HELP'): help(command) elif command=='LIST': #Lists all the loaded relations for i in relations: if not i.startswith('_'): print i elif command=='SURVEY': survey() elif command.startswith('LOAD '): #Loads a relation pars=command.split(' ') if len(pars)==1: print colorize("Missing parameter",ERROR_COLOR) return filename=pars[1] if len(pars)>2: defname=pars[2] else: defname=None load_relation(filename,defname) elif command.startswith('UNLOAD '): pars=command.split(' ') if len(pars)<2: print colorize("Missing parameter",ERROR_COLOR) return if pars[1] in relations: del relations[pars[1]] completer.remove_completion(pars[1]) else: print colorize("No such relation %s" % pars[1],ERROR_COLOR) pass elif command.startswith('SAVE '): pars=command.split(' ') if len(pars)!=3: print colorize("Missing parameter",ERROR_COLOR) return filename=pars[1] defname=pars[2] if defname not in relations: print colorize("No such relation %s" % defname,ERROR_COLOR) return try: relations[defname].save(filename) except Exception,e: print colorize(e,ERROR_COLOR) else: exec_query(command) def replacements(query): '''This funcion replaces ascii easy operators with the correct ones''' query=query.replace(u'_PRODUCT' , u'*') query=query.replace(u'_UNION' , u'ᑌ') query=query.replace(u'_INTERSECTION' , u'ᑎ') query=query.replace(u'_DIFFERENCE' , u'-') query=query.replace(u'_JOIN' , u'ᐅᐊ') query=query.replace(u'_LJOIN' , u'ᐅLEFTᐊ') query=query.replace(u'_RJOIN' , u'ᐅRIGHTᐊ') query=query.replace(u'_FJOIN' , u'ᐅFULLᐊ') query=query.replace(u'_PROJECTION' , u'π') query=query.replace(u'_RENAME_TO' , u'➡') query=query.replace(u'_SELECTION' , u'σ') query=query.replace(u'_RENAME' , u'ρ') query=query.replace(u'_DIVISION' , u'÷') return query def exec_query(command): '''This function executes a query and prints the result on the screen if the command terminates with ";" the result will not be printed ''' command=unicode(command,'utf-8') #If it terminates with ; doesn't print the result if command.endswith(';'): command=command[:-1] printrel=False else: printrel=True #Performs replacements for weird operators command=replacements(command) #Finds the name in where to save the query parts=command.split('=',1) if len(parts)>1 and rtypes.is_valid_relation_name(parts[0]): relname=parts[0] query=parts[1] else: relname='last_' query=command #Execute query try: pyquery=parser.parse(query) result=eval(pyquery,relations) print colorize("-> query: %s" % pyquery.encode('utf-8'),0x00ff00) if printrel: print print result relations[relname]=result completer.add_completion(relname) except Exception, e: print colorize(e,ERROR_COLOR) def main(files=[]): print colorize('> ',PROMPT_COLOR) + "; Type HELP to get the HELP" print colorize('> ',PROMPT_COLOR) + "; Completion is activated using the tab (if supported by the terminal)" for i in files: load_relation(i) readline.set_completer(completer.complete) readline.parse_and_bind('tab: complete') readline.parse_and_bind('set editing-mode emacs') readline.set_completer_delims(" ") while True: try: line = raw_input(colorize('> ',PROMPT_COLOR)) if isinstance(line,str) and len(line)>0: exec_line(line) except KeyboardInterrupt: print continue except EOFError: print sys.exit(0) if __name__ == "__main__": main()