#!/usr/bin/env python3 """ @modified: January 27, 2020 @author: Dale Hamel @author: Brian Fristensky @contact: frist@cc.umanitoba.ca The purpose of this class is to Provide support for the various actions that are repeated by several different python scripts """ import os import sys import tarfile import shutil class Birchmod: """ Purpose: This class provides common safety features to scripts, as well as making user interactions more intuitive. In particular, this class is intended to provide user-readable information when an error occurs """ def __init__(self, prog, use): """Initializes birchmod to contain the program usage and program name""" self.PROGRAM = prog self.USAGE = use def getHomeDir(self): """return the location of the user's home directory""" from os.path import expanduser return expanduser("~") def getEmailAddr(self): """return user's email address This is currently a bit of a hack. We need to find a better way to do this. 1. If MAILID environment variable is set, use that. 2. If MAILID="", then use the mailid in $BIRCH/local/admin/BIRCH.properties """ emailaddr = os.environ.get("MAILID") if emailaddr == "" : BIRCH = os.environ.get("BIRCH") propfile = os.path.join(BIRCH,'local','admin','BIRCH.properties') propfile_h = open(propfile,'r') lines = propfile_h.readlines() if any(line.startswith('BirchProps.adminEmail') for line in lines) : emailaddr = line.strip().split('=')[1] return str(emailaddr) def GetBIRCHProperties(self,BIRCHDIR,PropName) : """ Retrieve a value from BIRCH.properties. eg. To retrieve the value of BirchProps.adminEmail: GetBIRCHProperties(BIRCHDIR,"adminEmail") """ PFN = os.path.join(BIRCHDIR , 'local' , 'admin' , 'BIRCH.properties') pfile = open(PFN,'r') Value = "" Target = 'BirchProps.' + PropName lines = pfile.readlines() pfile.close() plen = len(lines) if plen > 0 : i = 0 while (i < plen) and (Value == "") : line = lines[i] # ignore blank lines and comment lines if not (line.startswith('#')) : tokens = line.split("=") if tokens[0] == Target : Value = tokens[1].strip() i += 1 return Value def wget(self, url, name): """Downloads from url specified to the filename/path specified and displays progress""" print("Fetching "+name+" from "+url+"\n\n") import urllib.request def reporthook(numblocks, blocksize, filesize, url=None): #base = os.path.basename(url) try: percent = min((numblocks * blocksize * 100) / filesize, 100) except: percent = 100 if numblocks != 0: MB_CONST = 1000000 # 1 MB is 1 million bytes out_str = self.PROGRAM + "Progress:" + str(percent) + '%' + " of " + str(filesize / MB_CONST) + "MB\r" sys.stdout.write(out_str) #urlStream = urllib.urlretrieve(url, name, reporthook) response = urllib.request.urlopen(url) content = response.read() f = open(name, 'wb') f.write(content) f.close() def untar(self, file, path="."): """Extracts the tarfile given by file to current working directory by default, or path""" tarball = tarfile.open(file) tarball.extractall(path) tarball.close() #def get_free_bytes(self, path, blocksize=1024): # """Returns the number of free megabytes in a directory""" # stat = os.statvfs(path) # free_bytes = (stat.f_bavail * stat.f_frsize) / blocksize # 1024 is constant across all systems tested # #free = free_bytes / 1000 # if we want free space in something managable, megabytes # return free_bytes def rmrf(self, path): """Mimics the behavior or unix rm -rf for the given path""" if (os.path.exists(path)): shutil.rmtree(path) def printusage(self): """Called when a program performs an illegal operation. Causes program to print its appropriate usage, and exit nicely""" print(self.PROGRAM + self.USAGE) raise(SystemExit) def file_error(self, file_name): """Called when reading a file fails, used to provide more comprehensive output""" file_name = str(file_name) print(self.PROGRAM + "An error occurred trying to process file named : \'" + file_name + "\'") if (os.path.isfile(file_name)): print(self.PROGRAM + "The file \'" + file_name + "\' exists, but may not be accessed.") else: print(self.PROGRAM + "The file \'" + file_name + "\' does not exist in \'" + os.getcwd() + "\'") raise(SystemExit) def exit_success(self): """Used to indicate to the user that a script did execute, and completed successfully""" print(self.PROGRAM + "Successfully completed execution") raise(SystemExit) def arg_given(self, argId): """Returns true if "argId" was a string passed on the command line""" if(not isinstance(argId, str)): print(self.PROGRAM + "Argument to be check for not supplied as string") else: if (argId in sys.argv): return True else: return False def documentor(self): """called by using "pydoc {path to script} pydoc" * note must pass "pydoc" as an argument to toggle documentation mode*""" if (self.arg_given("-pydoc")): print(self.PROGRAM + "Generating documentation...") import doctest doctest.testmod() return True else: return False class Argument: """ The purpose of this class is to provide a simplified and common way for all scripts retrieve arguments from the command line parameters provided (sys.argv) This is done by declaring an Argument variable, and specifying any advanced attributes with the various augmentation methods provided. BUG: Argument will split up a quoted command line argument containing blanks into individual tokens. Eg. 'this should be a single argument'. DEPRECATED in favor of optparse. Even optparse is deprecated in favor of agrparse, but argparse is new in Python 2.7. Therefore, it is safer to use optparse, which has a very similar syntax to argparse, unless you are sure you will be using Python 2.7 or later. It is also worth mentioning that optparse will correctly parse command line arguments enclosed in quotes. There have been reports that argparse will break up strings between blank spaces. Examples: # optional arguments with parameters self.AInf = Argument("-inf", str, BM) self.AInf.set_optional() self.AOutf = Argument("-outf", str, BM) self.AOutf.set_optional() # optional argument with no parameters ie. switch self.AInvert = Argument("-inv", str, BM) self.AInvert.set_is_switch() self.AInvert.set_optional() # required arguments at a specific position. Ainfile = Argument("", str, BM) Ainfile.set_position(-2) Aoutfile = Argument("", str, BM) Aoutfile.set_position(-1) try: if (BM.arg_given("-inf")): self.InFormat = self.AInf.fetch() if (BM.arg_given("-outf")): self.OutFormat = self.AOutf.fetch() self.Invert = BM.arg_given("-inv") self.Ifn = Ainfile.fetch() self.Ofn = Aoutfile.fetch() except ValueError: BM.printusage() """ def __init__(self, arg_id, arg_type, BMOD): """ Initializer: arg_id: The command line flag that specifies an argument parameter is to follow, ex: "-o outfile" type: The type that the argument is. This must be a basic type, such as str, float, bool. if you are unsure if something is a basic type, try running "type(yourtypename)" at interpretter BM: a pointer to the Birchmod module for the class using this argument (they are coupled) """ self.arg_id = str(arg_id) self.is_required = True # by default arguments are required self.position = 0 #by default the argument can go anywhere, so we put a sentinal value here self.exclude = None#by default not mutually exclusive self.is_flag = False self.BM = None #ensure that the type passed is a valid KNOWN type if(isinstance(arg_type, type)): self.arg_type = arg_type elif(arg_type == None): self.is_flag = True self.arg_type = None else: raise(ValueError) #The Argument class uses comon methods from the Birchmod class, so it must be passed an instance if(isinstance(BMOD, Birchmod)): self.BM = BMOD else: raise(ValueError) def set_position(self, position): """ Specify the position on sys.argv where the argument is always found. Argument 0 is the name of the program, so argument 1 is the first argument. Note that arguments near the end can be specified by len(sys.argv)-X, where X is the index from the end. "-1" refers to the last argument, "-2" refers to the next to last argument, etc. """ self.position = int(position)#if this argument needs to be a specific position, this is where it must be def add_exclusive(self, exclude): """Add an argument (flag) to the list of mutually exclusive argument flags for this Argument's flag""" if (exclude == self.arg_id): print(self.BM.PROGRAM + "An argument may not exclude itself") if(self.exclude == None): self.exclude = list() self.exclude.append(exclude) def set_optional(self): """Use this if the paramter passed is NOT required""" self.is_required = False def set_is_switch(self): """if this argument is just a switch (with no parameters), set this to true (ex ls -l, -l is a switch)""" self.is_flag = True self.arg_type = None def fetch(self): """ This method attempts to fetch the argument with the specified attributes from sys.argv """ #if a specified location is already given, get it from there if (abs(self.position) > 0): if (self.position < len(sys.argv)): to_return = sys.argv[self.position] else: raise(ValueError) #if it is a simple flag elif(self.is_flag): if (self.exclude != None): for each in self.exclude: if(each in sys.argv and self.arg_id in sys.argv): print(self.BM.PROGRAM + "Cannot combine mutually exclusive options \"" + each + "\" and \"" + self.arg_id + "\"") exit() to_return = (self.arg_id in sys.argv) #if it is a flag WITH a paramter else: if (self.is_required):#required arguments must provided if (not self.arg_id in sys.argv): print(sys.argv) raise(ValueError) else: to_return = self.__get_arg(self.arg_id) else:#optional if (self.exclude != None): for each in self.exclude: if(each in sys.argv and self.arg_id in sys.argv): raise(ValueError) to_return = self.__get_arg(self.arg_id) if(self.arg_type != None and self.arg_id in sys.argv): to_return = self.arg_type(to_return) if(self.arg_type == str and to_return == None): to_return = '' #print("got arg:"+str(to_return)) return to_return ###########################PRIVATE######################################## def __get_arg(self, argId): """ Used as a helper method. Retrieves a flags parameter, assuming it is one index to the right. ex "-o outfile", if argId= -o, this will return outfile """ argId = str(argId) try: position = sys.argv.index(argId) + 1 if (position < len(sys.argv)): return sys.argv[position] else: raise(ValueError) except ValueError: print (self.BM.PROGRAM + "Argument " + argId + " not supplied at command line.") return None class HTMLWriter: "Methods for writing html to a file" def __init__(self): """ Initializes arguments: indentwidth=3 col=0 lpad="" """ self.indentwidth = 3 self.col = 0 # current indentation column self.lpad = "" def indent(self): """ **indent is not currently used by htmlwriter** decrease indent using identwidth blank spaces """ self.col = self.col + self.indentwidth self.lpad = ' '.rjust(self.col) def undent(self): """ **undent is not currently used by htmlwriter** decrease indent using identwidth blank spaces """ self.col = self.col - self.indentwidth if self.col < 0: self.col = 0 self.lpad = "" else: self.lpad = ' '.rjust(self.col) def start(self, htmlfile, tagname, attributes): """ Write begin tag, with attributes @param htmlfile: The name of the html file to write the tag for @type htmlfile: str @param tagname: The name of the tag @type tagname: str @param attributes: The tag information itself @type attributes: str """ htmlfile.write('<' + tagname + attributes + '>\n') def end(self, htmlfile, tagname): """ Write end tag @param htmlfile: The of the html file to write tag for @type htmlfile: str @param tagname: The name of the tag to write @type tagname: str """ htmlfile.write('\n') def page_title(self, htmlfile, title): """ Write title @param htmlfile: The name of the html file to add the title to @type htmlfile: str @param title: The title to write @type title: str """ htmlfile.write('' + 'birch - ' + title + '\n') def link(self, htmlfile, url, attributes, text): """ FIXME @param htmlfile: @type htmlfile: @param url: @type url: @param attributes: @type attributes: @param text: @type text: """ "Write hypertext link" htmlfile.write('' + text + '') def start_page(self, htmlfile, title): """ FIXME @param htmlfile: @type htmlfile: @param title: @type title: """ "Information at the top of each page is the same." htmlfile.write('\n') self.start(htmlfile, 'html', '') self.page_title(htmlfile, title) attributes = ' style ="background-color: rgb(255, 204, 0);"' self.start(htmlfile, 'body', attributes) # main heading, including birch logo which links to birch # home page. self.start(htmlfile, 'h1', '') url = '../../index.html' text = 'birch - ' self.link(htmlfile, url, '', text) htmlfile.write(' ' + title) self.end(htmlfile, 'h1') htmlfile.write('
') def indent_text(self, htmlfile, text): """ FIXME @param htmlfile: @type htmlfile: @param text: @type text: """ "indent a line of text" attributes = ' style="margin-left: 40px;"' self.start(htmlfile, 'div', attributes) htmlfile.write(text) self.end(htmlfile, 'div') def end_page(self, htmlfile): """ FIXME @param htmlfile: @type htmlfile: """ "html tags for end of page" self.end(htmlfile, 'body') self.end(htmlfile, 'html') class Htmlutils: def __init__(self, CATDICT, PROGDICT): """ FIXME @param CATDICT: @type CATDICT: @param PROGDICT: @type PROGDICT: """ self.CATDICT = CATDICT self.PROGDICT = PROGDICT # - - - - - - - - - - - - - - - - - - - - - - - - def tokenize(self, line): """ FIXME @param line: @type line: """ "split up input line into tokens, where one or more spaces are seperators" "tokenize implicitly gets rid of the first token in the list" # parse the line into tokens tokens = line.split() # strip quotes that begin and end data values in .ace files i = 1 while i < len(tokens): tokens[i] = tokens[i].strip('"') # get rid of \ escape characters added by acedb tokens[i] = tokens[i].replace('\\', '') i = i + 1 return tokens def cmp_to_key(self, mycmp): """ this function simplifies the transition to python 3 by eleiminating the need for the "cmp" function this was a recommended workaround for using "cmp"'s in sorts (recommended by: http://wiki.python.org/moin/HowTo/Sorting/) """ class K(object): def __init__(self, obj, * args): """ FIXME @param obj: @type obj: @param *args: @type *args: """ self.obj = obj def __lt__(self, other): """ FIXME @param other: @type other: """ return mycmp(self.obj, other.obj) < 0 def __gt__(self, other): """ FIXME @param other: @type other: """ return mycmp(self.obj, other.obj) > 0 def __hash__(self, other): """ FIXME @param other: @type other: """ return mycmp(self.obj, other.obj) == 0 def __le__(self, other): """ FIXME @param other: @type other: """ return mycmp(self.obj, other.obj) <= 0 def __ge__(self, other): """ FIXME @param other: @type other: """ return mycmp(self.obj, other.obj) >= 0 def __ne__(self, other): """ FIXME @param other: @type other: """ return mycmp(self.obj, other.obj) != 0 return K def name_to_url(self, name, doc_prefix): """ FIXME @param name: @type name: @param doc_prefix: @type doc_prefix: """ "Convert a Unix path to a URL" "If the path begins with an environment variable like $doc," "assume that the name of the directory is the name of the variable" " ie. just delete the '$' and append the path to DOCPREFIX" "Otherwise, assume it is a URL of the form 'http:///'" if name[0] == '$': url = doc_prefix + '/' + name[1:] #url = name[1:] else: url = name return url def get_prefix(self, fn, p): """ FIXME @param fn: @type fn: @param p: @type p: """ "read $birch/install-scripts/newstr.param, to get the prefixes" "needed for urls" file = open(fn, 'r') i = 1 for line in file: if i == 3: p.DOCPREFIX = line.strip("\s") i = i + 1 file.close() if (p.DOCPREFIX.find('http://') == 0) or (p.DOCPREFIX.find('file:///') == 0): okay = True else: okay = False return okay class SimpleXML: "Simple methods for working wth XML files as an alternative to xml or defusedxml" def __init__(self) : """ """ def GetXMLField(self,File,FieldName): """ return a string value from a field of the form Value This function ONLY returns the first field in the file that matches the pattern, regardless of how many subsequent fields may match. """ Value = "" BeginTarget = '<' + FieldName + '>' EndTarget = '' h_XMLFile = open(File, 'r') line = h_XMLFile.readline() POSN = -1 while (line != "") and (Value == "") : LPOSN = line.find(BeginTarget) RPOSN = line.find(EndTarget) if LPOSN > -1 and RPOSN > -1 : Value = line[LPOSN+len(BeginTarget):RPOSN] line = h_XMLFile.readline() h_XMLFile.close() return Value #used to generate documentation if __name__ == "__main__": import doctest doctest.testmod()