#!/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('' + tagname + '>\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('