ISCE_INSAR/applications/iscehelp.py

560 lines
23 KiB
Python

#!/usr/bin/env python3
#Author:Giangi Sacco
#Copyright 2009-2014, by the California Institute of Technology.
import isce
import os
import sys
import json
import argparse
import collections
import importlib
from iscesys.DictUtils.DictUtils import DictUtils as DU
class Helper(object):
def getRegistered(self):
#Register all the factory that want to provide help
#Each .hlp file has a json structure like
'''
{TypeName
{'args':
{
#positional arguments have as key the position in str format
#since json only allows keys to be string
'0':{'value':values,'type':type},
'1':{'value':values,'type':type}
#keyword arguments have the name of the argument as key
argname:{'value':values,'type':type,'optional':bool,'default':default}
},
'factory':factory,
'package':package,
}
}
'''
registered = {}
helplist = os.listdir(self._helpDir)
for name in helplist:
fullname = os.path.join(self._helpDir,name)
if not name.endswith('.hlp'):
continue
with open(fullname) as fp:
registered.update(json.load(fp))
return collections.OrderedDict(sorted(registered.items()))
def getTypeFromFactory(self,factory):
instanceType = 'N/A'
for k,v in self._registered.items():
if v['factory'] == factory:
instanceType = k
break
return instanceType
def getInstance(self,typeobj):
obj2help = self._registered[typeobj]
args,kwargs = self.getPosAndKwArgs(obj2help)
factory = getattr(importlib.import_module(obj2help['package']),obj2help['factory'])
return factory(*args,**kwargs)
def convert(self,value,type_):
try:
module = importlib.import_module('builtins')
ret = getattr(module,type_)(value)
except:
print("Cannot convert",value,"to a type",type_)
raise Exception
return ret
def askHelp(self, instance, steps=False):
#since it can be called externally, make sure that we remove the
#arguments that are not understood by the isce Parser
try:
sys.argv = [sys.argv[0]]
instance._parameters()
instance.initProperties({})
instance._init()
instance._facilities()
instance._dictionaryOfFacilities = DU.renormalizeKeys(instance._dictionaryOfFacilities)
self.helper(instance, steps)
except Exception as e:
print("No help available.")
def getPosAndKwArgs(self,obj):
args = []
kwargs = {}
if self._inputs.args:#otherwise no args present
for arg,i in zip(self._inputs.args,range(len(self._inputs.args))):
try:
#positional argument
args.append(self.convert(arg,obj['args'][str(i)]['type']))
except Exception as e:
try:
kw,val = arg.split("=")
kwargs[kw] = self.convert(val,obj['args'][kw]['type'])
except Exception as e:
print(e)
raise
return (args,kwargs)
def step_help(self, instance):
instance.help_steps()
instance._add_methods()
instance._steps()
print()
print("Command line options for steps processing are formed by")
print("combining the following three options as required:\n")
print("'--start=<step>', '--end=<step>', '--dostep=<step>'\n")
print("The step names are chosen from the following list:")
print()
npl = 5
nfl = int(len(instance.step_list_help)/npl)
for i in range(nfl):
print(instance.step_list[i*npl:(i+1)*npl])
if len(instance.step_list) % npl:
print(instance.step_list[nfl*npl:])
print()
print("If --start is missing, then processing starts at the "+
"first step.")
print("If --end is missing, then processing ends at the final "+
"step.")
print("If --dostep is used, then only the named step is "+
"processed.")
print()
print("In order to use either --start or --dostep, it is "+
"necessary that a")
print("previous run was done using one of the steps options "+
"to process at least")
print("through the step immediately preceding the starting "+
"step of the current run.")
print()
sys.exit(0)
def helper(self,instance,steps=False):
#if facility is None we print the top level so the recursion ends right away
#if facility is defined (not None) and is not part of the facilities
# then keep going down the tree structure
instance.help()
print()
try:
try:
#only applications have it
instance.Usage()
except Exception:
pass
print()
if steps:
self.step_help(instance)
sys.exit(0)
except Exception as x:
sys.exit(0)
finally:
pass
#sometime there is no help available. Postpone the printing until
#there is something to print for sure
fullMessage = ""
fullMessage = "\nSee the table of configurable parameters listed \n"
fullMessage += "below for a list of parameters that may be specified in the\n"
fullMessage += "input file. See example input xml files in the isce 'examples'\n"
fullMessage += "directory. Read about the input file in the ISCE.pdf document.\n"
# maxname = max(len(n) for n in self.dictionaryOfVariables.keys())
# maxtype = max(len(str(x[1])) for x in self.dictionaryOfVariables.values())
# maxman = max(len(str(x[2])) for x in self.dictionaryOfVariables.values())
# maxdoc = max(len(x) for x in self.descriptionOfVariables.values())
maxname = 27
maxtype = 10
maxman = 10
maxdoc = 30
underman = "="*maxman
undertype = "="*maxtype
undername = "="*maxname
underdoc = "="*maxdoc
spc = " "
n = 1
spc0 = spc*n
fullMessage += "\nThe user configurable inputs are given in the following table.\n"
fullMessage += "Those inputs that are of type 'component' are also listed in\n"
fullMessage += "table of facilities below with additional information.\n"
fullMessage += "To configure the parameters, enter the desired value in the\n"
fullMessage += "input file using a property tag with name = to the name\n"
fullMessage += "given in the table.\n"
line = "name".ljust(maxname,' ')+spc0+"type".ljust(maxtype,' ')
line += spc0+"mandatory".ljust(maxman,' ')+spc0+"doc".ljust(maxdoc,' ')
fullMessage += line + '\n'
line = undername+spc0+undertype+spc0+underman+spc0+underdoc
fullMessage += line + '\n'
#make sure that there is something to print
shallPrint = False
instance.reformatDictionaryOfVariables()
for x, y in collections.OrderedDict(sorted(instance.dictionaryOfVariables.items())).items():
#skip the mandatory private. Those are parameters of Facilities that
#are only used by the framework and the user should not know about
if y['mandatory'] and y['private']:
continue
if x in instance.descriptionOfVariables:
z = instance.descriptionOfVariables[x]['doc']
elif x in instance._dictionaryOfFacilities and 'doc' in instance._dictionaryOfFacilities[x]:
z = instance._dictionaryOfFacilities[x]['doc']
else:
z = 'N/A'
shallPrint = True
try:
yt = str(y['type']).split("'")[1]
except:
yt = str(y['type'])
lines = []
self.cont_string = ''
lines.append(self.columnate_words(x, maxname, self.cont_string))
lines.append(self.columnate_words(yt, maxtype, self.cont_string))
lines.append(self.columnate_words(str(y['mandatory']), maxman, self.cont_string))
lines.append(self.columnate_words(z, maxdoc, self.cont_string))
nlines = max(map(len,lines))
for row in lines:
row += [' ']*(nlines-len(row))
for ll in range(nlines):
fullMessage += lines[0][ll].ljust(maxname,' ')
fullMessage += spc0+lines[1][ll].ljust(maxtype,' ')
fullMessage += spc0+lines[2][ll].ljust(maxman,' ')
fullMessage += spc0+lines[3][ll].ljust(maxdoc,' ') + '\n'
# line = spc0+x.ljust(maxname)+spc0+yt.ljust(maxtype)
# line += spc0+y[2].ljust(maxman)+spc0+z.ljust(maxdoc)
# print(line)
if(shallPrint):
print(fullMessage)
else:
print("No help available\n")
#only print the following if there are facilities
if(instance._dictionaryOfFacilities.keys()):
#maxname = max(len(n) for n in self._dictionaryOfFacilities.keys())
maxname = 20
undername = "="*maxname
# maxmod = max(
# len(x['factorymodule']) for x in
# self._dictionaryOfFacilities.values()
# )
maxmod = 15
undermod = "="*maxmod
# maxfac = max(
# len(x['factoryname']) for x in
# self._dictionaryOfFacilities.values()
# )
maxfac = 17
underfac = "="*maxfac
# maxarg = max(
# len(str(x['args'])) for x in self._dictionaryOfFacilities.values()
# )
maxarg = 20
underarg = "="*maxarg
# maxkwa = max(
# len(str(x['kwargs'])) for x in
# self._dictionaryOfFacilities.values()
# )
maxkwa = 7
# underkwa = "="*max(maxkwa, 6)
underkwa = "="*maxkwa
spc = " "
n = 1
spc0 = spc*n
firstTime = True
for x, y in collections.OrderedDict(sorted(instance._dictionaryOfFacilities.items())).items():
#skip the mandatory private. Those are parameters of Facilities that
#are only used by the framework and the user should not know about
if y['mandatory'] and y['private']:
continue
#only print if there is something
if firstTime:
firstTime = False
print()
print("The configurable facilities are given in the following table.")
print("Enter the component parameter values for any of these "+
"facilities in the")
print("input file using a component tag with name = to "+
"the name given in")
print("the table. The configurable parameters for a facility "+
"are entered with ")
print("property tags inside the component tag. Examples of the "+
"configurable")
print("parameters are available in the examples/inputs directory.")
print("For more help on a given facility run")
print("iscehelp.py -t type")
print("where type (if available) is the second entry in the table")
print()
line = "name".ljust(maxname)+spc0+"type".ljust(maxmod)
print(line)
line = " ".ljust(maxname)+spc0+" ".ljust(maxmod)
print(line)
line = undername+spc0+undermod
print(line)
lines = []
self.cont_string = ''
lines.append(self.columnate_words(x, maxname, self.cont_string))
z = self.columnate_words(self.getTypeFromFactory(y['factoryname']),maxmod, self.cont_string)
lines.append(z)
nlines = max(map(len,lines))
for row in lines:
row += [' ']*(nlines-len(row))
for ll in range(nlines):
out = lines[0][ll].ljust(maxname)
out += spc0+lines[1][ll].ljust(maxmod)
print(out)
# line = spc0+x.ljust(maxname)+spc0+y['factorymodule'].ljust(maxmod)
# line += spc0+y['factoryname'].ljust(maxfac)
# line += spc0+str(y['args']).ljust(maxarg)
# line += spc0+str(y['kwargs']).ljust(maxkwa)
# print(line)
return sys.exit(1)
def columnate_words(self, s, n, cont='',onePerLine=False):
"""
arguments = s (str), n (int), [cont (str)]
s is a sentence
n is the column width
Returns an array of strings of width <= n.
If any word is longer than n, then the word is split with
continuation character cont at the end of each column
"""
#Split the string s into a list of words
a = s.split()
#Check the first word as to whether it fits in n columns
if a:
if len(a[0]) > n:
y = [x for x in self.nsplit(a[0]+" ", n, cont)]
else:
y = [a[0]]
cnt = len(y[-1])
for i in range(1, len(a)):
cnt += len(a[i])+1
if cnt <= n:
if not onePerLine:
y[-1] += " "+a[i]
else:
y.append(a[i])
else:
y += self.nsplit(a[i], n, cont)
if not onePerLine:
cnt = len(y[-1])
else:
cnt = n+1
else:
y = ['']
return y
def nsplit(self, s, nc, cont=''):
x = []
ns = len(s)
n = nc - len(cont)
for i in range(int(ns/n)):
x.append(s[i*n:(i+1)*n]+cont)
if ns%n:
x.append(s[int(ns/n)*n:])
return x
def typeNeedsNoArgs(self,type_):
try:
ret = False
for k,v in self._registered[type_]['args'].items():
#it's positional so it need the args
if k.isdigit():
ret = True
break
elif (not 'optional' in v) or (not ('optional' in v and v['optional'])):
ret = True
break
except Exception:
ret = False
return (not ret)
def printInfo(self,type_,helpIfNoArg = False, steps=False):
#try to print the info of the arguments necessary to instanciate the instance
try:
sortedArgs = collections.OrderedDict(sorted(self._registered[type_]['args'].items()))
maxname = 17
undername = "="*maxname
maxtype = 10
undertype = "="*maxtype
maxargtype = 10
underargtype = "="*maxargtype
maxman = 10
underman = "="*maxman
maxvals = 20
undervals = "="*maxvals
maxdef = 10
underdef = "="*maxdef
spc = " "
n = 1
spc0 = spc*n
line = "name".ljust(maxname,' ')+spc0+"type".ljust(maxtype,' ')+spc0+"argtype".ljust(maxargtype,' ')
line += spc0+"mandatory".ljust(maxman,' ')+spc0+"values".ljust(maxvals,' ')+spc0+"default".ljust(maxdef,' ')
fullMessage = line + '\n'
line = undername+spc0+undertype+spc0+underargtype+spc0+underman+spc0+undervals+spc0+underdef
shallPrint = False
fullMessage += line + '\n'
for arg,val in sortedArgs.items():
try:
type = str(val['type'])
except Exception:
type = 'N/A'
if(arg.isdigit()):
argtype = 'positional'
else:
argtype = 'keyword'
try:
mandatory = 'False' if val['optional'] else 'True'
except Exception:
mandatory = 'True'
try:
default = str(val['default'])
except Exception:
default = 'Not set'
if isinstance(val['value'],list):
posarg = ' '.join(val['value'])
elif isinstance(val['value'],str) and val['value']:
posarg = val['value']
else:
posarg = ''
lines = []
self.cont_string = ''
lines.append(self.columnate_words(arg, maxname, self.cont_string))
lines.append(self.columnate_words(type, maxtype, self.cont_string))
lines.append(self.columnate_words(argtype, maxargtype, self.cont_string))
lines.append(self.columnate_words(mandatory, maxman, self.cont_string))
lines.append(self.columnate_words(posarg, maxvals, self.cont_string,True))
lines.append(self.columnate_words(default, maxdef, self.cont_string))
nlines = max(map(len,lines))
for row in lines:
try:
row += [' ']*(nlines-len(row))
except:
dummy = 1
for ll in range(nlines):
fullMessage += lines[0][ll].ljust(maxname,' ')
fullMessage += spc0+lines[1][ll].ljust(maxtype,' ')
fullMessage += spc0+lines[2][ll].ljust(maxargtype,' ')
fullMessage += spc0+lines[3][ll].ljust(maxman,' ')
fullMessage += spc0+lines[4][ll].ljust(maxvals,' ')
fullMessage += spc0+lines[5][ll].ljust(maxdef,' ') + '\n'
shallPrint = True
# line = spc0+x.ljust(maxname)+spc0+yt.ljust(maxtype)
# line += spc0+y[2].ljust(maxman)+spc0+z.ljust(maxdoc)
# print(line)
if(shallPrint):
print("\nType ",type_, ": Constructor requires arguments described in the\n" +
"table below. Use the -a option with the mandatory arguments\n"+
"to ask for more help. Run iscehelp.py -h for more info on the -a option.\n",sep="")
print(fullMessage)
except Exception:
print("\nType ",type_, ": constructor requires no arguments",sep="")
#try to see if one can create an instance and provide more help
if helpIfNoArg:
instance = self.getInstance(type_)
self.askHelp(instance, self._inputs.steps)
def printAll(self):
for k in self._registered.keys():
self.printInfo(k)
def run(self):
self.parse()
sys.argv = [sys.argv[0]]
noArgs = True
for k,v in self._inputs._get_kwargs():
if(v):
noArgs = False
break
if self._inputs.info or noArgs:
#if no arguments provided i.e. self._input has all the attributes = None
#then print the list of all available helps
self.printAll()
elif self._inputs.type and not self._inputs.args:
#if only -t type is provided print how to get help for that specific type
self.printInfo(self._inputs.type,helpIfNoArg=self.typeNeedsNoArgs(self._inputs.type))
elif self._inputs.type and (self._inputs.args):
#if type and arguments are provided then provide help for that type
if self._inputs.type in self._registered:
instance = self.getInstance(self._inputs.type)
self.askHelp(instance, self._inputs.steps)
else:
print("Help for",self._inputs.type,"is not available. Run iscehelp.py"+\
" with no options to see the list of available type of objects" +\
" one can get help for")
sys.exit(1)
elif self._inputs.type and self._inputs.steps and not self._inputs.args:
#if only -t type is provided print how to get help for that specific type
self.printInfo(self._inputs.type, helpIfNoArg=True,
steps=self._inputs.steps)
elif self._inputs.type and (self._inputs.args) and self._inputs.steps:
#if type and arguments are provided then provide help for that type
if self._inputs.type in self._registered:
instance = self.getInstance(self._inputs.type)
self.askHelp(instance, self._inputs.steps)
else:
print("Help for",self._inputs.type,"is not available. Run iscehelp.py"+\
" with -i (--info) to see the list of available type of objects" +\
" one can get help for")
sys.exit(1)
def parse(self):
epilog = 'Run iscehelp.py with no arguments or with -i option to list the available object\n'
epilog += 'types for which help is provided\n'
parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,epilog=epilog)
parser.add_argument('-i','--info',dest='info',action='store_true',help='Provides the list of registered object types')
parser.add_argument('-t','--type',dest='type',type=str,help='Specifies the object type for which help is sought')
parser.add_argument('-a','--args',dest='args',type=str,nargs='+',help='Set of positional and keyword arguments '\
+'that the factory of the object "type" takes.'\
+ 'The keyword arguments are specified as keyword=value with no spaces.')
parser.add_argument('-s','--steps',dest='steps',action='store_true',help='Provides the list of steps in the help message')
self._inputs = parser.parse_args()
def __init__(self):
import isce
#the directory is defined in SConstruct
self._helpDir = os.path.join(isce.__path__[0],'helper')
self._registered = self.getRegistered()
self._inputs = None
def main():
hp = Helper()
hp.run()
if __name__ == '__main__':
main()