pyraf-2.1.15/0000755000665500117240000000000013310762302013713 5ustar sontagpassdev00000000000000pyraf-2.1.15/lib/0000755000665500117240000000000013310762277014474 5ustar sontagpassdev00000000000000pyraf-2.1.15/lib/pyraf/0000755000665500117240000000000013310762300015600 5ustar sontagpassdev00000000000000pyraf-2.1.15/lib/pyraf/pyrafTk.py0000644000665500117240000000177713310762263017616 0ustar sontagpassdev00000000000000"""pyrafTk.py: modify tkinter root to print short PyRAF tracebacks $Id$ R. L. White, 2000 November 17 """ from __future__ import division # confidence high import sys import Tkinter as TKNTR # requires 2to3 import wutil class _PyrafTk(TKNTR.Tk): """Modified Tk class that prints short pyraf tracebacks""" def __init__(self, function): self._pyraf_showtraceback = function TKNTR.Tk.__init__(self) def report_callback_exception(self, exc, val, tb): sys.stderr.write("Exception in tkinter callback\n") sys.last_type = exc sys.last_value = val sys.last_traceback = tb self._pyraf_showtraceback() def setTkErrorHandler(function): """Create Tk root with error handler modified to call function If Tk root already exists, this function has no effect. """ if TKNTR._default_root is None and wutil.hasGraphics: try: root = _PyrafTk(function) root.withdraw() except TKNTR.TclError: pass pyraf-2.1.15/lib/pyraf/old_files/0000755000665500117240000000000013310762300017540 5ustar sontagpassdev00000000000000pyraf-2.1.15/lib/pyraf/old_files/iraffunctions_tests.py0000644000665500117240000000160713310762263024222 0ustar sontagpassdev00000000000000from __future__ import print_function from pyraf import sscanf def test_sscanf(): """ A basic unit test that sscanf was built/imported correctly and can run. """ assert sscanf is not None, 'Error importing sscanf during iraffunctions init' # aliveness l = sscanf.sscanf("seven 6 4.0 -7", "%s %d %g %d") assert l==['seven', 6, 4.0, -7], 'Unexpected! l = '+str(l) # bad format l = sscanf.sscanf("seven", "%d") assert l==[], 'Unexpected! l = '+str(l) # %c l = sscanf.sscanf("seven", "%c%3c%99c") assert l==['s', 'eve', 'n'], 'Unexpected! l = '+str(l) # hex l = sscanf.sscanf("0xabc90", "%x") assert l==[703632], 'Unexpected! l = '+str(l) # API error try: l = sscanf.sscanf() except TypeError: pass # this is expected - anything else should raise # finished successfully print('test_sscanf successful') pyraf-2.1.15/lib/pyraf/old_files/subproc_tests.py0000644000665500117240000000543413310762263023027 0ustar sontagpassdev00000000000000from __future__ import division, print_function import sys import time from stsci.tools.for2to3 import tobytes from pyraf.subproc import Subprocess, SubprocessError def test(fout=sys.stdout): fout.write("Starting test ...\n") assert hasattr(fout, 'write'), "Input not a file object: " + str(fout) print("\tOpening subprocess (28 Dec 2017):") p = Subprocess('cat', expire_noisily=1) # set to expire noisily... print(p) print("\tOpening bogus subprocess, should fail:") try: # grab stderr just to make sure the error message still appears b = Subprocess('/', 1, expire_noisily=1) assert b.wait(1), 'How is this still alive after 1 sec?' except SubprocessError: print("\t...yep, it failed.") print('\tWrite, then read, two newline-terminated lines, using readline:') p.write('first full line written\n') p.write('second\n') x = p.readline() print(repr(x)) y = p.readline() print(repr(y)) assert x == tobytes('first full line written\n'), 'was: "'+str(x)+'"' assert y == tobytes('second\n'), 'was: "'+str(y)+'"' print('\tThree lines, last sans newline, read using combination:') p.write('first\n') p.write('second\n') p.write('third, (no cr)') print('\tFirst line via readline:') x = p.readline() assert x == tobytes('first\n'), 'was: "'+str(x)+'"' print('\tRest via readPendingChars:') time.sleep(1) # seems like we are sometimes too fast for the subproc y = p.readPendingChars() # Temporarily disable full compliance on this next one. Re-evaluating test # driver in general. But allow to pass here to exercise rest of tests. # assert y == tobytes('second\nthird, (no cr)'), 'was: "'+str(y)+'"' assert y.startswith(tobytes('second\n')), 'was: "'+str(y)+'"' print("\tStopping then continuing subprocess (verbose):") junk = p.readPendingChars() # discard any data left over from previous test # verbose stop assert p.stop(1), 'Stop seems to have failed!' print('\tWriting line while subprocess is paused...') p.write('written while subprocess paused\n') print('\tNonblocking read of paused subprocess (should be empty):') x = p.readPendingChars() assert len(x) == 0, 'should have been empty, but had: "'+str(x)+'"' print('\tContinuing subprocess (verbose):') # verbose continue assert p.cont(1), 'Continue seems to have failed! Probably lost subproc...' print('\tReading accumulated line, blocking read:') x = p.readline() assert x == tobytes('written while subprocess paused\n'), 'was: "'+str(x)+'"' print("\tDeleting subproc (pid "+str(p.pid)+"), which was to die noisily:") del p print("\tTest Successful!") fout.write("Finished Test Successfully!\n") return True if __name__ == "__main__": test() pyraf-2.1.15/lib/pyraf/old_files/README.rst0000644000665500117240000000046713310762263021246 0ustar sontagpassdev00000000000000This directory contains old test files for Chris Sontag to run his custom diagnostic tests. These tests (as of March 2018), if applicable, have been ported to the "tests" directory. The ones here are not run by CI and will not be modified to be in sync with the ported counterparts. Use them at your own risk. pyraf-2.1.15/lib/pyraf/old_files/clcache_tests.py0000644000665500117240000000267113310762263022734 0ustar sontagpassdev00000000000000from __future__ import division, print_function from pyraf.clcache import codeCache # simple class to mimic pycode, for unit test (save us from importing others) class DummyCodeObj: def setFilename(self, f): self.filename = f def __str__(self): retval = '=0], reverse=1) for t in tsts: func = eval(t) print func.__doc__.strip() func() # If we get here with no exception, we have passed all of the tests print "\nSuccessfully passed "+str(len(tsts))+" tests" return len(tsts) # # main routine # if (__name__ == '__main__'): run_all() pyraf-2.1.15/lib/pyraf/old_files/gki_psikern_tests.py0000644000665500117240000002406513310762263023660 0ustar sontagpassdev00000000000000""" GKI PyRAF-to-psikern tests The first version of this will be under-representative of the total functionality, but tests will be added over time, as code issues are researched and addressed. $Id$ """ from __future__ import division # confidence high import glob, os, sys, time from pyraf import iraf diff = "diff" if 'PYRAF_TEST_DIFF' in os.environ: diff = os.environ['PYRAF_TEST_DIFF'] PSDEV = EXP2IGNORE = None def diffit(exp2ig, f_new, f_ref, cleanup=True): """ Run the diff and check the return status """ # don't do the diff if the new file isn't there or if it is empty assert os.path.exists(f_new), "New file unfound: "+f_new assert os.path.exists(f_ref), "Ref file unfound: "+f_ref # expect new file to at least be 80% as big as ref file, before we compare expected_sz = int(0.8*os.path.getsize(f_ref)) sz = os.path.getsize(f_new) if sz < expected_sz: # sometimes the psdump kernel takes a moment to write+close time.sleep(1) sz = os.path.getsize(f_new) if sz < expected_sz: time.sleep(5) sz = os.path.getsize(f_new) assert sz > 0, "New file is empty: "+f_new cmd = diff+" -I '"+exp2ig+"' "+f_ref+" "+f_new assert 0==os.system(cmd), "Diff of postscript failed! Command = "+cmd if cleanup: os.remove(f_new) def gki_single_prow_test(): """ Test a prow-plot of a single row from dev$pix to .ps """ iraf.plot(_doprint=0) # load plot for prow # get look at tmp dir before plot/flush flistBef = findAllTmpPskFiles() # plot iraf.prow("dev$pix", row=256, dev=PSDEV) # plot iraf.gflush() # get output postscript temp file name psOut = getNewTmpPskFile(flistBef, "single_prow") # diff diffit(EXP2IGNORE, psOut, os.environ['PYRAF_TEST_DATA']+os.sep+PSDEV+"_prow_256.ps") def gki_prow_1_append_test(): """ Test a prow-plot with 1 append (2 rows total, dev$pix) to .ps """ iraf.plot(_doprint=0) # load plot for prow # get look at tmp dir before plot/flush flistBef = findAllTmpPskFiles() # plot iraf.prow("dev$pix", row=256, dev=PSDEV) # plot iraf.prow("dev$pix", row=250, dev=PSDEV, append=True) # append #1 iraf.gflush() # get output postscript temp file name psOut = getNewTmpPskFile(flistBef, "prow_1_append") # diff diffit(EXP2IGNORE, psOut, os.environ['PYRAF_TEST_DATA']+os.sep+PSDEV+"_prow_256_250.ps") def gki_prow_2_appends_test(): """ Test a prow-plot with 2 appends (3 rows total, dev$pix) to .ps """ iraf.plot(_doprint=0) # load plot for prow # get look at tmp dir before plot/flush flistBef = findAllTmpPskFiles() # plot iraf.prow("dev$pix", row=256, dev=PSDEV) # plot iraf.prow("dev$pix", row=250, dev=PSDEV, append=True) # append #1 iraf.prow("dev$pix", row=200, dev=PSDEV, append=True) # append #2 iraf.gflush() # get output postscript temp file name psOut = getNewTmpPskFile(flistBef, "prow_2_appends") # diff diffit(EXP2IGNORE, psOut, os.environ['PYRAF_TEST_DATA']+os.sep+PSDEV+"_prow_256_250_200.ps") def gki_2_prows_no_append_test(): """ Test 2 prow calls with no append (2 dev$pix rows) to 2 .ps's """ iraf.plot(_doprint=0) # load plot for prow # get look at tmp dir before plot/flush flistBef = findAllTmpPskFiles() # plot iraf.prow("dev$pix", row=256, dev=PSDEV) # plot iraf.prow("dev$pix", row=250, dev=PSDEV) # plot again (flushes 1st) # get output postscript temp file name prf = None if os.uname()[0] == 'SunOS': prf = '.eps' # Solaris can leave extras here psOut = getNewTmpPskFile(flistBef, "2_prows_no_append - A", preferred=prf) # diff # NOTE - this seems to get 0-len files when (not stdin.isatty()) for psdump diffit(EXP2IGNORE, psOut, os.environ['PYRAF_TEST_DATA']+os.sep+PSDEV+"_prow_256.ps") # NOW flush second flistBef = findAllTmpPskFiles() iraf.gflush() # get output postscript temp file name prf = None if os.uname()[0] == 'SunOS': prf = '.eps' # Solaris can leave extras here psOut = getNewTmpPskFile(flistBef, "2_prows_no_append - B", preferred=prf) # diff diffit(EXP2IGNORE, psOut, os.environ['PYRAF_TEST_DATA']+os.sep+PSDEV+"_prow_250.ps") # 10 May 2012 - rename to disable for now - is sending niightly prints to hpc84 # It seems that the cups system takes the print to hp_dev_null and changes that # to an existing printer, knowing it is wrong ... # When there is time, look into a way to start this test up again without any # danger of prints going to an actual printer. def gki_prow_to_different_devices_tst(): # rename to disable for now """ Test 2 prow calls, each to different devices - one .ps written """ iraf.plot(_doprint=0) # load plot for prow # get look at tmp dir before plot/flush flistBef = findAllTmpPskFiles() # use a fake printer name so we don't waste a sheet of paper with each test os.environ['LPDEST'] = "hp_dev_null" os.environ['PRINTER'] = "hp_dev_null" # plot iraf.prow("dev$pix", row=256, dev=PSDEV) # plot (no .ps file yet) iraf.prow("dev$pix", row=333, dev="lw") # plot to fake printer, should flush # last plot, and should warn @ fake # get output postscript temp file name psOut = getNewTmpPskFile(flistBef, "prow_to_different_devices") # diff diffit(EXP2IGNORE, psOut, os.environ['PYRAF_TEST_DATA']+os.sep+PSDEV+"_prow_256.ps") # NOW flush - should do nothing flistBef = findAllTmpPskFiles() iraf.gflush() flistAft = findAllTmpPskFiles() assert flistBef==flistAft, "Extra tmp .ps file written? "+str(flistAft) def findAllTmpPskFiles(): """ Do a glob in the tmp dir (and cwd) looking for psikern files. Return the list. """ # Usually the files are dropped in the $tmp directory if PSDEV.find('dump') >= 0: flistCur = glob.glob('/tmp/irafdmp*.ps') # always in /tmp else: flistCur = glob.glob(os.environ['tmp']+os.sep+'psk*') # sometimes the tmp files disappear on Solaris if sys.platform=='sunos5': time.sleep(1) for f in flistCur: os.system("/bin/ls -ld "+f) if not os.path.exists(f): print "This existed then did not: "+f flistCur.remove(f) # for some reason, on Solaris (at least), some files are dumped to cwd if PSDEV.find('dump') >= 0: flistCur += glob.glob(os.getcwd()+os.sep+'irafdmp*.ps') else: flistCur += glob.glob(os.getcwd()+os.sep+'psk*') return flistCur def getNewTmpPskFile(theBeforeList, title, preferred=None): """ Do a glob in the tmp dir looking for psikern files, compare with the old list to find the new (expected) file. If preferred is None, then only a single new file is expected. If not None, then we assume that more than one new file may be present, and the arg is used as a search substring (regexp would be cooler) to choose which single file to return of the newly found set. Returns a single string filename. """ flistAft = findAllTmpPskFiles() assert len(flistAft) >= len(theBeforeList), \ "How can the list size be SMALLER now? ("+title+","+PSDEV+")\n"+ \ str(theBeforeList)+"\n"+str(flistAft) if len(flistAft) == len(theBeforeList): # sometimes the psdump kernel takes a moment to write+close (or start!) time.sleep(1) flistAft = findAllTmpPskFiles() assert len(flistAft) > len(theBeforeList), \ 'No postcript file(s) generated during: "'+title+'": '+ \ str(theBeforeList)+' : PSDEV is: '+PSDEV for f in theBeforeList: flistAft.remove(f) if preferred == None: # In this case, we expect only a single ps file. if len(flistAft) != 1: # But, if there are two+ (sometimes occurs on Solaris), and one # is in /tmp and another is a local .eps, let's debug it a bit. if len(flistAft) >= 2 and flistAft[0].find('/tmp/') == 0 and \ flistAft[-1].find('.eps') > 0: # Are these files related (copies?) print "Debugging multiple postscript files scenario" for f in flistAft: os.system("/bin/ls -ld "+f) # Or, did the /tmp version suddenly get deleted? if not os.path.exists(flistAft[0]): print "Am somehow missing the deletes. Test: "+title return flistAft[-1] # Either way, throw something raise Exception('Expected single postcript file during: "'+ \ title+'": '+str(flistAft)) else: # Here we allow more than one, and return the preferred option. for f in flistAft: if f.find(preferred) >= 0: return f return flistAft[0] def preTestCleanup(): """ For some reason, with the psdump kernel at least, having existing files in place during the test seems to affect whether new are 0-length. """ # So let's just start with a fresh area oldFlist = findAllTmpPskFiles() for f in oldFlist: try: os.remove(f) except: pass # may belong to another user - don't be chatty def run_all(): global PSDEV, EXP2IGNORE tsts = [x for x in globals().keys() if x.find('test')>=0] ran = 0 os.environ['LPDEST'] = "hp_dev_null" os.environ['PRINTER'] = "hp_dev_null" # the psi_land kernel seems not to be supported in default graphcap on OSX 10.9.5 if not sys.platform.lower().startswith('darwin'): PSDEV = 'psi_land' EXP2IGNORE = '.*CreationDate: .*' for t in tsts: preTestCleanup() func = eval(t) print PSDEV, ':', func.__doc__.strip() func() ran += 1 # this test (psdump kernel) is too temperamental on Linux if not sys.platform.lower().startswith('linux'): PSDEV = 'psdump' EXP2IGNORE = '(NOAO/IRAF ' for t in tsts: preTestCleanup() func = eval(t) print PSDEV, ':', func.__doc__.strip() func() ran += 1 # If we get here with no exception, we have passed all of the tests print "\nSuccessfully passed "+str(ran)+" tests" return ran # # if __name__ == "__main__": """ This main is not necessary for testing via nose, but it was handy in development. """ run_all() pyraf-2.1.15/lib/pyraf/old_files/interactive_tests.py0000644000665500117240000000431513310762263023664 0ustar sontagpassdev00000000000000# TODO: These were not ported to "tests" for Jenkins CI because # they are interactive. import sys import time import Tkinter as TKNTR # requires 2to3 from Tkinter import * # requires 2to3 from stsci.tools import irafutils from pyraf.msgiobuffer import MsgIOBuffer from pyraf.msgiowidget import MsgIOWidget from pyraf.splash import PyrafSplash def test_buffer(): """Test the MsgIOBuffer class""" width = 500 height = 300 vheight = 50 text = "Tiptop" top = Toplevel() f = Frame(top, width = width, height = height, bg = "red") m = MsgIOBuffer(top, width, vheight, text) m.msgIO.pack(side=BOTTOM, fill = X) f.pack(side = TOP, fill = BOTH, expand = TRUE) for i in range(10): t = "Text " + str(i) m.updateIO(t) m.updateIO("The quick brown fox jumps over the lazy dog with ease.") m.updateIO("What is your quest?") #inputValue = m.readline() #print "inputValue = ", inputValue top.mainloop() def test_widget(): m = None def quit(): sys.exit() def clicked(): m.updateIO("Clicked at "+time.asctime()) def ask(): m.updateIO("Type something in:") out = m.readline() # create the initial Tk window and immediately withdraw it irafutils.init_tk_default_root() # make our test window top = TKNTR.Toplevel() f = TKNTR.Frame(top, width=500, height=300) b = TKNTR.Button(f, text='Click Me', command=clicked) b.pack(side=TKNTR.LEFT, fill=TKNTR.X, expand=1) q = TKNTR.Button(f, text='Buh-Bye', command=quit) q.pack(side=TKNTR.LEFT) f.pack(side=TKNTR.TOP, fill=TKNTR.X) # , expand=1) p = TKNTR.Button(top, text='Prompt Me', command=ask) p.pack(side=TKNTR.TOP, fill=TKNTR.X, expand=1) fill = TKNTR.Frame(top, height=200, bg="green") fill.pack(side=TKNTR.TOP, fill=TKNTR.BOTH, expand=1) m = MsgIOWidget(top, 500, "Tiptop") m.pack(side=TKNTR.BOTTOM, fill=TKNTR.X) for i in range(10): t = "Text " + str(i) m.updateIO(t) m.updateIO("What is your quest?") inputValue = m.readline() # start top.mainloop() def test_splash(): s = PyrafSplash() print("Sleeping 2 seconds...") time.sleep(2) s.Destroy() pyraf-2.1.15/lib/pyraf/old_files/irafpar_tests.py0000644000665500117240000001210413310762263022766 0ustar sontagpassdev00000000000000from __future__ import division # confidence high import sys from stsci.tools import basicpar from stsci.tools.irafglobals import yes, no from pyraf.irafpar import IrafParList def test_IrafParList(fout=sys.stdout): """ Test the IrafParList class """ # check our input (may be stdout) assert hasattr(fout, 'write'), "Input not a file object: "+str(fout) # create default, empty parlist for task 'bobs_task' pl = IrafParList('bobs_pizza', 'bobs_pizza.par') x = pl.getName() assert x == 'bobs_pizza.par', "Unexpected name: "+str(x) x = pl.getFilename() assert x == 'bobs_pizza.par', "Unexpected fname: "+str(x) x = pl.getPkgname() assert x == '', "Unexpected pkg name: "+str(x) assert not pl.hasPar('jojo'), "How did we get jojo?" assert pl.hasPar('mode'), "We should have only: mode" # length of 'empty' list is 2 - it has 'mode' and '$nargs' assert len(pl) == 2, "Unexpected length: "+str(len(pl)) fout.write("lParam should show 1 par (mode)\n"+pl.lParamStr()+'\n') # let's add some pars par1 = basicpar.parFactory( ('caller','s','a','Ima Hungry','',None,'person calling Bobs'), True) x = par1.dpar().strip() assert x == "caller = 'Ima Hungry'", "par1 is off: "+str(x) par2 = basicpar.parFactory( ('diameter','i','a','12','',None,'pizza size'), True) x = par2.dpar().strip() assert x == "diameter = 12", "par2 is off: "+str(x) par3 = basicpar.parFactory( ('pi','r','a','3.14159','',None,'Bob makes circles!'), True) x = par3.dpar().strip() assert x == "pi = 3.14159", "par3 is off: "+str(x) par4 = basicpar.parFactory( ('delivery','b','a','yes','',None,'delivery? (or pickup)'), True) x = par4.dpar().strip() assert x == "delivery = yes", "par4 is off: "+str(x) par5 = basicpar.parFactory( ('topping','s','a','peps','|toms|peps|olives',None,'the choices'), True) x = par5.dpar().strip() assert x == "topping = 'peps'", "par5 is off: "+str(x) pl.addParam(par1) assert len(pl) == 3, "Unexpected length: "+str(len(pl)) pl.addParam(par2) pl.addParam(par3) pl.addParam(par4) pl.addParam(par5) assert len(pl) == 7, "Unexpected length: "+str(len(pl)) # now we have a decent IrafParList to play with - test some fout.write("lParam should show 6 actual pars (our 5 + mode)\n" + pl.lParamStr() + '\n') assert pl.__doc__ == 'List of Iraf parameters', "__doc__ = "+str(pl.__doc__) x = sorted(pl.getAllMatches('')) assert x == ['$nargs', 'caller', 'delivery', 'diameter', 'mode', 'pi', 'topping'], \ "Unexpected all: "+str(x) x = sorted(pl.getAllMatches('d')) assert x == ['delivery', 'diameter'], "Unexpected d's: "+str(x) x = sorted(pl.getAllMatches('jojo')) assert x == [], "Unexpected empty list: "+str(x) x = pl.getParDict() assert 'caller' in x, "Bad dict? "+str(x) x = pl.getParList() assert par1 in x, "Bad list? "+str(x) assert pl.hasPar('topping'), "hasPar call failed" # change a par val pl.setParam('topping', 'olives') # should be no prob assert 'olives' == pl.getParDict()['topping'].value, \ "Topping error: "+str(pl.getParDict()['topping'].value) try: # the following setParam should fail - not in choice list pl.setParam('topping', 'peanutbutter') # oh the horror raise RuntimeError("The bad setParam didn't fail?") except ValueError: pass # Now try some direct access (also tests IrafPar basics) assert pl.caller == "Ima Hungry", 'Ima? '+pl.getParDict()['caller'].value pl.pi = 42 assert pl.pi == 42.0, "pl.pi not 42, ==> "+str(pl.pi) try: pl.pi = 'strings are not allowed' # should throw raise RuntimeError("The bad pi assign didn't fail?") except ValueError: pass pl.diameter = '9.7' # ok, string to float to int assert pl.diameter == 9, "pl.diameter?, ==> "+str(pl.diameter) try: pl.diameter = 'twelve' # fails, not parseable to an int raise RuntimeError("The bad diameter assign didn't fail?") except ValueError: pass assert pl.diameter == 9, "pl.diameter after?, ==> "+str(pl.diameter) pl.delivery = False # converts assert pl.delivery == no, "pl.delivery not no? "+str(pl.delivery) pl.delivery = 1 # converts assert pl.delivery == yes, "pl.delivery not yes? "+str(pl.delivery) pl.delivery = 'NO' # converts assert pl.delivery == no, "pl.delivery not NO? "+str(pl.delivery) try: pl.delivery = "maybe, if he's not being recalcitrant" raise RuntimeError("The bad delivery assign didn't fail?") except ValueError: pass try: pl.topping = 'peanutbutter' # try again raise RuntimeError("The bad topping assign didn't fail?") except ValueError: pass try: x = pl.pumpkin_pie raise RuntimeError("The pumpkin_pie access didn't fail?") except KeyError: pass # If we get here, then all is well # sys.exit(0) fout.write("Test successful\n") return pl if __name__ == '__main__': pl = test_IrafParList() pyraf-2.1.15/lib/pyraf/pyrafglobals.py0000644000665500117240000000216213310762263020650 0ustar sontagpassdev00000000000000"""module pyrafglobals.py -- widely used PyRAF constants and objects pyrafDir Directory with these Pyraf programs _use_ecl Flag to turn on ECL mode in PyRAF This is defined so it is safe to say 'from pyrafglobals import *' $Id$ Broken out from irafglobals.py which was signed "R. White, 2000 January 5" """ from __future__ import division # confidence high import os, sys _os = os _sys = sys del os, sys _use_ecl = _os.environ.get("PYRAF_USE_ECL", False) # ----------------------------------------------------- # pyrafDir is directory containing this script # ----------------------------------------------------- if __name__ == "__main__": thisfile = _sys.argv[0] else: thisfile = __file__ # follow links to get to the real filename while _os.path.islink(thisfile): thisfile = _os.readlink(thisfile) pyrafDir = _os.path.dirname(thisfile) del thisfile from stsci.tools.irafglobals import userWorkingHome if not pyrafDir: pyrafDir = userWorkingHome # change relative directory paths to absolute and normalize path pyrafDir = _os.path.normpath(_os.path.join(userWorkingHome, pyrafDir)) del userWorkingHome pyraf-2.1.15/lib/pyraf/irafpar.py0000644000665500117240000016342613310762263017622 0ustar sontagpassdev00000000000000"""irafpar.py -- parse IRAF .par files and create lists of IrafPar objects $Id$ R. White, 2000 January 7 """ from __future__ import division # confidence high import copy, glob, os, re, sys, types from stsci.tools import basicpar, minmatch, irafutils, taskpars from stsci.tools.for2to3 import PY3K from stsci.tools.irafglobals import INDEF, Verbose, yes, no from stsci.tools.basicpar import (warning, _StringMixin, IrafPar, IrafParS, _cmdlineFlag) # also import basicpar.IrafPar* class names for cached scripts from stsci.tools.basicpar import (IrafParB, IrafParI, IrafParR, IrafParAB, IrafParAI, IrafParAR, IrafParAS) if __name__.find('.') >= 0: # not a unit test # use this form since the iraf import is circular import pyraf.iraf # ----------------------------------------------------- # IRAF parameter factory # ----------------------------------------------------- _string_list_types = ( '*struct', '*s', '*f', '*i' ) def IrafParFactory(fields, strict=0): """IRAF parameter factory fields is a list of the comma-separated fields (as in the .par file). Each entry is a string or None (indicating that field was omitted.) Set the strict parameter to a non-zero value to do stricter parsing (to find errors in the input.)""" # Sanity check if len(fields) < 3 or None in fields[0:3]: raise SyntaxError("At least 3 fields must be given") type = fields[1] # Handle special PyRAF/IRAF types, otherwise default to the standard types if type in _string_list_types: return IrafParLS(fields,strict) elif type == "*gcur" or type == "gcur": return IrafParGCur(fields,strict) elif type == "*imcur" or type == "imcur": return IrafParImCur(fields,strict) elif type == "*ukey" or type == "ukey": return IrafParUKey(fields,strict) elif type == "pset": return IrafParPset(fields,strict) else: return basicpar.parFactory(fields, strict) # ----------------------------------------------------- # make an IrafPar variable (another factory function, # using more descriptive notation for characteristics) # ----------------------------------------------------- # dictionary mapping verbose types to short par-file types _typedict = { 'string': 's', 'char': 's', 'file': 'f', 'struct': 'struct', 'int': 'i', 'bool': 'b', 'real': 'r', 'double': 'd', 'gcur': 'gcur', 'imcur': 'imcur', 'ukey': 'ukey', 'pset': 'pset', } def makeIrafPar(init_value, datatype=None, name="", mode="h", array_size=None, list_flag=0, min=None, max=None, enum=None, prompt="", strict=0, filename=None): """Create an IrafPar variable""" # Deprecation note - after 1.6 is released, remove the arg and this note if filename!=None and len(filename)>0 and filename!='string_proc': warning("Use of filename arg in makeIrafPar is rather deprecated\n"+\ ", filename = \'"+filename+"'", level=-1) # if init_value is already an IrafPar, just return it #XXX Could check parameters to see if they are ok if isinstance(init_value, IrafPar): return init_value #XXX Enhance this to determine datatype from init_value if it is omitted #XXX Could use _typedict.get(datatype,datatype) to allow short types to be used if datatype is None: raise ValueError("datatype must be specified") shorttype = _typedict[datatype] if array_size is None: shape = None else: shorttype = "a" + shorttype # array_size can be an integer or a tuple # get a tuple shape and make array_size the # combined size of all dimensions try: shape = tuple(array_size) except TypeError: shape = (array_size,) array_size = 1 for d in shape: array_size = array_size*d if list_flag: shorttype = "*" + shorttype # messy stuff -- construct strings like we would read # from .par file for this parameter if shape is None: # scalar parameter fields = [ name, shorttype, mode, init_value, min, max, prompt ] if fields[4] is None: fields[4] = enum else: # N-dimensional array parameter fields = [ name, shorttype, mode, str(len(shape)), # number of dims ] for d in shape: fields.extend([d, # dimension "1"]) # apparently always 1 if min is None: fields.extend([ enum, max, prompt ]) else: fields.extend([ min, max, prompt ]) if init_value is not None: if len(init_value) != array_size: raise ValueError("Initial value list does not match array size for parameter `%s'" % name) for iv in init_value: fields.append(iv) else: fields = fields + array_size*[None] for i in range(len(fields)): if fields[i] is not None: fields[i] = str(fields[i]) try: return IrafParFactory(fields, strict=strict) except ValueError, e: errmsg = "Bad value for parameter `%s'\n%s" % (name, str(e)) raise ValueError(errmsg) # ----------------------------------------------------- # IRAF pset parameter class # ----------------------------------------------------- class IrafParPset(IrafParS): """IRAF pset parameter class""" def __init__(self,fields,strict=0): IrafParS.__init__(self,fields,strict) # omitted pset parameters default to null string if self.value is None: self.value = "" def get(self, field=None, index=None, lpar=0, prompt=1, native=0, mode="h"): """Return pset value (IrafTask object)""" if index: raise SyntaxError("Parameter " + self.name + " is pset, cannot use index") if field: return self._getField(field) if lpar: return str(self.value) # assume there are no query or indirection pset parameters # see if parameter value has .par extension, if so, it is a file name f = self.value.split('.') if len(f) > 1 and f[-1] == 'par': # must be a file name from iraffunctions import IrafTaskFactory irf_val = pyraf.iraf.Expand(self.value) return IrafTaskFactory(taskname=irf_val.split(".")[0], value=irf_val) else: # must be a task name if self.value: # The normal case here is that the value is a task name string # so we get&return that task. There is a quirky case where in # some CL scripts (e.g. ccdproc.cl), the CL script writers use # this place as a temporarty place to store values; handle that. if self.value.startswith('<') and self.value.endswith('>') and self.name in self.value: # don't lookup task for self.value, it is something like: # "" return pyraf.iraf.getTask(self.name) # this is only a safe assumption to make in a PSET else: return pyraf.iraf.getTask(self.value) else: return pyraf.iraf.getTask(self.name) # ----------------------------------------------------- # IRAF list parameter base class # ----------------------------------------------------- class IrafParL(_StringMixin, IrafPar): """IRAF list parameter base class""" def __init__(self,fields,strict=0): IrafPar.__init__(self,fields,strict) # filehandle for input file self.__dict__['fh'] = None # lines used to store input when not reading from a tty self.__dict__['lines'] = None # flag inidicating error message has been printed if file does not exist # message only gets printed once for each file self.__dict__['errMsg'] = 0 # omitted list parameters default to null string if self.value is None: self.value = "" #-------------------------------------------- # public methods #-------------------------------------------- def set(self, value, field=None, index=None, check=1): """Set value of this parameter from a string or other value. Field is optional parameter field (p_prompt, p_minimum, etc.) Index is optional array index (zero-based). Set check=0 to assign the value without checking to see if it is within the min-max range or in the choice list.""" if index is not None: raise SyntaxError("Parameter "+self.name+" is not an array") if field: self._setField(value,field,check=check) else: if check: self.value = self.checkValue(value) else: self.value = self._coerceValue(value) self.setChanged() # close file if it is open if self.fh: try: self.fh.close() except IOError: pass self.fh = None self.lines = None self.errMsg = 0 def get(self, field=None, index=None, lpar=0, prompt=1, native=0, mode="h"): """Return value of this parameter as a string (or in native format if native is non-zero.)""" if field: return self._getField(field,native=native,prompt=prompt) if lpar: if self.value is None and native == 0: return "" else: return self.value # assume there are no query or indirection list parameters if index is not None: raise SyntaxError("Parameter "+self.name+" is not an array") if self.value: # non-null value means we're reading from a file try: if not self.fh: self.fh = open(pyraf.iraf.Expand(self.value), "r") if self.fh.isatty(): self.lines = None else: # read lines in a block # reverse to make pop more convenient & faster self.lines = self.fh.readlines() self.lines.reverse() if self.lines is None: value = self.fh.readline() elif self.lines: value = self.lines.pop() else: value = '' if not value: # EOF -- raise exception raise EOFError("EOF from list parameter `%s'" % self.name) if value[-1:] == "\n": value = value[:-1] except IOError, e: if not self.errMsg: warning("Unable to read values for list parameter `%s' " "from file `%s'\n%s" % (self.name, self.value,str(e)), level=-1) # only print message one time self.errMsg = 1 # fall back on default behavior if file is not readable value = self._getNextValue() else: # if self.value is null, use the special _getNextValue method # (which should always return a string) if prompt: value = self._getNextValue() else: return self.value if native: return self._coerceValue(value) else: return value #-------------------------------------------- # private methods #-------------------------------------------- # Use _getNextValue() method to implement a particular type def _getNextValue(self): """Return a string with next value""" raise RuntimeError("Bug: base class IrafParL cannot be used directly") def _getPFilename(self,native,prompt): """Get p_filename field for this parameter (returns filename)""" #XXX is this OK? should we check for self.value==None? return self.value def _getPType(self): """Get underlying datatype for this parameter Strip off '*' from list params """ return self.type[1:] # ----------------------------------------------------- # IRAF string list parameter class # ----------------------------------------------------- class IrafParLS(IrafParL): """IRAF string list parameter class""" def _getNextValue(self): """Return next string value""" # save current values (in case this got called # because filename was in error) saveVal = self.value saveErr = self.errMsg try: # get rid of default value in prompt self.value = None self.getWithPrompt() retval = self.value return retval finally: # restore original values self.value = saveVal self.errMsg = saveErr # ----------------------------------------------------- # IRAF cursor parameter class # ----------------------------------------------------- class IrafParCursor(IrafParL): """Base class for cursor parameters""" def _coerceOneValue(self,value,strict=0): if isinstance(value,IrafParCursor): return value.p_filename else: return IrafParL._coerceOneValue(self,value,strict) # ----------------------------------------------------- # IRAF gcur (graphics cursor) parameter class # ----------------------------------------------------- class IrafParGCur(IrafParCursor): """IRAF graphics cursor parameter class""" def _getNextValue(self): """Return next graphics cursor value""" import gki # lazy import - reduce circular imports on startup return gki.kernel.gcur() # ----------------------------------------------------- # IRAF imcur (image display cursor) parameter class # ----------------------------------------------------- class IrafParImCur(IrafParCursor): """IRAF image display cursor parameter class""" def _getNextValue(self): """Return next image display cursor value""" import irafimcur # lazy import - reduce circular imports on startup return irafimcur.imcur() # ----------------------------------------------------- # IRAF ukey (user typed key) parameter class # ----------------------------------------------------- class IrafParUKey(IrafParL): """IRAF user typed key parameter class""" def _getNextValue(self): """Return next typed character""" import irafukey # lazy import - reduce circular imports on startup return irafukey.ukey() # ----------------------------------------------------- # IRAF parameter list synchronized to disk file # ----------------------------------------------------- if __name__.find('.') < 0: # for unit test need absolute import exec('import filecache', globals()) # 2to3 messes up simpler form else: import filecache class ParCache(filecache.FileCache): """Parameter cache that updates from .par file when necessary""" def __init__(self, filename, parlist, strict=0): self.initparlist = parlist # special filename used by cl2py if filename is None or filename == 'string_proc': filename = '' try: filecache.FileCache.__init__(self, filename) except (OSError, IOError): # whoops, couldn't open that file # start with a null file instead unless strict is set if strict: raise filename = '' filecache.FileCache.__init__(self, filename) def getValue(self): return self.pars, self.pardict, self.psetlist def newValue(self): """Called to create initial value""" # initparlist dominates .par file during initialization if self.initparlist is not None: self.pars = self.initparlist elif self.filename: self.pars = _readpar(self.filename) else: # create empty list if no filename is specified self.pars = [] # build auxiliary attributes from pars list self._buildFromPars() def updateValue(self): """Initialize parameter list from parameter file""" if self.filename: # .par file dominates initparlist on update self.pars = _readpar(self.filename) elif self.initparlist is not None: self.pars = self.initparlist else: # create empty list if no filename is specified self.pars = [] # build auxiliary attributes from pars list self._buildFromPars() def _buildFromPars(self): # build minmatch dictionary of all parameters, including # those in psets self.pardict = minmatch.MinMatchDict() psetlist = [] for p in self.pars: self.pardict.add(p.name, p) if isinstance(p, IrafParPset): psetlist.append(p) # add mode, $nargs to parameter list if not already present if not self.pardict.has_exact_key("mode"): p = makeIrafPar("al", name="mode", datatype="string", mode="h") self.pars.append(p) self.pardict.add(p.name, p) if not self.pardict.has_exact_key("$nargs"): p = makeIrafPar(0, name="$nargs", datatype="int", mode="h") self.pars.append(p) self.pardict.add(p.name, p) # save the list of pset parameters # Defer adding the parameters until later because saved parameter # sets may not be defined yet when restoring from save file. self.psetlist = psetlist # ----------------------------------------------------- # IRAF parameter list class # ----------------------------------------------------- # Note that all methods are mixed case and all attributes are private # (start with __) to avoid conflicts with parameter names class IrafParList(taskpars.TaskPars): """List of Iraf parameters""" def __init__(self, taskname, filename="", parlist=None): """Create a parameter list for task taskname If parlist is specified, uses it as a list of IrafPar objects. Else if filename is specified, reads a .par file. If neither is specified, generates a default list. """ self.__pars = [] self.__hasPsets = False self.__psets2merge = None # is a list when populated self.__psetLock = False self.__filename = filename self.__name = taskname self.__filecache = ParCache(filename, parlist) # initialize parameter list self.Update() def Update(self): """Check to make sure this list is in sync with parameter file""" self.__pars, self.__pardict, self.__psets2merge = \ self.__filecache.get() if self.__psets2merge: self.__addPsetParams() def setFilename(self, filename): """Change filename and create ParCache object Retains current parameter values until an unlearn is done """ if hasattr(filename, 'name') and hasattr(filename, 'read'): filename = filename.name if isinstance(filename,str): root, ext = os.path.splitext(filename) if ext != ".par": # Only .par files are used as basis for parameter cache -- see if there # is one # Note that parameters specified in CL scripts are automatically updated # when the script changes filename = root + ".par" if not os.path.exists(filename): filename = "" else: filename = "" if self.__filename != filename: if filename: # it is an error if this file does not exist self.__filecache = ParCache(filename, None, strict=1) else: # for null filename, default parameter list is fixed self.__filecache = ParCache(filename, self.__pars) self.__filename = filename def __addPsetParams(self): """ Merge pset parameters into the parameter lists. Developer note - the original intention of this may have been to ensure that the pset par which appears in this list is NOT a copy of the original par (from the pset) but a reference to the same object, and if so, that would make things work smoothly, but it was found in Feb of 2013 that this is not happening correctly, and may be an unsafe plan. Therefore the code was changed to allow clients to access both copies; see getParObjects() and any related code. """ # return immediately if they have already been added or # if we are in the midst of a recursive call tree if self.__psetLock or self.__psets2merge is None: return # otherwise, merge in any PSETs if len(self.__psets2merge) > 0: self.__hasPsets = True # never reset self.__psetLock = True # prevent us from coming in recursively # Work from the pset's pardict because then we get # parameters from nested psets too for p in self.__psets2merge: # silently ignore parameters from psets that already are defined psetdict = p.get().getParDict() for pname in psetdict.keys(): if not self.__pardict.has_exact_key(pname): self.__pardict.add(pname, psetdict[pname]) # back to normal state self.__psets2merge = None self.__psetLock = False def addParam(self, p): """Add a parameter to the list""" if not isinstance(p, IrafPar): t = type(p) if issubclass(t, types.InstanceType): tname = p.__class__.__name__ else: tname = t.__name__ raise TypeError("Parameter must be of type IrafPar (value: "+ \ tname+", type: "+str(t)+", object: "+repr(p)+")") elif self.__pardict.has_exact_key(p.name): if p.name in ["$nargs", "mode"]: # allow substitution of these default parameters self.__pardict[p.name] = p for i in range(len(self.__pars)): j = -i-1 if self.__pars[j].name == p.name: self.__pars[j] = p return else: raise RuntimeError("Bug: parameter `%s' is in dictionary " "__pardict but not in list __pars??" % p.name) raise ValueError("Parameter named `%s' is already defined" % p.name) # add it just before the mode and $nargs parameters (if present) j = -1 for i in range(len(self.__pars)): j = -i-1 if self.__pars[j].name not in ["$nargs", "mode"]: break else: j = -len(self.__pars)-1 self.__pars.insert(len(self.__pars)+j+1, p) self.__pardict.add(p.name, p) if isinstance(p, IrafParPset): # parameters from this pset will be added too if self.__psets2merge is None: # add immediately self.__psets2merge = [p] self.__addPsetParams() else: # just add to the pset list self.__psets2merge.append(p) # can't call __addPsetParams here as we may now be inside a call def isConsistent(self, other): """Compare two IrafParLists for consistency Returns true if lists are consistent, false if inconsistent. Only checks immutable param characteristics (name & type). Allows hidden parameters to be in any order, but requires non-hidden parameters to be in identical order. """ if not isinstance(other, self.__class__): if Verbose>0: print 'Comparison list is not a %s' % self.__class__.__name__ return 0 # compare minimal set of parameter attributes thislist = self._getConsistentList() otherlist = other._getConsistentList() if thislist == otherlist: return 1 else: if Verbose>0: _printVerboseDiff(thislist, otherlist) return 0 def _getConsistentList(self): """Return simplified parameter dictionary used for consistency check Dictionary is keyed by param name, with value of type and (for non-hidden parameters) sequence number. """ dpar = {} j = 0 hflag = -1 for par in self.__pars: if par.mode == "h": dpar[par.name] = (par.type, hflag) else: dpar[par.name] = (par.type, j) j = j+1 return dpar def _dlen(self): """ For diagnostic use only: return length of class attr name dict. """ return len(self.__dict__) def clearFlags(self): """Clear all status flags for all parameters""" for p in self.__pars: p.setFlags(0) def setAllFlags(self): """Set all status flags to indicate parameters were set on cmdline""" for p in self.__pars: p.setCmdline() # parameters are accessible as attributes def __getattr__(self,name): # DBG: id(self), len(self.__dict__), "__getattr__ for: "+str(name) if name and name[0] == '_': raise AttributeError(name) try: return self.getValue(name,native=1) except SyntaxError, e: raise AttributeError(str(e)) def __setattr__(self,name,value): # DBG: id(self), len(self.__dict__), "__setattr__ for: "+str(name)+", value: "+str(value)[0:20] # hidden Python parameters go into the standard dictionary # (hope there are none of these in IRAF tasks) if name and name[0] == '_': if PY3K: object.__setattr__(self, name, value) # new-style class objects else: self.__dict__[name] = value # for old-style classes else: self.setParam(name,value) def __len__(self): return len(self.__pars) # public accessor functions for attributes def hasPar(self,param): """Test existence of parameter named param""" if self.__psets2merge: self.__addPsetParams() param = irafutils.untranslateName(param) return param in self.__pardict def getFilename(self): return self.__filename def getParList(self, docopy=0): if docopy: # return copy of the list if docopy flag set pars = copy.deepcopy(self.__pars) for p in pars: p.setFlags(0) return pars else: # by default return the list itself return self.__pars def getParDict(self): if self.__psets2merge: self.__addPsetParams() return self.__pardict def getParObject(self,param): """ Returns an IrafPar object matching the name given (param). This looks only at the "top level" (which includes any duplicated PSET pars via __addPsetParams), but does not look down into PSETs. Note the difference between this and getParObjects in their different return types. """ if self.__psets2merge: self.__addPsetParams() try: param = irafutils.untranslateName(param) return self.__pardict[param] except KeyError, e: raise e.__class__("Error in parameter '" + param + "' for task " + self.__name + "\n" + str(e)) def getParObjects(self, param, typecheck=True): """ Returns all IrafPar objects matching the string name given (param), in the form of a dict like: { scopename : , ... } where scopename is '' if par was found as a regular par in this list, or, where scopename is psetname if the par was found inside a PSET. It is possible that some dict values will actually be the same object in memory (see docs for __addPsetParams). This _will_ raise a KeyError if the given param name was not found at the "top level" (a regular par inside this par list) even if it is also in a PSET. typecheck: If multiple par objects are found, and typecheck is set to True, only the first (e.g. top level) will be returned if those par objects have a different value for their .type attribute. Otherwise all par objects found are returned in the dict. Note the difference between this and getParObject in their different return types. """ # Notes: # To accomplish the parameter setting (e.g. setParam) this calls up # all possible exact-name-matching pars in this par list, whether # they be on the "top" level with that name (param), or down in some # PSET with that name (param). If we are simply an IRAFish task, then # this is fine as we can assume the task likely does not have a par of # its own and a PSET par, both of which have the same name. Thus any # such case will acquire a copy of the PSET par at the top level. See # discussion of this in __addPsetParams(). # BUT, if we are a CL script (e.g. mscombine.cl), we could have local # vars which happen to have the same names as PSET pars. This is an # issue that we need to handle and be aware of (see typecheck arg). if self.__psets2merge: self.__addPsetParams() param = irafutils.untranslateName(param) retval = {} # First find the single "top-level" matching par try: pobj = self.__pardict[param] retval[''] = pobj except KeyError, e: raise e.__class__("Error in parameter '" + param + "' for task " + self.__name + "\n" + str(e)) # Next, see if there are any pars by this name inside any PSETs if not self.__hasPsets: return retval # There is a PSET in here somewhere... allpsets = [p for p in self.__pars if isinstance(p, IrafParPset)] for pset in allpsets: # Search the pset's pars. We definitely do NOT want a copy, # we need the originals to edit. its_task = pset.get() its_plist = its_task.getParList(docopy=0) # assume full paramname given (no min-matching inside of PSETs) matching_pars = [pp for pp in its_plist if pp.name == param] if len(matching_pars) > 1: raise RuntimeError('Unexpected multiple matches for par: '+ \ param+', are: '+str([p.name for p in matching_pars])) # found one with that name; add it to outgoing dict if len(matching_pars) > 0: addit = True if typecheck and '' in retval: # in this case we already found a top-level and we've been # asked to make sure to return only same-type matches addit = matching_pars[0].type == retval[''].type # attr is a char if addit: retval[pset.name] = matching_pars[0] return retval def getAllMatches(self,param): """Return list of all parameter names that may match param""" if param == "": return self.__pardict.keys() else: return self.__pardict.getallkeys(param, []) def getValue(self,param,native=0,prompt=1,mode="h"): """Return value for task parameter 'param' (with min-match) If native is non-zero, returns native format for value. Default is to return a string. If prompt is zero, does not prompt for parameter. Default is to prompt for query parameters. """ par = self.getParObject(param) value = par.get(native=native, mode=mode, prompt=prompt) if isinstance(value,str) and value and value[0] == ")": # parameter indirection: ')task.param' try: task = pyraf.iraf.getTask(self.__name) value = task.getParam(value[1:],native=native,mode="h") except KeyError: # if task is not known, use generic function to get param value = pyraf.iraf.clParGet(value[1:],native=native,mode="h", prompt=prompt) return value def setParam(self, param, value, scope='', check=0, idxHint=None): """Set task parameter 'param' to value (with minimum-matching). scope, idxHint, and check are included for use as a task object but they are currently ignored.""" matches_dict = self.getParObjects(param) for par_obj in matches_dict.values(): par_obj.set(value) def setParList(self,*args,**kw): """Set value of multiple parameters from list""" # first undo translations that were applied to keyword names for key in kw.keys(): okey = key key = irafutils.untranslateName(key) if okey != key: value = kw[okey] del kw[okey] kw[key] = value # then expand all keywords to their full names and add to fullkw fullkw = {} dupl_pset_pars = [] for key in kw.keys(): # recall, kw is just simple { namestr: valstr, ... } try: # find par obj for this key # (read docs for getParObjects - note the 's') results_dict = self.getParObjects(key) # results_dict is of form: { psetname : } # where results_dict may be (and most often is) empty string ''. # if no KeyError, then there exists a top-level entry ('') if '' not in results_dict: raise RuntimeError('No top-level match; expected KeyError') # assume results_dict[''].name.startswith(key) or .name==key # recall that key might be shortened version of par's .name param = (results_dict[''].name, '') # this means (paramname, [unused]) results_dict.pop('') # if there are others, then they are pars with the same name # but located down inside a PSET. So we save them for further # handling down below. for psetname in results_dict: if not results_dict[psetname].name.startswith(key): raise RuntimeError('PSET name non-match; par name: '+ \ key+'; got: '+results_dict[psetname].name) dupl_pset_pars.append( (psetname, results_dict[psetname].name, key) ) except KeyError, e: # Perhaps it is pset.param ? This would occur if the caller # used kwargs like gemcube(..., geofunc.axis1 = 1, ...) # (see help call #3454 for Mark Sim.) i = key.find('.') if i<=0: raise e # recall that key[:i] might be shortened version of par's .name param = (self.getParObject(key[:i]).name, key[i+1:]) # here param is (pset name, par name) if param in fullkw: msg_full_pname = param[0] if param[1]: msg_full_pname = '.'.join(param) # at this point, msg_full_pname is fully qualified raise SyntaxError("Multiple values given for parameter " + msg_full_pname + " in task " + self.__name) # Add it fullkw[param] = kw[key] # At this point, an example of fullkw might be: # {('extname', ''): 'mef', ('long', ''): no, ('ccdtype', ''): ''} # NOTE that the keys to this dict are EITHER in the form # (top level par name, '') -OR- (pset name, par name) # CDS June2014 - this is ugly - love to change this soon... # Now add any duplicated pars that were found, both up at top level and # down inside a PSET (saved as dupl_pset_pars list). The top level # version has already been added to fullkw, so we add the PSET version. for par_tup in dupl_pset_pars: # par_tup is of form: # (pset name (full), par name (full), par name (short/given), ) if par_tup[0:2] not in fullkw: # use par_tup[2]; its the given kw arg w/out the # identifying pset name fullkw[par_tup[0:2]] = kw[par_tup[2]] # Now add positional parameters to the keyword list, checking # for duplicates ipar = 0 for value in args: while ipar < len(self.__pars): if self.__pars[ipar].mode != "h": break ipar = ipar+1 else: # executed if we run out of non-hidden parameters raise SyntaxError("Too many positional parameters for task " + self.__name) # at this point, ipar is set to index of next found non-hidden # par in self.__pars param = (self.__pars[ipar].name, '') if param in fullkw: # uh-oh, it was already in our fullkw list, but now we got a # positional value for it (occurs in _ccdtool; help call #5901) msg_full_pname = param[0] if param[1]: msg_full_pname = '.'.join(param) msg_val_from_kw = fullkw[param] msg_val_from_pos = value # let's say we only care if the 2 values are, in fact, different if msg_val_from_kw != msg_val_from_pos: raise SyntaxError('Both a positional value ("'+str(msg_val_from_pos)+ \ '") and a keyword value ("'+str(msg_val_from_kw)+ \ '") were given for parameter "'+msg_full_pname+ \ '" in task "'+self.__name+'"') # else:, we'll now just overwite the old value with the same new value fullkw[param] = value ipar = ipar+1 # Now set all keyword parameters ... # clear changed flags and set cmdline flags for arguments self.clearFlags() # Count number of positional parameters set on cmdline # Note that this counts positional parameters set through # keywords in $nargs -- that is different from IRAF, which # counts only non-keyword parameters. That is a bug in IRAF. nargs = 0 for key, value in fullkw.items(): param, tail = key p = self.getParObject(param) if tail: # is pset parameter - get parameter object from its task p = p.get().getParObject(tail) # what if *this* p is a IrafParPset ? skip for now, # since we think no one is doubly nesting PSETs p.set(value) p.setFlags(_cmdlineFlag) if p.mode != "h": nargs = nargs+1 # Number of arguments on command line, $nargs, is used by some IRAF # tasks (e.g. imheader). self.setParam('$nargs',nargs) def eParam(self): import epar epar.epar(self) def tParam(self): import tpar tpar.tpar(self) def lParam(self,verbose=0): print self.lParamStr(verbose=verbose) def lParamStr(self,verbose=0): """List the task parameters""" retval = [] # Do the non-hidden parameters first for i in xrange(len(self.__pars)): p = self.__pars[i] if p.mode != 'h': if Verbose>0 or p.name != '$nargs': retval.append(p.pretty(verbose=verbose or Verbose>0)) # Now the hidden parameters for i in xrange(len(self.__pars)): p = self.__pars[i] if p.mode == 'h': if Verbose>0 or p.name != '$nargs': retval.append(p.pretty(verbose=verbose or Verbose>0)) return '\n'.join(retval) def dParam(self, taskname="", cl=1): """Dump the task parameters in executable form Default is to write CL version of code; if cl parameter is false, writes Python executable code instead. """ if taskname and taskname[-1:] != ".": taskname = taskname + "." for i in xrange(len(self.__pars)): p = self.__pars[i] if p.name != '$nargs': print "%s%s" % (taskname,p.dpar(cl=cl)) if cl: print "# EOF" def saveParList(self, filename=None, comment=None): """Write .par file data to filename (string or filehandle)""" if filename is None: filename = self.__filename if not filename: raise ValueError("No filename specified to save parameters") # but not if user turned off parameter writes writepars = int(pyraf.iraf.envget("writepars", 1)) if writepars < 1: msg = "No parameters written to disk." print msg return msg # ok, go ahead and write 'em - set up file if hasattr(filename,'write'): fh = filename else: absFileName = pyraf.iraf.Expand(filename) absDir = os.path.dirname(absFileName) if len(absDir) and not os.path.isdir(absDir): os.makedirs(absDir) fh = open(absFileName,'w') nsave = len(self.__pars) if comment: fh.write('# '+comment+'\n') for par in self.__pars: if par.name == '$nargs': nsave = nsave-1 else: fh.write(par.save()+'\n') if fh != filename: fh.close() return "%d parameters written to %s" % (nsave, filename) elif hasattr(fh, 'name'): return "%d parameters written to %s" % (nsave, fh.name) else: return "%d parameters written" % (nsave,) def __getinitargs__(self): """Return parameters for __init__ call in pickle""" return (self.__name, self.__filename, self.__pars) # # These two methods were set to do nothing (they were previously # needed for pickle) but having them this way makes PY3K deepcopy # fail in an extremely difficult to diagnose way. # # def __getstate__(self): # """Return additional state for pickle""" # # nothing beyond init # return None # # def __setstate__(self, state): # """Restore additional state from pickle""" # pass def __str__(self): s = '' return s # these methods are provided so an IrafParList can be treated like # an IrafTask object by epar (and other modules) def getDefaultParList(self): return self.getParList() def getName(self): return self.__filename def getPkgname(self): return '' def run(self, *args, **kw): pass def _printVerboseDiff(list1, list2): """Print description of differences between parameter lists""" pd1, hd1 = _extractDiffInfo(list1) pd2, hd2 = _extractDiffInfo(list2) _printHiddenDiff(pd1,hd1,pd2,hd2) # look for hidden/positional changes _printDiff(pd1, pd2, 'positional') # compare positional parameters _printDiff(hd1, hd2, 'hidden') # compare hidden parameters def _extractDiffInfo(alist): hflag = -1 pd = {} hd = {} for key, value in alist.items(): if value[1] == hflag: hd[key] = value else: pd[key] = value return (pd,hd) def _printHiddenDiff(pd1,hd1,pd2,hd2): for key in pd1.keys(): if key in hd2: print "Parameter `%s' is hidden in list 2 but not list 1" % (key,) del pd1[key] del hd2[key] for key in pd2.keys(): if key in hd1: print "Parameter `%s' is hidden in list 1 but not list 2" % (key,) del pd2[key] del hd1[key] def _printDiff(pd1, pd2, label): if pd1 == pd2: return noextra = 1 k1 = pd1.keys() k1.sort() k2 = pd2.keys() k2.sort() if k1 != k2: # parameter name lists differ i1 = 0 i2 = 0 noextra = 0 while i1 0: _updateSpecialParFileDict(dirToCheck=uparmAux, strict=True) # If the _updateSpecialParFileDict processing is found to be # be taking too long, we could easily add a global flag here like # _alreadyCheckedUparmAux = True # Also check the current directory _updateSpecialParFileDict(dirToCheck=os.getcwd()) # For performance, note that there is nothing yet in place to stop us # from rereading a large dir of par files every time this is called return # we've done enough # Do a glob in the given dir flist = glob.glob(dirToCheck+"/*.par") if len(flist) <= 0: return # At this point, we have files. Foreach, figure out the task and # package it is for, and add it's pathname to the dict. for supfname in flist: buf = [] try: supfile = open(supfname, 'r') buf = supfile.readlines() supfile.close() except: pass if len(buf) < 1: warning("Unable to read special use parameter file: "+supfname, level = -1) continue # get task and pkg names, and verify this is a correct file tupKey = None for line in buf: mo = _re_taskmeta.match(line) if mo: # the syntax is right, get the task and pkg names tupKey = ( mo.group(1), mo.group(2) ) break # only one TASKMETA line per file if tupKey: if tupKey in _specialUseParFileDict: supflist = _specialUseParFileDict[tupKey] if supfname not in supflist: _specialUseParFileDict[tupKey].append(supfname) else: _specialUseParFileDict[tupKey] = [supfname,] # If it does not have the TASKMETA line, then it is likely a regular # IRAF .par file. How it got here we don't know, but it got dropped # here somehow and warning the user continuously about this would be # very annoying, so be quiet about it. def newSpecialParFile(taskName, pkgName, pathName): """ Someone has just created a new one and we are being notified of that fact so that we can update the dict. """ # We could at this point simply re-scan the disk for files, but for # now let's assume the user doesnt want that. Just add this entry to # the dict. Someday, after we gauge usage, we could change this to # re-scan and add this entry to the dict if not there yet. global _specialUseParFileDict # lazy init - only search disk here when abs. necessary if _specialUseParFileDict == None: _updateSpecialParFileDict() tupKey = (taskName, pkgName) if tupKey in _specialUseParFileDict: if not pathName in _specialUseParFileDict[tupKey]: _specialUseParFileDict[tupKey].append(pathName) else: _specialUseParFileDict[tupKey] = [pathName,] def haveSpecialVersions(taskName, pkgName): """ This is a simple check to see if special-purpose parameter files have been found for the given task/package. This returns True or False. If the dictionary has not been created yet, this initializes it. Note that this may take some time reading the disk. """ global _specialUseParFileDict # Always update the _specialUseParFileDict, since we may have changed # directories into a new work area with as-yet-unseen .par files _updateSpecialParFileDict() # Check and return answer tupKey = (taskName, pkgName) return tupKey in _specialUseParFileDict def getSpecialVersionFiles(taskName, pkgName): """ Returns a (possibly empty) list of path names for special versions of parameter files. This also causes lazy initialization.""" global _specialUseParFileDict tupKey = (taskName, pkgName) if haveSpecialVersions(taskName, pkgName): return _specialUseParFileDict[tupKey] else: return [] # ----------------------------------------------------- # Read IRAF .par file and return list of parameters # ----------------------------------------------------- # Parameter file is basically comma-separated fields, but # with some messy variations involving embedded quotes # and the ability to break the final field across lines. # First define regular expressions used in parsing # Patterns that match a quoted string with embedded \" or \' # From Freidl, Mastering Regular Expressions, p. 176. # # Modifications: # - I'm using the "non-capturing" parentheses (?:...) where # possible; I only capture the part of the string between # the quotes. # - Match leading white space and optional trailing comma. # - Pick up any non-whitespace between the closing quote and # the comma or end-of-line (which is a syntax error.) # Any matched string gets captured into djunk or sjunk # variable, depending on which quotes were matched. whitespace = r'[ \t]*' optcomma = r',?' noncommajunk = r'[^,]*' double = whitespace + r'"(?P[^"\\]*(?:\\.[^"\\]*)*)"' + \ whitespace + r'(?P[^,]*)' + optcomma single = whitespace + r"'(?P[^'\\]*(?:\\.[^'\\]*)*)'" + \ whitespace + r'(?P[^,]*)' + optcomma # Comma-terminated string that doesn't start with quote # Match explanation: # - match leading white space # - if end-of-string then done with capture # - elif lookahead == comma then done with capture # - else match not-[comma | blank | quote] followed # by string of non-commas; then done with capture # - match trailing comma if present # # Trailing blanks do get captured (which I think is # the right thing to do) comma = whitespace + r"(?P$|(?=,)|(?:[^, \t'" + r'"][^,]*))' + optcomma # Combined pattern field = '(?:' + comma + ')|(?:' + double + ')|(?:' + single + ')' _re_field = re.compile(field,re.DOTALL) # Pattern that matches trailing backslashes at end of line _re_bstrail = re.compile(r'\\*$') # clean up unnecessary global variables del whitespace, field, comma, optcomma, noncommajunk, double, single def _readpar(filename,strict=0): """Read IRAF .par file and return list of parameters""" global _re_field, _re_bstrail param_dict = {} param_list = [] fh = open(os.path.expanduser(filename),'r') lines = fh.readlines() fh.close() # reverse order of lines so we can use pop method lines.reverse() while lines: # strip whitespace (including newline) off both ends line = lines.pop().strip() # skip comments and blank lines # "..." is weird line that occurs in cl.par if len(line)>0 and line[0] != '#' and line != "...": # Append next line if this line ends with continuation character. while line[-1:] == "\\": # odd number of trailing backslashes means this is continuation if (len(_re_bstrail.search(line).group()) % 2 == 1): try: line = line[:-1] + lines.pop().rstrip() except IndexError: raise SyntaxError(filename + ": Continuation on last line\n" + line) else: break flist = [] i1 = 0 while len(line) > i1: mm = _re_field.match(line,i1) if mm is None: # Failure occurs only for unmatched leading quote. # Append more lines to get quotes to match. (Probably # want to restrict this behavior to only the prompt # field.) while mm is None: try: nline = lines.pop() except IndexError: # serious error, run-on quote consumed entire file sline = line.split('\n') raise SyntaxError(filename + ": Unmatched quote\n" + sline[0]) line = line + '\n' + nline.rstrip() mm = _re_field.match(line,i1) if mm.group('comma') is not None: g = mm.group('comma') # completely omitted field (,,) if g == "": g = None # check for trailing quote in unquoted string elif g[-1:] == '"' or g[-1:] == "'": warning(filename + "\n" + line + "\n" + "Unquoted string has trailing quote", strict) elif mm.group('double') is not None: if mm.group('djunk'): warning(filename + "\n" + line + "\n" + "Non-blank follows quoted string", strict) g = mm.group('double') elif mm.group('single') is not None: if mm.group('sjunk'): warning(filename + "\n" + line + "\n" + "Non-blank follows quoted string", strict) g = mm.group('single') else: raise SyntaxError(filename + "\n" + line + "\n" + \ "Huh? mm.groups()="+`mm.groups()`+"\n" + \ "Bug: doesn't match single, double or comma??") flist.append(g) # move match pointer i1 = mm.end() try: par = IrafParFactory(flist, strict=strict) except KeyboardInterrupt: raise except Exception, exc: #XXX Shouldn't catch all exceptions here -- this could #XXX screw things up if Verbose: import traceback traceback.print_exc() raise SyntaxError(filename + "\n" + line + "\n" + \ str(flist) + "\n" + str(exc)) if par.name in param_dict: warning(filename + "\n" + line + "\n" + "Duplicate parameter " + par.name, strict) else: param_dict[par.name] = par param_list.append(par) return param_list pyraf-2.1.15/lib/pyraf/irafinst.py0000644000665500117240000002133113310762263020001 0ustar sontagpassdev00000000000000""" module irafinst.py - Defines bare-bones functionality needed when IRAF is not present. This is NOT a replacement for any major parts of IRAF. For now, in general, we assume that IRAF exists until we are told otherwise. Obviously, this module should refrain as much as possible from importing any IRAF related code (at least globally), since this is heavily relied upon in non-IRAF situations. $Id$ """ from __future__ import division # confidence high import os, shutil, sys, tempfile # File name prefix signal NO_IRAF_PFX = '*no~iraf*/' # Are we running without an IRAF installation? If no, EXISTS == False if sys.platform.startswith('win'): EXISTS = False else: EXISTS = 'PYRAF_NO_IRAF' not in os.environ # Keep track of any tmp files created _tmp_dir = None # cleanup (for exit) def cleanup(): """ Try to cleanup. Don't complain if the dir isn't there. """ global _tmp_dir if _tmp_dir: shutil.rmtree(_tmp_dir) _tmp_dir = None # Create files on the fly when needed def tmpParFile(fname): """ Create a tmp file for the given par file, and return the filename. """ assert fname and fname.endswith('.par'), 'Unexpected file: '+fname if fname == 'cl.par': content = """ # Variables effecting cl operation. args,s,h,,,,CL command line arguments gcur,*gcur,a,,,,Graphics cursor imcur,*imcur,a,,,,Image cursor ukey,*ukey,a,,,,Global user terminal keyboard keylist abbreviate,b,h,yes,,,Allow abbreviations in operand names? echo,b,h,no,,,Echo CL command input on stderr? ehinit,s,h,"nostandout eol noverify",,,Ehistory options string epinit,s,h,"standout showall",,,Eparam options string keeplog,b,h,no,,,Record all interactive commands in logfile? logfile,f,h,"home$logfile.cl",,,Name of the logfile logmode,s,h,"commands nobackground noerrors notrace",,,Logging control lexmodes,b,h,yes,,,Enable conversational mode menus,b,h,yes,,,Display menu when changing packages? showtype,b,h,no,,,Add task-type suffix in menus? notify,b,h,yes,,,Send done message when bkgrnd task finishes? szprcache,i,h,4,1,10,Size of the process cache version,s,h,"IRAF V2.14EXPORT Nov 2007",,,IRAF version logver,s,h,"",,,login.cl version logregen,b,h,no,,,Updating of login.cl to current version is advised release,s,h,"2.14",,,IRAF release mode,s,h,ql,,,CL mode of execution (query or query+learn) # auto,s,h,a,,,The next 4 params are read-only. query,s,h,q hidden,s,h,h learn,s,h,l menu,s,h,m # # Handy boolean variables for interactive use. b1,b,h,,,,b1 b2,b,h,,,,b2 b3,b,h,,,,b3 # Handy integer variables for interactive use. i,i,h,,,,i j,i,h,,,,j k,i,h,,,,k # Handy real variables for interactive use. x,r,h,,,,x y,r,h,,,,y z,r,h,,,,z # Handy string variables for interactive use. s1,s,h,,,,s1 s2,s,h,,,,s2 s3,s,h,,,,s3 # Handy parameter for reading lists (text files). list,*s,h,,,,list # Line buffer for list files. line,struct,h,,,,line ... """ elif fname == 'system.par': content = """ version,s,h,"12-Nov-83" mode,s,h,ql """ else: # For now that's it - this must be a file we don't handle raise RuntimeError('Unexpected .par file: '+fname) return _writeTmpFile(fname, content) def _writeTmpFile(base_fname, text): """ Utility function for writing our tmp files. Return the full fname.""" global _tmp_dir if not _tmp_dir: u = os.environ.get('USER','') if not u: u = os.environ.get('LOGNAME','') _tmp_dir = tempfile.mkdtemp(prefix='pyraf_'+u+'_tmp_',suffix='.no-iraf') tmpf = _tmp_dir+os.sep+base_fname if os.path.exists(tmpf): os.remove(tmpf) f = open(tmpf, 'w') f.write(text) f.close() return tmpf def getNoIrafClFor(fname, useTmpFile=False): """ Generate CL file text on the fly when missing IRAF, return the full text sting. If useTmpFile, then returns the temp file name. """ assert fname and fname.endswith('.cl'), 'Unexpected file: '+fname # First call ourselves to get the text if we need to write it to a tmp file if useTmpFile: return _writeTmpFile(fname, getNoIrafClFor(fname, useTmpFile=False)) # bare-bones clpackage.cl if fname == 'clpackage.cl': return """ # IRAF standard system package script task declarations. task language.pkg = "language$language.cl" task system.pkg = "system$system.cl" # Handy task to call the user's logout.cl file. task $_logout = "home$logout.cl" # (might use as a hook) Define any external (user-configurable) packages. # cl < hlib$extern.pkg # if (menus) { menus = no; system; menus = yes } else system keep """ # basic login.cl for the case of no IRAF if fname == 'login.cl': usr = None try: if hasattr(os, 'getlogin'): usr = os.getlogin() except OSError: pass # "Inappropriate ioctl for device" - happens in a cron job if not usr and 'USER' in os.environ: usr = os.environ['USER'] if not usr and 'USERNAME' in os.environ: usr = os.environ['USERNAME'] if not usr and 'LOGNAME' in os.environ: usr = os.environ['LOGNAME'] ihome = os.getcwd()+'/' ihome = ihome.replace('\\', '/') # for windoze content = '# LOGIN.CL -- User login file.\n'+ \ 'set home = "'+ihome+'"\nset userid = "'+usr+'"\n'+ \ 'set uparm = "home$uparm/"\n'+ \ 'stty xterm\n'+ \ 'showtype = yes\n'+ \ '# Load default CL pkg - allow overrides via loginuser.cl\n'+\ 'clpackage\n'+ \ '# Default USER package - to be modified by the user\n'+ \ 'package user\n'+ \ '# Basic foreign tasks from UNIX\n'+ \ 'task $adb $bc $cal $cat $comm $cp $csh $date $dbx = "$foreign"\n' +\ 'task $df $diff $du $find $finger $ftp $grep $lpq = "$foreign"\n' +\ 'task $lprm $mail $make $man $mon $mv $nm $od = "$foreign"\n' +\ 'task $ps $rcp $rlogin $rsh $ruptime $rwho $sh = "$foreign"\n' +\ 'task $spell $sps $strings $su $telnet $tip $top = "$foreign"\n' +\ 'task $vi $emacs $w $wc $less $more $rusers $sync = "$foreign"\n' +\ 'task $pwd $gdb $xc $mkpkg $generic $rtar $wtar = "$foreign"\n' +\ 'task $tar $bash $tcsh $buglog $who $ssh $scp = "$foreign"\n' +\ 'task $mkdir $rm $chmod $sort = "$foreign"\n' if sys.platform.startswith('win'): content += '# Basic foreign tasks for Win\n'+ \ 'task $cmd $cls $DIR $erase $start $title $tree = "$foreign"\n'+\ 'task $ls = "$DIR" \n' else: content += '# Conveniences\n'+ \ 'task $ls = "$foreign"\n' +\ 'task $cls = "$clear;ls"\n' +\ 'task $clw = "$clear;w"\n' content += 'if (access ("home$loginuser.cl"))\n' +\ ' cl < "home$loginuser.cl"\n' +\ ';\n' +\ '# more ...\nkeep\n' return content # bare-bones system.cl if fname == 'system.cl': return """ # lists (not using these for now in this case) # { SYSTEM.CL -- Package script task for the SYSTEM package. This package is # loaded by the CL upon startup, and is always in the search path. package system # These tasks might be useful to convert to Python where no IRAF exists #task cmdstr, # concatenate, # copy, # count, # delete, # directory, # files, # head, # lprint, # match, # mkdir, # movefiles, # mtclean, # $netstatus, # page, # pathnames, # protect, # rename, # sort, # tail, # tee, # touch, # type, # rewind, # unprotect, # help = "system$x_system.e" #hidetask cmdstr #hidetask mtclean task mkscript = "system$mkscript.cl" task $news = "system$news.cl" task allocate = "hlib$allocate.cl" task gripes = "hlib$gripes.cl" task deallocate = "hlib$deallocate.cl" task devstatus = "hlib$devstatus.cl" task $diskspace = "hlib$diskspace.cl" task $spy = "hlib$spy.cl" task $devices = "system$devices.cl" task references = "system$references.cl" task phelp = "system$phelp.cl" keep """ # For now that's it - this must be a file we don't handle raise RuntimeError('Unexpected .cl file: '+fname) def getIrafVer(): """ Return current IRAF version as a string """ import iraffunctions cltask = iraffunctions.getTask('cl') # must use default par list, in case they have a local override plist = cltask.getDefaultParList() # get the 'release' par and then get it's value release = [p.value for p in plist if p.name == 'release'] return release[0] # only 1 item in list def getIrafVerTup(): """ Return current IRAF version as a tuple (ints until last item) """ verlist = getIrafVer().split('.') outlist = [] for v in verlist: if v.isdigit(): outlist.append(int(v)) else: outlist.append(v) return tuple(outlist) pyraf-2.1.15/lib/pyraf/gkitkplot.py0000644000665500117240000003416013310762263020176 0ustar sontagpassdev00000000000000""" Tkplot implementation of the gki kernel class $Id$ """ from __future__ import division # confidence high import numpy, sys, string import Tkinter as TKNTR # requires 2to3 from stsci.tools.for2to3 import ndarr2str import wutil, Ptkplot import gki, gkitkbase, gkigcur, tkplottext, textattrib, irafgwcs TK_LINE_STYLE_PATTERNS = ['.','.','_','.','.._'] #----------------------------------------------- class GkiTkplotKernel(gkitkbase.GkiInteractiveTkBase): """Tkplot graphics kernel implementation""" def makeGWidget(self, width=600, height=420): """Make the graphics widget""" self.gwidget = Ptkplot.PyrafCanvas(self.top, width=width, height=height) self.gwidget.firstPlotDone = 0 self.colorManager = tkColorManager(self.irafGkiConfig) self.startNewPage() self._gcursorObject = gkigcur.Gcursor(self) self.gRedraw() def gcur(self): """Return cursor value after key is typed""" return self._gcursorObject() def gcurTerminate(self, msg='Window destroyed by user'): """Terminate active gcur and set EOF flag""" if self._gcursorObject.active: self._gcursorObject.eof = msg # end the gcur mainloop -- this is what allows # closing the window to act the same as EOF self.top.quit() def taskDone(self, name): """Called when a task is finished""" # Hack to prevent the double redraw after first Tk plot self.doubleRedrawHack() def update(self): """Update for all Tk events This should not be called unless necessary since it can cause double redraws. It is used in the imcur task to allow window resize (configure) events to be caught while a task is running. Possibly it should be called during long-running tasks too, but that will probably lead to more extra redraws""" # Hack to prevent the double redraw after first Tk plot self.doubleRedrawHack() self.top.update() def doubleRedrawHack(self): # This is a hack to prevent the double redraw on first plots. # There is a mysterious Expose event that appears on the # idle list, but not until the Tk loop actually becomes idle. # The only approach that seems to work is to set this flag # and to ignore the event. # This is ugly but appears to work as far as I can tell. gwidget = self.gwidget if gwidget and not gwidget.firstPlotDone: gwidget.ignoreNextRedraw = 1 gwidget.firstPlotDone = 1 def prepareToRedraw(self): """Clear glBuffer in preparation for complete redraw from metacode""" self.drawBuffer.reset() def getHistory(self): """Additional information for page history""" return self.drawBuffer def setHistory(self, info): """Restore using additional information from page history""" self.drawBuffer = info def startNewPage(self): """Setup for new page""" self.drawBuffer = gki.DrawBuffer() def clearPage(self): """Clear buffer for new page""" self.drawBuffer.reset() def isPageBlank(self): """Returns true if this page is blank""" return len(self.drawBuffer) == 0 # ----------------------------------------------- # GkiKernel implementation def incrPlot(self): """Plot any new commands in the buffer""" gwidget = self.gwidget if gwidget: active = gwidget.isSWCursorActive() if active: gwidget.deactivateSWCursor() # render new contents of glBuffer self.activate() for (function, args) in self.drawBuffer.getNewCalls(): function(*args) gwidget.flush() if active: gwidget.activateSWCursor() # special methods that go into the function tables def _tkplotAppend(self, tkplot_function, *args): """append a 2-tuple (tkplot_function, args) to the glBuffer""" self.drawBuffer.append((tkplot_function,args)) def gki_clearws(self, arg): # don't put clearws command in the tk buffer, just clear the display self.clear() # This is needed to clear all the previously plotted objects # within tkinter (it has its own buffer it uses to replot) #self.gwidget.delete(TKNTR.ALL) def gki_cancel(self, arg): self.gki_clearws(arg) def gki_flush(self, arg): # don't put flush command in tk buffer # render current plot immediately on flush self.incrPlot() def gki_polyline(self, arg): # commit pending WCS changes when draw is found self.wcs.commit() self._tkplotAppend(self.tkplot_polyline, gki.ndc(arg[1:])) def gki_polymarker(self, arg): self.wcs.commit() self._tkplotAppend(self.tkplot_polymarker, gki.ndc(arg[1:])) def gki_text(self, arg): self.wcs.commit() x = gki.ndc(arg[0]) y = gki.ndc(arg[1]) text = ndarr2str(arg[3:].astype(numpy.int8)) self._tkplotAppend(self.tkplot_text, x, y, text) def gki_fillarea(self, arg): self.wcs.commit() self._tkplotAppend(self.tkplot_fillarea, gki.ndc(arg[1:])) def gki_putcellarray(self, arg): self.wcs.commit() self.errorMessage(gki.standardNotImplemented % "GKI_PUTCELLARRAY") def gki_setcursor(self, arg): cursorNumber = arg[0] x = gki.ndc(arg[1]) y = gki.ndc(arg[2]) self._tkplotAppend(self.tkplot_setcursor, cursorNumber, x, y) def gki_plset(self, arg): linetype = arg[0] # Handle case where some terms (eg. xgterm) allow higher values, # by looping over the possible visible patterns. (ticket #172) if linetype >= len(TK_LINE_STYLE_PATTERNS): num_visible = len(TK_LINE_STYLE_PATTERNS)-1 linetype = 1 + (linetype % num_visible) linewidth = arg[1]/gki.GKI_FLOAT_FACTOR color = arg[2] self._tkplotAppend(self.tkplot_plset, linetype, linewidth, color) def gki_pmset(self, arg): marktype = arg[0] #XXX Is this scaling for marksize correct? marksize = gki.ndc(arg[1]) color = arg[2] self._tkplotAppend(self.tkplot_pmset, marktype, marksize, color) def gki_txset(self, arg): charUp = float(arg[0]) charSize = arg[1]/gki.GKI_FLOAT_FACTOR charSpace = arg[2]/gki.GKI_FLOAT_FACTOR textPath = arg[3] textHorizontalJust = arg[4] textVerticalJust = arg[5] textFont = arg[6] textQuality = arg[7] textColor = arg[8] self._tkplotAppend(self.tkplot_txset, charUp, charSize, charSpace, textPath, textHorizontalJust, textVerticalJust, textFont, textQuality, textColor) def gki_faset(self, arg): fillstyle = arg[0] color = arg[1] self._tkplotAppend(self.tkplot_faset, fillstyle, color) def gki_getcursor(self, arg): raise NotImplementedError(gki.standardNotImplemented % "GKI_GETCURSOR") def gki_getcellarray(self, arg): raise NotImplementedError(gki.standardNotImplemented % "GKI_GETCELLARRAY") def gki_unknown(self, arg): self.errorMessage(gki.standardWarning % "GKI_UNKNOWN") def gRedraw(self): if self.gwidget: self.gwidget.tkRedraw() def redraw(self, o=None): """Redraw for expose or resize events This method generally should not be called directly -- call gwidget.tkRedraw() instead since it does some other preparations. """ # Note argument o is not needed because we only get redraw # events for our own gwidget ta = self.textAttributes ta.setFontSize(self) # finally ready to do the drawing self.activate() # Have Tk remove all previously plotted objects self.gwidget.delete(TKNTR.ALL) # Clear the screen self.tkplot_faset(0,0) self.tkplot_fillarea(numpy.array([0.,0.,1.,0.,1.,1.,0.,1.])) # Plot the current buffer for (function, args) in self.drawBuffer.get(): function(*args) self.gwidget.flush() #----------------------------------------------- # These are the routines for the innermost loop in the redraw # function. They are supposed to be stripped down to make # redraws as fast as possible. (Still could be improved.) def tkplot_flush(self, arg): self.gwidget.flush() def tkplot_polyline(self, vertices): # First, set all relevant attributes la = self.lineAttributes # XXX not handling linestyle yet, except for clear stipple = 0 npts = len(vertices)//2 if la.linestyle == 0: # clear color = self.colorManager.setDrawingColor(0) else: color = self.colorManager.setDrawingColor(la.color) options = {"fill":color,"width":la.linewidth} if la.linestyle > 1: options['dash'] = TK_LINE_STYLE_PATTERNS[la.linestyle] # scale coordinates gw = self.gwidget h = gw.winfo_height() w = gw.winfo_width() scaled = (numpy.array([w,-h]) * (numpy.reshape(vertices, (npts, 2)) - numpy.array([0.,1.]))) gw.create_line(*(tuple(scaled.ravel().astype(numpy.int32))), **options) def tkplot_polymarker(self, vertices): # IRAF only implements points for poly marker, that makes it simple ma = self.markerAttributes # Marker attributes don't appear # to be set when this mode is used though. npts = len(vertices)//2 color = self.colorManager.setDrawingColor(ma.color) gw = self.gwidget h = gw.winfo_height() w = gw.winfo_width() scaled = (numpy.array([w,-h]) * (numpy.reshape(vertices, (npts, 2)) - numpy.array([0.,1.]))).astype(numpy.int32) # Lack of intrinsic Tk point mode means that they must be explicitly # looped over. for i in xrange(npts): gw.create_rectangle(scaled[i,0], scaled[i,1], scaled[i,0], scaled[i,1], fill=color, outline='') def tkplot_text(self, x, y, text): tkplottext.softText(self,x,y,text) def tkplot_fillarea(self, vertices): fa = self.fillAttributes clear = 0 polystipple = 0 npts = len(vertices)//2 if fa.fillstyle != 0: color = self.colorManager.setDrawingColor(fa.color) else: # clear region color = self.colorManager.setDrawingColor(0) options = {"fill":color} # scale coordinates gw = self.gwidget h = gw.winfo_height() w = gw.winfo_width() scaled = (numpy.array([w,-h]) * (numpy.reshape(vertices, (npts, 2)) - numpy.array([0.,1.]))) coords = tuple(scaled.ravel().astype(numpy.int32)) if fa.fillstyle == 1: # hollow gw.create_line(*(coords+(coords[0],coords[1])), **options) else: # solid or clear cases gw.create_polygon(*coords, **options) def tkplot_setcursor(self, cursornumber, x, y): gwidget = self.gwidget # Update the sw cursor object (A clear example of why this update # is needed is how 'apall' re-centers the cursor w/out changing y, when # the user types 'r'; without this update, the two cursors separate.) swCurObj = gwidget.getSWCursor() if swCurObj: swCurObj.moveTo(x, y, SWmove=1) # wutil.MoveCursorTo uses 0,0 <--> upper left, need to convert sx = int( x * gwidget.winfo_width()) sy = int((1-y) * gwidget.winfo_height()) rx = gwidget.winfo_rootx() ry = gwidget.winfo_rooty() # call the wutil version to move the cursor wutil.moveCursorTo(gwidget.winfo_id(), rx, ry, sx, sy) def tkplot_plset(self, linestyle, linewidth, color): self.lineAttributes.set(linestyle, linewidth, color) def tkplot_pmset(self, marktype, marksize, color): self.markerAttributes.set(marktype, marksize, color) def tkplot_txset(self, charUp, charSize, charSpace, textPath, textHorizontalJust, textVerticalJust, textFont, textQuality, textColor): self.textAttributes.set(charUp, charSize, charSpace, textPath, textHorizontalJust, textVerticalJust, textFont, textQuality, textColor) def tkplot_faset(self, fillstyle, color): self.fillAttributes.set(fillstyle, color) #----------------------------------------------- class tkColorManager: """Encapsulates the details of setting the graphic's windows colors. Needed since we may be using rgba mode or color index mode and we do not want any of the graphics programs to have to deal with the mode being used. The current design applies the same colors to all graphics windows for color index mode (but it isn't required). An 8-bit display depth results in color index mode, otherwise rgba mode is used. If no new colors are available, we take what we can get. We do not attempt to get a private colormap. """ def __init__(self, config): self.config = config self.rgbamode = 0 self.indexmap = len(self.config.defaultColors)*[None] # call setColors to allocate colors after widget is created def setColors(self, widget): """Not needed for Tkplot, a nop""" pass def setCursorColor(self, irafColorIndex=None): """Set crosshair cursor color to given index Only has an effect in index color mode.""" if irafColorIndex is not None: self.config.setCursorColor(irafColorIndex) def setDrawingColor(self, irafColorIndex): """Return the specified iraf color usable by TKNTR""" color = self.config.defaultColors[irafColorIndex] red = int(255*color[0]) green = int(255*color[1]) blue = int(255*color[2]) return "#%02x%02x%02x" % (red,green,blue) pyraf-2.1.15/lib/pyraf/iraffunctions.py0000644000665500117240000035373713310762263021056 0ustar sontagpassdev00000000000000"""module iraffunctions.py -- IRAF emulation tasks and functions This is not usually used directly -- the relevant public classes and functions get included in iraf.py. The implementations are kept here to avoid possible problems with name conflicts in iraf.py (which is the home for all the IRAF task and package names.) Private routines have names beginning with '_' and do not get imported by the iraf module. The exception is that iraffunctions can be used directly for modules that must be compiled and executed early, before the pyraf module initialization is complete. $Id$ R. White, 2000 January 20 """ from __future__ import division, print_function # define INDEF, yes, no, EOF, Verbose, IrafError, userIrafHome from stsci.tools.irafglobals import * from subproc import SubprocessError # ----------------------------------------------------- # setVerbose: set verbosity level # ----------------------------------------------------- def setVerbose(value=1, **kw): """Set verbosity level when running tasks. Level 0 (default) prints almost nothing. Level 1 prints warnings. Level 2 prints info on progress. This accepts **kw so it can be used on the PyRAF command-line. This cannot avail itself of the decorator which wraps redirProcess since it needs to be defined here up front. """ if isinstance(value,(str,unicode)): try: value = int(value) except ValueError: pass Verbose.set(value) def _writeError(msg): """Write a message to stderr""" _sys.stdout.flush() _sys.stderr.write(msg) if msg[-1:] != "\n": _sys.stderr.write("\n") # ----------------------------------------------------- # now it is safe to import other iraf modules # ----------------------------------------------------- import sys, os, string, re, math, struct, types, time, fnmatch, glob, tempfile import linecache from stsci.tools import minmatch, irafutils, teal import numpy import subproc, wutil import irafnames, irafinst, irafpar, iraftask, irafexecute, cl2py import gki import irafecl try: from . import sscanf # sscanf import does not get 'fixed' during 2to3 except: # basic usage does not actually require sscanf sscanf = None print("Warning: sscanf library not installed on "+sys.platform) try: import cPickle as pickle except ImportError: import pickle try: import cStringIO as StringIO except ImportError: import StringIO as StringIO # survives 2to3 # hide these modules so we can use 'from iraffunctions import *' _sys = sys _os = os _string = string _re = re _math = math _struct = struct _types = types _time = time _fnmatch = fnmatch _glob = glob _tempfile = tempfile _linecache = linecache _StringIO = StringIO _pickle = pickle _numpy = numpy _minmatch = minmatch _teal = teal _subproc = subproc _wutil = wutil _irafnames = irafnames _irafutils = irafutils _irafinst = irafinst _iraftask = iraftask _irafpar = irafpar _irafexecute = irafexecute _cl2py = cl2py del sys, os, string, re, math, struct, types, time, fnmatch, glob, linecache del StringIO, pickle, tempfile del numpy, minmatch, subproc, teal, wutil del irafnames, irafutils, irafinst, iraftask, irafpar, irafexecute, cl2py # Number of bits per long BITS_PER_LONG = _struct.calcsize('l') * 8 # is 64 on a 64-bit machine # FP_EPSILON is the smallest number such that: 1.0 + epsilon > 1.0; Use None # in the finfo ctor to make it use the default precision for a Python float. FP_EPSILON = _numpy.finfo(None).eps # ----------------------------------------------------- # private dictionaries: # # _varDict: dictionary of all IRAF cl variables (defined with set name=value) # _tasks: all IRAF tasks (defined with task name=value) # _mmtasks: minimum-match dictionary for tasks # _pkgs: min-match dictionary for all packages (defined with # task name.pkg=value) # _loaded: loaded packages # ----------------------------------------------------- # Will want to enhance this to allow a "bye" function that unloads packages. # That might be done using a stack of definitions for each task. _varDict = {} _tasks = {} _mmtasks = _minmatch.MinMatchDict() _pkgs = _minmatch.MinMatchDict() _loaded = {} # ----------------------------------------------------- # public variables: # # loadedPath: list of loaded packages in order of loading # Used as search path to find fully qualified task name # ----------------------------------------------------- loadedPath = [] # cl is the cl task pointer (frequently used because cl parameters # are always available) cl = None # ----------------------------------------------------- # help: implemented in irafhelp.py # ----------------------------------------------------- from irafhelp import help # ----------------------------------------------------- # Init: basic initialization # ----------------------------------------------------- # This could be executed automatically when the module first # gets imported, but that would not allow control over output # (which is available through the doprint and hush parameters.) def Init(doprint=1,hush=0,savefile=None): """Basic initialization of IRAF environment""" global _pkgs, cl if savefile is not None: restoreFromFile(savefile,doprint=doprint) return if len(_pkgs) == 0: try: iraf = _os.environ['iraf'] arch = _os.environ['IRAFARCH'] except KeyError: # iraf or IRAFARCH environment variable not defined # try to get them from cl startup file try: d = _getIrafEnv() for key, value in d.items(): if not key in _os.environ: _os.environ[key] = value iraf = _os.environ['iraf'] arch = _os.environ['IRAFARCH'] except IOError: raise SystemExit(""" Your "iraf" and "IRAFARCH" environment variables are not defined and could not be determined from /usr/local/bin/cl. These are needed to find IRAF tasks. Before starting pyraf, define them by doing (for example): setenv iraf /iraf/iraf/ setenv IRAFARCH linux at the Unix command line. Actual values will depend on your IRAF installation, and they are set during the IRAF user installation (see iraf.net), or via Ureka installation (see http://ssb.stsci.edu/ureka). Also be sure to run the "mkiraf" command to create a logion.cl (http://www.google.com/search?q=mkiraf). """) #stacksize problem on linux if arch == 'redhat' or \ arch == 'linux' or \ arch == 'linuxppc' or \ arch == 'suse': import resource if resource.getrlimit(resource.RLIMIT_STACK)[1]==-1 : resource.setrlimit(resource.RLIMIT_STACK,(-1,-1)) else: pass else: pass # ensure trailing slash is present iraf = _os.path.join(iraf,'') host = _os.environ.get('host', _os.path.join(iraf,'unix','')) hlib = _os.environ.get('hlib', _os.path.join(host,'hlib','')) tmp = _os.environ.get('tmp', '/tmp/') set(iraf = iraf) set(host = host) set(hlib = hlib) set(tmp = tmp) if arch and arch[0] != '.': arch = '.' + arch set(arch = arch) global userIrafHome set(home = userIrafHome) # define initial symbols if _irafinst.EXISTS: clProcedure(Stdin='hlib$zzsetenv.def') # define clpackage global clpkg clpkg = IrafTaskFactory('', 'clpackage', '.pkg', 'hlib$clpackage.cl', 'clpackage', 'bin$') # add the cl as a task, because its parameters are sometimes needed, # but make it a hidden task # cl is implemented as a Python task cl = IrafTaskFactory('','cl','','cl$cl.par','clpackage','bin$', function=_clProcedure) cl.setHidden() # load clpackage clpkg.run(_doprint=0, _hush=hush, _save=1) if access('login.cl'): fname = _os.path.abspath('login.cl') elif access('home$login.cl'): fname = 'home$login.cl' elif access(_os.path.expanduser('~/.iraf/login.cl')): fname = _os.path.expanduser('~/.iraf/login.cl') elif not _irafinst.EXISTS: fname = _irafinst.getNoIrafClFor('login.cl', useTmpFile=True) else: fname = None if fname: # define and load user package userpkg = IrafTaskFactory('', 'user', '.pkg', fname, 'clpackage', 'bin$') userpkg.run(_doprint=0, _hush=hush, _save=1) else: _writeError("Warning: no login.cl found") # make clpackage the current package loadedPath.append(clpkg) if doprint: listTasks('clpackage') def _getIrafEnv(file='/usr/local/bin/cl',vars=('IRAFARCH','iraf')): """Returns dictionary of environment vars defined in cl startup file""" if not _irafinst.EXISTS: return {'iraf': '/iraf/is/not/here/', 'IRAFARCH': 'arch_is_unused'} if not _os.path.exists(file): raise IOError("CL startup file %s does not exist" % file) lines = open(file,'r').readlines() # replace commands that exec cl with commands to print environment vars pat = _re.compile(r'^\s*exec\s+') newlines = [] nfound = 0 for line in lines: if pat.match(line): nfound += 1 for var in vars: newlines.append('echo "%s=$%s"\n' % (var, var)) newlines.append('exit 0\n') else: newlines.append(line) if nfound == 0: raise IOError("No exec statement found in script %s" % file) # write new script to temporary file (fd, newfile) = _tempfile.mkstemp() _os.close(fd) f = open(newfile, 'w') f.writelines(newlines) f.close() _os.chmod(newfile,0700) # run new script and capture output fh = _StringIO.StringIO() status = clOscmd(newfile,Stdout=fh) if status: raise IOError("Execution error in script %s (derived from %s)" % (newfile, file)) _os.remove(newfile) result = fh.getvalue().split('\n') fh.close() # extract environment variables from the output d = {} for entry in result: if entry.find('=') >= 0: key, value = entry.split('=',1) d[key] = value return d # module variables that don't get saved (they get # initialized when this module is imported) unsavedVars = [ 'BITS_PER_LONG', 'EOF', 'FP_EPSILON', 'IrafError', 'SubprocessError', '_NullFileList', '_NullPath', '__builtins__', '__doc__', '__package__', '__file__', '__name__', '__re_var_match', '__re_var_paren', '_badFormats', '_backDir', '_clearString', '_denode_pat', '_exitCommands', '_nscan', '_fDispatch', '_radixDigits', '_re_taskname', '_reFormat', '_sttyArgs', '_tmpfileCounter', '_clExecuteCount', '_unsavedVarsDict', 'IrafTask', 'IrafPkg', 'cl', 'division', 'epsilon', 'iraf', 'no', 'yes', 'userWorkingHome', ] _unsavedVarsDict = {} for v in unsavedVars: _unsavedVarsDict[v] = 1 del unsavedVars, v # there are a few tricky things here: # # - I restore userIrafHome, which therefore can be inconsistent with # with the IRAF environment variable. # # - I do not restore userWorkingHome, so it always tells where the # user actually started this pyraf session. Is that the right thing # to do? # # - I am restoring the INDEF object, which means that there could be # multiple versions of this supposed singleton floating around. # I changed the __cmp__ method for INDEF so it produces 'equal' # if the two objects are both INDEFs -- I hope that will take care # of any possible problems. def saveToFile(savefile, **kw): """Save IRAF environment to pickle file savefile may be a filename or a file handle. Set clobber keyword (or CL environment variable) to overwrite an existing file. """ if hasattr(savefile, 'write'): fh = savefile if hasattr(savefile, 'name'): savefile = fh.name doclose = 0 else: # if clobber is not set, do not overwrite file savefile = Expand(savefile) if (not kw.get('clobber')) and envget("clobber","") != yes and _os.path.exists(savefile): raise IOError("Output file `%s' already exists" % savefile) # open binary pickle file fh = open(savefile,'wb') doclose = 1 # make a shallow copy of the dictionary and edit out # functions, modules, and objects named in _unsavedVarsDict gdict = globals().copy() for key in gdict.keys(): item = gdict[key] if isinstance(item, (_types.FunctionType, _types.ModuleType)) or \ key in _unsavedVarsDict: del gdict[key] # print('\n\n\n',gdict.keys()) # DBG: debug line # save just the value of Verbose, not the object global Verbose gdict['Verbose'] = Verbose.get() p = _pickle.Pickler(fh,1) p.dump(gdict) if doclose: fh.close() def restoreFromFile(savefile, doprint=1, **kw): """Initialize IRAF environment from pickled save file (or file handle)""" if hasattr(savefile, 'read'): fh = savefile if hasattr(savefile, 'name'): savefile = fh.name doclose = 0 else: savefile = Expand(savefile) fh = open(savefile, 'rb') doclose = 1 u = _pickle.Unpickler(fh) udict = u.load() if doclose: fh.close() # restore the value of Verbose global Verbose Verbose.set(udict['Verbose']) del udict['Verbose'] # replace the contents of loadedPath global loadedPath loadedPath[:] = udict['loadedPath'] del udict['loadedPath'] # update the values globals().update(udict) # replace INDEF everywhere we can find it # this does not replace references in parameters, unfortunately INDEF = udict['INDEF'] from stsci.tools import irafglobals import __main__ import pyraf import irafpar, cltoken for module in (__main__, pyraf, irafpar, irafglobals, cltoken): if hasattr(module,'INDEF'): module.INDEF = INDEF # replace cl in the iraf module (and possibly other locations) global cl _addTask(cl) if doprint: listCurrent() # ----------------------------------------------------- # _addPkg: Add an IRAF package to the pkgs list # ----------------------------------------------------- def _addPkg(pkg): """Add an IRAF package to the packages list""" global _pkgs name = pkg.getName() _pkgs.add(name,pkg) # add package to global namespaces _irafnames.strategy.addPkg(pkg) # packages are tasks too, so add to task lists _addTask(pkg) # ----------------------------------------------------- # _addTask: Add an IRAF task to the tasks list # ----------------------------------------------------- def _addTask(task, pkgname=None): """Add an IRAF task to the tasks list""" global _tasks, _mmtasks name = task.getName() if not pkgname: pkgname = task.getPkgname() fullname = pkgname + '.' + name _tasks[fullname] = task _mmtasks.add(name,fullname) # add task to global namespaces _irafnames.strategy.addTask(task) # add task to list for its package getPkg(pkgname).addTask(task,fullname) # -------------------------------------------------------------------------- # Use decorators to consolidate repeated code used in command-line functions. # (09/2009) # These decorator functions are not the simplest form in that they each also # define a function (the actual wrapper) and return that function. This # is needed to get to both the before and after parts of the target. This # approach was performance tested to ensure that PyRAF functionality would # not suffer for the sake of code maintainability. The results showed (under # Python 2.4/.5/.6) that performance can be degraded (by 65%) for only the very # simplest target function (e.g. "pass"), but that for functions which take # any amount of time to do their work (e.g. taking 0.001 sec), the performance # degradation is effectively unmeasurable. This same approach can be done # with a decorator class instead of a decorator function, but the performance # degradation is always greater by a factor of at least 3. # # These decorators could all be combined into a single function with arguments # deciding their different capabilities, but that would add another level (i.e. # a function within a function within a function) and for the sake of simplicity # and robustness as we move into PY3K, we'll write them out separately for now. # -------------------------------------------------------------------------- def handleRedirAndSaveKwds(target): """ This decorator is used to consolidate repeated code used in command-line functions, concerning standard pipe redirection. Typical 'target' functions will: take 0 or more positional arguments, take NO keyword args (except redir's & _save), and return nothing. """ # create the wrapper function here which handles the redirect keywords, # and return it so it can replace 'target' def wrapper(*args, **kw): # handle redirection and save keywords redirKW, closeFHList = redirProcess(kw) if '_save' in kw: del kw['_save'] if len(kw): raise TypeError('unexpected keyword argument: ' + `kw.keys()`) resetList = redirApply(redirKW) try: # call 'target' to do the interesting work of this function target(*args) finally: rv = redirReset(resetList, closeFHList) return rv # return wrapper so it can replace 'target' return wrapper def handleRedirAndSaveKwdsPlus(target): """ This decorator is used to consolidate repeated code used in command-line functions, concerning standard pipe redirection. Typical 'target' functions will: take 0 or more positional arguments, take AT LEAST ONE keyword arg (not including redir's & _save), and return nothing. """ # create the wrapper function here which handles the redirect keywords, # and return it so it can replace 'target' def wrapper(*args, **kw): # handle redirection and save keywords redirKW, closeFHList = redirProcess(kw) if '_save' in kw: del kw['_save'] # the missing check here on len(kw) is the main difference between # this and handleRedirAndSaveKwds (also the sig. of target()) resetList = redirApply(redirKW) try: # call 'target' to do the interesting work of this function target(*args, **kw) finally: rv = redirReset(resetList, closeFHList) return rv # return wrapper so it can replace 'target' return wrapper # ----------------------------------------------------- # addLoaded: Add an IRAF package to the loaded pkgs list # ----------------------------------------------------- # This is public because Iraf Packages call it to register # themselves when they are loaded. def addLoaded(pkg): """Add an IRAF package to the loaded pkgs list""" global _loaded _loaded[pkg.getName()] = len(_loaded) # ----------------------------------------------------- # load: Load an IRAF package by name # ----------------------------------------------------- def load(pkgname,args=(),kw=None,doprint=1,hush=0,save=1): """Load an IRAF package by name""" if isinstance(pkgname,_iraftask.IrafPkg): p = pkgname else: p = getPkg(pkgname) if kw is None: kw = {} if not '_doprint' in kw: kw['_doprint'] = doprint if not '_hush' in kw: kw['_hush'] = hush if not '_save' in kw: kw['_save'] = save p.run(*tuple(args), **kw) # ----------------------------------------------------- # run: Run an IRAF task by name # ----------------------------------------------------- def run(taskname,args=(),kw=None,save=1): """Run an IRAF task by name""" if isinstance(taskname,_iraftask.IrafTask): t = taskname else: t = getTask(taskname) if kw is None: kw = {} if not '_save' in kw: kw['_save'] = save ## if not '_parent' in kw: kw['parent'] = "'iraf.cl'" t.run(*tuple(args), **kw) # ----------------------------------------------------- # getAllTasks: Get list of all IRAF tasks that match partial name # ----------------------------------------------------- def getAllTasks(taskname): """Returns list of names of all IRAF tasks that may match taskname""" return _mmtasks.getallkeys(taskname, []) # ----------------------------------------------------- # getAllPkgs: Get list of all IRAF pkgs that match partial name # ----------------------------------------------------- def getAllPkgs(pkgname): """Returns list of names of all IRAF pkgs that may match pkgname""" return _pkgs.getallkeys(pkgname, []) # ----------------------------------------------------- # getTask: Find an IRAF task by name # ----------------------------------------------------- def getTask(taskname, found=0): """Find an IRAF task by name using minimum match Returns an IrafTask object. Name may be either fully qualified (package.taskname) or just the taskname. taskname is also allowed to be an IrafTask object, in which case it is simply returned. Does minimum match to allow abbreviated names. If found is set, returns None when task is not found; default is to raise exception if task is not found. """ if isinstance(taskname,_iraftask.IrafTask): return taskname elif not isinstance(taskname, (str,unicode)): raise TypeError("Argument to getTask is not a string or IrafTask instance") # undo any modifications to the taskname taskname = _irafutils.untranslateName(taskname) # Try assuming fully qualified name first task = _tasks.get(taskname) if task is not None: if Verbose>1: print('found',taskname,'in task list') return task # Look it up in the minimum-match dictionary # Note _mmtasks.getall returns list of full names of all matching tasks fullname = _mmtasks.getall(taskname) if not fullname: if found: return None else: raise KeyError("Task "+taskname+" is not defined") if len(fullname) == 1: # unambiguous match task = _tasks[fullname[0]] if Verbose>1: print('found',task.getName(),'in task list') return task # Ambiguous match is OK only if taskname is the full name # or if all matched tasks have the same task name. For example, # (1) 'mem' matches package 'mem0' and task 'restore.mem' -- return # 'restore.mem'. # (2) 'imcal' matches tasks named 'imcalc' in several different # packages -- return the most recently loaded version. # (3) 'imcal' matches several 'imcalc's and also 'imcalculate'. # That is an error. # look for exact matches, . trylist = [] pkglist = [] for name in fullname: sp = name.split('.') if sp[-1] == taskname: trylist.append(name) pkglist.append(sp[0]) # return a single exact match if len(trylist) == 1: return _tasks[trylist[0]] if not trylist: # no exact matches, see if all tasks have same name sp = fullname[0].split('.') name = sp[-1] pkglist = [ sp[0] ] for i in xrange(len(fullname)-1): sp = fullname[i+1].split('.') if name != sp[-1]: if len(fullname)>3: fullname[3:] = ['...'] if found: return None else: raise _minmatch.AmbiguousKeyError( "Task `%s' is ambiguous, could be %s" % (taskname, ', '.join(fullname))) pkglist.append(sp[0]) trylist = fullname # trylist has a list of several candidate tasks that differ # only in package. Search loaded packages in reverse to find # which one was loaded most recently. for i in xrange(len(loadedPath)): pkg = loadedPath[-1-i].getName() if pkg in pkglist: # Got it at last j = pkglist.index(pkg) return _tasks[trylist[j]] # None of the packages are loaded? This presumably cannot happen # now, but could happen if package unloading is implemented. if found: return None else: raise KeyError("Task "+taskname+" is not in a loaded package") # ----------------------------------------------------- # getPkg: Find an IRAF package by name # ----------------------------------------------------- def getPkg(pkgname,found=0): """Find an IRAF package by name using minimum match Returns an IrafPkg object. pkgname is also allowed to be an IrafPkg object, in which case it is simply returned. If found is set, returns None when package is not found; default is to raise exception if package is not found. """ try: if isinstance(pkgname,_iraftask.IrafPkg): return pkgname if not pkgname: raise TypeError("Bad package name `%s'" % `pkgname`) # undo any modifications to the pkgname pkgname = _irafutils.untranslateName(pkgname) return _pkgs[pkgname] except _minmatch.AmbiguousKeyError, e: # re-raise the error with a bit more info raise e.__class__("Package "+pkgname+": "+str(e)) except KeyError, e: if found: return None raise KeyError("Package `%s' not found" % (pkgname,)) # ----------------------------------------------------- # Miscellaneous access routines: # getTaskList: Get list of names of all defined IRAF tasks # getPkgList: Get list of names of all defined IRAF packages # getLoadedList: Get list of names of all loaded IRAF packages # getVarList: Get list of names of all defined IRAF variables # ----------------------------------------------------- def getTaskList(): """Returns list of names of all defined IRAF tasks""" return _tasks.keys() def getTaskObjects(): """Returns list of all defined IrafTask objects""" return _tasks.values() def getPkgList(): """Returns list of names of all defined IRAF packages""" return _pkgs.keys() def getLoadedList(): """Returns list of names of all loaded IRAF packages""" return _loaded.keys() def getVarDict(): """Returns dictionary all IRAF variables""" return _varDict def getVarList(): """Returns list of names of all IRAF variables""" return _varDict.keys() # ----------------------------------------------------- # listAll, listPkg, listLoaded, listTasks, listCurrent, listVars: # list contents of the dictionaries # ----------------------------------------------------- @handleRedirAndSaveKwds def listAll(hidden=0): """List IRAF packages, tasks, and variables""" print('Packages:') listPkgs() print('Loaded Packages:') listLoaded() print('Tasks:') listTasks(hidden=hidden) print('Variables:') listVars() @handleRedirAndSaveKwds def listPkgs(): """List IRAF packages""" keylist = getPkgList() if len(keylist) == 0: print('No IRAF packages defined') else: keylist.sort() # append '/' to identify packages for i in xrange(len(keylist)): keylist[i] = keylist[i] + '/' _irafutils.printCols(keylist) @handleRedirAndSaveKwds def listLoaded(): """List loaded IRAF packages""" keylist = getLoadedList() if len(keylist) == 0: print('No IRAF packages loaded') else: keylist.sort() # append '/' to identify packages for i in xrange(len(keylist)): keylist[i] = keylist[i] + '/' _irafutils.printCols(keylist) @handleRedirAndSaveKwdsPlus def listTasks(pkglist=None, hidden=0, **kw): """List IRAF tasks, optionally specifying a list of packages to include Package(s) may be specified by name or by IrafPkg objects. """ keylist = getTaskList() if len(keylist) == 0: print('No IRAF tasks defined') return # make a dictionary of pkgs to list if pkglist is None: pkgdict = _pkgs else: pkgdict = {} if isinstance(pkglist, (str,unicode,_iraftask.IrafPkg)): pkglist = [ pkglist ] for p in pkglist: try: pthis = getPkg(p) if pthis.isLoaded(): pkgdict[pthis.getName()] = 1 else: _writeError('Package %s has not been loaded' % pthis.getName()) except KeyError, e: _writeError(str(e)) if not len(pkgdict): print('No packages to list') return # print each package separately keylist.sort() lastpkg = '' tlist = [] for tname in keylist: pkg, task = tname.split('.') tobj = _tasks[tname] if hidden or not tobj.isHidden(): if isinstance(tobj,_iraftask.IrafPkg): task = task + '/' elif isinstance(tobj,_iraftask.IrafPset): task = task + '@' if pkg == lastpkg: tlist.append(task) else: if len(tlist) and lastpkg in pkgdict: print(lastpkg + '/:') _irafutils.printCols(tlist) tlist = [task] lastpkg = pkg if len(tlist) and lastpkg in pkgdict: print(lastpkg + '/:') _irafutils.printCols(tlist) @handleRedirAndSaveKwds def listCurrent(n=1, hidden=0): """List IRAF tasks in current package (equivalent to '?' in the cl) If parameter n is specified, lists n most recent packages.""" if len(loadedPath): if n > len(loadedPath): n = len(loadedPath) plist = n*[None] for i in xrange(n): plist[i] = loadedPath[-1-i].getName() listTasks(plist,hidden=hidden) else: print('No IRAF tasks defined') @handleRedirAndSaveKwdsPlus def listVars(prefix="", equals="\t= "): """List IRAF variables""" keylist = getVarList() if len(keylist) == 0: print('No IRAF variables defined') else: keylist.sort() for word in keylist: print("%s%s%s%s" % (prefix, word, equals, envget(word))) @handleRedirAndSaveKwds def gripes(): """ Hide the system call - direct the user to support """ print("Please email your concern directly to support@stsci.edu") gripe = gripes @handleRedirAndSaveKwds def which(*args): """ Emulate the which function in IRAF. """ for arg in args: try: print(getTask(arg).getPkgname()) # or: getTask(arg).getPkgname()+"."+getTask(arg).getName() except _minmatch.AmbiguousKeyError, e: print(str(e)) except (KeyError, TypeError): if deftask(arg): print('language') # handle, e.g. 'which which', 'which cd' else: _writeError(arg+": task not found.") @handleRedirAndSaveKwds def whereis(*args): """ Emulate the whereis function in IRAF. """ for arg in args: matches = _mmtasks.getall(arg) if matches: matches.reverse() # this reverse isn't necessary - they arrive # in the right order, but CL seems to do this print(" ".join(matches)) else: _writeError(arg+": task not found.") @handleRedirAndSaveKwds def taskinfo(*args): ''' show information about task definitions taskinfo [ pattern(s) ] pattern is a glob pattern describing the package or task name that you are interested in. The output is a hierarchical view of the task definitions that match the input pattern. Each line shows the task name, the file name, pkgbinary and class. pkgbinary is a list of where you look for the file if it is not where you expect. class is the type of task definition from iraftask.py At this point, this is not exactly friendly for an end-user, but an SE could use it or ask the user to run it and send in the output. ''' for x in args : _iraftask.showtasklong( x ) # ----------------------------------------------------- # IRAF utility functions # ----------------------------------------------------- # these do not have extra keywords because they should not # be called as tasks def clParGet(paramname,pkg=None,native=1,mode=None,prompt=1): """Return value of IRAF parameter Parameter can be a cl task parameter, a package parameter for any loaded package, or a fully qualified (task.param) parameter from any known task. """ if pkg is None: pkg = loadedPath[-1] # if taskname is '_', use current package as task if paramname[:2] == "_.": paramname = pkg.getName() + paramname[1:] return pkg.getParam(paramname,native=native,mode=mode,prompt=prompt) def envget(var,default=None): """Get value of IRAF or OS environment variable""" try: return _varDict[var] except KeyError: try: return _os.environ[var] except KeyError: if default is not None: return default elif var == 'TERM': # Return a default value for TERM # TERM gets caught as it is found in the default # login.cl file setup by IRAF. print("Using default TERM value for session.") return 'xterm' else: raise KeyError("Undefined environment variable `%s'" % var) _tmpfileCounter = 0 def mktemp(root): """Make a temporary filename starting with root""" global _tmpfileCounter basename = root + `_os.getpid()` while 1: _tmpfileCounter = _tmpfileCounter + 1 if _tmpfileCounter <= 26: # use letters to start suffix = chr(ord("a")+_tmpfileCounter-1) else: # use numbers once we've used up letters suffix = "_" + `_tmpfileCounter-26` file = basename + suffix if not _os.path.exists(Expand(file)): return file _NullFileList = ["dev$null", "/dev/null"] _NullPath = None def isNullFile(s): """Returns true if this is the CL null file""" global _NullFileList, _NullPath if s in _NullFileList: return 1 sPath = Expand(s) if _NullPath is None: _NullPath = Expand(_NullFileList[0]) _NullFileList.append(_NullPath) if sPath == _NullPath: return 1 else: return 0 def substr(s,first,last): """Return sub-string using IRAF 1-based indexing""" if s == INDEF: return INDEF # If the first index is zero, always return a zero-length string if first == 0: return '' return s[first-1:last] def strlen(s): """Return length of string""" if s == INDEF: return INDEF return len(s) def isindef(s): """Returns true if argument is INDEF""" if s == INDEF: return 1 else: return 0 def stridx(test, s): """Return index of first occurrence of any of the characters in 'test' that are in 's' using IRAF 1-based indexing""" if INDEF in (s,test): return INDEF _pos2 = len(s) for _char in test: _pos = s.find(_char) if _pos != -1: _pos2 = min(_pos2, _pos) if _pos2 == len(s): return 0 else: return _pos2+1 def strldx(test, s): """Return index of last occurrence of any of the characters in 'test' that are in 's' using IRAF 1-based indexing""" if INDEF in (s,test): return INDEF _pos2 = -1 for _char in test: _pos = s.rfind(_char) if _pos != -1: _pos2 = max(_pos2, _pos) return _pos2+1 def strlwr(s): """Return string converted to lower case""" if s == INDEF: return INDEF return s.lower() def strupr(s): """Return string converted to upper case""" if s == INDEF: return INDEF return s.upper() def strstr(str1,str2): """Search for first occurrence of 'str1' in 'str2', returns index of the start of 'str1' or zero if not found. IRAF 1-based indexing""" if INDEF in (str1,str2): return INDEF return str2.find(str1)+1 def strlstr(str1,str2): """Search for last occurrence of 'str1' in 'str2', returns index of the start of 'str1' or zero if not found. IRAF 1-based indexing""" if INDEF in (str1, str2): return INDEF return str2.rfind(str1)+1 def trim(str, trimchars=None): """Trim any of the chars in 'trimchars' (default = whitesspace) from both ends of 'str'.""" if INDEF in (str, trimchars): return INDEF return str.strip(trimchars) def triml(str, trimchars=None): """Trim any of the chars in 'trimchars' (default = whitesspace) from the left side of 'str'.""" if INDEF in (str, trimchars): return INDEF return str.lstrip(trimchars) def trimr(str, trimchars=None): """Trim any of the chars in 'trimchars' (default = whitesspace) from the right side of 'str'.""" if INDEF in (str, trimchars): return INDEF return str.rstrip(trimchars) def frac(x): """Return fractional part of x""" if x == INDEF: return INDEF frac_part, int_part = _math.modf(x) return frac_part def real(x): """Return real/float representation of x""" if x == INDEF: return INDEF elif isinstance(x, (str,unicode)): x = x.strip() if x.find(':') >= 0: #...handle the special a:b:c case here... sign = 1 if x[0] in ["-", "+"]: if x[0] == "-": sign = -1. x = x[1:] m = _re.search(r"[^0-9:.]", x) if m: x = x[0:m.start()] f = map(float,x.split(":")) f = map(abs, f) return sign*clSexagesimal(*f) else: x = _re.sub("[EdD]", "e", x, count=1) m = _re.search(r"[^0-9.e+-]", x) if m: x = x[0:m.start()] return float(x) else: return float(x) def integer(x): """Return integer representation of x""" if x==INDEF: return INDEF elif isinstance(x, (str,unicode)): x = x.strip() i = 0 j = len(x) if x[0] in ["+", "-"]: i = 1 x = _re.sub("[EdD]", "e", x, count=1) m = _re.search(r"[^0-9.e+-]", x[i:]) if m: j = i + m.start() return int(float(x[:j])) else: return int(x) def mod(a, b): """Return a modulo b""" if INDEF in (a,b): return INDEF return (a % b) def nint(x): """Return nearest integer of x""" if x == INDEF: return INDEF return int(round(x)) _radixDigits = list(_string.digits+_string.ascii_uppercase) def radix(value, base=10, length=0): """Convert integer value to string expressed using given base If length is given, field is padded with leading zeros to reach length. Note that if value is negative, this routine returns the actual bit-pattern of the twos-complement integer (even for base 10) rather than a signed value. This is consistent with IRAF's behavior. """ if INDEF in (value,base,length): return INDEF if not (2 <= base <= 36): raise ValueError("base must be between 2 and 36 (inclusive)") ivalue = int(value) if ivalue == 0: # handle specially so don't have to worry about it below return "%0*d" % (length, ivalue) # convert to an unsigned long integer hexIvalue = hex(ivalue) # hex() can return a string for an int or a long isLong = hexIvalue[-1] == 'L' if not isLong: hexIvalue += 'L' lvalue = eval(hexIvalue) outdigits = [] while lvalue > 0 or lvalue < -1: lvalue, digit = divmod(lvalue, base) outdigits.append(int(digit)) outdigits = map(lambda index: _radixDigits[index], outdigits) # zero-pad if needed (automatically do so for negative numbers) if ivalue < 0: maxlen = 32 if isLong: maxlen = BITS_PER_LONG outdigits.extend((maxlen-len(outdigits))*["1"]) if length>len(outdigits): outdigits.extend((length-len(outdigits))*["0"]) outdigits.reverse() return ''.join(outdigits) def rad(value): """Convert arg in degrees to radians""" if value==INDEF: return INDEF else: return _math.radians(value) def deg(value): """Convert arg in radians to degrees""" if value==INDEF: return INDEF else: return _math.degrees(value) def sin(value): """Trigonometric sine function. Input in radians.""" if value==INDEF: return INDEF else: return _math.sin(value) def asin(value): """Trigonometric arc sine function. Result in radians.""" if value==INDEF: return INDEF else: return _math.asin(value) def cos(value): """Trigonometric cosine function. Input in radians.""" if value==INDEF: return INDEF else: return _math.cos(value) def acos(value): """Trigonometric arc cosine function. Result in radians.""" if value==INDEF: return INDEF else: return _math.acos(value) def tan(value): """Trigonometric tangent function. Input in radians.""" if value==INDEF: return INDEF else: return _math.tan(value) def atan2(x,y): """Trigonometric 2-argument arctangent function. Result in radians.""" if INDEF in (x,y): return INDEF else: return _math.atan2(x,y) def dsin(value): """Trigonometric sine function. Input in degrees.""" if value==INDEF: return INDEF else: return _math.sin(_math.radians(value)) def dasin(value): """Trigonometric arc sine function. Result in degrees.""" if value==INDEF: return INDEF else: return _math.degrees(_math.asin(value)) def dcos(value): """Trigonometric cosine function. Input in degrees.""" if value==INDEF: return INDEF else: return _math.cos(_math.radians(value)) def dacos(value): """Trigonometric arc cosine function. Result in degrees.""" if value==INDEF: return INDEF else: return _math.degrees(_math.acos(value)) def dtan(value): """Trigonometric tangent function. Input in degrees.""" if value==INDEF: return INDEF else: return _math.tan(_math.radians(value)) def datan2(x,y): """Trigonometric 2-argument arctangent function. Result in degrees.""" if INDEF in (x,y): return INDEF else: return _math.degrees(_math.atan2(x,y)) def exp(value): """Exponential function""" if value==INDEF: return INDEF else: return _math.exp(value) def log(value): """Natural log function""" if value==INDEF: return INDEF else: return _math.log(value) def log10(value): """Base 10 log function""" if value==INDEF: return INDEF else: return _math.log10(value) def sqrt(value): """Square root function""" if value==INDEF: return INDEF else: return _math.sqrt(value) def absvalue(value): """Absolute value function""" if value==INDEF: return INDEF else: return abs(value) def minimum(*args): """Minimum of list of arguments""" if INDEF in args: return INDEF else: return min(*args) def maximum(*args): """Maximum of list of arguments""" if INDEF in args: return INDEF else: return max(*args) def hypot(x,y): """Return the Euclidean distance, sqrt(x*x + y*y).""" if INDEF in (x,y): return INDEF else: return _math.hypot(x,y) def sign(value): """Sign of argument (-1 or 1)""" if value==INDEF: return INDEF if value>=0.0: return 1 else: return -1 def clNot(value): """Bitwise boolean NOT of an integer""" if value==INDEF: return INDEF return ~(int(value)) def clAnd(x,y): """Bitwise boolean AND of two integers""" if INDEF in (x,y): return INDEF return x&y def clOr(x,y): """Bitwise boolean OR of two integers""" if INDEF in (x,y): return INDEF return x|y def clXor(x,y): """Bitwise eXclusive OR of two integers""" if INDEF in (x,y): return INDEF return x^y def osfn(filename): """Convert IRAF virtual path name to OS pathname""" # Try to emulate the CL version closely: # # - expands IRAF virtual file names # - strips blanks around path components # - if no slashes or relative paths, return relative pathname # - otherwise return absolute pathname if filename == INDEF: return INDEF ename = Expand(filename) dlist = [s.strip() for s in ename.split(_os.sep)] if len(dlist)==1 and dlist[0] not in [_os.curdir,_os.pardir]: return dlist[0] # I use string.join instead of os.path.join here because # os.path.join("","") returns "" instead of "/" epath = _os.sep.join(dlist) fname = _os.path.abspath(epath) # append '/' if relative directory was at end or filename ends with '/' if fname[-1] != _os.sep and dlist[-1] in ['', _os.curdir,_os.pardir]: fname = fname + _os.sep return fname def clSexagesimal(d, m, s=0): """Convert d:m:s value to float""" return (d+(m+s/60.0)/60.0) def clDms(x,digits=1,seconds=1): """Convert float to d:m:s.s Number of decimal places on seconds is set by digits. If seconds is false, prints just d:m.m (omits seconds). """ if x<0: sign = '-' x = -x else: sign = '' if seconds: d = int(x) x = 60*(x-d) m = int(x) s = 60*(x-m) # round to avoid printing (e.g.) 60.0 seconds digits = max(digits, 0) if s+0.5/10**digits >= 60: s = 0.0 m = m+1 if seconds and m==60: m = 0 d = d+1 if digits==0: secform = "%02d" else: secform = "%%0%d.%df" % (digits+3, digits) if seconds: return ("%s%2d:%02d:"+secform) % (sign, d, m, s) else: return ("%s%02d:"+secform) % (sign, m, s) def defpar(paramname): """Returns true if parameter is defined""" if paramname == INDEF: return INDEF try: value = clParGet(paramname,prompt=0) return 1 except IrafError, e: # treat all errors (including ambiguous task and parameter names) # as a missing parameter return 0 def access(filename): """Returns true if file exists""" if filename == INDEF: return INDEF filename = _denode(filename) # Magic values that trigger special behavior magicValues = { "STDIN": 1, "STDOUT": 1, "STDERR": 1} return filename in magicValues or _os.path.exists(Expand(filename)) def fp_equal(x, y): """Floating point compare to within machine precision. This logic is taken directly from IRAF's fp_equald function.""" # Check the easy answers first if INDEF in (x,y): return INDEF if x==y: return True # We can't normalize zero, so handle the zero operand cases first. # Note that the case where 0 equals 0 is handled above. if x==0.0 or y==0.0: return False # Now normalize the operands and do an epsilon comparison normx, ex = _fp_norm(x) normy, ey = _fp_norm(y) # Here is an easy false check if ex != ey: return False # Finally compare the values x1 = 1.0 + abs(normx-normy) x2 = 1.0 + (32.0 * FP_EPSILON) return x1 <= x2 def _fp_norm(x): """Normalize a floating point number x to the value normx, in the range [1-10). expon is returned such that: x = normx * (10.0 ** expon) This logic is taken directly from IRAF's fp_normd function.""" # See FP_EPSILON description elsewhere. tol = FP_EPSILON * 10.0 absx = abs(x) expon = 0 if absx > 0: while absx < (1.0-tol): absx *= 10.0 expon -= 1 if absx == 0.0: # check for underflow to zero return 0, 0 while absx >= (10.0+tol): absx /= 10.0 expon += 1 if x < 0: normx = -absx else: normx = absx return normx, expon _denode_pat = _re.compile(r'[^/]*!') def _denode(filename): """Remove IRAF "node!" specification from filename""" mm = _denode_pat.match(filename) if mm is None: return filename else: return filename[len(mm.group()):] def _imextn(): """Returns list of image types and extensions The return value is (ktype, globlist) where ktype is the image kernel (oif, fxf, etc.) and globlist is a list of glob-style patterns that match extensions. """ # imextn environment variable has list (or use default) s = envget("imextn", "oif:imh fxf:fits,fit plf:pl qpf:qp stf:hhh,??h") fields = s.split() extlist = [] for f in fields: ilist = f.split(":") if len(ilist) != 2: raise IrafError("Illegal field `%s' in IRAF variable imextn" % f) exts = ilist[1].split(",") extlist.append((ilist[0], exts)) return extlist def _checkext(ext, extlist): """Returns image type if ext is in extlist, else returns None Assumes ext starts with a '.' (as returned by os.path.split) and that null extensions can't match. """ if not ext: return None ext = ext[1:] for ktype, elist in extlist: for pat in elist: if _fnmatch.fnmatch(ext, pat): return ktype return None def _searchext(root, extlist): """Returns image type if file root.ext is found (ext from extlist)""" for ktype, elist in extlist: for pat in elist: flist = _glob.glob(root + '.' + pat) if flist: return ktype return None def imaccess(filename): """Returns true if image matching name exists and is readable""" if filename == INDEF: return INDEF # See if the filename contains any wildcard characters. # First strip any extension or section specification. tfilename = filename i = tfilename.find('[') if i>=0: tfilename = filename[:i] if '*' in tfilename or '?' in tfilename: return 0 # Edge case not handled below: if filename.find('[]') != -1: return 0 # If we get this far, use imheader to test existence. # Any error output is taken to mean failure. sout = _StringIO.StringIO() serr = _StringIO.StringIO() import pyraf.iraf pyraf.iraf.imhead(filename, Stdout=sout, Stderr=serr) errstr = serr.getvalue().lower() outstr = sout.getvalue().lower() if errstr: # Handle exceptional cases: # 1) ambiguous files (imaccess accepts this) # 2) non specification of extension # for multi-extension fits files # (imaccess doesn't require) # This approach, while adaptable, is brittle in its dependency on # IRAF error strings if ((errstr.find('must specify which fits extension') >= 0) or (errstr.find('ambiguous')) >= 0): return 1 else: return 0 elif outstr: # If the filename string is blank(s), imhead writes "no images found" # to Stdout if (outstr.find('no images found') >= 0): return 0 else: return 1 def defvar(varname): """Returns true if CL variable is defined""" if varname == INDEF: return INDEF return varname in _varDict or varname in _os.environ def deftask(taskname): """Returns true if CL task is defined""" if taskname == INDEF: return INDEF try: import pyraf.iraf t = getattr(pyraf.iraf, taskname) return 1 except AttributeError, e: # treat all errors (including ambiguous task names) as a missing task return 0 def defpac(pkgname): """Returns true if CL package is defined and loaded""" if pkgname == INDEF: return INDEF try: t = getPkg(pkgname) return t.isLoaded() except KeyError, e: # treat all errors (including ambiguous package names) as a missing pkg return 0 def curpack(): """Returns name of current CL package""" if loadedPath: return loadedPath[-1].getName() else: return "" def curPkgbinary(): """Returns name pkgbinary directory for current CL package""" if loadedPath: return loadedPath[-1].getPkgbinary() else: return "" # utility functions for boolean conversions def bool2str(value): """Convert IRAF boolean value to a string""" if value in [None, INDEF]: return "INDEF" elif value: return "yes" else: return "no" def boolean(value): """Convert Python native types (string, int, float) to IRAF boolean Accepts integer/float values 0,1 or string 'no','yes' Also allows INDEF as value, or existing IRAF boolean type. """ if value in [0,1]: return value elif value in (no, yes): return int(value) elif value in [INDEF, "", None]: return INDEF if isinstance(value, (str,unicode)): v2 = _irafutils.stripQuotes(value.strip()) if v2 == "INDEF": return INDEF ff = v2.lower() if ff == "no": return 0 elif ff == "yes": return 1 elif isinstance(value,float): # try converting to integer try: ival = int(value) if (ival == value) and (ival == 0 or ival == 1): return ival except (ValueError, OverflowError): pass raise ValueError("Illegal boolean value %s" % `value`) # ----------------------------------------------------- # scan functions # Abandon all hope, ye who enter here # ----------------------------------------------------- _nscan = 0 def fscan(theLocals, line, *namelist, **kw): """fscan function sets parameters from a string or list parameter Uses local dictionary (passed as first argument) to set variables specified by list of following names. (This is a bit messy, but it is by far the cleanest approach I've thought of. I'm literally using call-by-name for these variables.) Accepts an additional keyword argument strconv with names of conversion functions for each argument in namelist. Returns number of arguments set to new values, which may be fewer than the number of variables if an unexpected character is encountered in 'line'. If there are too few space-delimited arguments on the input line, it does not set all the arguments. Returns EOF on end-of-file. """ # get the value of the line (which may be a variable, string literal, # expression, or an IRAF list parameter) global _nscan try: import pyraf.iraf line = eval(line, {'iraf': pyraf.iraf}, theLocals) except EOFError: _weirdEOF(theLocals, namelist) _nscan = 0 return EOF f = line.split() n = min(len(f),len(namelist)) # a tricky thing -- null input is OK if the first variable is # a struct if n==0 and namelist and _isStruct(theLocals, namelist[0]): f = [''] n = 1 if 'strconv' in kw: strconv = kw['strconv'] del kw['strconv'] else: strconv = n*[None] if len(kw): raise TypeError('unexpected keyword argument: ' + `kw.keys()`) n_actual = 0 # this will be the actual number of values converted for i in range(n): # even messier: special handling for struct type variables, which # consume the entire remaining string if _isStruct(theLocals, namelist[i]): if i < len(namelist)-1: raise TypeError("Struct type param `%s' must be the final" " argument to scan" % namelist[i]) # ultramessy -- struct needs rest of line with embedded whitespace if i==0: iend = 0 else: # construct a regular expression that matches the line so far pat = [r'\s*']*(2*i) for j in range(i): pat[2*j+1] = f[j] # a single following whitespace character also gets removed # (don't blame me, this is how IRAF does it!) pat.append(r'\s') pat = ''.join(pat) mm = _re.match(pat, line) if mm is None: raise RuntimeError("Bug: line '%s' pattern '%s' failed" % (line, pat)) iend = mm.end() if line[-1:] == '\n': cmd = namelist[i] + ' = ' + `line[iend:-1]` else: cmd = namelist[i] + ' = ' + `line[iend:]` elif strconv[i]: cmd = namelist[i] + ' = ' + strconv[i] + '(' + `f[i]` + ')' else: cmd = namelist[i] + ' = ' + `f[i]` try: exec(cmd, theLocals) n_actual += 1 except ValueError: break _nscan = n_actual return n_actual def fscanf(theLocals, line, format, *namelist, **kw): """fscanf function sets parameters from a string/list parameter with format Implementation is similar to fscan but is a bit simpler because special struct handling is not needed. Does not allow strconv keyword. Returns number of arguments set to new values, which may be fewer than the number of variables if an unexpected character is encountered in 'line'. If there are too few space-delimited arguments on the input line, it does not set all the arguments. Returns EOF on end-of-file. """ # get the value of the line (which may be a variable, string literal, # expression, or an IRAF list parameter) global _nscan try: import pyraf.iraf line = eval(line, {'iraf': pyraf.iraf}, theLocals) # format also needs to be evaluated format = eval(format, theLocals) except EOFError: _weirdEOF(theLocals, namelist) _nscan = 0 return EOF if sscanf == None: raise RuntimeError("fscanf is not supported on this platform") f = sscanf.sscanf(line, format) n = min(len(f),len(namelist)) # if list is null, add a null string # ugly but should be right most of the time if n==0 and namelist: f = [''] n = 1 if len(kw): raise TypeError('unexpected keyword argument: ' + `kw.keys()`) n_actual = 0 # this will be the actual number of values converted for i in range(n): cmd = namelist[i] + ' = ' + `f[i]` try: exec(cmd, theLocals) n_actual += 1 except ValueError: break _nscan = n_actual return n_actual def _weirdEOF(theLocals, namelist): # Replicate a weird IRAF behavior -- if the argument list # consists of a single struct-type variable, and it does not # currently have a defined value, set it to the null string. # (I warned you to abandon hope!) if namelist and _isStruct(theLocals, namelist[0], checklegal=1): if len(namelist) > 1: raise TypeError("Struct type param `%s' must be the final" " argument to scan" % namelist[0]) # it is an undefined struct, so set it to null string cmd = namelist[0] + ' = ""' exec(cmd, theLocals) def _isStruct(theLocals, name, checklegal=0): """Returns true if the variable `name' is of type struct If checklegal is true, returns true only if variable is struct and does not currently have a legal value. """ c = name.split('.') if len(c)>1: # must get the parameter object, not the value c[-1] = 'getParObject(%s)' % `c[-1]` fname = '.'.join(c) try: par = eval(fname, theLocals) except KeyboardInterrupt: raise except: # assume all failures mean this is not an IrafPar return 0 if isinstance(par, _irafpar.IrafPar) and par.type == 'struct': if checklegal: return (not par.isLegal()) else: return 1 else: return 0 def scan(theLocals, *namelist, **kw): """Scan function sets parameters from line read from stdin This can be used either as a function or as a task (it accepts redirection and the _save keyword.) """ # handle redirection and save keywords # other keywords are passed on to fscan redirKW, closeFHList = redirProcess(kw) if '_save' in kw: del kw['_save'] resetList = redirApply(redirKW) try: line = _irafutils.tkreadline() # null line means EOF if line == "": _weirdEOF(theLocals, namelist) global _nscan _nscan = 0 return EOF else: args = (theLocals, repr(line),) + namelist return fscan(*args, **kw) except Exception, ex: print('iraf.scan exception: '+str(ex)) finally: # note return value not used here rv = redirReset(resetList, closeFHList) def scanf(theLocals, format, *namelist, **kw): """Formatted scan function sets parameters from line read from stdin This can be used either as a function or as a task (it accepts redirection and the _save keyword.) """ # handle redirection and save keywords # other keywords are passed on to fscan redirKW, closeFHList = redirProcess(kw) if '_save' in kw: del kw['_save'] resetList = redirApply(redirKW) try: line = _irafutils.tkreadline() # null line means EOF if line == "": _weirdEOF(theLocals, namelist) global _nscan _nscan = 0 return EOF else: args = (theLocals, repr(line), format,) + namelist return fscanf(*args, **kw) except Exception, ex: print('iraf.scanf exception: '+str(ex)) finally: # note return value not used here rv = redirReset(resetList, closeFHList) def nscan(): """Return number of items read in last scan function""" global _nscan return _nscan # ----------------------------------------------------- # IRAF utility procedures # ----------------------------------------------------- # these have extra keywords (redirection, _save) because they can # be called as tasks @handleRedirAndSaveKwdsPlus def set(*args, **kw): """Set IRAF environment variables""" if len(args) == 0: if len(kw) != 0: # normal case is only keyword,value pairs msg = [] for keyword, value in kw.items(): keyword = _irafutils.untranslateName(keyword) svalue = str(value) if keyword == "erract": irafecl.erract.adjust(svalue) else: # add keyword:svalue to the dict, but first check for '#' if svalue.find('#') > 0 and svalue.find("'") < 0 and \ svalue.find('"') < 0: # this can happen when translating .cl scripts with # vars with sequential commented-out continuation lines svalue = svalue[0:svalue.find('#')] _varDict[keyword] = svalue msg.append("set %s=%s\n" % (keyword, svalue)) _irafexecute.processCache.setenv("".join(msg)) else: # set with no arguments lists all variables (using same format # as IRAF) listVars(" ", "=") else: # The only other case allowed is the peculiar syntax # 'set @filename', which only gets used in the zzsetenv.def file, # where it reads extern.pkg. That file also gets read (in full cl # mode) by clpackage.cl. I get errors if I read this during # zzsetenv.def, so just ignore it here... # # Flag any other syntax as an error. if len(args) != 1 or len(kw) != 0 or \ (not isinstance(args[0],(str,unicode))) or args[0][:1] != '@': raise SyntaxError("set requires name=value pairs") # currently do not distinguish set from reset # this will change when keep/bye/unloading are implemented reset = set @handleRedirAndSaveKwds def show(*args): """Print value of IRAF or OS environment variables""" if len(args) and args[0].startswith("erract"): print(irafecl.erract.states()) else: if args: for arg in args: print(envget(arg)) else: # print them all listVars(" ", "=") @handleRedirAndSaveKwds def unset(*args): """Unset IRAF environment variables. This is not a standard IRAF task, but it is obviously useful. It makes the resulting variables undefined. It silently ignores variables that are not defined. It does not change the os environment variables. """ for arg in args: if arg in _varDict: del _varDict[arg] @handleRedirAndSaveKwds def time(): """Print current time and date""" print(_time.strftime('%a %H:%M:%S %d-%b-%Y')) # Note - we really should not give this a default (should require an int), # because why run "sleep 0"?, but some legacy .cl scripts call it that way. @handleRedirAndSaveKwds def sleep(seconds=0): """Sleep for specified time""" _time.sleep(float(seconds)) def beep(**kw): """Beep to terminal (even if output is redirected)""" # just ignore keywords _sys.__stdout__.write("") _sys.__stdout__.flush() def clOscmd(s, **kw): """Execute a system-dependent command in the shell, returning status""" # handle redirection and save keywords redirKW, closeFHList = redirProcess(kw) if '_save' in kw: del kw['_save'] if len(kw): raise TypeError('unexpected keyword argument: ' + `kw.keys()`) resetList = redirApply(redirKW) try: # if first character of s is '!' then force to Bourne shell if s[:1] == '!': shell = "/bin/sh" s = s[1:] else: # otherwise use default shell shell=None # ignore null commands if not s: return 0 # use subshell to execute command so wildcards, etc. are handled status = _subproc.subshellRedir(s, shell=shell) return status finally: rv = redirReset(resetList, closeFHList) return rv _sttyArgs = _minmatch.MinMatchDict({ 'terminal': None, 'baud': 9600, 'ncols': 80, 'nlines': 24, 'show': no, 'all': no, 'reset': no, 'resize': no, 'clear': no, 'ucasein': no, 'ucaseout': no, 'login': None, 'logio': None, 'logout': None, 'playback': None, 'verify': no, 'delay': 500, }) @handleRedirAndSaveKwdsPlus def stty(terminal=None, **kw): """IRAF stty command (mainly not implemented)""" expkw = _sttyArgs.copy() if terminal is not None: expkw['terminal'] = terminal for key, item in kw.items(): if key in _sttyArgs: expkw[key] = item else: raise TypeError('unexpected keyword argument: '+key) if terminal is None and len(kw) == 0: # will need default values for the next step; try _wutil for them dftNcol = '80' dftNlin = '24' try: if _sys.stdout.isatty(): nlines,ncols = _wutil.getTermWindowSize() dftNcol = str(ncols) dftNlin = str(nlines) except: pass # No error message here - may not always be available # no args: print terminal type and size print('%s ncols=%s nlines=%s' % (envget('terminal','undefined'), envget('ttyncols',dftNcol), envget('ttynlines',dftNlin))) elif expkw['resize'] or expkw['terminal'] == "resize": # resize: sets CL env parameters giving screen size; show errors if _sys.stdout.isatty(): nlines,ncols = _wutil.getTermWindowSize() set(ttyncols=str(ncols), ttynlines=str(nlines)) elif expkw['terminal']: set(terminal=expkw['terminal']) # They are setting the terminal type. Let's at least try to # get the dimensions if not given. This is more than the CL does. if (not 'nlines' in kw) and (not 'ncols' in kw) and \ _sys.stdout.isatty(): try: nlines,ncols = _wutil.getTermWindowSize() set(ttyncols=str(ncols), ttynlines=str(nlines)) except: pass # No error msg here - may not always be available elif expkw['playback'] is not None: _writeError("stty playback not implemented") @handleRedirAndSaveKwds def eparam(*args): """Edit parameters for tasks. Starts up epar GUI.""" for taskname in args: try: # maybe it is an IRAF task taskname.eParam() except AttributeError: try: # maybe it is an IRAF task name getTask(taskname).eParam() except (KeyError, TypeError): try: # maybe it is a task which uses .cfg files _wrapTeal(taskname) except _teal.cfgpars.NoCfgFileError: _writeError('Warning: Could not find task "'+taskname+'"') def _wrapTeal(taskname): """ Wrap the call to TEAL. Try to use focus switching here. """ # place focus on gui oldFoc = _wutil.getFocalWindowID() _wutil.forceFocusToNewWindow() # pop up TEAL x = 0 try: # use simple-auto-close mode (like EPAR) by no return dict x = _teal.teal(taskname, returnAs="status", errorsToTerm=True, strict=False, autoClose=True) # put focus back on terminal, even if there is an exception finally: # Turns out, for the majority of TEAL-enabled tasks, users don't like # having the focus jump back to the terminal for them (especially if # it is a long-running task) after executing, so only move focus # back if they didn't execute if x < 1: _wutil.setFocusTo(oldFoc) @handleRedirAndSaveKwds def tparam(*args): """Edit parameters for tasks. Starts up epar GUI.""" for taskname in args: try: taskname.tParam() except AttributeError: # try: getTask(taskname).tParam() # except (KeyError, TypeError): # _writeError("Warning: Could not find task %s for tpar\n" % # taskname) @handleRedirAndSaveKwds def lparam(*args): """List parameters for tasks""" for taskname in args: try: taskname.lParam() except AttributeError: try: getTask(taskname).lParam() except (KeyError, TypeError): _writeError("Warning: Could not find task %s for lpar\n" % taskname) @handleRedirAndSaveKwdsPlus def dparam(*args, **kw): """Dump parameters for task in executable form""" # only keyword: pyraf-specific 'cl=' used to specify CL or Python syntax cl = 1 if 'cl' in kw: cl = kw['cl'] del kw['cl'] if len(kw): raise TypeError('unexpected keyword argument: ' + `kw.keys()`) for taskname in args: try: taskname.dParam(cl=cl) except AttributeError: try: getTask(taskname).dParam(cl=cl) except (KeyError, TypeError): _writeError("Warning: Could not find task %s for dpar\n" % taskname) @handleRedirAndSaveKwds def update(*args): """Update task parameters on disk""" for taskname in args: try: getTask(taskname).saveParList() except KeyError, e: _writeError("Warning: Could not find task %s for update" % taskname) @handleRedirAndSaveKwdsPlus def unlearn(*args, **kw): """Unlearn task parameters -- restore to defaults""" force = False if 'force' in kw: force = kw['force'] in (True, '+', 'yes') del kw['force'] if len(kw): raise TypeError('unexpected keyword argument: ' + `kw.keys()`) for taskname in args: try: # maybe it is an IRAF task name getTask(taskname).unlearn() except KeyError, e: try: # maybe it is a task which uses .cfg files ans = _teal.unlearn(taskname, deleteAll=force) if ans != 0: _writeError('Error: multiple user-owned files found'+ \ ' to unlearn for task "'+taskname+ \ '".\nNone were deleted. Please review and move/'+ \ 'delete these files:\n\n\t'+\ '\n\t'.join(ans)+ \ '\n\nor type "unlearn '+taskname+' force=yes"') except _teal.cfgpars.NoCfgFileError: _writeError("Warning: Could not find task %s to unlearn" % taskname) @handleRedirAndSaveKwdsPlus def teal(taskArg, **kw): """ Synonym for epar. Open the TEAL GUI but keep logic in eparam. There is no return dict.""" eparam(taskArg, **kw) @handleRedirAndSaveKwds def edit(*args): """Edit text files""" editor = envget('editor') margs = map(Expand, args) _os.system(' '.join([editor,]+margs)) _clearString = None @handleRedirAndSaveKwds def clear(*args): """Clear screen if output is terminal""" global _clearString if not _os.path.exists('/usr/bin/tput'): _clearString = '' if _clearString is None: # get the clear command by running system clear fh = _StringIO.StringIO() try: clOscmd('/usr/bin/tput clear', Stdout=fh) _clearString = fh.getvalue() except SubprocessError: _clearString = "" fh.close() del fh if _sys.stdout == _sys.__stdout__: _sys.stdout.write(_clearString) _sys.stdout.flush() @handleRedirAndSaveKwds def flprcache(*args): """Flush process cache. Takes optional list of tasknames.""" _irafexecute.processCache.flush(*args) if Verbose>0: print("Flushed process cache") @handleRedirAndSaveKwds def prcacheOff(): """Disable process cache. No process cache will be employed for the rest of this session.""" _irafexecute.processCache.setSize(0) if Verbose>0: print("Disabled process cache") @handleRedirAndSaveKwds def prcacheOn(): """Re-enable process cache. A process cache will again be employed for the rest of this session. This may be useful after prcacheOff().""" _irafexecute.processCache.resetSize() if Verbose>0: print("Enabled process cache") @handleRedirAndSaveKwds def prcache(*args): """Print process cache. If args are given, locks tasks into cache.""" if args: _irafexecute.processCache.lock(*args) else: _irafexecute.processCache.list() @handleRedirAndSaveKwds def gflush(): """Flush any buffered graphics output.""" gki.kernel.flush() @handleRedirAndSaveKwdsPlus def pyexecute(filename, **kw): """Execute python code in filename (which may include IRAF path). This is callable from within CL scripts. There is a corresponding pyexecute.cl task that runs outside the PyRAF environment and just prints a warning. """ # these keyword parameters are relevant only outside PyRAF for keyword in ['_save', 'verbose', 'tasknames']: if keyword in kw: del kw[keyword] # get package info if 'PkgName' in kw: pkgname = kw['PkgName'] del kw['PkgName'] else: pkgname = curpack() if 'PkgBinary' in kw: pkgbinary = kw['PkgBinary'] del kw['PkgBinary'] else: pkgbinary = curPkgbinary() # fix illegal package names spkgname = pkgname.replace('.', '_') if spkgname != pkgname: _writeError("Warning: `.' illegal in task name, changing " "`%s' to `%s'" % (pkgname, spkgname)) pkgname = spkgname if len(kw): raise TypeError('unexpected keyword argument: ' + `kw.keys()`) # execute code in a new namespace (including PkgName, PkgBinary) efilename = Expand(filename) namespace = {'PkgName': pkgname, 'PkgBinary': pkgbinary, '__file__': efilename} execfile(efilename, namespace) # history routines @handleRedirAndSaveKwds def history(n=20): """Print history. Does not replicate the IRAF behavior of changing default number of lines to print. (That seems fairly useless to me.) """ # Seems like there ought to be a way to do this using readline, but I have # not been able to figure out any readline command that lists the history import __main__ try: n = abs(int(n)) __main__._pycmdline.printHistory(n) except (NameError,AttributeError): pass @handleRedirAndSaveKwds def ehistory(*args): """Dummy history function""" print('ehistory command not required: Use arrow keys to recall commands') print('or ctrl-R to search for a string in the command history.') # dummy routines (must allow *args and **kw) @handleRedirAndSaveKwdsPlus def clNoBackground(*args, **kw): """Dummy background function""" _writeError('Background jobs not implemented') jobs = service = kill = wait = clNoBackground # dummy (do-nothing) routines def clDummy(*args, **kw): """Dummy do-nothing function""" # just ignore keywords and arguments pass bye = keep = logout = clbye = cache = language = clDummy # unimplemented but no exception raised (and no message # printed if not in verbose mode) def _notImplemented(cmd): """Dummy unimplemented function""" if Verbose>0: _writeError("The %s task has not been implemented" % cmd) @handleRedirAndSaveKwdsPlus def putlog(*args, **kw): _notImplemented('putlog') @handleRedirAndSaveKwdsPlus def clAllocate(*args, **kw): _notImplemented('_allocate') @handleRedirAndSaveKwdsPlus def clDeallocate(*args, **kw): _notImplemented('_deallocate') @handleRedirAndSaveKwdsPlus def clDevstatus(*args, **kw): _notImplemented('_devstatus') # unimplemented -- raise exception def fprint(*args, **kw): """Error unimplemented function""" # The fprint task is never used in CL scripts, as far as I can tell raise IrafError("The fprint task has not been implemented") # various helper functions @handleRedirAndSaveKwds def pkgHelp(pkgname=None): """Give help on package (equivalent to CL '? [taskname]')""" if pkgname is None: listCurrent() else: listTasks(pkgname) @handleRedirAndSaveKwds def allPkgHelp(): """Give help on all packages (equivalent to CL '??')""" listTasks() def _clProcedure(*args, **kw): """Core function for the CL task Gets passed to IrafPythonTask as function argument. Note I/O redirection has already been set up before calling this. """ # just ignore the arguments -- they are only used through .par list # if input is not redirected, don't do anything if _sys.stdin == _sys.__stdin__: return # initialize environment theLocals = {} exec('from pyraf import iraf', theLocals) exec('from pyraf.irafpar import makeIrafPar', theLocals) exec('from stsci.tools.irafglobals import *', theLocals) exec('from pyraf.pyrafglobals import *', theLocals) # feed the input to clExecute # redirect input to sys.__stdin__ after reading the CL script from sys.stdin clExecute(_sys.stdin.read(), locals=theLocals, Stdin=_sys.__stdin__) def clProcedure(input=None, mode="", DOLLARnargs=0, **kw): """Run CL commands from a file (cl < input) -- OBSOLETE This is obsolete, replaced by the IrafPythonTask version of the cl, using above _clProcedure function. It is being retained only for backward compatibility since translated versions of CL scripts could use it. New versions will not use it. Also, this cannot use handleRedirAndSaveKwds. """ # handle redirection and save keywords redirKW, closeFHList = redirProcess(kw) if '_save' in kw: del kw['_save'] if len(kw): raise TypeError('unexpected keyword argument: ' + `kw.keys()`) # get the input if 'stdin' in redirKW: stdin = redirKW['stdin'] del redirKW['stdin'] if hasattr(stdin,'name'): filename = stdin.name.split('.')[0] else: filename = 'tmp' elif input is not None: if isinstance(input,(str,unicode)): # input is a string -- stick it in a StringIO buffer stdin = _StringIO.StringIO(input) filename = input elif hasattr(input,'read'): # input is a filehandle stdin = input if hasattr(stdin,'name'): filename = stdin.name.split('.')[0] else: filename = 'tmp' else: raise TypeError("Input must be a string or input filehandle") else: # CL without input does nothing return # apply the I/O redirections resetList = redirApply(redirKW) # create and run the task try: # create task object with no package newtask = _iraftask.IrafCLTask('', filename, '', stdin, '', '') newtask.run() finally: # reset the I/O redirections rv = redirReset(resetList, closeFHList) return rv @handleRedirAndSaveKwds def hidetask(*args): """Hide the CL task in package listings""" for taskname in args: try: getTask(taskname).setHidden() except KeyError, e: _writeError("Warning: Could not find task %s to hide" % taskname) # pattern matching single task name, possibly with $ prefix and/or # .pkg or .tb suffix # also matches optional trailing comma and whitespace optional_whitespace = r'[ \t]*' taskname = r'(?:' + r'(?P\$?)' + \ r'(?P[a-zA-Z_][a-zA-Z0-9_]*)' + \ r'(?P\.(?:pkg|tb))?' + \ r',?' + optional_whitespace + r')' _re_taskname = _re.compile(taskname) del taskname, optional_whitespace @handleRedirAndSaveKwdsPlus def task(*args, **kw): """Define IRAF tasks""" redefine = 0 iscmdstring = False if 'Redefine' in kw: redefine = kw['Redefine'] del kw['Redefine'] # get package info if 'PkgName' in kw: pkgname = kw['PkgName'] del kw['PkgName'] else: pkgname = curpack() if 'PkgBinary' in kw: pkgbinary = kw['PkgBinary'] del kw['PkgBinary'] else: pkgbinary = curPkgbinary() if 'IsCmdString' in kw: iscmdstring = kw['IsCmdString'] del kw['IsCmdString'] # fix illegal package names spkgname = pkgname.replace('.', '_') if spkgname != pkgname: _writeError("Warning: `.' illegal in task name, changing " "`%s' to `%s'" % (pkgname, spkgname)) pkgname = spkgname # get the task name if len(kw) > 1: raise SyntaxError("More than one `=' in task definition") elif len(kw) < 1: raise SyntaxError("Must be at least one `=' in task definition") s = kw.keys()[0] value = kw[s] # To handle when actual CL code is given, not a file name, we will # replace the code with the name of the tmp file that we write it to. if iscmdstring: # write it to a temp file in the home$ dir, then use filename (fd, tmpCl) = _tempfile.mkstemp(suffix=".cl", prefix=str(s)+'_', dir=userIrafHome, text=True) _os.close(fd) # Check basename for invalid chars as far as python func. names go. # yes this goes against the use of mkstemp from a purity point # of view but it can't much be helped. verify later is it unique orig_tmpCl = tmpCl tmpClPath, tmpClFname = _os.path.split(tmpCl) tmpClFname = tmpClFname.replace('-','_') tmpClFname = tmpClFname.replace('+','_') tmpCl = _os.path.join(tmpClPath, tmpClFname) assert tmpCl == orig_tmpCl or not _os.path.exists(tmpCl), \ 'Abused mkstemp usage in some way; fname: '+tmpCl # write inline code to .cl file; len(kw) is checked below f = open(tmpCl, 'w') f.write(value+'\n') # Add text at end to auto-delete this temp file f.write('#\n# this last section automatically added\n') f.write('delete '+tmpCl+' verify-\n') f.close() # exchange for tmp .cl file name value = tmpCl # untranslateName s = _irafutils.untranslateName(s) args = args + (s,) # assign value to each task in the list global _re_taskname for tlist in args: mtl = _re_taskname.match(tlist) if not mtl: raise SyntaxError("Illegal task name `%s'" % (tlist,)) name = mtl.group('taskname') prefix = mtl.group('taskprefix') suffix = mtl.group('tasksuffix') newtask = IrafTaskFactory(prefix,name,suffix,value, pkgname,pkgbinary,redefine=redefine) def redefine(*args, **kw): """Redefine an existing task""" kw['Redefine'] = 1 task(*args, **kw) def package(pkgname=None, bin=None, PkgName='', PkgBinary='', **kw): """Define IRAF package, returning tuple with new package name and binary PkgName, PkgBinary are old default values. If Stdout=1 is specified, returns output as string array (normal task behavior) instead of returning PkgName, PkgBinary. This inconsistency is necessary to replicate the inconsistent behavior of the package command in IRAF. """ module = irafecl.getTaskModule() # handle redirection and save keywords redirKW, closeFHList = redirProcess(kw) if '_save' in kw: del kw['_save'] if len(kw): raise TypeError('unexpected keyword argument: ' + `kw.keys()`) resetList = redirApply(redirKW) try: if pkgname is None: # no argument: list all loaded packages in search order printed = {} lp = loadedPath[:] lp.reverse() for pkg in lp: pkgname = pkg.getName() if not pkgname in printed: printed[pkgname] = 1 print(' %s' % pkgname) rv1 = (PkgName, PkgBinary) else: spkgname = pkgname.replace('.', '_') # remove trailing comma if spkgname[-1:] == ",": spkgname = spkgname[:-1] if (spkgname != pkgname) and (Verbose > 0): _writeError("Warning: illegal characters in task name, " "changing `%s' to `%s'" % (pkgname, spkgname)) pkgname = spkgname # is the package defined? # if not, is there a CL task by this name? # otherwise there is an error pkg = getPkg(pkgname, found=1) if pkg is None: pkg = getTask(pkgname, found=1) if pkg is None or not isinstance(pkg,_iraftask.IrafCLTask) or \ pkg.getName() != pkgname: raise KeyError("Package `%s' not defined" % pkgname) # Hack city -- there is a CL task with the package name, but it was # not defined to be a package. Convert it to an IrafPkg object. module.mutateCLTask2Pkg(pkg) # We must be currently loading this package if we encountered # its package statement (XXX can I confirm that?). # Add it to the lists of loaded packages (this usually # is done by the IrafPkg run method, but we are executing # as an IrafCLTask instead.) _addPkg(pkg) loadedPath.append(pkg) addLoaded(pkg) if Verbose>0: _writeError("Warning: CL task `%s' apparently is " "a package" % pkgname) # Make sure that this is the current package, even # if another package was loaded in the package script # but before the package statement if loadedPath[-1] is not pkg: loadedPath.append(pkg) rv1 = (pkgname, bin or PkgBinary) finally: rv = redirReset(resetList, closeFHList) # return output as array of strings if not None, else return name,bin return rv or rv1 @handleRedirAndSaveKwds def clPrint(*args): """CL print command -- emulates CL spacing and uses redirection keywords""" nargs = len(args) for n, arg in enumerate(args, start=1): print(arg, end='') # add separator space, except after string arguments and at the end if n < nargs and not isinstance(arg, (str, unicode)): print(end=' ') print() # printf format conversion utilities def _quietConv(w, d, c, args, i): """Format codes that are quietly converted to %s""" return "%%%ss" % w def _boolConv(w, d, c, args, i): """Boolean gets converted to upper case before printing""" args[i] = str(args[i]).upper() return "%%%ss" % w def _badConv(w, d, c, args, i): """Format codes that are converted to %s with warning""" _writeError("Warning: printf cannot handle format '%%%s', " "using '%%%ss' instead\n" % (w+d+c, w)) return "%%%ss" % w def _hConv(w, d, c, args, i): """Handle %h %m %H %M dd:mm:ss.s formats""" if i-?\d*)(?P(?:\.\d*)?)(?P[a-zHM])") # dispatch table for format conversions _fDispatch = {} for b in _string.ascii_lowercase: _fDispatch[b] = None # formats that get quietly converted to uppercase and translated to %s badList = ["b"] for b in badList: _fDispatch[b] = _boolConv # formats that get translated to %s with warning badList = ["t", "z"] for b in badList: _fDispatch[b] = _badConv # other cases _fDispatch["r"] = _rConv _fDispatch["w"] = _wConv _fDispatch["h"] = _hConv _fDispatch["m"] = _hConv _fDispatch["H"] = _hConv _fDispatch["M"] = _hConv del badList, b @handleRedirAndSaveKwds def printf(format, *args): """Formatted print function""" # make argument list mutable args = list(args) newformat = [] # find all format strings and translate them (and arg) if needed iend = 0 mm = _reFormat.search(format, iend) i = 0 while mm: oend = iend istart = mm.start() iend = mm.end() # append the stuff preceding the format newformat.append(format[oend:istart]) c = mm.group('c') # special handling for INDEF arguments if args[i] == INDEF and c != 'w': # INDEF always gets printed as string except for '%w' format f = _quietConv else: # dispatch function for this format type f = _fDispatch[c] if f is None: # append the format newformat.append(mm.group()) else: w = mm.group('w') d = mm.group('d') # ugly special case for 'r' format if c == 'r': c = format[iend-1:iend+1] iend = iend+1 # append the modified format newformat.append(f(w, d, c, args, i)) mm = _reFormat.search(format, iend) i = i+1 newformat.append(format[iend:]) format = ''.join(newformat) # finally ready to print try: _sys.stdout.write(format % tuple(args)) _sys.stdout.flush() except ValueError, e: raise IrafError(str(e)) except TypeError, e: raise IrafError('%s\nFormat/datatype mismatch in printf ' '(format is %s)' % (str(e), `format`)) # _backDir is previous working directory _backDir = None @handleRedirAndSaveKwds def pwd(): """Print working directory""" print(_os.getcwd()) @handleRedirAndSaveKwds def chdir(directory=None): """Change working directory""" global _backDir try: _newBack = _os.getcwd() except OSError: # OSError for getcwd() means current directory does not exist _newBack = _backDir if directory is None: # use startup directory as home if argument is omitted directory = userWorkingHome if not isinstance(directory, (str,unicode)): raise IrafError("Illegal non-string value for directory:"+ \ +repr(directory)) if Verbose > 2: print(('chdir to: '+str(directory))) # Check for (1) local directory and (2) iraf variable # when given an argument like 'dev'. In IRAF 'cd dev' is # the same as 'cd ./dev' if there is a local directory named # dev but is equivalent to 'cd dev$' if there is no local # directory. try: edir = Expand(directory) _os.chdir(edir) _backDir = _newBack _irafexecute.processCache.setenv('chdir %s\n' % edir) except (IrafError, OSError): try: edir = Expand(directory + '$') _os.chdir(edir) _backDir = _newBack _irafexecute.processCache.setenv('chdir %s\n' % edir) except (IrafError, OSError): raise IrafError("Cannot change directory to `%s'" % (directory,)) cd = chdir @handleRedirAndSaveKwds def back(): """Go back to previous working directory""" global _backDir if _backDir is None: raise IrafError("no previous directory for back()") try: _newBack = _os.getcwd() except OSError: # OSError for getcwd() means current directory does not exist _newBack = _backDir _os.chdir(_backDir) print(_backDir) _irafexecute.processCache.setenv('chdir %s\n' % _backDir) _backDir = _newBack def error(errno=0,errmsg='',task="error",_save=False, suppress=True): """Print error message""" e = IrafError("ERROR: %s\n" % errmsg, errno=errno, errmsg=errmsg, errtask=task) e._ecl_suppress_first_trace = suppress raise e def errno(_save=None): """Returns status from last call to error()""" return irafecl._ecl_parent_task().DOLLARerrno errcode = errno def errmsg(_save=None): """Returns message from last call to error()""" irafecl._ecl_parent_task().DOLLARerrmsg def errtask(_save=None): """Returns task from last call to error()""" return irafecl._ecl_parent_task().DOLLARerrtask # ----------------------------------------------------- # clCompatibilityMode: full CL emulation (with Python # syntax accessible only through !P escape) # ----------------------------------------------------- _exitCommands = { "logout": 1, "exit": 1, "quit": 1, ".exit": 1, } def clCompatibilityMode(verbose=0, _save=0): """Start up full CL-compatibility mode""" import traceback, __main__ if verbose: vmode = ' (verbose)' else: vmode = '' print('Entering CL-compatibility%s mode...' % vmode) # logging may be active if Monty is in use if hasattr(__main__,'_pycmdline'): logfile = __main__._pycmdline.logfile else: logfile = None theLocals = {} local_vars_dict = {} local_vars_list = [] # initialize environment exec('from pyraf import iraf', theLocals) exec('from pyraf.irafpar import makeIrafPar', theLocals) exec('from stsci.tools.irafglobals import *', theLocals) exec('from pyraf.pyrafglobals import *', theLocals) exec('from pyraf.irafecl import EclState', theLocals) prompt2 = '>>> ' while (1): try: if not _sys.stdin.isatty(): prompt = '' elif loadedPath: prompt = loadedPath[-1].getName()[:2] + '> ' else: prompt = 'cl> ' line = raw_input(prompt) # simple continuation escape handling while line[-1:] == '\\': line = line + '\n' + raw_input(prompt2) line = line.strip() if line in _exitCommands: break elif line[:2] == '!P': # Python escape -- execute Python code exec(line[2:].strip(), theLocals) elif line and (line[0] != '#'): code = clExecute(line, locals=theLocals, mode='single', local_vars_dict=local_vars_dict, local_vars_list=local_vars_list) if logfile is not None: # log CL code as comment cllines = line.split('\n') for oneline in cllines: logfile.write('# '+oneline+'\n') logfile.write(code) logfile.flush() if verbose: print('----- Python -----') print(code, end=' ') print('------------------') except EOFError: break except KeyboardInterrupt: _writeError("Use `logout' or `.exit' to exit CL-compatibility mode") except: _sys.stdout.flush() traceback.print_exc() print() print('Leaving CL-compatibility mode...') # ----------------------------------------------------- # clArray: IRAF array class with type checking # Note that subscripts start zero, in Python style -- # the CL-to-Python translation takes care of the offset # in CL code, and Python code should use zero-based # subscripts. # ----------------------------------------------------- def clArray(array_size, datatype, name="", mode="h", min=None, max=None, enum=None, prompt=None, init_value=None, strict=0): """Create an IrafPar object that can be used as a CL array""" try: return _irafpar.makeIrafPar(init_value, name=name, datatype=datatype, mode=mode, min=min, max=max, enum=enum, prompt=prompt, array_size=array_size, strict=strict) except ValueError, e: raise ValueError("Error creating Cl array `%s'\n%s" % (name, str(e))) # ----------------------------------------------------- # clExecute: execute a single cl statement # ----------------------------------------------------- # count number of CL tasks currently executing # used to give unique name to each one _clExecuteCount = 0 def clExecute(s, locals=None, mode="proc", local_vars_dict={}, local_vars_list=[], verbose=0, **kw): """Execute a single cl statement""" # handle redirection keywords redirKW, closeFHList = redirProcess(kw) if len(kw): raise TypeError('unexpected keyword argument: ' + `kw.keys()`) resetList = redirApply(redirKW) try: global _clExecuteCount _clExecuteCount = _clExecuteCount + 1 pycode = _cl2py.cl2py(string=s, mode=mode, local_vars_dict=local_vars_dict, local_vars_list=local_vars_list) # use special scriptname taskname = "CL%s" % (_clExecuteCount,) scriptname = "" % (taskname,) code = pycode.code.lstrip() #XXX needed? # DBG('*'*80) # DBG('pycode for task,script='+str((taskname,scriptname,))+':\n'+code) # DBG('*'*80) # force compile to inherit future division so we don't rely on 2.x div. codeObject = compile(code,scriptname,'exec',0,0) # add this script to linecache codeLines = code.split('\n') _linecache.cache[scriptname] = (0,0,codeLines,taskname) if locals is None: locals = {} exec(codeObject, locals) if pycode.vars.proc_name: exec(pycode.vars.proc_name+"(taskObj=iraf.cl)", locals) return code finally: _clExecuteCount = _clExecuteCount - 1 # note return value not used rv = redirReset(resetList, closeFHList) def clLineToPython(line): """Returns the Python code corresponding to a single cl statement.""" pycode = _cl2py.cl2py(string=line, mode='single', local_vars_dict={}, local_vars_list=[]) code = pycode.code if pycode.vars.proc_name: code += pycode.vars.proc_name+"(taskObj=iraf.cl)\n" return code.lstrip() # ----------------------------------------------------- # Expand: Expand a string with embedded IRAF variables # (IRAF virtual filename) # ----------------------------------------------------- # Input string is in format 'name$rest' or 'name$str(name2)' where # name and name2 are defined in the _varDict dictionary. The # name2 string may have embedded dollar signs, which are ignored. # There may be multiple embedded parenthesized variable names. # # Returns string with IRAF variable name expanded to full host name. # Input may also be a comma-separated list of strings to Expand, # in which case an expanded comma-separated list is returned. # search for leading string without embedded '$' __re_var_match = _re.compile(r'(?P[^$]*)\$') # search for string embedded in parentheses __re_var_paren = _re.compile(r'\((?P[^()]*)\)') def Expand(instring, noerror=0): """Expand a string with embedded IRAF variables (IRAF virtual filename) Allows comma-separated lists. Also uses os.path.expanduser to replace '~' symbols. Set noerror flag to silently replace undefined variables with just the variable name or null (so Expand('abc$def') = 'abcdef' and Expand('(abc)def') = 'def'). This is the IRAF behavior, though it is confusing and hides errors. """ # call _expand1 for each entry in comma-separated list wordlist = instring.split(",") outlist = [] for word in wordlist: outlist.append(_os.path.expanduser(_expand1(word, noerror=noerror))) return ",".join(outlist) def _expand1(instring, noerror): """Expand a string with embedded IRAF variables (IRAF virtual filename)""" # first expand names in parentheses # note this works on nested names too, expanding from the # inside out (just like IRAF) mm = __re_var_paren.search(instring) while mm is not None: # remove embedded dollar signs from name varname = mm.group('varname').replace('$','') if defvar(varname): varname = envget(varname) elif noerror: varname = "" else: raise IrafError("Undefined variable `%s' in string `%s'" % (varname, instring)) instring = instring[:mm.start()] + varname + instring[mm.end():] mm = __re_var_paren.search(instring) # now expand variable name at start of string mm = __re_var_match.match(instring) if mm is None: return instring varname = mm.group('varname') if defvar(varname): # recursively expand string after substitution return _expand1(envget(varname) + instring[mm.end():], noerror) elif noerror: return _expand1(varname + instring[mm.end():], noerror) else: raise IrafError("Undefined variable `%s' in string `%s'" % (varname, instring)) def IrafTaskFactory(prefix='', taskname=None, suffix='', value=None, pkgname=None, pkgbinary=None, redefine=0, function=None): """Returns a new or existing IrafTask, IrafPset, or IrafPkg object Type of returned object depends on value of suffix and value. Returns a new object unless this task or package is already defined. In that case if the old task appears consistent with the new task, a reference to the old task is returned. Otherwise a warning is printed and a reference to a new task is returned. If redefine keyword is set, the behavior is the same except a warning is printed if it does *not* exist. """ module = irafecl.getTaskModule() if pkgname is None: pkgname = curpack() if pkgbinary is None: pkgbinary = curPkgbinary() elif pkgbinary is None: pkgbinary = '' # fix illegal names spkgname = pkgname.replace('.', '_') if spkgname != pkgname: _writeError("Warning: `.' illegal in package name, changing " "`%s' to `%s'" % (pkgname, spkgname)) pkgname = spkgname staskname = taskname.replace('.', '_') if staskname != taskname: _writeError("Warning: `.' illegal in task name, changing " "`%s' to `%s'" % (taskname, staskname)) taskname = staskname if suffix == '.pkg': return IrafPkgFactory(prefix,taskname,suffix,value,pkgname,pkgbinary, redefine=redefine) root, ext = _os.path.splitext(value) if ext == '.par' and function is None: return IrafPsetFactory(prefix,taskname,suffix,value,pkgname,pkgbinary, redefine=redefine) # normal task definition fullname = pkgname + '.' + taskname # existing task object (if any) task = _tasks.get(fullname) if task is None and redefine: _writeError("Warning: `%s' is not a defined task" % taskname) if function is not None: newtask = module.IrafPythonTask(prefix,taskname,suffix,value, pkgname,pkgbinary,function=function) elif ext == '.cl': newtask = module.IrafCLTask(prefix,taskname,suffix,value, pkgname,pkgbinary) elif value[:1] == '$': newtask = module.IrafForeignTask(prefix,taskname,suffix,value, pkgname,pkgbinary) else: newtask = module.IrafTask(prefix,taskname,suffix,value, pkgname,pkgbinary) if task is not None: # check for consistency of definition by comparing to the # new object if not task.isConsistent(newtask): # looks different -- print warning and continue if not redefine: _writeError("Warning: `%s' is a task redefinition" % fullname) else: # new task is consistent with old task, so return old task if task.getPkgbinary() != newtask.getPkgbinary(): # package binary differs -- add it to search path if Verbose>1: print('Adding',pkgbinary,'to',task,'path') task.addPkgbinary(pkgbinary) return task # add it to the task list _addTask(newtask) return newtask def IrafPsetFactory(prefix,taskname,suffix,value,pkgname,pkgbinary,redefine=0): """Returns a new or existing IrafPset object Returns a new object unless this task is already defined. In that case if the old task appears consistent with the new task, a reference to the old task is returned. Otherwise a warning is printed and a reference to a new task is returned. If redefine keyword is set, the behavior is the same except a warning is printed if it does *not* exist. """ module = irafecl.getTaskModule() fullname = pkgname + '.' + taskname task = _tasks.get(fullname) if task is None and redefine: _writeError("Warning: `%s' is not a defined task" % taskname) newtask = module.IrafPset(prefix,taskname,suffix,value,pkgname,pkgbinary) if task is not None: # check for consistency of definition by comparing to the new # object (which will be discarded) if task.getFilename() != newtask.getFilename(): if redefine: _writeError("Warning: `%s' is a task redefinition" % fullname) else: # old version of task is same as new return task # add it to the task list _addTask(newtask) return newtask def IrafPkgFactory(prefix,taskname,suffix,value,pkgname,pkgbinary,redefine=0): """Returns a new or existing IrafPkg object Returns a new object unless this package is already defined, in which case a warning is printed and a reference to the existing task is returned. Redefine parameter currently ignored. Returns a new object unless this package is already defined. In that case if the old package appears consistent with the new package, a reference to the old package is returned. Else if the old package has already been loaded, a warning is printed and the redefinition is ignored. Otherwise a warning is printed and a reference to a new package is returned. If redefine keyword is set, the behavior is the same except a warning is printed if it does *not* exist. """ module = irafecl.getTaskModule() # does package with exactly this name exist in minimum-match # dictionary _pkgs? pkg = _pkgs.get_exact_key(taskname) if pkg is None and redefine: _writeError("Warning: `%s' is not a defined task" % taskname) newpkg = module.IrafPkg(prefix,taskname,suffix,value,pkgname,pkgbinary) if pkg is not None: if pkg.getFilename() != newpkg.getFilename() or \ pkg.hasParfile() != newpkg.hasParfile(): if pkg.isLoaded(): _writeError("Warning: currently loaded package `%s' was not " "redefined" % taskname) return pkg else: if not redefine: _writeError("Warning: `%s' is a task redefinition" % taskname) _addPkg(newpkg) return newpkg if pkg.getPkgbinary() != newpkg.getPkgbinary(): # only package binary differs -- add it to search path if Verbose>1: print('Adding',pkgbinary,'to',pkg,'path') pkg.addPkgbinary(pkgbinary) if pkgname != pkg.getPkgname(): # add existing task as an item in the new package _addTask(pkg,pkgname=pkgname) return pkg _addPkg(newpkg) return newpkg # ----------------------------------------------------- # Utilities to handle I/O redirection keywords # ----------------------------------------------------- def redirProcess(kw): """Process Stdout, Stdin, Stderr keywords used for redirection Removes the redirection keywords from kw Returns (redirKW, closeFHList) which are a dictionary of the filehandles for stdin, stdout, stderr and a list of filehandles to close after execution. Image and Stdplot redirection not handled (but it isn't clear that these are ever used anyway) """ redirKW = {} closeFHList = [] # Dictionary of redirection keywords # Values are (outputFlag, standardName, openArgs) # Still need to add graphics redirection keywords redirDict = { 'Stdin': (0, "stdin", "r"), 'Stdout': (1, "stdout", "w"), 'StdoutAppend': (1, "stdout", "a"), 'Stderr': (1, "stderr", "w"), 'StderrAppend': (1, "stderr", "a"), 'StdoutG': (1, "stdgraph", "wb"), 'StdoutAppendG': (1, "stdgraph", "ab") } # Magic values that trigger special behavior magicValues = { "STDIN": 1, "STDOUT": 1, "STDERR": 1} PipeOut = None for key in redirDict.keys(): if key in kw: outputFlag, standardName, openArgs = redirDict[key] # if it is a string, open as a file # otherwise assume it is a filehandle value = kw[key] if isinstance(value, str): if value in magicValues: if outputFlag and value == "STDOUT": fh = _sys.__stdout__ elif outputFlag and value == "STDERR": fh = _sys.__stderr__ elif (not outputFlag) and value == "STDIN": fh = _sys.__stdin__ else: # IRAF doesn't raise an exception here (e.g., on # input redirection from "STDOUT"), but it should raise IOError("Illegal value `%s' for %s redirection" % (value, key)) else: # expand IRAF variables value = Expand(value) if outputFlag: # output file # check to see if it is dev$null if isNullFile(value): if _sys.platform.startswith('win'): value = 'NUL' else: value = '/dev/null' elif "w" in openArgs and \ envget("clobber","") != yes and \ _os.path.exists(value): # don't overwrite unless clobber is set raise IOError("Output file `%s' already exists" % value) fh = open(value, openArgs) # close this when we're done closeFHList.append(fh) elif isinstance(value, int): # integer is OK for output keywords -- it indicates # that output should be captured and returned as # function value if not outputFlag: raise IrafError("%s redirection must " "be from a file handle or string\n" "Value is `%s'" % (key, value)) if not value: fh = None else: if PipeOut is None: # several outputs can be written to same buffer # (e.g. Stdout=1, Stderr=1 is legal) PipeOut = _StringIO.StringIO() # stick this in the close list too so we know that # output should be returned # wrap it in a tuple to make it easy to recognize closeFHList.append((PipeOut,)) fh = PipeOut elif isinstance(value, (list,tuple)): # list/tuple of strings is OK for input if outputFlag: raise IrafError("%s redirection must " "be to a file handle or string\n" "Value is type %s" % (key, type(value))) try: if value and value[0][-1:] == '\n': s = ''.join(value) elif value: s = '\n'.join(value) + '\n' else: # empty value means null input s = '' fh = _StringIO.StringIO(s) # close this when we're done closeFHList.append(fh) except TypeError: raise IrafError("%s redirection must " "be from a sequence of strings\n" % key) else: # must be a file handle if outputFlag: if not hasattr(value, 'write'): raise IrafError("%s redirection must " "be to a file handle or string\n" "Value is `%s'" % (key, value)) elif not hasattr(value, 'read'): raise IrafError("%s redirection must " "be from a file handle or string\n" "Value is `%s'" % (key, value)) fh = value if fh is not None: redirKW[standardName] = fh del kw[key] # Now handle IRAF semantics for redirection of stderr to mean stdout # also redirects to stderr file handle if Stdout not also specified if 'stderr' in redirKW and not 'stdout' in redirKW: redirKW['stdout'] = redirKW['stderr'] return redirKW, closeFHList def redirApply(redirKW): """Modify _sys.stdin, stdout, stderr using the redirKW dictionary Returns a list of the original filehandles so they can be restored (by redirReset) """ sysDict = { 'stdin': 1, 'stdout': 1, 'stderr': 1 } resetList = [] for key, value in redirKW.items(): if key in sysDict: resetList.append((key, getattr(_sys,key))) setattr(_sys,key,value) elif key == 'stdgraph': resetList.append((key, gki.kernel)) gki.kernel = gki.GkiRedirection(value) return resetList def redirReset(resetList, closeFHList): """Restore _sys.stdin, stdout, stderr to their original values Also closes the filehandles in closeFHList. If a tuple with a StringIO pipe is included in closeFHList, that means the value should be returned as the return value of the function. Returns an array of lines (without newlines.) """ PipeOut = None for fh in closeFHList: if isinstance(fh, tuple): PipeOut = fh[0] else: fh.close() for key, value in resetList: if key == 'stdgraph': gki.kernel = value else: setattr(_sys,key,value) if PipeOut is not None: # unfortunately cStringIO.StringIO has no readlines method: # PipeOut.seek(0) # rv = PipeOut.readlines() rv = PipeOut.getvalue().split('\n') PipeOut.close() # delete trailing null value if rv[-1] == '': del rv[-1] return rv pyraf-2.1.15/lib/pyraf/gwm.py0000644000665500117240000001741313310762263016762 0ustar sontagpassdev00000000000000""" Graphics window manager, creates multiple toplevel togl widgets for use by python plotting $Id$ """ from __future__ import division # confidence high import os, string from stsci.tools import capable if capable.OF_GRAPHICS: import Tkinter as TKNTR # requires 2to3 import wutil, gki class GWMError(Exception): pass class GraphicsWindowManager(gki.GkiProxy): """Proxy for active graphics window and manager of multiple windows Each window is an instance of a graphics kernel. stdgraph holds the active window pointer. """ def __init__(self, GkiKernelClass): """GkiKernelClass is the class of kernel objects created Class must implement both GkiKernel and FocusEntity interfaces and must have: - activate() method to make widget active - raiseWindow() method to deiconify and raise window - gwidget attribute with the actual widget - top attribute with the top level widget The last 2 seem unneccesarily implemenation-specific and probably should be eliminated if possible. """ gki.GkiProxy.__init__(self) self.GkiKernelClass = GkiKernelClass self.windows = {} # save list of window names in order of creation self.createList = [] self.windowVar = None def getNewWindowName(self, root="graphics"): """Return a new (unused) window name of form root+number""" number = 1 while 1: windowName = root + str(number) if not windowName in self.windows: return windowName number = number + 1 def window(self, windowName=None): if windowName is not None: windowName = str(windowName).strip() if not windowName: windowName = self.getNewWindowName() if not windowName in self.windows: self.windows[windowName] = self.GkiKernelClass(windowName, self) self.createList.append(windowName) if self.windowVar is None: # create Tk string variable with active window name self.windowVar = TKNTR.StringVar() self.windowVar.trace('w', self._setWindowVar) self.windowVar.set(windowName) def _setWindowVar(self, *args): windowName = self.windowVar.get().strip() if not windowName: self.stdgraph = None else: self.stdgraph = self.windows[windowName] self.stdgraph.activate() # register with focus manager wutil.focusController.addFocusEntity(windowName,self.stdgraph) def windowNames(self): """Return list of all window names""" return self.windows.keys() def getWindowVar(self): """Return Tk variable associated with selected window""" return self.windowVar def delete(self, windowName): windowName = str(windowName).strip() window = self.windows.get(windowName) if window is None: print "error: graphics window `%s' doesn't exist" % (windowName,) else: changeActiveWindow = (self.stdgraph == window) window.top.destroy() del self.windows[windowName] try: self.createList.remove(windowName) except ValueError: pass if len(self.windows) == 0: self.windowVar.set('') elif changeActiveWindow: # change to most recently created window while self.createList: wname = self.createList.pop() if wname in self.windows: self.createList.append(wname) break else: # something's messed up # change to randomly selected active window wname = self.windows.keys()[0] self.windowVar.set(wname) wutil.focusController.removeFocusEntity(windowName) def flush(self): for window in self.windows.values(): window.flush() def openKernel(self): self.window() # # Module-level functions # def _setGraphicsWindowManager(): """ Decide which graphics kernel to use and generate a GWM object. This is only meant to be called internally! """ if wutil.hasGraphics: # see which kernel to use if 'PYRAFGRAPHICS' in os.environ: kernelname = os.environ['PYRAFGRAPHICS'].lower() if kernelname == "tkplot": import gkitkplot kernel = gkitkplot.GkiTkplotKernel elif kernelname == "opengl": print "OpenGL kernel no longer exists, using default instead" kernelname = "default" elif kernelname == "matplotlib": try: import GkiMpl kernel = GkiMpl.GkiMplKernel except ImportError: print "matplotlib is not installed, using default instead" kernelname = "default" else: print 'Graphics kernel specified by "PYRAFGRAPHICS='+ \ kernelname+'" not found.' print "Using default kernel instead." kernelname = "default" else: kernelname = "default" if 'PYRAFGRAPHICS_TEST' in os.environ: print "Using graphics kernel: "+kernelname if kernelname == "default": import gkitkplot kernel = gkitkplot.GkiTkplotKernel wutil.isGwmStarted = 1 return GraphicsWindowManager(kernel) else: wutil.isGwmStarted = 0 return None # Create a module instance of the GWM object that can be referred to # by anything that imports this module. It is in effect a singleton # object intended to be instantiated only once and be accessible from # the module. _g = _setGraphicsWindowManager() # # Public routines to access windows managed by _g # def _resetGraphicsWindowManager(): """ For development only (2010), risky but useful in perf tests """ global _g _g = _setGraphicsWindowManager() def getGraphicsWindowManager(): """Return window manager object (None if none defined)""" return _g def window(windowName=None): """Create a new graphics window if the named one doesn't exist or make it the active one if it does. If no argument is given a new name is constructed.""" if not _g: raise GWMError("No graphics window manager is available") _g.window(windowName) def delete(windowName=None): """Delete the named window (or active window if none specified)""" if not _g: raise GWMError("No graphics window manager is available") if windowName is None: windowName = getActiveWindowName() if windowName is not None: _g.delete(windowName) def getActiveWindowName(): """Return name of active window (None if none defined)""" if _g and _g.windowVar: return _g.windowVar.get() or None def getActiveWindowGwidget(): """Get the active window widget (None if none defined)""" if _g and _g.stdgraph: return _g.stdgraph.gwidget def getActiveGraphicsWindow(): """Get the active graphics kernel object (None if none defined)""" if _g and _g.stdgraph: return _g.stdgraph def getActiveWindowTop(): """Get the top window (None if none defined)""" if _g and _g.stdgraph: #XXX top is implementation-specific return _g.stdgraph.top def raiseActiveWindow(): """Deiconify if not mapped, and raise to top""" stdgraph = getActiveGraphicsWindow() if not stdgraph: raise GWMError("No plot has been created yet") stdgraph.raiseWindow() def resetFocusHistory(): """Reset focus history after an error occurs""" wutil.focusController.resetFocusHistory() pyraf-2.1.15/lib/pyraf/textattrib.py0000644000665500117240000000576513310762263020371 0ustar sontagpassdev00000000000000"""Implements text rendering using stroked font and OpenGL $Id$ General description and discussion about the assumptions of how text is to be handled. This will be a phased implementation and initially, some IRAF text features may not be implmented From experiments, these are some properties of IRAF text: 1) Text does not zoom with plot. (affected by gki coordinate transformation only in cl level manipulations. Does not affect this code. 2) Escape sequences for fonts do not appear to match the documentation or code comments. Greek characters do not appear to be supported, nor does bold text. Will eventually support here though 3) Characters do not retain a constant aspect ratio. It appears to change with the aspect ratio of the screen. Either mode can be chosen 4) Characters are fixed width font. Same here, for now (and maybe forever). This implementation will allow some of these properties to be overriden by a system-wide configuration state. See gkiopengl.py """ from __future__ import division # confidence high import fontdata CHARPATH_LEFT = 2 CHARPATH_RIGHT = 3 CHARPATH_UP = 4 CHARPATH_DOWN = 5 JUSTIFIED_NORMAL = 0 JUSTIFIED_CENTER = 1 JUSTIFIED_TOP = 6 JUSTIFIED_BOTTOM = 7 JUSTIFIED_LEFT = 2 JUSTIFIED_RIGHT = 3 FONT_ROMAN = 8 FONT_GREEK = 9 FONT_ITALIC = 10 FONT_BOLD = 11 FQUALITY_NORMAL = 0 FQUALITY_LOW = 12 FQUALITY_MEDIUM = 13 FQUALITY_HIGH = 14 class TextAttributes: # Used as a structure definition basically, perhaps it should be made # more sophisticated. def __init__(self): self.charUp = 90. self.charSize = 1. self.charSpace = 0. self.textPath = CHARPATH_RIGHT self.textHorizontalJust = JUSTIFIED_NORMAL self.textVerticalJust = JUSTIFIED_NORMAL self.textFont = FONT_ROMAN self.textQuality = FQUALITY_NORMAL self.textColor = 1 self.font = fontdata.font1 # Place to keep font size and aspect for current window dimensions self.hFontSize = None self.fontAspect = None def set(self,charUp=90., charSize=1.,charSpace=0., textPath=CHARPATH_RIGHT, textHorizontalJust=JUSTIFIED_NORMAL, textVerticalJust=JUSTIFIED_NORMAL, textFont=FONT_ROMAN, textQuality=FQUALITY_NORMAL, textColor=1): self.charUp = charUp self.charSize = charSize self.charSpace = charSpace self.textPath = textPath self.textHorizontalJust = textHorizontalJust self.textVerticalJust = textVerticalJust self.textFont = textFont self.textQuality = textQuality self.textColor = textColor # Place to keep font size and aspect for current window dimensions def setFontSize(self, win): """Set the unit font size for a given window using the iraf configuration parameters contained in an attribute class""" conf = win.irafGkiConfig self.hFontSize, self.fontAspect = conf.fontSize(win.gwidget) def getFontSize(self): return self.hFontSize, self.fontAspect pyraf-2.1.15/lib/pyraf/irafukey.py0000644000665500117240000000550113310762263020002 0ustar sontagpassdev00000000000000""" implement IRAF ukey functionality $Id$ """ from __future__ import division # confidence high import os, string, sys import wutil from stsci.tools import capable, for2to3, irafutils try: import termios except: if 0==sys.platform.find('win'): # not on win*, but IS on darwin & cygwin termios = None else: raise # TERMIOS is deprecated in Python 2.1 if hasattr(termios, 'ICANON') or termios==None: TERMIOS = termios else: import TERMIOS # This class emulates the IRAF ukey parameter mechanism. IRAF calls for # a ukey parameter and expects that the user will type a character in # response. The value of this character is then returned to the iraf task def getSingleTTYChar(): # return type str in all Python versions """Returns None if Control-C is typed or any other exception occurs""" # Ripped off from python FAQ fd = sys.stdin.fileno() old = termios.tcgetattr(fd) new = termios.tcgetattr(fd) new[3] = new[3] & ~(TERMIOS.ICANON | TERMIOS.ECHO | TERMIOS.ISIG) new[6][TERMIOS.VMIN] = 1 new[6][TERMIOS.VTIME] = 0 termios.tcsetattr(fd, TERMIOS.TCSANOW, new) c = None try: # allow Tk mainloop to run while waiting... # vanilla version would be c = os.read(fd, 1) if capable.OF_GRAPHICS: c = irafutils.tkread(fd, 1) else: c = os.read(fd, 1) if for2to3.PY3K: c = c.decode('ascii', 'replace') finally: termios.tcsetattr(fd, TERMIOS.TCSAFLUSH, old) return c def ukey(): """Returns the string expected for the IRAF ukey parameter""" # set focus to terminal if it is not already there wutil.focusController.setFocusTo('terminal') char = getSingleTTYChar() if not char: # on control-C, raise KeyboardInterrupt raise KeyboardInterrupt elif char == '\004': # on control-D, raise EOF raise EOFError elif ord(char) <= ord(' '): # convert to octal ascii representation returnStr = '\\'+"%03o" % ord(char) elif char == ':': # suck in colon string until newline is encountered done = 0 sys.stdout.write(':') sys.stdout.flush() colonString = '' while not done: char = getSingleTTYChar() if (not char) or (char == '\n'): done = 1 elif char == '\b': # backspace colonString = colonString[:-1] sys.stdout.write('\b \b') sys.stdout.flush() elif ord(char) >= ord(' '): colonString = colonString+char sys.stdout.write(char) sys.stdout.flush() else: # ignore all other characters pass returnStr = ': '+colonString else: returnStr = char return returnStr pyraf-2.1.15/lib/pyraf/msgiowidget.py0000644000665500117240000002070213310762263020505 0ustar sontagpassdev00000000000000""" 'msgiowidget.py' -- this is a replacement for the msgiobuffer module. This contains the MsgIOWidget class, which is an optionally hidden scrolling canvas composed of a text widget and frame. When "hidden", it turns into a single-line text widget. $Id$ """ from __future__ import division # confidence high # System level modules import Tkinter as TKNTR # requires 2to3 # Our modules from stsci.tools import tkrotext def is_USING_X(): """ This is specifically in a function and not at the top of this module so that it is not done until needed. We do not want to casually import wutil anywhere. The import mechanism makes this speedy on the 2nd-Nth attempt anyway. """ from . import wutil return wutil.WUTIL_USING_X class MsgIOWidget(TKNTR.Frame): """MsgIOWidget class""" def __init__(self, parent, width=100, text=""): """Constructor""" # We are the main frame that holds everything we do TKNTR.Frame.__init__(self, parent) self._parent = parent # Create two sub-frames, one to hold the 1-liner msg I/O, and # the other one to hold the whole scrollable history. self._nowFrame = TKNTR.Frame(self, bd=2, relief=TKNTR.SUNKEN, takefocus=False) self._histFrame = TKNTR.Frame(self, bd=2, relief=TKNTR.SUNKEN, takefocus=False) # Put in the expand/collapse button (determine it's sizes) self._expBttnHasTxt = True btxt= '+' if is_USING_X(): px = 2 py = 0 else: # Aqua px = 5 py = 3 if TKNTR.TkVersion > 8.4: px = py = 0 btxt = '' self._expBttnHasTxt = False self._expBttn = TKNTR.Checkbutton(self._nowFrame, command=self._expand, padx=px, pady=py, text=btxt, indicatoron=0, state = TKNTR.DISABLED) self._expBttn.pack(side=TKNTR.LEFT, padx=3)#, ipadx=0) # Overlay a label on the frame self._msgLabelVar = TKNTR.StringVar() self._msgLabelVar.set(text) self._msgLabelMaxWidth = 65 # 70 works but causes plot redraws when # the history panel is opened/closed self._msgLabel = TKNTR.Label(self._nowFrame, textvariable=self._msgLabelVar, anchor=TKNTR.W, justify=TKNTR.LEFT, width=self._msgLabelMaxWidth, wraplength=width-100, takefocus=False) self._msgLabel.pack(side=TKNTR.LEFT, fill=TKNTR.X, expand=False) self._msgLabel.bind('', self._lblDblClk) self._entry = TKNTR.Entry(self._nowFrame, state=TKNTR.DISABLED, width=1, takefocus=False, relief=TKNTR.FLAT, highlightthickness=0) self._entry.pack(side=TKNTR.LEFT, fill=TKNTR.X, expand=True) self._entry.bind('', self._enteredText) self._entryTyping = TKNTR.BooleanVar() self._entryTyping.set(False) # put in a spacer here for label height stability self._spacer = TKNTR.Label(self._nowFrame, text='', takefocus=False) self._spacer.pack(side=TKNTR.LEFT, expand=False, padx=5) self._nowFrame.pack(side=TKNTR.TOP, fill=TKNTR.X, expand=True) self._hasHistory = False self._histScrl = TKNTR.Scrollbar(self._histFrame) self._histScrl.pack(side=TKNTR.RIGHT, fill=TKNTR.Y) self._histText = tkrotext.ROText(self._histFrame, wrap=TKNTR.WORD, takefocus=False, height=10, yscrollcommand=self._histScrl.set) # (use if just TKNTR.Text) state=TKNTR.DISABLED, takefocus=False, # exportselection=True is the default self._histText.pack(side=TKNTR.TOP, fill=TKNTR.X, expand=True) self._histScrl.config(command=self._histText.yview) # don't pack this one now - start out with it hidden # self._histFrame.pack(side=TKNTR.TOP, fill=TKNTR.X) ### Do not pack the main frame here. Let the application do it. ### # At very end of ctor, add the init text to our history self._appendToHistory(text) def _lblDblClk(self, event=None): if self._hasHistory: # change the button appearance self._expBttn.toggle() # or .select() / .deselect() # and then act as if it was clicked self._expand() def _expand(self): ism = self._histFrame.winfo_ismapped() if ism: # need to collapse self._histFrame.pack_forget() if self._expBttnHasTxt: self._expBttn.configure(text='+') else: # need to expand self._histFrame.pack(side=TKNTR.TOP, fill=TKNTR.BOTH, expand=True) #.X) if self._expBttnHasTxt: self._expBttn.configure(text='-') if self._hasHistory: self._histText.see(TKNTR.END) def updateIO(self, text=""): """ Update the text portion of the scrolling canvas """ # Update the class variable with the latest text, and append the # new text to the end of the history. self._appendToHistory(text) self._msgLabelVar.set(text) # this is a little debugging "easter egg" if text.find('long debug line') >=0: self.updateIO('and now we are going to talk and talk for a while'+ ' about nothing at all because we want a lot of text') self._nowFrame.update_idletasks() def readline(self): """ Set focus to the entry widget and return it's contents """ # Find what had focus up to this point lastFoc = self.focus_get() # Collapse the label as much as possible, it is too big on Linux & OSX lblTxt = self._msgLabelVar.get() lblTxtLen = len(lblTxt.strip()) lblTxtLen -= 3 self._msgLabel.configure(width=min(self._msgLabelMaxWidth, lblTxtLen)) # Enable the entry widget self._entry.configure(state=TKNTR.NORMAL, relief=TKNTR.SUNKEN, width=15, takefocus=True, highlightthickness=2) self._entry.focus_set() self._entryTyping.set(True) # Wait until they are done entering their answer self._entry.wait_variable(self._entryTyping) # By now they have hit enter ans = self._entry.get().strip() # Clear and disable the entry widget self._entry.delete(0, TKNTR.END) self._entry.configure(state=TKNTR.DISABLED, takefocus=False, width=1, relief=TKNTR.FLAT, highlightthickness=0) self._entryTyping.set(False) # Expand the label back to normal width self._msgLabel.configure(width=self._msgLabelMaxWidth) # list the answer self.updateIO(ans) # return focus if lastFoc: lastFoc.focus_set() # return the answer - important to have the "\n" on it return ans+"\n" def _enteredText(self, event=None): self._entryTyping.set(False) # end the waiting self._expBttn.focus_set() def _appendToHistory(self, txt): # sanity check - need no blank lines in the history if len(txt.strip()) < 1: return # enable widget temporarily so we can add text # self._histText.config(state=TKNTR.NORMAL) # self._histText.delete(1.0, END) # add the new text if self._hasHistory: self._histText.insert(TKNTR.END, '\n'+txt.strip(), force=True) else: self._histText.insert(TKNTR.END, txt.strip(), force=True) self._hasHistory = True # disable it again # self._histText.config(state=TKNTR.DISABLED) # show it if self._histFrame.winfo_ismapped(): self._histText.see(TKNTR.END) # self._histFrame.update_idletasks() # finally, make sure expand/collapse button is enabled now self._expBttn.configure(state = TKNTR.NORMAL) pyraf-2.1.15/lib/pyraf/tkplottext.py0000644000665500117240000001310513310762263020404 0ustar sontagpassdev00000000000000"""Implements text rendering using stroked font and Tkplot/X $Id$ General description and discussion about the assumptions of how text is to be handled. This will be a phased implementation and initially, some IRAF text features may not be implmented From experiments, these are some properties of IRAF text: 1) Text does not zoom with plot. (affected by gki coordinate transformation only in cl level manipulations. Does not affect this code. 2) Escape sequences for fonts do not appear to match the documentation or code comments. Greek characters do not appear to be supported, nor does bold text. Will eventually support here though 3) Characters do not retain a constant aspect ratio. It appears to change with the aspect ratio of the screen. Either mode can be chosen 4) Characters are fixed width font. Same here, for now (and maybe forever). This implementation will allow some of these properties to be overriden by a system-wide configuration state. See gkiopengl.py """ from __future__ import division # confidence high import fontdata import numpy import math from textattrib import * def softText(win,x,y,textstr): # Generate text using software generated stroked fonts # except for the input x,y, all coordinates are in units of pixels # get unit font size ta = win.textAttributes hsize, fontAspect = ta.getFontSize() vsize = hsize * fontAspect # get current size in unit font units (!) fsize = ta.charSize # We draw the line at fontSizes less than 1/2! Get real. fsize = max(fsize,0.5) # Character spacing depends on whether text is 'vertical' or 'horizontal' # (relative to character orientation). Figure out what the character # delta offset is in the coordinate system where charUp is the y axis. # First include added space if any hspace = fsize * hsize * (1. + ta.charSpace) vspace = fsize * vsize * (1. + ta.charSpace) if ta.textPath in (CHARPATH_LEFT, CHARPATH_RIGHT): dx = hspace if ta.textPath == CHARPATH_LEFT: dx = -dx dy = 0. else: dx = 0. dy = -vspace if ta.textPath == CHARPATH_UP: dy = -dy # Figure out 'path' size of the text string for use in justification xpath,ypath = (dx*(len(textstr)-1),dy*(len(textstr)-1)) charUp = math.fmod(ta.charUp, 360.) if charUp < 0: charUp = charUp + 360. if ta.textPath == CHARPATH_RIGHT: textdir = math.fmod(charUp+270,360.) elif ta.textPath == CHARPATH_LEFT: textdir = math.fmod(charUp+90,360.) elif ta.textPath == CHARPATH_UP: textdir = charUp elif ta.textPath == CHARPATH_DOWN: textdir = math.fmod(charUp+180,360.) # IRAF definition of justification is a bit weird, justification is # for the text string relative to the window. So a rotated string will # be justified relative to the window horizontal and vertical, not the # string's. Thus the need to compute the offsets in window oriented # coordinates. up = 0. < textdir < 180. left = 90. < textdir < 270. deg2rad = math.pi/180. cosv = math.cos((charUp-90.)*deg2rad) sinv = math.sin((charUp-90.)*deg2rad) xpathwin, ypathwin = (cosv*xpath-sinv*ypath,sinv*xpath+cosv*ypath) xcharsize = fsize * max(abs(cosv*hsize+sinv*vsize), abs(cosv*hsize-sinv*vsize)) ycharsize = fsize * max(abs(-sinv*hsize+cosv*vsize), abs(-sinv*hsize-cosv*vsize)) xoffset, yoffset = (0., 0.) xcharoff, ycharoff = (0., 0.) if ta.textHorizontalJust == JUSTIFIED_CENTER: xoffset = -xpathwin/2. elif ta.textHorizontalJust == JUSTIFIED_RIGHT: if not left: xoffset = -xpathwin xcharoff = -xcharsize/2. elif ta.textHorizontalJust in (JUSTIFIED_LEFT,JUSTIFIED_NORMAL): if left: xoffset = xpathwin xcharoff = xcharsize/2. if ta.textVerticalJust == JUSTIFIED_CENTER: yoffset = -ypathwin/2. elif ta.textVerticalJust == JUSTIFIED_TOP: if up: yoffset = -ypathwin ycharoff = -ycharsize/2. elif ta.textVerticalJust in (JUSTIFIED_BOTTOM, JUSTIFIED_NORMAL): if not up: yoffset = ypathwin ycharoff = ycharsize/2. xNetOffset = xoffset + xcharoff yNetOffset = yoffset + ycharoff # note, these offsets presume that the origin of character coordinates # is the center of the character box. This will be taken into account # when drawing the character. # Now start drawing! gw = win.gwidget xwin = float(gw.winfo_width()) ywin = float(gw.winfo_height()) color = win.colorManager.setDrawingColor(ta.textColor) options = {"fill":color} font = ta.font size = fsize*hsize cosrot = numpy.cos((charUp-90)*numpy.pi/180) sinrot = numpy.sin((charUp-90)*numpy.pi/180) nchar = 0 # The main event! for char in textstr: # draw character with origin at bottom left corner of character box charstrokes = ta.font[ord(char)-ord(' ')] for i in xrange(len(charstrokes[0])): vertex = numpy.zeros((len(charstrokes[0][i]),2),numpy.float64) xf = size * charstrokes[0][i]/27. -fsize*hsize/2. yf = size * charstrokes[1][i]*fontAspect/27. - fsize*vsize/2. vertex[:,0]= cosrot*(xf + nchar*dx) \ - sinrot*(yf + nchar*dy) + xNetOffset + xwin*x vertex[:,1]=ywin-(sinrot*(xf + nchar*dx) \ + cosrot*(yf + nchar*dy) + yNetOffset + ywin*y) gw.create_line(*(tuple(vertex.ravel().astype(numpy.int32))), **options) nchar = nchar + 1 pyraf-2.1.15/lib/pyraf/fontdata.py0000644000665500117240000003171213310762263017766 0ustar sontagpassdev00000000000000"""fontdata.py $Id$ """ from __future__ import division # confidence high from numpy import * font1 = [([], []), ( [array([10, 10],dtype=int8), array([ 9, 9, 11, 11, 9, 11],dtype=int8)], [array([35, 16],dtype=int8), array([9, 7, 7, 9, 9, 7],dtype=int8)]), ( [array([5, 5],dtype=int8), array([5, 5],dtype=int8), array([14, 14],dtype=int8), array([14, 14],dtype=int8)], [array([35, 31],dtype=int8), array([31, 35],dtype=int8), array([35, 31],dtype=int8), array([31, 35],dtype=int8)]), ( [array([9, 6],dtype=int8), array([13, 16],dtype=int8), array([20, 3],dtype=int8), array([ 2, 18],dtype=int8)], [array([27, 10],dtype=int8), array([10, 27],dtype=int8), array([22, 22],dtype=int8), array([15, 15],dtype=int8)]), ( [array([9, 9],dtype=int8), array([ 1, 5, 13, 17, 17, 13, 5, 1, 1, 5, 13, 17],dtype=int8)], [array([35, 8],dtype=int8), array([14, 10, 10, 13, 18, 22, 22, 25, 29, 33, 33, 29],dtype=int8)]), ( [array([6, 3, 1, 1, 3, 6, 8, 8, 6],dtype=int8), array([18, 1],dtype=int8), array([11, 11, 13, 16, 18, 18, 16, 13, 11],dtype=int8)], [array([35, 35, 33, 30, 28, 28, 30, 33, 35],dtype=int8), array([35, 8],dtype=int8), array([13, 10, 8, 8, 10, 13, 15, 15, 13],dtype=int8)]), ( [array([18, 3, 3, 5, 8, 10, 10, 1, 1, 4, 11, 18],dtype=int8)], [array([ 8, 28, 32, 35, 35, 32, 29, 17, 12, 8, 8, 17],dtype=int8)]), ( [array([ 9, 8, 9, 10, 9],dtype=int8)], [array([35, 31, 31, 35, 35],dtype=int8)]), ( [array([11, 9, 8, 8, 9, 11],dtype=int8)], [array([35, 31, 26, 17, 12, 8],dtype=int8)]), ( [array([ 8, 10, 11, 11, 10, 8],dtype=int8)], [array([35, 31, 26, 17, 12, 8],dtype=int8)]), ( [array([ 1, 17],dtype=int8), array([9, 9],dtype=int8), array([ 1, 17],dtype=int8)], [array([29, 14],dtype=int8), array([32, 11],dtype=int8), array([14, 29],dtype=int8)]), ( [array([9, 9],dtype=int8), array([ 1, 17],dtype=int8)], [array([28, 12],dtype=int8), array([20, 20],dtype=int8)]), ( [array([8, 9, 9, 8, 8, 9],dtype=int8)], [array([4, 6, 9, 9, 8, 8],dtype=int8)]), ( [array([ 1, 17],dtype=int8)], [array([20, 20],dtype=int8)]), ( [array([ 9, 9, 11, 11, 9, 11],dtype=int8)], [array([9, 7, 7, 9, 9, 7],dtype=int8)]), ( [array([ 1, 18],dtype=int8)], [array([ 8, 35],dtype=int8)]), ( [array([ 6, 1, 1, 6, 13, 18, 18, 13, 6],dtype=int8)], [array([35, 29, 14, 8, 8, 14, 29, 35, 35],dtype=int8)]), ( [array([ 3, 10, 10],dtype=int8)], [array([29, 35, 8],dtype=int8)]), ( [array([ 1, 5, 14, 18, 18, 1, 1, 18],dtype=int8)], [array([31, 35, 35, 31, 23, 12, 8, 8],dtype=int8)]), ( [array([ 1, 6, 13, 18, 18, 13, 9],dtype=int8), array([13, 18, 18, 13, 6, 1],dtype=int8)], [array([31, 35, 35, 31, 26, 22, 22],dtype=int8), array([22, 18, 12, 8, 8, 13],dtype=int8)]), ( [array([14, 14, 12, 1, 1, 14],dtype=int8), array([14, 18],dtype=int8)], [array([ 8, 35, 35, 17, 15, 15],dtype=int8), array([15, 15],dtype=int8)]), ( [array([ 2, 13, 18, 18, 13, 6, 1, 1, 18],dtype=int8)], [array([ 8, 8, 13, 20, 25, 25, 21, 35, 35],dtype=int8)]), ( [array([ 1, 6, 13, 18, 18, 13, 6, 1, 1, 2, 10],dtype=int8)], [array([19, 24, 24, 19, 13, 8, 8, 13, 19, 27, 35],dtype=int8)]), ( [array([ 1, 18, 6],dtype=int8)], [array([35, 35, 8],dtype=int8)]), ( [array([ 5, 1, 1, 5, 1, 1, 5, 14, 18, 18, 14, 5],dtype=int8), array([14, 18, 18, 14, 5],dtype=int8)], [array([35, 31, 26, 22, 18, 12, 8, 8, 12, 18, 22, 22],dtype=int8), array([22, 26, 31, 35, 35],dtype=int8)]), ( [array([10, 17, 18, 18, 13, 6, 1, 1, 6, 13, 18],dtype=int8)], [array([ 8, 16, 24, 30, 35, 35, 30, 24, 19, 19, 24],dtype=int8)]), ( [array([ 9, 11, 11, 9, 9, 11],dtype=int8), array([ 9, 9, 11, 11, 9, 11],dtype=int8)], [array([26, 26, 23, 23, 26, 23],dtype=int8), array([16, 13, 13, 16, 16, 13],dtype=int8)]), ( [array([ 9, 10, 10, 9, 9, 10],dtype=int8), array([ 9, 10, 10, 9, 9, 10],dtype=int8)], [array([26, 26, 23, 23, 26, 23],dtype=int8), array([ 9, 12, 16, 16, 15, 15],dtype=int8)]), ( [array([15, 2, 15],dtype=int8)], [array([28, 20, 12],dtype=int8)]), ( [array([17, 2],dtype=int8), array([ 2, 17],dtype=int8)], [array([24, 24],dtype=int8), array([16, 16],dtype=int8)]), ( [array([ 2, 15, 2],dtype=int8)], [array([28, 20, 12],dtype=int8)]), ( [array([ 3, 3, 6, 11, 15, 15, 8, 8],dtype=int8), array([ 8, 8, 10, 10, 8, 10],dtype=int8)], [array([29, 32, 35, 34, 31, 26, 19, 15],dtype=int8), array([9, 7, 7, 9, 9, 7],dtype=int8)]), ( [array([14, 5, 1, 1, 5, 14, 18, 18, 16, 14, 13, 13, 11, 8, 6, 6, 8, 11, 13],dtype=int8)], [array([12, 12, 16, 27, 31, 31, 27, 19, 17, 17, 19, 23, 26, 26, 23, 19, 17, 17, 19],dtype=int8)]), ( [array([ 1, 7, 11, 17],dtype=int8), array([ 3, 15],dtype=int8)], [array([ 8, 35, 35, 8],dtype=int8), array([18, 18],dtype=int8)]), ( [array([ 1, 14, 18, 18, 14, 1],dtype=int8), array([14, 18, 18, 14, 1, 1],dtype=int8)], [array([35, 35, 31, 26, 22, 22],dtype=int8), array([22, 18, 12, 8, 8, 35],dtype=int8)]), ( [array([18, 13, 6, 1, 1, 6, 13, 18],dtype=int8)], [array([13, 8, 8, 13, 30, 35, 35, 30],dtype=int8)]), ( [array([ 1, 13, 18, 18, 13, 1, 1],dtype=int8)], [array([35, 35, 30, 13, 8, 8, 35],dtype=int8)]), ( [array([ 1, 1, 18],dtype=int8), array([ 1, 12],dtype=int8), array([ 1, 18],dtype=int8)], [array([35, 8, 8],dtype=int8), array([22, 22],dtype=int8), array([35, 35],dtype=int8)]), ( [array([1, 1],dtype=int8), array([ 1, 12],dtype=int8), array([ 1, 18],dtype=int8)], [array([35, 8],dtype=int8), array([22, 22],dtype=int8), array([35, 35],dtype=int8)]), ( [array([11, 18, 18],dtype=int8), array([18, 13, 6, 1, 1, 6, 13, 18],dtype=int8)], [array([18, 18, 8],dtype=int8), array([13, 8, 8, 13, 30, 35, 35, 30],dtype=int8)]), ( [array([1, 1],dtype=int8), array([ 1, 18],dtype=int8), array([18, 18],dtype=int8)], [array([35, 8],dtype=int8), array([21, 21],dtype=int8), array([ 8, 35],dtype=int8)]), ( [array([ 4, 14],dtype=int8), array([9, 9],dtype=int8), array([ 5, 14],dtype=int8)], [array([35, 35],dtype=int8), array([35, 8],dtype=int8), array([8, 8],dtype=int8)]), ( [array([ 1, 6, 12, 17, 17],dtype=int8)], [array([13, 8, 8, 13, 35],dtype=int8)]), ( [array([1, 1],dtype=int8), array([18, 6],dtype=int8), array([ 1, 18],dtype=int8)], [array([35, 8],dtype=int8), array([ 8, 23],dtype=int8), array([18, 35],dtype=int8)]), ( [array([ 1, 1, 18],dtype=int8)], [array([35, 8, 8],dtype=int8)]), ( [array([ 1, 1, 9, 17, 17],dtype=int8)], [array([ 8, 35, 21, 35, 8],dtype=int8)]), ( [array([ 1, 1, 18, 18],dtype=int8)], [array([ 8, 35, 8, 35],dtype=int8)]), ( [array([ 1, 6, 13, 18, 18, 13, 6, 1, 1],dtype=int8), array([13, 18],dtype=int8)], [array([30, 35, 35, 30, 13, 8, 8, 13, 30],dtype=int8), array([30, 35],dtype=int8)]), ( [array([ 1, 1, 14, 18, 18, 14, 1],dtype=int8)], [array([ 8, 35, 35, 31, 24, 20, 20],dtype=int8)]), ( [array([ 1, 1, 6, 13, 18, 18, 13, 6, 1],dtype=int8), array([ 8, 18],dtype=int8)], [array([30, 13, 8, 8, 13, 30, 35, 35, 30],dtype=int8), array([24, 4],dtype=int8)]), ( [array([ 1, 1, 14, 18, 18, 14, 1],dtype=int8), array([14, 18],dtype=int8)], [array([ 8, 35, 35, 31, 25, 22, 22],dtype=int8), array([22, 8],dtype=int8)]), ( [array([ 1, 5, 14, 18, 18, 14, 5, 1, 1, 5, 14, 18],dtype=int8)], [array([12, 8, 8, 12, 17, 21, 22, 26, 31, 35, 35, 31],dtype=int8)]), ( [array([10, 10],dtype=int8), array([ 1, 19],dtype=int8)], [array([ 8, 35],dtype=int8), array([35, 35],dtype=int8)]), ( [array([ 1, 1, 6, 13, 18, 18],dtype=int8)], [array([35, 13, 8, 8, 13, 35],dtype=int8)]), ( [array([ 1, 9, 17],dtype=int8)], [array([35, 8, 35],dtype=int8)]), ( [array([ 1, 5, 10, 15, 19],dtype=int8)], [array([35, 8, 21, 8, 35],dtype=int8)]), ( [array([ 1, 18],dtype=int8), array([ 1, 18],dtype=int8)], [array([35, 8],dtype=int8), array([ 8, 35],dtype=int8)]), ( [array([1, 9, 9],dtype=int8), array([ 9, 17],dtype=int8)], [array([35, 23, 8],dtype=int8), array([23, 35],dtype=int8)]), ( [array([ 1, 18, 1, 18],dtype=int8)], [array([35, 35, 8, 8],dtype=int8)]), ( [array([12, 7, 7, 12],dtype=int8)], [array([37, 37, 7, 7],dtype=int8)]), ( [array([ 1, 18],dtype=int8)], [array([35, 8],dtype=int8)]), ( [array([ 6, 11, 11, 6],dtype=int8)], [array([37, 37, 7, 7],dtype=int8)]), ( [array([ 4, 9, 14],dtype=int8)], [array([32, 35, 32],dtype=int8)]), ( [array([ 0, 19],dtype=int8)], [array([3, 3],dtype=int8)]), ( [array([ 8, 10],dtype=int8)], [array([35, 29],dtype=int8)]), ( [array([ 4, 11, 14, 14, 10, 5, 1, 1, 4, 11, 14],dtype=int8), array([14, 18],dtype=int8)], [array([23, 23, 20, 11, 7, 7, 11, 14, 17, 17, 15],dtype=int8), array([11, 7],dtype=int8)]), ( [array([1, 1],dtype=int8), array([ 1, 5, 12, 16, 16, 12, 5, 1],dtype=int8)], [array([35, 8],dtype=int8), array([12, 8, 8, 12, 19, 23, 23, 19],dtype=int8)]), ( [array([15, 12, 5, 1, 1, 5, 12, 15],dtype=int8)], [array([21, 23, 23, 19, 12, 8, 8, 10],dtype=int8)]), ( [array([16, 12, 5, 1, 1, 5, 12, 16],dtype=int8), array([16, 16],dtype=int8)], [array([19, 23, 23, 19, 12, 8, 8, 12],dtype=int8), array([ 8, 35],dtype=int8)]), ( [array([ 1, 16, 16, 12, 5, 1, 1, 5, 12, 16],dtype=int8)], [array([16, 16, 19, 23, 23, 19, 12, 8, 8, 11],dtype=int8)]), ( [array([ 3, 14],dtype=int8), array([ 7, 7, 10, 14, 16],dtype=int8)], [array([23, 23],dtype=int8), array([ 8, 30, 33, 33, 31],dtype=int8)]), ( [array([ 1, 5, 12, 16, 16],dtype=int8), array([16, 12, 5, 1, 1, 5, 12, 16],dtype=int8)], [array([ 3, 0, 0, 4, 23],dtype=int8), array([19, 23, 23, 19, 12, 8, 8, 12],dtype=int8)]), ( [array([1, 1],dtype=int8), array([ 1, 5, 12, 16, 16],dtype=int8)], [array([35, 8],dtype=int8), array([19, 23, 23, 19, 8],dtype=int8)]), ( [array([ 8, 8, 10, 10, 8, 10],dtype=int8), array([8, 9, 9],dtype=int8)], [array([29, 27, 27, 29, 29, 27],dtype=int8), array([21, 21, 8],dtype=int8)]), ( [array([ 8, 8, 10, 10, 8, 10],dtype=int8), array([8, 9, 9, 7, 4, 2],dtype=int8)], [array([29, 27, 27, 29, 29, 27],dtype=int8), array([21, 21, 2, 0, 0, 2],dtype=int8)]), ( [array([1, 1],dtype=int8), array([ 1, 13],dtype=int8), array([ 5, 16],dtype=int8)], [array([35, 8],dtype=int8), array([20, 26],dtype=int8), array([22, 8],dtype=int8)]), ( [array([ 7, 9, 9, 11],dtype=int8)], [array([35, 33, 10, 8],dtype=int8)]), ( [array([1, 1],dtype=int8), array([9, 9],dtype=int8), array([ 1, 4, 7, 9, 11, 14, 17, 17],dtype=int8)], [array([23, 8],dtype=int8), array([ 8, 20],dtype=int8), array([20, 23, 23, 20, 23, 23, 20, 8],dtype=int8)]), ( [array([1, 1],dtype=int8), array([ 1, 5, 12, 16, 16],dtype=int8)], [array([23, 8],dtype=int8), array([19, 23, 23, 19, 8],dtype=int8)]), ( [array([ 1, 1, 5, 12, 16, 16, 12, 5, 1],dtype=int8)], [array([19, 12, 8, 8, 12, 19, 23, 23, 19],dtype=int8)]), ( [array([1, 1],dtype=int8), array([ 1, 5, 12, 16, 16, 12, 5, 1],dtype=int8)], [array([23, 0],dtype=int8), array([19, 23, 23, 19, 12, 8, 8, 12],dtype=int8)]), ( [array([16, 16],dtype=int8), array([16, 12, 5, 1, 1, 5, 12, 16],dtype=int8)], [array([23, 0],dtype=int8), array([12, 8, 8, 12, 19, 23, 23, 19],dtype=int8)]), ( [array([1, 1],dtype=int8), array([ 1, 5, 12, 16],dtype=int8)], [array([23, 8],dtype=int8), array([19, 23, 23, 20],dtype=int8)]), ( [array([ 1, 5, 12, 16, 16, 12, 5, 1, 1, 5, 12, 15],dtype=int8)], [array([10, 8, 8, 10, 14, 16, 16, 18, 21, 23, 23, 21],dtype=int8)]), ( [array([ 4, 14],dtype=int8), array([15, 13, 10, 8, 8],dtype=int8)], [array([23, 23],dtype=int8), array([10, 8, 8, 10, 33],dtype=int8)]), ( [array([ 1, 1, 5, 12, 16],dtype=int8), array([16, 16],dtype=int8)], [array([23, 12, 8, 8, 12],dtype=int8), array([ 8, 23],dtype=int8)]), ( [array([ 2, 9, 16],dtype=int8)], [array([23, 8, 23],dtype=int8)]), ( [array([ 1, 5, 9, 13, 17],dtype=int8)], [array([23, 8, 21, 8, 23],dtype=int8)]), ( [array([ 2, 16],dtype=int8), array([ 2, 16],dtype=int8)], [array([23, 8],dtype=int8), array([ 8, 23],dtype=int8)]), ( [array([1, 9],dtype=int8), array([ 5, 17],dtype=int8)], [array([23, 8],dtype=int8), array([ 0, 23],dtype=int8)]), ( [array([ 2, 16, 2, 16],dtype=int8)], [array([23, 23, 8, 8],dtype=int8)]), ( [array([12, 10, 8, 8, 7, 5, 7, 8, 8, 10, 12],dtype=int8)], [array([37, 37, 34, 25, 23, 22, 21, 19, 9, 7, 7],dtype=int8)]), ( [array([9, 9],dtype=int8), array([9, 9],dtype=int8)], [array([35, 25],dtype=int8), array([18, 8],dtype=int8)]), ( [array([ 7, 9, 11, 11, 12, 14, 12, 11, 11, 9, 7],dtype=int8)], [array([37, 37, 34, 25, 23, 22, 21, 19, 9, 7, 7],dtype=int8)]), ( [array([ 1, 4, 7, 12, 15, 18],dtype=int8)], [array([19, 22, 22, 17, 17, 20],dtype=int8)])] pyraf-2.1.15/lib/pyraf/generic.py0000644000665500117240000005272613310762263017612 0ustar sontagpassdev00000000000000"""generic.py: John Aycock's little languages (SPARK) framework $Id$ """ # Copyright (c) 1998-2000 John Aycock # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # Modifications by R. White: # - Allow named groups in scanner patterns (other than the ones # inserted by the class constructor.) # - Speed up GenericScanner.tokenize(). # + Keep a list (indexlist) of the group numbers to check. # + Break out of loop after finding a match, since more than # one of the primary patterns cannot match (by construction of # the pattern.) # - Add optional value parameter to GenericParser.error. # - Add check for assertion error in ambiguity resolution. from __future__ import division # confidence high __version__ = 'SPARK-0.6.1rlw' import re import sys import string import cltoken token = cltoken def _namelist(instance): namelist, namedict, classlist = [], {}, [instance.__class__] for c in classlist: for b in c.__bases__: classlist.append(b) for name in c.__dict__.keys(): if name not in namedict: namelist.append(name) namedict[name] = 1 return namelist class GenericScanner: def __init__(self): pattern = self.reflect() self.re = re.compile(pattern, re.VERBOSE) self.index2func = {} self.indexlist = [] for name, number in self.re.groupindex.items(): # allow other named groups if hasattr(self, 't_' + name): self.index2func[number-1] = getattr(self, 't_' + name) self.indexlist.append(number-1) def makeRE(self, name): doc = getattr(self, name).__doc__ rv = '(?P<%s>%s)' % (name[2:], doc) return rv def reflect(self): rv = [] for name in _namelist(self): if name[:2] == 't_' and name != 't_default': rv.append(self.makeRE(name)) rv.append(self.makeRE('t_default')) return '|'.join(rv) def error(self, s, pos): print "Lexical error at position %s" % pos raise SystemExit def tokenize(self, s): pos = 0 n = len(s) match = self.re.match indexlist = self.indexlist index2func = self.index2func while pos < n: m = match(s, pos) if m is None: self.error(s, pos) groups = m.groups() for i in indexlist: if groups[i] is not None: index2func[i](groups[i]) # assume there is only a single match break pos = m.end() def t_default(self, s): r'( . | \n )+' pass class GenericParser: def __init__(self, start): self.rules = {} self.rule2func = {} self.rule2name = {} self.collectRules() self.startRule = self.augment(start) self.ruleschanged = 1 _START = 'START' _EOF = 'EOF' # # A hook for GenericASTBuilder and GenericASTMatcher. # def preprocess(self, rule, func): return rule, func def addRule(self, doc, func): rules = doc.split() index = [] for i in range(len(rules)): if rules[i] == '::=': index.append(i-1) index.append(len(rules)) for i in range(len(index)-1): lhs = rules[index[i]] rhs = rules[index[i]+2:index[i+1]] rule = (lhs, tuple(rhs)) rule, fn = self.preprocess(rule, func) if lhs in self.rules: self.rules[lhs].append(rule) else: self.rules[lhs] = [ rule ] self.rule2func[rule] = fn self.rule2name[rule] = func.__name__[2:] self.ruleschanged = 1 def collectRules(self): for name in _namelist(self): if name[:2] == 'p_': func = getattr(self, name) doc = func.__doc__ self.addRule(doc, func) def augment(self, start): # # Tempting though it is, this isn't made into a call # to self.addRule() because the start rule shouldn't # be subject to preprocessing. # startRule = (self._START, ( start, self._EOF )) self.rule2func[startRule] = lambda args: args[0] self.rules[self._START] = [ startRule ] self.rule2name[startRule] = '' return startRule def makeFIRST(self): # make the FIRST sets first = {} for key in self.rules.keys(): first[key] = {} changed = 1 npass = 0 while (changed > 0): npass = npass+1 changed = 0 for key, this in first.items(): for lhs, rhs in self.rules[key]: for token in rhs: # add token or first[token] to this set # also track whether token derives epsilon; if it # does, need to add FIRST set for next token too derivesEpsilon = 0 if token not in self.rules: # this is a terminal if token not in this: this[token] = 1 changed = changed+1 else: # this is a nonterminal -- add its FIRST set for ntkey in first[token].keys(): if ntkey == "": derivesEpsilon = 1 elif ntkey != key and ntkey not in this: this[ntkey] = 1 changed = changed+1 if not derivesEpsilon: break else: # if get all the way through, add epsilon too if "" not in this: this[""] = 1 changed = changed+1 # make the rule/token lists self.makeTokenRules(first) def makeTokenRules(self,first): # make dictionary indexed by (nextSymbol, nextToken) with # list of all rules for nextSymbol that could produce nextToken tokenRules = {} # make a list of all terminal tokens allTokens = {} for key, rule in self.rules.items(): for lhs, rhs in rule: for token in rhs: if token not in self.rules: # this is a terminal allTokens[token] = 1 for nextSymbol, flist in first.items(): for nextToken in flist.keys(): tokenRules[(nextSymbol, nextToken)] = [] if "" in flist: for nextToken in allTokens.keys(): tokenRules[(nextSymbol, nextToken)] = [] for prule in self.rules[nextSymbol]: prhs = prule[1] done = {} for element in prhs: pflist = first.get(element) if pflist is not None: if element not in done: done[element] = 1 # non-terminal for nextToken in pflist.keys(): if nextToken and nextToken not in done: done[nextToken] = 1 tokenRules[(nextSymbol, nextToken)].append(prule) if "" not in pflist: break else: # terminal token if element not in done: done[element] = 1 tokenRules[(nextSymbol, element)].append(prule) break else: # this entire rule can produce null # add it to all FIRST symbols and to null list tokenRules[(nextSymbol, "")].append(prule) for nextToken in allTokens.keys(): if nextToken not in done: done[nextToken] = 1 tokenRules[(nextSymbol, nextToken)].append(prule) self.tokenRules = tokenRules # # An Earley parser, as per J. Earley, "An Efficient Context-Free # Parsing Algorithm", CACM 13(2), pp. 94-102. Also J. C. Earley, # "An Efficient Context-Free Parsing Algorithm", Ph.D. thesis, # Carnegie-Mellon University, August 1968, p. 27. # def typestring(self, token): return None def error(self, token, value=None): print "Syntax error at or near `%s' token" % token if value is not None: print str(value) raise SystemExit def parse(self, tokens): tree = {} # add a Token instead of a string so references to # token.type in buildState work for EOF symbol tokens.append(token.Token(self._EOF)) states = (len(tokens)+1)*[None] states[0] = [ (self.startRule, 0, 0) ] if self.ruleschanged: self.makeFIRST() self.ruleschanged = 0 for i in xrange(len(tokens)): states[i+1] = [] if states[i] == []: break self.buildState(tokens[i], states, i, tree) if i < len(tokens)-1 or states[i+1] != [ (self.startRule, 2, 0) ]: del tokens[-1] self.error(tokens[i-1]) rv = self.buildTree(tokens, tree, ((self.startRule, 2, 0), i+1)) del tokens[-1] return rv def buildState(self, token, states, i, tree): state = states[i] predicted = {} completed = {} # optimize inner loops state_append = state.append tokenRules_get = self.tokenRules.get for item in state: rule, pos, parent = item lhs, rhs = rule # # A -> a . (completer) # if pos == len(rhs): # track items completed within this rule if parent == i: if lhs in completed: completed[lhs].append((item,i)) else: completed[lhs] = [(item,i)] lhstuple = (lhs,) for prule, ppos, pparent in states[parent]: plhs, prhs = prule if prhs[ppos:ppos+1] == lhstuple: new = (prule, ppos+1, pparent) key = (new, i) if key in tree: tree[key].append((item, i)) else: state_append(new) tree[key] = [(item, i)] continue nextSym = rhs[pos] # # A -> a . B (predictor) # if nextSym in self.rules: if nextSym in predicted: # # this was already predicted -- but if it was # also completed entirely within this step, then # need to add completed version here too # if nextSym in completed: new = (rule, pos+1, parent) key = (new, i) if key in tree: tree[key].extend(completed[nextSym]) else: state_append(new) tree[key] = completed[nextSym] else: predicted[nextSym] = 1 # # Predictor using FIRST sets # Use cached list for this (nextSym, token) combo # for prule in tokenRules_get((nextSym, token.type),[]): state_append((prule, 0, i)) # # A -> a . c (scanner) # elif token.type == nextSym: #assert new not in states[i+1] states[i+1].append((rule, pos+1, parent)) def buildTree(self, tokens, tree, root): stack = [] self.buildTree_r(stack, tokens, -1, tree, root) return stack[0] def buildTree_r(self, stack, tokens, tokpos, tree, root): (rule, pos, parent), state = root while pos > 0: want = ((rule, pos, parent), state) if want not in tree: # # Since pos > 0, it didn't come from closure, # and if it isn't in tree[], then there must # be a terminal symbol to the left of the dot. # (It must be from a "scanner" step.) # pos = pos - 1 state = state - 1 stack.insert(0, tokens[tokpos]) tokpos = tokpos - 1 else: # # There's a NT to the left of the dot. # Follow the tree pointer recursively (>1 # tree pointers from it indicates ambiguity). # Since the item must have come about from a # "completer" step, the state where the item # came from must be the parent state of the # item the tree pointer points to. # children = tree[want] if len(children) > 1: # RLW I'm leaving in this try block for the moment, # RLW although the current ambiguity resolver never # RLW raises and assertion error (which I think may # RLW be a bug.) try: child = self.ambiguity(children) except AssertionError: del tokens[-1] print stack[0] # self.error(tokens[tokpos], 'Parsing ambiguity'+str(children[:])) self.error(stack[0], 'Parsing ambiguity'+str(children[:])) else: child = children[0] tokpos = self.buildTree_r(stack, tokens, tokpos, tree, child) pos = pos - 1 (crule, cpos, cparent), cstate = child state = cparent lhs, rhs = rule result = self.rule2func[rule](stack[:len(rhs)]) stack[:len(rhs)] = [result] return tokpos def ambiguity(self, children): # # XXX - problem here and in collectRules() if the same # rule appears in >1 method. But in that case the # user probably gets what they deserve :-) Also # undefined results if rules causing the ambiguity # appear in the same method. # RLW Modified so it uses rule as the key # RLW If we stick with this, can eliminate rule2name # sortlist = [] name2index = {} for i in range(len(children)): ((rule, pos, parent), index) = children[i] lhs, rhs = rule # name = self.rule2name[rule] sortlist.append((len(rhs), rule)) name2index[rule] = i sortlist.sort() list = map(lambda (a,b): b, sortlist) return children[name2index[self.resolve(list)]] def resolve(self, list): # # Resolve ambiguity in favor of the shortest RHS. # Since we walk the tree from the top down, this # should effectively resolve in favor of a "shift". # # RLW Question -- This used to raise an assertion error # RLW here if there were two choices with the same RHS length. # RLW Why doesn't that happen now? Looks like an error? return list[0] # # GenericASTBuilder automagically constructs a concrete/abstract syntax tree # for a given input. The extra argument is a class (not an instance!) # which supports the "__setslice__" and "__len__" methods. # # XXX - silently overrides any user code in methods. # class GenericASTBuilder(GenericParser): def __init__(self, AST, start): GenericParser.__init__(self, start) self.AST = AST def preprocess(self, rule, func): rebind = lambda lhs, self=self: \ lambda args, lhs=lhs, self=self: \ self.buildASTNode(args, lhs) lhs, rhs = rule return rule, rebind(lhs) def buildASTNode(self, args, lhs): children = [] for arg in args: if isinstance(arg, self.AST): children.append(arg) else: children.append(self.terminal(arg)) return self.nonterminal(lhs, children) def terminal(self, token): return token def nonterminal(self, type, args): rv = self.AST(type) rv[:len(args)] = args return rv # # GenericASTTraversal is a Visitor pattern according to Design Patterns. For # each node it attempts to invoke the method n_, falling # back onto the default() method if the n_* can't be found. The preorder # traversal also looks for an exit hook named n__exit (no default # routine is called if it's not found). To prematurely halt traversal # of a subtree, call the prune() method -- this only makes sense for a # preorder traversal. # class GenericASTTraversalPruningException(Exception): pass class GenericASTTraversal: def __init__(self, ast): self.ast = ast self.collectRules() def collectRules(self): self.rules = {} self.exitrules = {} for name in _namelist(self): if name[:2] == 'n_': self.rules[name[2:]] = getattr(self, name) if name[-5:] == '_exit': self.exitrules[name[2:-5]] = getattr(self, name) def prune(self): raise GenericASTTraversalPruningException def preorder(self, node=None): if node is None: node = self.ast try: name = node.type func = self.rules.get(name) if func is None: # add rule to cache so next time it is faster func = self.default self.rules[name] = func func(node) except GenericASTTraversalPruningException: return for kid in node: # if kid.type=='term' and len(kid._kids)==3 and kid._kids[1].type=='/': # # Not the place to check for integer divsion - the type is # # either INTEGER or IDENT (and we dont know yet what the # # underlying type of the IDENT is...) # print(kid._kids[0].type, kid._kids[1].type, kid._kids[2].type) self.preorder(kid) func = self.exitrules.get(name) if func is not None: func(node) def postorder(self, node=None): if node is None: node = self.ast for kid in node: self.postorder(kid) name = node.type func = self.rules.get(name) if func is None: # add rule to cache so next time it is faster func = self.default self.rules[name] = func func(node) def default(self, node): pass # # GenericASTMatcher. AST nodes must have "__getitem__" and "__cmp__" # implemented. # # XXX - makes assumptions about how GenericParser walks the parse tree. # class GenericASTMatcher(GenericParser): def __init__(self, start, ast): GenericParser.__init__(self, start) self.ast = ast def preprocess(self, rule, func): rebind = lambda func, self=self: \ lambda args, func=func, self=self: \ self.foundMatch(args, func) lhs, rhs = rule rhslist = list(rhs) rhslist.reverse() return (lhs, tuple(rhslist)), rebind(func) def foundMatch(self, args, func): func(args[-1]) return args[-1] def match_r(self, node): self.input.insert(0, node) children = 0 for child in node: if children == 0: self.input.insert(0, '(') children = children + 1 self.match_r(child) if children > 0: self.input.insert(0, ')') def match(self, ast=None): if ast is None: ast = self.ast self.input = [] self.match_r(ast) self.parse(self.input) def resolve(self, list): # # Resolve ambiguity in favor of the longest RHS. # return list[-1] pyraf-2.1.15/lib/pyraf/gki.py0000644000665500117240000014145713310762263016750 0ustar sontagpassdev00000000000000 """ IRAF GKI interpreter -- abstract implementation The main classes here are GkiKernel and GkiController. GkiKernel is the base class for graphics kernel implementations. Methods: control() append() Called by irafexecute to plot IRAF GKI metacode. pushStdio() popStdio() getStdin/out/err() Hooks to allow text I/O to special graphics devices, e.g. the status line. flush() Flush graphics. May print or write a file for hardcopy devices. clearReturnData() Empty out return data buffer. gcur() Activate interactive graphics and return key pressed, position, etc. redrawOriginal() Redraw graphics without any annotations, overlays, etc. undoN() Allows annotations etc. to be removed. Classes that implement a kernel provide methods named gki_* and control_* which are called by the translate and control methods using dispatch tables (functionTable and controlFunctionTable). The complete lists of methods are in opcode2name and control2name. Python introspection is used to determine which methods are implemented; it is OK for unused methods to be omitted. GkiProxy is a GkiKernel proxy class that implements the GkiKernel interface and allows switching between GkiKernel objects (effectively allowing the kernel type to change.) GkiController is a GkiProxy that allows switching between different graphics kernels as directed by commands embedded in the metacode stream. $Id$ """ from __future__ import division import numpy from types import * import os, sys, string, re from stsci.tools.irafglobals import IrafError from stsci.tools.for2to3 import ndarr2str, ndarr2bytes import wutil, graphcap, irafgwcs import fontdata from textattrib import * # use this form since the iraf import is circular import pyraf.iraf nIrafColors = 16 BOI = -1 # beginning of instruction sentinel NOP = 0 # no op value GKI_MAX = 32767 GKI_MAX_FLOAT = float(GKI_MAX) NDC_MAX = GKI_MAX_FLOAT/(GKI_MAX_FLOAT+1) GKI_MAX_OP_CODE = 27 GKI_FLOAT_FACTOR = 100. MAX_ERROR_COUNT = 7 # gki opcode constants GKI_EOF = 0 GKI_OPENWS = 1 GKI_CLOSEWS = 2 GKI_REACTIVATEWS = 3 GKI_DEACTIVATEWS = 4 GKI_MFTITLE = 5 GKI_CLEARWS = 6 GKI_CANCEL = 7 GKI_FLUSH = 8 GKI_POLYLINE = 9 GKI_POLYMARKER = 10 GKI_TEXT = 11 GKI_FILLAREA = 12 GKI_PUTCELLARRAY = 13 GKI_SETCURSOR = 14 GKI_PLSET = 15 GKI_PMSET = 16 GKI_TXSET = 17 GKI_FASET = 18 GKI_GETCURSOR = 19 GKI_GETCELLARRAY = 20 GKI_ESCAPE = 25 GKI_SETWCS = 26 GKI_GETWCS = 27 GKI_ILLEGAL_LIST = (21,22,23,24) CONTROL_OPENWS = 1 CONTROL_CLOSEWS = 2 CONTROL_REACTIVATEWS = 3 CONTROL_DEACTIVATEWS = 4 CONTROL_CLEARWS = 6 CONTROL_SETWCS = 26 CONTROL_GETWCS = 27 # Names of methods in GkiKernel that handle the various opcodes # This also can be useful for debug prints of opcode values. # Initial dictionaries with all opcodes unknown opcode2name = {} control2name = {} for i in range(GKI_MAX_OP_CODE+1): opcode2name[i] = 'gki_unknown' control2name[i] = 'control_unknown' opcode2name.update({ GKI_EOF: 'gki_eof', GKI_OPENWS: 'gki_openws', GKI_CLOSEWS: 'gki_closews', GKI_REACTIVATEWS: 'gki_reactivatews', GKI_DEACTIVATEWS: 'gki_deactivatews', GKI_MFTITLE: 'gki_mftitle', GKI_CLEARWS: 'gki_clearws', GKI_CANCEL: 'gki_cancel', GKI_FLUSH: 'gki_flush', GKI_POLYLINE: 'gki_polyline', GKI_POLYMARKER: 'gki_polymarker', GKI_TEXT: 'gki_text', GKI_FILLAREA: 'gki_fillarea', GKI_PUTCELLARRAY: 'gki_putcellarray', GKI_SETCURSOR: 'gki_setcursor', GKI_PLSET: 'gki_plset', GKI_PMSET: 'gki_pmset', GKI_TXSET: 'gki_txset', GKI_FASET: 'gki_faset', GKI_GETCURSOR: 'gki_getcursor', GKI_GETCELLARRAY: 'gki_getcellarray', GKI_ESCAPE: 'gki_escape', GKI_SETWCS: 'gki_setwcs', GKI_GETWCS: 'gki_getwcs', }) # control channel opcodes control2name.update({ CONTROL_OPENWS: 'control_openws', CONTROL_CLOSEWS: 'control_closews', CONTROL_REACTIVATEWS: 'control_reactivatews', CONTROL_DEACTIVATEWS: 'control_deactivatews', CONTROL_CLEARWS: 'control_clearws', CONTROL_SETWCS: 'control_setwcs', CONTROL_GETWCS: 'control_getwcs', }) standardWarning = """ The graphics kernel for IRAF tasks has just received a metacode instruction (%s) it never expected to see. Please inform the STSDAS group of this occurrence.""" standardNotImplemented = \ """This IRAF task requires a graphics kernel facility not implemented in the Pyraf graphics kernel (%s).""" class EditHistory: """Keeps track of where undoable appends are made so they can be removed from the buffer on request. All it needs to know is how much as been added to the metacode stream for each edit. Since the process may add more gki, we distinguish specific edits with a marker, and those are used when undoing.""" def __init__(self): self.editinfo = [] def add(self, size, undomarker=0): self.editinfo.append((undomarker,size)) def NEdits(self): count = 0 for undomarker,size in self.editinfo: if undomarker: count = count+1 return count def popLastSize(self): tsize = 0 while len(self.editinfo) > 0: marker, size = self.editinfo.pop() tsize = tsize + size if marker: break return tsize def split(self,n): """Split edit buffer at metacode length n. Modifies this buffer to stop at n and returns a new EditHistory object with any edits beyond n.""" newEditHistory = EditHistory() tsize = 0 for i in xrange(len(self.editinfo)): marker, size = self.editinfo[i] tsize = tsize + size if tsize >= n: break else: # looks like all edits stay here return newEditHistory newEditHistory.editinfo = self.editinfo[i+1:] self.editinfo = self.editinfo[:i+1] if tsize != n: # split last edit newEditHistory.editinfo.insert(0, (marker, tsize-n)) self.editinfo[i] = (marker, n-(tsize-size)) return newEditHistory def acopy(a): """Return copy of numpy array a""" return numpy.array(a, copy=1) # GKI opcodes that clear the buffer _clearCodes = [ GKI_EOF, GKI_OPENWS, GKI_REACTIVATEWS, GKI_CLEARWS, GKI_CANCEL, ] #********************************************************************** class GkiBuffer: """A buffer for gki which allocates memory in blocks so that a new memory allocation is not needed everytime metacode is appended. Internally, buffer is numpy array: (numpy.zeros(N, numpy.int16).""" INCREMENT = 50000 def __init__(self, metacode=None): self.init(metacode) self.redoBuffer = [] def init(self, metacode=None): """Initialize to empty buffer or to metacode""" if metacode is not None: self.buffer = metacode self.bufferSize = len(metacode) self.bufferEnd = len(metacode) else: self.buffer = numpy.zeros(0, numpy.int16) self.bufferSize = 0 self.bufferEnd = 0 self.editHistory = EditHistory() self.prepareToRedraw() def prepareToRedraw(self): """Reset pointers in preparation for redraw""" # nextTranslate is pointer to next element in buffer to be # translated. It is needed because we may get truncated # messages, leaving some metacode to be prepended to next # message. self.nextTranslate = 0 # lastTranslate points to beginning of last metacode translated, # which may need to be removed if buffer is split self.lastTranslate = 0 self.lastOpcode = None def reset(self, last=0): """Discard everything up to end pointer End is lastTranslate if last is true, else nextTranslate """ if last: end = self.lastTranslate else: end = self.nextTranslate newEnd = self.bufferEnd - end if newEnd > 0: self.buffer[0:newEnd] = self.buffer[end:self.bufferEnd] self.bufferEnd = newEnd self.nextTranslate = self.nextTranslate-end self.lastTranslate = 0 if not last: self.lastOpcode = None else: # complete reset so buffer can shrink sometimes self.init() def split(self): """Split this buffer at nextTranslate and return a new buffer object with the rest of the metacode. lastOpcode may be removed if it triggered the buffer split (so we can append more metacode later if desired.) """ tail = acopy(self.buffer[self.nextTranslate:self.bufferEnd]) if self.lastTranslate < self.nextTranslate and \ self.lastOpcode in _clearCodes: # discard last opcode, it cleared the page self.bufferEnd = self.lastTranslate self.nextTranslate = self.lastTranslate else: # retain last opcode self.bufferEnd = self.nextTranslate # return object of same class as this newbuffer = self.__class__(tail) newbuffer.editHistory = self.editHistory.split(self.bufferEnd) return newbuffer def append(self, metacode, isUndoable=0): """Append metacode to buffer""" if self.bufferSize < (self.bufferEnd + len(metacode)): # increment buffer size and copy into new array diff = self.bufferEnd + len(metacode) - self.bufferSize nblocks = diff//self.INCREMENT + 1 self.bufferSize = self.bufferSize + nblocks * self.INCREMENT newbuffer = numpy.zeros(self.bufferSize, numpy.int16) if self.bufferEnd > 0: newbuffer[0:self.bufferEnd] = self.buffer[0:self.bufferEnd] self.buffer = newbuffer self.buffer[self.bufferEnd:self.bufferEnd+len(metacode)] = metacode self.bufferEnd = self.bufferEnd + len(metacode) self.editHistory.add(len(metacode), isUndoable) def isUndoable(self): """Returns true if there is anything to undo on this plot""" return (self.editHistory.NEdits() > 0) def undoN(self, nUndo=1): """Undo last nUndo edits and replot. Returns true if plot changed.""" changed = 0 while nUndo>0: size = self.editHistory.popLastSize() if size == 0: break self.bufferEnd = self.bufferEnd - size # add this chunk to end of buffer (use copy, not view) self.redoBuffer.append( acopy(self.buffer[self.bufferEnd:self.bufferEnd+size]) ) nUndo = nUndo-1 changed = 1 if changed: if self.bufferEnd <= 0: self.init() # reset translation pointer to beginning so plot gets redone # entirely self.nextTranslate = 0 self.lastTranslate = 0 return changed def isRedoable(self): """Returns true if there is anything to redo on this plot""" return len(self.redoBuffer)>0 def redoN(self, nRedo=1): """Redo last nRedo edits and replot. Returns true if plot changed.""" changed = 0 while self.redoBuffer and nRedo>0: code = self.redoBuffer.pop() self.append(code, isUndoable=1) nRedo = nRedo-1 changed = 1 return changed def get(self): """Return buffer contents (as numpy array, even if empty)""" return self.buffer[0:self.bufferEnd] def delget(self, last=0): """Return buffer up to end pointer, deleting those elements End is lastTranslate if last is true, else nextTranslate """ if last: end = self.lastTranslate else: end = self.nextTranslate b = acopy(self.buffer[:end]) self.reset(last) return b def getNextCode(self): """Read next opcode and argument from buffer, returning a tuple with (opcode, arg). Skips no-op codes and illegal codes. Returns (None,None) on end of buffer or when opcode is truncated.""" ip = self.nextTranslate lenMC = self.bufferEnd buffer = self.buffer while ip < lenMC: if buffer[ip] == NOP: ip = ip+1 elif buffer[ip] != BOI: print "WARNING: missynched graphics data stream" # find next possible beginning of instruction ip = ip + 1 while ip < lenMC: if buffer[ip] == BOI: break ip = ip + 1 else: # Unable to resync print "WARNING: unable to resynchronize in graphics data stream" break else: if ip+2 >= lenMC: break opcode = int(buffer[ip+1]) arglen = buffer[ip+2] if (ip+arglen) > lenMC: break self.lastTranslate = ip self.lastOpcode = opcode arg = buffer[ip+3:ip+arglen].astype(numpy.int) ip = ip + arglen if ((opcode < 0) or (opcode > GKI_MAX_OP_CODE) or (opcode in GKI_ILLEGAL_LIST)): print "WARNING: Illegal graphics opcode = ",opcode else: # normal return self.nextTranslate = ip return (opcode, arg) # end-of-buffer return self.nextTranslate = ip return (None, None) def __len__(self): return self.bufferEnd def __getitem__(self,i): if i >= self.bufferEnd: raise IndexError("buffer index out of range") return self.buffer[i] def __getslice__(self,i,j): if j > self.bufferEnd: j = self.bufferEnd return self.buffer[i:j] #********************************************************************** class GkiReturnBuffer: """A fifo buffer used to queue up metacode to be returned to the IRAF subprocess""" # Only needed for getcursor and getcellarray, neither of which are # currently implemented. def __init__(self): self.fifo = [] def reset(self): self.fifo = [] def put(self, metacode): self.fifo[:0] = metacode def get(self): if len(self.fifo): metacode = self.fifo.pop() else: raise Exception("Attempted read on empty gki input buffer") # stack of active IRAF tasks, used to identify source of plot tasknameStack = [] #********************************************************************** class GkiKernel: """Abstract class intended to be subclassed by implementations of GKI kernels. This is to provide a standard interface to irafexecute""" def __init__(self): # Basics needed for all instances self.createFunctionTables() self.returnData = None self.errorMessageCount = 0 self.stdin = None self.stdout = None self.stderr = None self._stdioStack = [] self.gkiPreferTtyIpc = None # see notes in the getter # no harm in allocating gkibuffer, doesn't actually allocate # space unless appended to. self.gkibuffer = GkiBuffer() def preferTtyIpc(self): """Getter. Return the attribute, set 1st if need be (lazy init).""" # Allow users to set the behavior of redirection choices # for special uses of PyRAF (e.g. embedded in other GUI's). Do not # set this without knowing what you are doing - it breaks some commonly # used command-line redirection within PyRAF. (thus default = False) if self.gkiPreferTtyIpc == None: self.gkiPreferTtyIpc = pyraf.iraf.envget('gkiprefertty','') == 'yes' return self.gkiPreferTtyIpc def createFunctionTables(self): """Use Python introspection to create function tables""" self.functionTable = [None]*(GKI_MAX_OP_CODE+1) self.controlFunctionTable = [None]*(GKI_MAX_OP_CODE+1) # to protect against typos, make list of all gki_ & control_ methods gkidict, classlist = {}, [self.__class__] for c in classlist: for b in c.__bases__: classlist.append(b) for name in c.__dict__.keys(): if name[:4] == "gki_" or name[:8] == "control_": gkidict[name] = 0 # now loop over all methods that might be present for opcode, name in opcode2name.items(): if name in gkidict: self.functionTable[opcode] = getattr(self, name) gkidict[name] = 1 # do same for control methods for opcode, name in control2name.items(): if name in gkidict: self.controlFunctionTable[opcode] = getattr(self, name) gkidict[name] = 1 # did we use all the gkidict methods? badlist = [] for name, value in gkidict.items(): if not value: badlist.append(name) if badlist: raise SyntaxError("Bug: error in definition of class %s\n" "Special method name is incorrect: %s" % (self.__class__.__name__, " ".join(badlist))) def control(self, gkiMetacode): gkiTranslate(gkiMetacode, self.controlFunctionTable) return self.returnData def append(self, gkiMetacode, isUndoable=0): # append metacode to the buffer buffer = self.getBuffer() buffer.append(gkiMetacode, isUndoable) # translate and display the metacode self.translate(buffer,0) def translate(self, gkiMetacode, redraw=0): # Note, during the perf. testing of #122 it was noticed that this # doesn't seem to get called; should be by self.append/undoN/redoN # (looks to be hidden in subclasses, by GkiInteractiveTkBase.translate) gkiTranslate(gkiMetacode, self.functionTable) def errorMessage(self, text): if self.errorMessageCount < MAX_ERROR_COUNT: print text self.errorMessageCount = self.errorMessageCount + 1 def getBuffer(self): # Normally, the buffer will be an attribute of the kernel, but # in some cases some kernels need more than one instance (interactive # graphics for example). In those cases, this method may be # overridden and the buffer will actually reside elsewhere return self.gkibuffer def flush(self): pass def clear(self): self.gkibuffer.reset() def taskStart(self, name): """Hook for stuff that needs to be done at start of task""" pass def taskDone(self, name): """Hook for stuff that needs to be done at completion of task""" pass def pre_imcur(self): """Hook for stuff that needs to be done right before imcur() call""" pass def undoN(self, nUndo=1): # Remove the last nUndo interactive appends to the metacode buffer buffer = self.getBuffer() if buffer.undoN(nUndo): self.prepareToRedraw() self.translate(buffer,1) def redoN(self, nRedo=1): # Redo the last nRedo edits to the metacode buffer buffer = self.getBuffer() if buffer.redoN(nRedo): self.translate(buffer,1) def prepareToRedraw(self): """Hook for things that need to be done before redraw from metacode""" pass def redrawOriginal(self): buffer = self.getBuffer() nUndo = buffer.editHistory.NEdits() if nUndo: self.undoN(nUndo) else: # just redraw it buffer.prepareToRedraw() self.prepareToRedraw() self.translate(buffer,1) def clearReturnData(self): # intended to be called after return data is used by the client self.returnData = None def gcur(self): # a default gcur routine to handle all the kernels that aren't # interactive raise EOFError("The specified graphics device is not interactive") # some special routines for getting and setting stdin/out/err attributes def pushStdio(self, stdin=None, stdout=None, stderr=None): """Push current stdio settings onto stack at set new values""" self._stdioStack.append((self.stdin, self.stdout, self.stderr)) self.stdin = stdin self.stdout = stdout self.stderr = stderr def popStdio(self): """Restore stdio settings from stack""" if self._stdioStack: self.stdin, self.stdout, self.stderr = self._stdioStack.pop() else: self.stdin, self.stdout, self.stderr = None, None, None def getStdin(self, default=None): # return our own or the default, depending on what is defined # and what is a tty try: if self.preferTtyIpc() and self.stdin and self.stdin.isatty(): return self.stdin elif (not self.stdin) or \ (default and not default.isatty()): return default except AttributeError: pass # OK if isatty is missing return self.stdin def getStdout(self, default=None): # return our own or the default, depending on what is defined # and what is a tty try: if self.preferTtyIpc() and self.stdout and self.stdout.isatty(): return self.stdout elif (not self.stdout) or \ (default and not default.isatty()): return default except AttributeError: pass # OK if isatty is missing return self.stdout def getStderr(self, default=None): # return our own or the default, depending on what is defined # and what is a tty try: if self.preferTtyIpc() and self.stderr and self.stderr.isatty(): return self.stderr elif (not self.stderr) or \ (default and not default.isatty()): return default except AttributeError: pass # OK if isatty is missing return self.stderr #********************************************************************** def gkiTranslate(metacode, functionTable): """General Function that can be used for decoding and interpreting the GKI metacode stream. FunctionTable is a 28 element list containing the functions to invoke for each opcode encountered. This table should be different for each kernel that uses this function and the control method. This may be called with either a gkiBuffer or a simple numerical array. If a gkiBuffer, it translates only the previously untranslated part of the gkiBuffer and updates the nextTranslate pointer.""" if isinstance(metacode, GkiBuffer): gkiBuffer = metacode else: gkiBuffer = GkiBuffer(metacode) opcode, arg = gkiBuffer.getNextCode() while opcode != None: f = functionTable[opcode] if f is not None: f(arg) # ! DEBUG ! timer("in gkiTranslate, for: "+opcode2name[opcode]) # good dbg spot opcode, arg = gkiBuffer.getNextCode() #********************************************************************** class DrawBuffer: """implement a buffer for draw commands which allocates memory in blocks so that a new memory allocation is not needed everytime functions are appended""" INCREMENT = 500 def __init__(self): self.buffer = None self.bufferSize = 0 self.bufferEnd = 0 self.nextTranslate = 0 def __len__(self): return self.bufferEnd def reset(self): """Discard everything up to nextTranslate pointer""" newEnd = self.bufferEnd - self.nextTranslate if newEnd > 0: self.buffer[0:newEnd] = self.buffer[self.nextTranslate:self.bufferEnd] self.bufferEnd = newEnd else: self.buffer = None self.bufferSize = 0 self.bufferEnd = 0 self.nextTranslate = 0 def append(self, funcargs): """Append a single (function,args) tuple to the list""" if self.bufferSize < self.bufferEnd + 1: # increment buffer size and copy into new array self.bufferSize = self.bufferSize + self.INCREMENT newbuffer = self.bufferSize*[None] if self.bufferEnd > 0: newbuffer[0:self.bufferEnd] = self.buffer[0:self.bufferEnd] self.buffer = newbuffer self.buffer[self.bufferEnd] = funcargs self.bufferEnd = self.bufferEnd + 1 def get(self): """Get current contents of buffer Note that this returns a view into the numpy array, so if the return value is modified the buffer will change too. """ if self.buffer: return self.buffer[0:self.bufferEnd] else: return [] def getNewCalls(self): """Return tuples (function, args) with all new calls in buffer""" ip = self.nextTranslate if ip < self.bufferEnd: self.nextTranslate = self.bufferEnd return self.buffer[ip:self.bufferEnd] else: return [] #----------------------------------------------- class GkiProxy(GkiKernel): """Base class for kernel proxy stdgraph is an instance of a GkiKernel to which calls are deferred. openKernel() method must be supplied to create a kernel and assign it to stdgraph. """ def __init__(self): GkiKernel.__init__(self) self.stdgraph = None def __del__(self): self.flush() def openKernel(self): raise Exception("bug: do not use GkiProxy class directly") # methods simply defer to stdgraph # some create kernel and some simply return if no kernel is defined def errorMessage(self, text): if not self.stdgraph: self.openKernel() return self.stdgraph.errorMessage(text) def getBuffer(self): if not self.stdgraph: self.openKernel() return self.stdgraph.getBuffer() def undoN(self, nUndo=1): if not self.stdgraph: self.openKernel() return self.stdgraph.undoN(nUndo) def prepareToRedraw(self): if self.stdgraph: return self.stdgraph.prepareToRedraw() def redrawOriginal(self): if not self.stdgraph: self.openKernel() return self.stdgraph.redrawOriginal() def translate(self, gkiMetacode, redraw=0): if not self.stdgraph: self.openKernel() return self.stdgraph.translate(gkiMetacode,redraw) def clearReturnData(self): if not self.stdgraph: self.openKernel() return self.stdgraph.clearReturnData() def gcur(self): if not self.stdgraph: self.openKernel() return self.stdgraph.gcur() # keep both local and stdgraph stdin/out/err up-to-date def pushStdio(self, stdin=None, stdout=None, stderr=None): """Push current stdio settings onto stack at set new values""" if self.stdgraph: self.stdgraph.pushStdio(stdin,stdout,stderr) #XXX still need some work here? self._stdioStack.append((self.stdin, self.stdout, self.stderr)) self.stdin = stdin self.stdout = stdout self.stderr = stderr def popStdio(self): """Restore stdio settings from stack""" #XXX still need some work here? if self.stdgraph: self.stdgraph.popStdio() if self._stdioStack: self.stdin, self.stdout, self.stderr = self._stdioStack.pop() else: self.stdin, self.stdout, self.stderr = None, None, None def getStdin(self, default=None): if self.stdgraph: return self.stdgraph.getStdin(default) else: return GkiKernel.getStdin(self, default) def getStdout(self, default=None): if self.stdgraph: return self.stdgraph.getStdout(default) else: return GkiKernel.getStdout(self, default) def getStderr(self, default=None): if self.stdgraph: return self.stdgraph.getStderr(default) else: return GkiKernel.getStderr(self, default) def append(self, arg, isUndoable=0): if self.stdgraph: self.stdgraph.append(arg,isUndoable) def control(self, gkiMetacode): if not self.stdgraph: self.openKernel() return self.stdgraph.control(gkiMetacode) def flush(self): if self.stdgraph: self.stdgraph.flush() def clear(self): if self.stdgraph: self.stdgraph.clear() def taskStart(self, name): if self.stdgraph: self.stdgraph.taskStart(name) def taskDone(self, name): if self.stdgraph: self.stdgraph.taskDone(name) #********************************************************************** class GkiController(GkiProxy): """Proxy that switches between interactive and other kernels This can gracefully handle changes in kernels which can appear in any open workstation instruction. It also uses lazy instantiation of the real kernel (which can be expensive). In one sense it is a factory class that will instantiate the necessary kernels as they are requested. Most external modules should access the gki functions through an instance of this class, gki.kernel. """ def __init__(self): GkiProxy.__init__(self) self.interactiveKernel = None self.lastDevice = None self.wcs = None def taskStart(self, name): # GkiController manages the tasknameStack tasknameStack.append(name) if self.stdgraph: self.stdgraph.taskStart(name) def taskDone(self, name): # delete name from stack; pop until we find it if necessary while tasknameStack: lastname = tasknameStack.pop() if lastname == name: break if self.stdgraph: self.stdgraph.taskDone(name) def control(self, gkiMetacode): # some control functions get executed here because they can # change the kernel gkiTranslate(gkiMetacode, self.controlFunctionTable) # rest of control is handled by the kernel if not self.stdgraph: self.openKernel() return self.stdgraph.control(gkiMetacode) def control_openws(self, arg): mode = arg[0] device = ndarr2str(arg[2:].astype(numpy.int8)).strip() self.openKernel(device) def openKernel(self, device=None): """Open kernel specified by device or by current value of stdgraph""" device = self.getDevice(device) graphcap = getGraphcap() # In either of these 3 cases we want to create a new kernel. The last # is the most complex, and it needs to be revisited (when the Device # class is refactored) but suffice it to say we only want to compare # the dict for the device, not the "master dict". if None == self.lastDevice or \ device != self.lastDevice or \ graphcap[device].dict[device] != graphcap.get(self.lastDevice)[self.lastDevice]: self.flush() executable = graphcap[device]['kf'] if executable == 'cl': # open (persistent) interactive kernel if not self.interactiveKernel: if wutil.hasGraphics: import gwm self.interactiveKernel = gwm.getGraphicsWindowManager() else: self.interactiveKernel = GkiNull() self.stdgraph = self.interactiveKernel else: import gkiiraf self.stdgraph = gkiiraf.GkiIrafKernel(device) self.stdin = self.stdgraph.stdin self.stdout = self.stdgraph.stdout self.stderr = self.stdgraph.stderr self.lastDevice = device def getDevice(self, device=None): """Starting with stdgraph, drill until a device is found in the graphcap or isn't""" if not device: device = pyraf.iraf.envget("stdgraph","") graphcap = getGraphcap() # protect against circular definitions devstr = device tried = {devstr: None} while not devstr in graphcap: pdevstr = devstr devstr = pyraf.iraf.envget(pdevstr,"") if not devstr: raise IrafError( "No entry found for specified stdgraph device `%s'" % device) elif devstr in tried: # track back through circular definition s = [devstr] next = pdevstr while next and (next != devstr): s.append(next) next = tried[next] if next: s.append(next) s.reverse() raise IrafError( "Circular definition in graphcap for device\n%s" % ' -> '.join(s)) else: tried[devstr] = pdevstr return devstr #********************************************************************** class GkiNull(GkiKernel): """A version of the graphics kernel that does nothing except warn the user that it does nothing. Used when graphics display isn't possible""" def __init__(self): print "No graphics display available for this session." print "Graphics tasks that attempt to plot to an interactive " + \ "screen will fail." GkiKernel.__init__(self) self.name = 'Null' def control_openws(self, arg): raise IrafError("Unable to plot graphics to screen") def control_reactivatews(self, arg): raise IrafError("Attempt to access graphics when " "it isn't available") def control_getwcs(self, arg): raise IrafError("Attempt to access graphics when " "it isn't available") def translate(self, gkiMetacode, redraw=0): pass #********************************************************************** class GkiRedirection(GkiKernel): """A graphics kernel whose only responsibility is to redirect metacode to a file-like object. Currently doesn't handle WCS get or set commands. (This is needed for situations when you append to a graphics file - RIJ)""" def __init__(self, filehandle): # Differs from all other constructors in that it takes a # file-like object as an argument. GkiKernel.__init__(self) self.filehandle = filehandle self.wcs = None def append(self, metacode): # Overloads the baseclass implementation. # metacode is array of 16-bit ints self.filehandle.write(ndarr2bytes(metacode)) # control needs to get and set WCS data def control_setwcs(self, arg): self.wcs = irafgwcs.IrafGWcs(arg) # Need to store this in the (persistent) kernel kernel.wcs = self.wcs def control_getwcs(self, arg): if not self.wcs: self.wcs = irafgwcs.IrafGWcs() if self.returnData: self.returnData = self.returnData + self.wcs.pack() else: self.returnData = self.wcs.pack() def getStdin(self, default=None): return default def getStdout(self, default=None): return default def getStderr(self, default=None): return default #********************************************************************** class GkiNoisy(GkiKernel): """Print metacode stream information""" def __init__(self): GkiKernel.__init__(self) self.name = 'Noisy' def control_openws(self, arg): print 'control_openws' def control_closews(self, arg): print 'control_closews' def control_reactivatews(self, arg): print 'control_reactivatews' def control_deactivatews(self, arg): print 'control_deactivatews' def control_clearws(self, arg): print 'control_clearws' def control_setwcs(self, arg): print 'control_setwcs' def control_getwcs(self, arg): print 'control_getwcs' def gki_eof(self, arg): print 'gki_eof' def gki_openws(self, arg): print 'gki_openws' def gki_closews(self, arg): print 'gki_closews' def gki_reactivatews(self, arg): print 'gki_reactivatews' def gki_deactivatews(self, arg): print 'gki_deactivatews' def gki_mftitle(self, arg): print 'gki_mftitle' def gki_clearws(self, arg): print 'gki_clearws' def gki_cancel(self, arg): print 'gki_cancel' def gki_flush(self, arg): print 'gki_flush' def gki_polyline(self, arg): print 'gki_polyline' def gki_polymarker(self, arg): print 'gki_polymarker' def gki_text(self, arg): print 'gki_text' def gki_fillarea(self, arg): print 'gki_fillarea' def gki_putcellarray(self, arg): print 'gki_putcellarray' def gki_setcursor(self, arg): print 'gki_setcursor' def gki_plset(self, arg): print 'gki_plset' def gki_pmset(self, arg): print 'gki_pmset' def gki_txset(self, arg): print 'gki_txset' def gki_faset(self, arg): print 'gki_faset' def gki_getcursor(self, arg): print 'gki_getcursor' def gki_getcellarray(self, arg): print 'gki_getcellarray' def gki_unknown(self, arg): print 'gki_unknown' def gki_escape(self, arg): print 'gki_escape' def gki_setwcs(self, arg): print 'gki_setwcs' def gki_getwcs(self, arg): print 'gki_getwcs' # Dictionary of all graphcap files known so far graphcapDict = {} def getGraphcap(filename=None): """Get graphcap file from filename (or cached version if possible)""" if filename is None: filename = pyraf.iraf.osfn(pyraf.iraf.envget('graphcap','dev$graphcap')) if not filename in graphcapDict: graphcapDict[filename] = graphcap.GraphCap(filename) return graphcapDict[filename] #XXX printPlot belongs in gwm, not gki? #XXX or maybe should be a method of gwm window manager def printPlot(window=None): """Print contents of window (default active window) to stdplot window must be a GkiKernel object (with a gkibuffer attribute.) """ import gwm, gkiiraf if window is None: window = gwm.getActiveGraphicsWindow() if window is None: return gkibuff = window.gkibuffer.get() if len(gkibuff): graphcap = getGraphcap() stdplot = pyraf.iraf.envget('stdplot','') if not stdplot: msg = "No hardcopy device defined in stdplot" elif not stdplot in graphcap: msg = "Unknown hardcopy device stdplot=`%s'" % stdplot else: printer = gkiiraf.GkiIrafKernel(stdplot) printer.append(gkibuff) printer.flush() msg = "snap completed" stdout = kernel.getStdout(default=sys.stdout) stdout.write("%s\n" % msg) #********************************************************************** class IrafGkiConfig: """Holds configurable aspects of IRAF plotting behavior This gets instantiated as a singleton instance so all windows can share the same configuration. """ def __init__(self): # All set to constants for now, eventually allow setting other # values # h = horizontal font dimension, v = vertical font dimension # ratio of font height to width self.fontAspect = 42./27. self.fontMax2MinSizeRatio = 4. # Empirical constants for font sizes self.UnitFontHWindowFraction = 1./80 self.UnitFontVWindowFraction = 1./45 # minimum unit font size in pixels (set to None if not relevant) self.minUnitHFontSize = 5. self.minUnitVFontSize = self.minUnitHFontSize * self.fontAspect # maximum unit font size in pixels (set to None if not relevant) self.maxUnitHFontSize = \ self.minUnitHFontSize * self.fontMax2MinSizeRatio self.maxUnitVFontSize = self.maxUnitHFontSize * self.fontAspect # offset constants to match iraf's notion of where 0,0 is relative # to the coordinates of a character self.vFontOffset = 0.0 self.hFontOffset = 0.0 # font sizing switch self.isFixedAspectFont = 1 # List of rgb tuples (0.0-1.0 range) for the default IRAF set of colors self.defaultColors = [ (0.,0.,0.), # black (1.,1.,1.), # white (1.,0.,0.), # red (0.,1.,0.), # green (0.,0.,1.), # blue (0.,1.,1.), # cyan (1.,1.,0.), # yellow (1.,0.,1.), # magenta (1.,1.,1.), # white # (0.32,0.32,0.32), # gray32 (0.18,0.31,0.31), # IRAF blue-green (1.,1.,1.), # white (1.,1.,1.), # white (1.,1.,1.), # white (1.,1.,1.), # white (1.,1.,1.), # white (1.,1.,1.), # white ] self.cursorColor = 2 # red if len(self.defaultColors) != nIrafColors: raise ValueError("defaultColors should have %d elements (has %d)" % (nIrafColors, len(self.defaultColors))) # old colors # (1.,0.5,0.), # coral # (0.7,0.19,0.38), # maroon # (1.,0.65,0.), # orange # (0.94,0.9,0.55), # khaki # (0.85,0.45,0.83), # orchid # (0.25,0.88,0.82), # turquoise # (0.91,0.53,0.92), # violet # (0.96,0.87,0.72) # wheat def setCursorColor(self, color): if not 0 <= color < len(self.defaultColors): raise ValueError("Bad cursor color (%d) should be >=0 and <%d" % (color, len(self.defaultColors)-1)) self.cursorColor = color def fontSize(self, gwidget): """Determine the unit font size for the given setup in pixels. The unit size refers to the horizonal size of fixed width characters (allow for proportionally sized fonts later?). Basically, if font aspect is not fixed, the unit font size is proportional to the window dimension (for v and h independently), with the exception that if min or max pixel sizes are enabled, they are 'clipped' at the specified value. If font aspect is fixed, then the horizontal size is the driver if the window is higher than wide and vertical size for the converse. """ hwinsize = gwidget.winfo_width() vwinsize = gwidget.winfo_height() hsize = hwinsize * self.UnitFontHWindowFraction vsize = vwinsize * self.UnitFontVWindowFraction if self.minUnitHFontSize is not None: hsize = max(hsize,self.minUnitHFontSize) if self.minUnitVFontSize is not None: vsize = max(vsize,self.minUnitVFontSize) if self.maxUnitHFontSize is not None: hsize = min(hsize,self.maxUnitHFontSize) if self.maxUnitVFontSize is not None: vsize = min(vsize,self.maxUnitVFontSize) if not self.isFixedAspectFont: fontAspect = vsize/hsize else: hsize = min(hsize, vsize/self.fontAspect) vsize = hsize * self.fontAspect fontAspect = self.fontAspect return (hsize, fontAspect) def getIrafColors(self): return self.defaultColors # create the singleton instance _irafGkiConfig = IrafGkiConfig() #----------------------------------------------- class IrafLineStyles: def __init__(self): self.patterns = [0x0000,0xFFFF,0x00FF,0x5555,0x33FF] class IrafHatchFills: def __init__(self): # Each fill pattern is a 32x4 ubyte array (represented as 1-d). # These are computed on initialization rather than using a # 'data' type initialization since they are such simple patterns. # these arrays are stored in a pattern list. Pattern entries # 0-2 should never be used since they are not hatch patterns. # so much for these, currently PyOpenGL does not support # glPolygonStipple()! But adding it probably is not too hard. self.patterns = [None]*7 # pattern 3, vertical stripes p = numpy.zeros(128,numpy.int8) p[0:4] = [0x92,0x49,0x24,0x92] for i in xrange(31): p[(i+1)*4:(i+2)*4] = p[0:4] self.patterns[3] = p # pattern 4, horizontal stripes p = numpy.zeros(128,numpy.int8) p[0:4] = [0xFF,0xFF,0xFF,0xFF] for i in xrange(10): p[(i+1)*12:(i+1)*12+4] = p[0:4] self.patterns[4] = p # pattern 5, close diagonal striping p = numpy.zeros(128,numpy.int8) p[0:12] = [0x92,0x49,0x24,0x92,0x24,0x92,0x49,0x24,0x49,0x24,0x92,0x49] for i in xrange(9): p[(i+1)*12:(i+2)*12] = p[0:12] p[120:128] = p[0:8] self.patterns[5] = p # pattern 6, diagonal stripes the other way p = numpy.zeros(128,numpy.int8) p[0:12] = [0x92,0x49,0x24,0x92,0x49,0x24,0x92,0x49,0x24,0x92,0x49,0x24] for i in xrange(9): p[(i+1)*12:(i+2)*12] = p[0:12] p[120:128] = p[0:8] self.patterns[6] = p class LineAttributes: def __init__(self): self.linestyle = 1 self.linewidth = 1.0 self.color = 1 def set(self, linestyle, linewidth, color): self.linestyle = linestyle self.linewidth = linewidth self.color = color class FillAttributes: def __init__(self): self.fillstyle = 1 self.color = 1 def set(self, fillstyle, color): self.fillstyle = fillstyle self.color = color class MarkerAttributes: def __init__(self): # the first two attributes are not currently used in IRAF, so ditch'em self.color = 1 def set(self, markertype, size, color): self.color = color class TextAttributes: # Used as a structure definition basically, perhaps it should be made # more sophisticated. def __init__(self): self.charUp = 90. self.charSize = 1. self.charSpace = 0. self.textPath = CHARPATH_RIGHT self.textHorizontalJust = JUSTIFIED_NORMAL self.textVerticalJust = JUSTIFIED_NORMAL self.textFont = FONT_ROMAN self.textQuality = FQUALITY_NORMAL self.textColor = 1 self.font = fontdata.font1 # Place to keep font size and aspect for current window dimensions self.hFontSize = None self.fontAspect = None def set(self,charUp=90., charSize=1.,charSpace=0., textPath=CHARPATH_RIGHT, textHorizontalJust=JUSTIFIED_NORMAL, textVerticalJust=JUSTIFIED_NORMAL, textFont=FONT_ROMAN, textQuality=FQUALITY_NORMAL, textColor=1): self.charUp = charUp self.charSize = charSize self.charSpace = charSpace self.textPath = textPath self.textHorizontalJust = textHorizontalJust self.textVerticalJust = textVerticalJust self.textFont = textFont self.textQuality = textQuality self.textColor = textColor # Place to keep font size and aspect for current window dimensions def setFontSize(self, win): """Set the unit font size for a given window using the iraf configuration parameters contained in an attribute class""" conf = win.irafGkiConfig self.hFontSize, self.fontAspect = conf.fontSize(win.gwidget) def getFontSize(self): return self.hFontSize, self.fontAspect #----------------------------------------------- class FilterStderr: """Filter GUI messages out of stderr during plotting""" pat = re.compile('\031[^\035]*\035\037') def __init__(self): self.fh = sys.stderr def write(self, text): # remove GUI junk edit = self.pat.sub('',text) if edit: self.fh.write(edit) def flush(self): self.fh.flush() def close(self): pass #----------------------------------------------- class StatusLine: def __init__(self, status, name): self.status = status self.windowName = name def readline(self): """Shift focus to graphics, read line from status, restore focus""" wutil.focusController.setFocusTo(self.windowName) rv = self.status.readline() return rv def read(self, n=0): """Return up to n bytes from status line Reads only a single line. If n<=0, just returns the line. """ s = self.readline() if n>0: return s[:n] else: return s def write(self, text): self.status.updateIO(text=text.strip()) def flush(self): self.status.update_idletasks() def close(self): # clear status line self.status.updateIO(text="") def isatty(self): return 1 #----------------------------------------------- #******************************** def ndc(intarr): return intarr/(GKI_MAX_FLOAT+1) def ndcpairs(intarr): f = ndc(intarr) return f[0::2],f[1::2] # This is the proxy for the current graphics kernel kernel = GkiController() # Beware! This is highly experimental and was made only for a test case. def _resetGraphicsKernel(): global kernel import gwm if kernel: kernel.clearReturnData() kernel.flush() gwm.delete() kernel = None gwm._resetGraphicsWindowManager() kernel = GkiController() pyraf-2.1.15/lib/pyraf/iraf.py0000644000665500117240000000067713310762263017115 0ustar sontagpassdev00000000000000"""module iraf.py -- home for all the IRAF tasks and basic access functions $Id$ R. White, 1999 Jan 25 """ from __future__ import division # confidence high from iraffunctions import * # a few CL tasks have modified names (because they start with '_') import iraffunctions _curpack = iraffunctions.curpack _allocate = iraffunctions.clAllocate _deallocate = iraffunctions.clDeallocate _devstatus = iraffunctions.clDevstatus del iraffunctions pyraf-2.1.15/lib/pyraf/irafecl.py0000644000665500117240000003663313310762263017602 0ustar sontagpassdev00000000000000"""This module adds IRAF ECL style error handling to PyRAF.""" # $Id$ from __future__ import division # confidence high import inspect, sys from stsci.tools.irafglobals import Verbose import pyrafglobals, iraftask, irafexecute # this is better than what 2to3 does, since the iraf import is circular import pyraf.iraf executionMonitor = None class EclState(object): """An object which records the ECL state for one invocation of a CL proc: 1. The procedure's linemap converting Python line numberss to CL line numbers. 2. A mutable counter for tracking iferr blocking. """ def __init__(self, linemap): self._value = 0 self._linemap = linemap def __iadd__(self, value): self._value += value return self def __int__(self): return self._value def getTaskModule(): """Returns the module which supplies Task classes for the current language mode, either ECL or classic CL. """ if pyrafglobals._use_ecl: import irafecl return irafecl else: return iraftask class Erract(object): """Erract is a state variable (singleton) which corresponds to the IRAF ECL environment variable 'erract'. erract has the following properties which control ECL exception handling: abort | noabort An ECL task should stop and unwind when it encounters an untrapped error. trace | notrace Output to stderr for each task failure. flpr | noflpr Flush the process cache for failed tasks. clear | noclear Reset the $errno, $errmsg, $errtask variables with each task invocation, or not. full | nofull Show tracebacks for the entire ECL call stack or just the erring task. ecl | noecl Use ECL style error handling or classic PyRAF exception handling. """ def __init__(self, clear=True, flpr=True, abort=True, trace=True, full=True, ecl=True): self.clear = clear self.flpr = flpr self.abort = abort self.trace = trace self.full = full self.ecl = ecl self._fields = ["abort", "trace", "flpr", "clear", "full", "ecl"] def states(self): ans = "" for s in self._fields: if not self.__dict__[s]: s = "no" + s ans += s + " " return ans def set_one(self, field): flag = not field.startswith("no") if not flag: field = field[2:] if field in self._fields: self.__dict__[field] = flag else: raise ValueError("set erract: unknown behavior '" + field + "'") def adjust(self, values): for a in values.split(): self.set_one(a) erract = Erract() # IrafExecute --> IrafTask._run --> IrafTask.run # user_code --> IrafPyTask._run --> IrafTask.run def _ecl_runframe(frame): """Determines if frame corresponds to an IrafTask._run() method call.""" # print "runframe:",frame.f_code.co_name if frame.f_code.co_name != "_run": # XXXX necessary but not sufficient return False return True def _ecl_parent_task(): """Returns the local variables of the task which called this one. """ f = inspect.currentframe() while f and not _ecl_runframe(f): f = f.f_back if not f: return pyraf.iraf.cl return f.f_locals["self"] def _ecl_interpreted_frame(frame=None): """Returns the stack frame corresponding to the executing Python code of the nearest enclosing CL task. """ if frame is None: f = inspect.currentframe() else: f = frame priors = [] while f and not _ecl_runframe(f): priors.append(f) f = f.f_back if len(priors) >= 2: return priors[-2] else: return None class EclBase: def __init__(self, *args, **kw): self.__dict__['DOLLARerrno'] = 0 self.__dict__['DOLLARerrmsg'] = "" self.__dict__['DOLLARerrtask'] = "" self.__dict__['DOLLARerr_dzvalue'] = 1 self.__dict__['_ecl_pseudos'] = [ 'DOLLARerrno', 'DOLLARerrmsg', 'DOLLARerrtask', 'DOLLARerr_dzvalue' ] def is_pseudo(self, name): """Returns True iff 'name' is a pseudo variable or begins with _ecl""" return (name in self.__dict__["_ecl_pseudos"]) or name.startswith("_ecl") def run(self,*args,**kw): # OVERRIDE IrafTask.run """Execute this task with the specified arguments""" self.initTask(force=1) # Special _save keyword turns on parameter-saving. # Default is *not* to save parameters (so it is necessary # to use _save=1 to get parameter changes to be persistent.) if '_save' in kw: save = kw['_save'] del kw['_save'] else: save = 0 # Handle other special keywords specialKW = self._specialKW(kw) # Special Stdout, Stdin, Stderr keywords are used to redirect IO redirKW, closeFHList = pyraf.iraf.redirProcess(kw) # set parameters kw['_setMode'] = 1 self.setParList(*args, **kw) if Verbose>1: print "run %s (%s: %s)" % (self._name, self.__class__.__name__, self._fullpath) if self._runningParList: self._runningParList.lParam() # delete list of param dictionaries so it will be # recreated in up-to-date version if needed self._parDictList = None # apply IO redirection resetList = self._applyRedir(redirKW) self._ecl_clear_error_params() def _runcore(): try: # Hook for execution monitor if executionMonitor: executionMonitor(self) self._run(redirKW, specialKW) self._updateParList(save) if Verbose>1: print >> sys.stderr, 'Successful task termination' finally: rv = self._resetRedir(resetList, closeFHList) self._deleteRunningParList() if self._parDictList: self._parDictList[0] = (self._name, self.getParDict()) if executionMonitor: executionMonitor() return rv # if self._ecl_iferr_entered() and if erract.ecl: try: return _runcore() except Exception, e: self._ecl_handle_error(e) else: return _runcore() def _run(self, redirKW, specialKW): # OVERRIDE IrafTask._run for primitive (SPP, C, etc.) tasks to avoid exception trap. irafexecute.IrafExecute(self, pyraf.iraf.getVarDict(), **redirKW) def _ecl_push_err(self): """Method call emitted in compiled CL code to start an iferr block. Increments local iferr state counter to track iferr block nesting. """ s = self._ecl_state() s += 1 self._ecl_set_error_params(0, '', '') def _ecl_pop_err(self): """Method call emitted in compiled CL code to close an iferr block and start the handler. Returns $errno which is 0 iff no error occurred. Decrements local iferr state counter to track block nesting. """ s = self._ecl_state() s += -1 return self.DOLLARerrno def _ecl_handle_error(self, e): """IrafTask version of handle error: register error with calling task but continue.""" self._ecl_record_error(e) if erract.flpr: pyraf.iraf.flpr(self) parent = _ecl_parent_task() parent._ecl_record_error(e) self._ecl_trace(parent._ecl_err_msg(e)) def _ecl_trace(self, *args): """Outputs an ECL error message to stderr iff erract.trace is True.""" if erract.trace: s = "" for a in args: s += str(a) + " " sys.stderr.write(s+"\n") sys.stderr.flush() def _ecl_exception_properties(self, e): """This is a 'safe wrapper' which extracts the ECL pseudo parameter values from an exception. It works for both ECL and non-ECL exceptions. """ return (getattr(e, "errno", -1), getattr(e, "errmsg", str(e)), getattr(e, "errtask", "")) def _ecl_record_error(self, e): self._ecl_set_error_params(*self._ecl_exception_properties(e)) def _ecl_set_error_params(self, errno, msg, taskname): """Sets the ECL pseduo parameters for this task.""" self.DOLLARerrno = errno self.DOLLARerrmsg = msg self.DOLLARerrtask = taskname def _ecl_clear_error_params(self): """Clears the ECL pseudo parameters to a non-error condition.""" if erract.clear: self._ecl_set_error_params(0, "", "") def _ecl_err_msg(self, e): """Formats an ECL error message from an exception and returns it as a string.""" errno, errmsg, errtask = self._ecl_exception_properties(e) if errno and errmsg and errtask: text = "Error (%d): on line %d of '%s' from '%s':\n\t'%s'" % \ (errno, self._ecl_get_lineno(), self._name, errtask, errmsg) else: text = str(e) return text def _ecl_get_lineno(self, frame=None): """_ecl_get_lineno fetches the innermost frame of Python code compiled from a CL task. and then translates the current line number in that frame into it's CL line number and returns it. """ try: f = _ecl_interpreted_frame(frame) map = f.f_locals["_ecl"]._linemap return map[f.f_lineno] except: return 0 def _ecl_state(self, frame=None): """returns the EclState object corresponding to this task invocation.""" locals = _ecl_interpreted_frame(frame).f_locals return locals["_ecl"] def _ecl_iferr_entered(self): """returns True iff the current invocation of the task self is in an iferr or ifnoerr guarded block.""" try: return int(self._ecl_state()) > 0 except KeyError: return False def _ecl_safe_divide(self, a, b): """_ecl_safe_divide is used to wrap the division operator for ECL code and trap divide-by-zero errors.""" if b == 0: if not erract.abort or self._ecl_iferr_entered(): self._ecl_trace("Warning on line %d of '%s': divide by zero - using $err_dzvalue =" % (self._ecl_get_lineno(), self._name), self.DOLLARerr_dzvalue) return self.DOLLARerr_dzvalue else: pyraf.iraf.error(1, "divide by zero", self._name, suppress=False) return a / b def _ecl_safe_modulo(self, a, b): """_ecl_safe_modulus is used to wrap the modulus operator for ECL code and trap mod-by-zero errors.""" if b == 0: if not erract.abort or self._ecl_iferr_entered(): self._ecl_trace("Warning on line %d of task '%s': modulo by zero - using $err_dzvalue =" % (self._ecl_get_lineno(), self._name), self.DOLLARerr_dzvalue) return self.DOLLARerr_dzvalue else: pyraf.iraf.error(1, "modulo by zero", self._name, suppress=False) return a % b class SimpleTraceback(EclBase): def _ecl_handle_error(self, e): self._ecl_record_error(e) raise e class EclTraceback(EclBase): def _ecl_handle_error(self, e): """Python task version of handle_error: do traceback and possibly abort.""" self._ecl_record_error(e) parent =_ecl_parent_task() if parent: parent._ecl_record_error(e) if hasattr(e, "_ecl_traced"): if erract.full: self._ecl_traceback(e) raise e else: try: self._ecl_trace("ERROR (%d): %s" % (e.errno, e.errmsg)) except: self._ecl_trace("ERROR:", str(e)) self._ecl_traceback(e) if erract.abort: # and not self._ecl_iferr_entered(): e._ecl_traced = True raise e def _ecl_get_code(self, task, frame=None): pass def _ecl_traceback(self, e): raising_frame = inspect.trace()[-1][0] lineno = self._ecl_get_lineno(frame=raising_frame) cl_file = self.getFilename() try: cl_code = open(cl_file).readlines()[lineno-1].strip() except: cl_code = "" if hasattr(e, "_ecl_suppress_first_trace") and \ e._ecl_suppress_first_trace: del e._ecl_suppress_first_trace else: self._ecl_trace(" ", repr(cl_code)) self._ecl_trace(" line %d: %s" % (lineno , cl_file)) parent = _ecl_parent_task() if parent: parent_lineno = self._ecl_get_lineno() parent_file = parent.getFilename() try: parent_code = open(parent_file).readlines()[parent_lineno-1].strip() self._ecl_trace(" called as:", repr(parent_code)) except: pass ## The following classes exist as "ECL enabled" drop in replacements for the original ## PyRAF task classes. I factored things this way in an attempt to minimize the impact ## of ECL changes on ordinary PyRAF CL. class EclTask(EclBase, iraftask.IrafTask): def __init__(self, *args, **kw): EclBase.__init__(self, *args, **kw) iraftask.IrafTask.__init__(self, *args, **kw) IrafTask = EclTask class EclGKITask(SimpleTraceback, iraftask.IrafGKITask): def __init__(self, *args, **kw): SimpleTraceback.__init__(self, *args, **kw) iraftask.IrafGKITask.__init__(self, *args, **kw) IrafGKITask = EclGKITask class EclPset(SimpleTraceback, iraftask.IrafPset): def __init__(self, *args, **kw): SimpleTraceback.__init__(self, *args, **kw) iraftask.IrafPset.__init__(self, *args, **kw) def _run(self, *args, **kw): return iraftask.IrafPset._run(self, *args, **kw) IrafPset = EclPset class EclPythonTask(EclTraceback, iraftask.IrafPythonTask): def __init__(self, *args, **kw): EclTraceback.__init__(self, *args, **kw) iraftask.IrafPythonTask.__init__(self, *args, **kw) def _run(self, *args, **kw): return iraftask.IrafPythonTask._run(self, *args, **kw) IrafPythonTask = EclPythonTask class EclCLTask(EclTraceback, iraftask.IrafCLTask): def __init__(self, *args, **kw): EclTraceback.__init__(self, *args, **kw) iraftask.IrafCLTask.__init__(self, *args, **kw) def _run(self, *args, **kw): return iraftask.IrafCLTask._run(self, *args, **kw) IrafCLTask = EclCLTask class EclForeignTask(SimpleTraceback, iraftask.IrafForeignTask): def __init__(self, *args, **kw): SimpleTraceback.__init__(self, *args, **kw) iraftask.IrafForeignTask.__init__(self, *args, **kw) def _run(self, *args, **kw): return iraftask.IrafForeignTask._run(self, *args, **kw) IrafForeignTask = EclForeignTask class EclPkg(EclTraceback, iraftask.IrafPkg): def __init__(self, *args, **kw): EclTraceback.__init__(self, *args, **kw) iraftask.IrafPkg.__init__(self, *args, **kw) def _run(self, *args, **kw): return iraftask.IrafPkg._run(self, *args, **kw) IrafPkg = EclPkg def mutateCLTask2Pkg(o, loaded=1, klass=EclPkg): return iraftask.mutateCLTask2Pkg(o, loaded=loaded, klass=klass) pyraf-2.1.15/lib/pyraf/irafimport.py0000644000665500117240000001671413310762263020347 0ustar sontagpassdev00000000000000"""module irafimport.py -- modify import mechanism Modify module import mechanism so that (1) 'from iraf import pkg' automatically loads the IRAF package 'pkg' (2) 'import iraf' returns a wrapped module instance that allows minimum-match access to task names (e.g. iraf.imhead, not just iraf.imheader) Assumes that all IRAF tasks and packages are accessible as iraf module attributes. Only affects imports of iraf module. $Id$ R. White, 1999 August 17 """ from __future__ import absolute_import, division # confidence high import __builtin__ import sys from stsci.tools import minmatch PY3K = sys.version_info[0] > 2 _importHasLvlArg = PY3K or sys.version_info[1] >= 5 # is 2.*, handle no 1.* _reloadIsBuiltin = sys.version_info[0] < 3 IMPORT_DEBUG = False # Save the original hooks; replaced at bottom of module... _originalImport = __builtin__.__import__ if _reloadIsBuiltin: _originalReload = __builtin__.reload else: import imp _originalReload = imp.reload def restoreBuiltins(): """ Called before exiting pyraf - this puts import and reload back. """ __builtin__.__import__ = _originalImport if _reloadIsBuiltin: __builtin__.reload = _originalReload else: imp.reload = _originalReload def _irafImport(name, globals={}, locals={}, fromlist=[], level=-1): if IMPORT_DEBUG: print("irafimport called: "+name+", "+str(fromlist)+", "+str(level)) # do first: the default value for level changed to 0 as of Python 3.3 if PY3K and sys.version_info[1] >= 3 and level < 0: level = 0 # e.g. "from iraf import stsdas, noao" or "from .iraf import noao" if fromlist and (name in ["iraf", "pyraf.iraf", ".iraf"]): for task in fromlist: pkg = the_iraf_module.getPkg(task,found=1) if pkg is not None and not pkg.isLoaded(): pkg.run(_doprint=0, _hush=1) # must return a module for 'from' import if IMPORT_DEBUG: print("irafimport: case: from "+name+" import "+str(fromlist)) return _irafModuleProxy.module # e.g. "import iraf" or "from . import iraf" (fromlist is a list OR tuple) # (extra Python2 cases are not used in PY3K - double check this) if (PY3K and not fromlist and name == 'iraf') or \ ((not PY3K) and (name == "iraf")) or \ ((not PY3K) and name=='' and level==1 and len(fromlist)==1 and 'iraf' in fromlist): if IMPORT_DEBUG: print("irafimport: iraf case: n="+name+", fl="+str(fromlist)+ \ ", l="+str(level)) return _irafModuleProxy # e.g. "import pyraf.iraf" (return module is for pyraf, not iraf) if not fromlist and name == 'pyraf.iraf' and level == 0: assert 'pyraf' in sys.modules, 'Unexpected import error - contact STScI' if IMPORT_DEBUG: print("irafimport: pyraf.iraf case: n="+name+", fl="+str(fromlist)+ ", l="+str(level)+", will modify pyraf module") # builtin import below will return pyraf module, after having set up an # attr of it called 'iraf' which is the iraf module. Instead we want # to set the attr to be our proxy (this case maybe unused in Python 2). retval = sys.modules['pyraf'] retval.iraf = _irafModuleProxy return retval # ALL OTHER CASES (PASS-THROUGH) # e.g. "import sys" or "import stsci.tools.alert" # e.g. "import pyraf" or "from pyraf import wutil, gki" # e.g. Note! "import os, sys, re, glob" calls this 4 separate times, but # "from . import gki, gwm, iraf" is only a single call here! # !!! TEMPORARY KLUDGE !!! keep this code until cache files are updated if name: for module in ['minmatch', 'irafutils', 'dialog', 'listdlg', 'filedlg', 'alert', 'irafglobals']: if name == ('pyraf.%s' % module): name = 'stsci.tools.%s' % module # Replace any instances of 'pytools' with 'stsci.tools' -- the # new name of the former pytools package name = name.replace('pytools.', 'stsci.tools.') # Same for everything in fromlist (which is a tuple in PY3K) if fromlist: fromlist = tuple([item.replace('pytools', 'stsci.tools') for item in fromlist]) # !!! END TEMPORARY KLUDGE !!! hadIrafInList = fromlist and 'iraf' in fromlist and name=='' and level>0 if IMPORT_DEBUG: print("irafimport - PASSTHRU: n="+name+", fl="+str(fromlist)+", l="+ str(level)) if _importHasLvlArg: retval = _originalImport(name, globals, locals, fromlist, level) else: # we could assert here that level is default, but it's safe to assume retval = _originalImport(name, globals, locals, fromlist) if hadIrafInList: # Use case is: "from . import gki, gwm, iraf" # Overwrite with our proxy (see pyraf.iraf case) retval.__setattr__('iraf', _irafModuleProxy) return retval def _irafReload(module): if isinstance(module, _irafModuleClass): #XXX Not sure this is correct module.module = _originalReload(module.module) return module else: return _originalReload(module) class _irafModuleClass: """Proxy for iraf module that makes tasks appear as attributes""" def __init__(self): self.__dict__['module'] = None def _moduleInit(self): global the_iraf_module self.__dict__['module'] = the_iraf_module self.__dict__['__name__'] = the_iraf_module.__name__ # create minmatch dictionary of current module contents self.__dict__['mmdict'] = minmatch.MinMatchDict(vars(self.module)) def __getattr__(self, attr): if self.module is None: self._moduleInit() # first try getting this attribute directly from the usual module try: return getattr(self.module, attr) except AttributeError: pass # if that fails, try getting a task with this name try: return self.module.getTask(attr) except minmatch.AmbiguousKeyError, e: raise AttributeError(str(e)) except KeyError, e: pass # last try is minimum match dictionary of rest of module contents try: return self.mmdict[attr] except KeyError: raise AttributeError("Undefined IRAF task `%s'" % (attr,)) def __setattr__(self, attr, value): # add an attribute to the module itself setattr(self.module, attr, value) self.mmdict.add(attr, value) def getAllMatches(self, taskname): """Get list of names of all tasks that may match taskname Useful for command completion. """ if self.module is None: self._moduleInit() if taskname == "": matches = self.mmdict.keys() else: matches = self.mmdict.getallkeys(taskname, []) matches.extend(self.module.getAllTasks(taskname)) return matches # Install our hooks __builtin__.__import__ = _irafImport if _reloadIsBuiltin: __builtin__.reload = _irafReload else: imp.reload = _irafReload # create the module proxy _irafModuleProxy = _irafModuleClass() # import iraf module using original mechanism if PY3K: # necessary as of Python 3.3+ : try "import pyraf.iraf" pyrafmod = _originalImport('pyraf.iraf', globals(), locals(), []) the_iraf_module = pyrafmod.iraf else: the_iraf_module = _originalImport('iraf', globals(), locals(), []) # leaving if IMPORT_DEBUG: print("irafimport: passed final import") pyraf-2.1.15/lib/pyraf/irafcompleter.py0000644000665500117240000003241713310762263021025 0ustar sontagpassdev00000000000000"""irafcompleter.py: command-line completion for pyraf Does taskname and filename completion using tab key. Another thought would be to use raw_input to do the input when IRAF tasks prompt for input, and to use a special completer function that just completes filenames (but knows about IRAF virtual filenames as well as the native file system.) See the notes in the (standard Python) module rlcompleter.py for more information. $Id$ RLW, 2000 February 13 """ from __future__ import division # confidence high import __builtin__ import __main__ import string, re, keyword, glob, os, sys import iraf from stsci.tools import minmatch try: import readline from rlcompleter import Completer except ImportError: readline = None Completer = object print('readline is not installed, some functionality will be lost') # dictionaries mapping between characters and readline names char2lab = {} lab2char = {} for i in range(1,27): char = chr(i) ichar = chr(ord('a')+i-1) lab = "Control-%s" % ichar char2lab[char] = lab lab2char[lab] = char lab2char["Control-%s" % ichar] = char lab2char[r"\C-%s" % ichar] = char char2lab["\t"] = "tab" lab2char["tab"] = "\t" char2lab["\033"] = "esc" lab2char["esc"] = "\033" lab2char["escape"] = "\033" lab2char[r"\e"] = "\033" # commands that take a taskname as argument taskArgDict = minmatch.MinMatchDict({ 'unlearn': 1, 'eparam': 1, 'lparam': 1, 'dparam': 1, 'update': 1, 'help': 1, 'prcache': 1, 'flprcache': 1, }) # commands that take a package name as argument pkgArgDict = { '?': 1, } completer = None class IrafCompleter(Completer): def __init__(self): global completer completer = self if hasattr(Completer, '__init__'): Completer.__init__(self) self.completionChar = None self.taskpat = re.compile(r"(\?|(?:\w+))[ \t]+(?=$|[\w.<>|/~'" +r'"])') # executive commands dictionary (must be set by user) self.executiveDict = minmatch.MinMatchDict() def activate(self, char="\t"): """Turn on completion using the specified character""" if readline == None: return self.deactivate() lab = char2lab.get(char, char) if lab==char: char = lab2char.get(lab, lab) readline.set_completer(self.complete) readline.parse_and_bind("%s: complete" % lab) readline.parse_and_bind("set bell-style none") readline.parse_and_bind("set show-all-if-ambiguous") self.completionChar = char # remove dash from delimiter set (fix submitted by Joe P. Ninan 4/16/14) delims = readline.get_completer_delims() delims = delims.replace('-','') readline.set_completer_delims(delims) # load any cmd history hfile = os.getenv('HOME','.')+os.sep+'.pyraf_history' if os.path.exists(hfile): try: readline.read_history_file(hfile) except IOError, e: # we do NOT want this to prevent startup. see ticket #132 print 'ERROR reading "'+hfile+'" -> '+str(e) def deactivate(self): """Turn off completion, restoring old behavior for character""" if readline != None and self.completionChar: # restore normal behavior for previous completion character lab = char2lab.get(self.completionChar, self.completionChar) readline.parse_and_bind("%s: self-insert" % lab) self.completionChar = None def executive(self, elist): """Add list of executive commands (assumed to start with '.')""" self.executiveDict = minmatch.MinMatchDict() for cmd in elist: self.executiveDict.add(cmd, 1) def global_matches(self, text): """Compute matches when text is a simple name. Return a list of all keywords, built-in functions and names currently defined in __main__ that match. Also return IRAF task matches. """ line = self.get_line_buffer() if line == "" and self.completionChar == "\t": # Make tab insert blanks at the beginning of an empty line # Insert 4 spaces for tabs (readline adds an additional blank) #XXX is converting to blanks really a good idea? #XXX ought to allow user to change this mapping return [" "] elif line == text: # first token on line return self.primary_matches(text) else: # different completion strategy if not the first token on the line return self.secondary_matches(text, line) def get_line_buffer(self): """Returns current line through cursor position with leading whitespace stripped """ if readline == None: return '' else: line = readline.get_line_buffer()[:readline.get_endidx()] return line.lstrip() def primary_matches(self, text): """Return matches when text is at beginning of the line""" matches = [] n = len(text) for list in [keyword.kwlist, __builtin__.__dict__.keys(), __main__.__dict__.keys()]: for word in list: if word[:n] == text: matches.append(word) # IRAF module functions matches.extend(iraf.getAllMatches(text)) return matches def secondary_matches(self, text, line): """Compute matches for tokens when not at start of line""" # Check first character following initial alphabetic string. # If next char is alphabetic (or null) use filename matches. # Also use filename matches if line starts with '!'. # Otherwise use matches from Python dictionaries. lt = len(line)-len(text) if line[:1] == "!": # Matching filename for OS escapes # Ideally would use tcsh-style matching of commands # as first argument, but that looks unreasonably hard return self.filename_matches(text, line[:lt]) m = self.taskpat.match(line) if m is None or keyword.iskeyword(m.group(1)): if line[lt-1:lt] in ['"', "'"]: # use filename matches for quoted strings return self.filename_matches(text, line[:lt]) else: if not hasattr(self, "namespace"): self.namespace = {} return Completer.global_matches(self,text) else: taskname = m.group(1) # check for pipe/redirection using last non-blank character mpipe = re.search(r"[|><][ \t]*$", line[:lt]) if mpipe: s = mpipe.group(0) if s[0] == "|": # pipe -- use task matches return iraf.getAllMatches(text) else: # redirection -- just match filenames return self.filename_matches(text, line[:lt]) elif taskname in taskArgDict: # task takes task names as arguments return iraf.getAllTasks(text) elif taskname in pkgArgDict: # task takes package names as arguments return iraf.getAllPkgs(text) else: return self.argument_matches(text, taskname, line) def argument_matches(self, text, taskname, line): """Compute matches for tokens that could be file or parameter names""" matches = [] # only look at keywords if this one was whitespace-delimited # this avoids matching keywords after e.g. directory part of filename lt = len(line)-len(text) if line[lt-1:lt] in " \t": m = re.match(r"\w*$", text) if m is not None: # could be a parameter name task = iraf.getTask(taskname, found=1) # get all parameters that could match (null list if none) if task is not None: matches = task.getAllMatches(text) # add matching filenames matches.extend(self.filename_matches(text, line[:lt])) return matches def filename_matches(self, text, line): """return matching filenames unless text contains wildcard characters""" if glob.has_magic(text): return [] # look for IRAF virtual filenames #XXX This might be simplified if '$' and '/' were added to the set #XXX of characters permitted in words. Can't do that now, as #XXX far as I can tell, but Python 1.6 should allow it. #XXX Need to improve this for filenames that include characters #XXX not included in the spanned text. E.g. .csh does not #XXX work because the '.' is not part of the name, and filenames #XXX with embedded '-' or '+' do not work. if line[-1] == '$': # preceded by IRAF environment variable m = re.search(r'\w*\$$', line) dir = iraf.Expand(m.group()) elif line[-1] == os.sep: # filename is preceded by path separator # match filenames with letters, numbers, $, ~, ., -, +, and # directory separator m = re.search(r'[\w.~$+-%s]*$' % os.sep, line) dir = iraf.Expand(m.group()) else: dir = '' return self._dir_matches(text, dir) def _dir_matches(self, text, dir): """Return list of files matching text in the given directory""" # note this works whether the expanded dir variable is # actually a directory (with a slash at the end) or not flist = glob.glob(dir + text + '*') # Strip path and append / to directories l = len(dir) for i in range(len(flist)): s = flist[i] if os.path.isdir(s): flist[i] = s[l:] + os.sep else: flist[i] = s[l:] # If only a single directory matches, get a list of the files # in the directory too. This has the side benefit of suppressing # the extra space added to the name by readline. # Include directory itself in the list to avoid autocompleting # parts of filenames when the directory has just been filled in. #--------------------------------------------------------------------- # Commented out on 12 Oct 2010. While some people may enjoy this # convenience, it seems to be disturbing to the majority of users, see # ticket #113. Will comment out but leave code here. # if len(flist)==1 and flist[0][-1] == os.sep: # flist.extend(self._dir_matches(flist[0], dir)) #--------------------------------------------------------------------- return flist def attr_matches(self, text): """Compute matches when text contains a dot.""" line = self.get_line_buffer() if line == text: # at start of line, special handling for iraf.xxx and # taskname.xxx fields = text.split(".") if fields[0] == "": # line starts with dot, look in executive commands return self.executive_matches(text) elif fields[0] == "iraf": return self.taskdot_matches(fields) elif iraf.getTask(fields[0], found=1): # include both eval results and task. matches fields.insert(0, 'iraf') matches = self.taskdot_matches(fields) try: matches.extend(Completer.attr_matches(self,text)) except KeyboardInterrupt: raise except: pass return matches else: return Completer.attr_matches(self,text) else: # Check first character following initial alphabetic string # If next char is alphabetic (or null) use filename matches # Otherwise use matches from Python dictionaries #XXX need to make this consistent with the other places #XXX where same tests are done m = self.taskpat.match(line) if m is None or keyword.iskeyword(m.group(1)): fields = text.split(".") if fields[0] == "iraf": return self.taskdot_matches(fields) else: return Completer.attr_matches(self,text) else: #XXX Could try to match pset.param keywords too? lt = len(line)-len(text) return self.filename_matches(text, line[:lt]) def executive_matches(self, text): """Return matches to executive commands""" return self.executiveDict.getallkeys(text) def taskdot_matches(self, fields): """Return matches for iraf.package.task.param...""" head = ".".join(fields[:-1]) tail = fields[-1] matches = eval("%s.getAllMatches(%s)" % (head, `tail`)) def addhead(s, head=head+"."): return head+s return map(addhead, matches) def activate(c="\t"): completer.activate(c) def deactivate(): completer.deactivate() pyraf-2.1.15/lib/pyraf/gkigcur.py0000644000665500117240000002172113310762263017620 0ustar sontagpassdev00000000000000""" implement IRAF gcur functionality $Id$ """ from __future__ import division # confidence high import string, os, sys, numpy import Tkinter as TKNTR from stsci.tools import irafutils import wutil # The following class attempts to emulate the standard IRAF gcursor # mode of operation. That is to say, it is basically a keyboard driven # system that uses the same keys that IRAF does for the same purposes. # The keyboard I/O will use tkinter event handling instead of terminal # I/O primarily because it is simpler and it is necessary to use tkinter # anyway. class Gcursor: """This handles the classical IRAF gcur mode""" def __init__(self, window): self.x = 0 self.y = 0 self.window = window self.gwidget = window.gwidget self.top = window.top self.markcur = 0 self.retString = None self.active = 0 self.eof = None def __call__(self): return self.startCursorMode() def startCursorMode(self): # bind event handling from this graphics window self.window.raiseWindow() self.window.update() wutil.focusController.setFocusTo(self.window) self.cursorOn() self.bind() activate = self.window.getStdout() is None if activate: self.window.control_reactivatews(None) try: self.active = 1 self.eof = None self.top.mainloop() finally: try: self.active = 0 self.unbind() self.cursorOff() except TKNTR.TclError: pass # EOF flag can get set by window-close event or 'I' keystroke # It should be set to string message if self.eof: if self.eof[:9] == 'interrupt': raise KeyboardInterrupt(self.eof) else: raise EOFError(self.eof) if activate: self.window.control_deactivatews(None) return self.retString def cursorOn(self): """Turn cross-hair cursor on""" if self.gwidget.lastX is not None: self.gwidget.activateSWCursor( (self.gwidget.lastX+0.5)/self.gwidget.width, (self.gwidget.lastY+0.5)/self.gwidget.height) else: self.gwidget.activateSWCursor() def cursorOff(self): """Turn cross-hair cursor off""" self.gwidget.deactivateSWCursor() self.gwidget.lastX = int(self.x/self.gwidget.width) self.gwidget.lastY = int(self.y/self.gwidget.height) def bind(self): self.gwidget.bind("",self.getMousePosition) self.gwidget.bind("",self.getKey) self.gwidget.bind("",self.moveUp) self.gwidget.bind("",self.moveDown) self.gwidget.bind("",self.moveRight) self.gwidget.bind("",self.moveLeft) self.gwidget.bind("",self.moveUpBig) self.gwidget.bind("",self.moveDownBig) self.gwidget.bind("",self.moveRightBig) self.gwidget.bind("",self.moveLeftBig) def unbind(self): self.gwidget.unbind("") self.gwidget.unbind("") self.gwidget.unbind("") self.gwidget.unbind("") self.gwidget.unbind("") self.gwidget.unbind("") self.gwidget.unbind("") self.gwidget.unbind("") self.gwidget.unbind("") self.gwidget.unbind("") def getNDCCursorPos(self): """Do an immediate cursor read and return coordinates in NDC coordinates""" gwidget = self.gwidget cursorobj = gwidget.getSWCursor() if cursorobj.isLastSWmove: ndcX = cursorobj.lastx ndcY = cursorobj.lasty else: sx = gwidget.winfo_pointerx() - gwidget.winfo_rootx() sy = gwidget.winfo_pointery() - gwidget.winfo_rooty() ndcX = (sx+0.5)/self.gwidget.width ndcY = (self.gwidget.height-0.5-sy)/self.gwidget.height return ndcX, ndcY def getMousePosition(self, event): self.x = event.x self.y = event.y def moveCursorRelative(self, event, deltaX, deltaY): gwidget = self.gwidget width = self.gwidget.width height = self.gwidget.height # only move cursor if window is viewable if not wutil.isViewable(self.top.winfo_id()): return # if no previous position, ignore cursorobj = gwidget.getSWCursor() newX = cursorobj.lastx * width + deltaX newY = cursorobj.lasty * height + deltaY if newX < 0: newX = 0 if newY < 0: newY = 0 if newX >= width: newX = width - 1 if newY >= height: newY = height - 1 gwidget.moveCursorTo(newX, newY, SWmove=1) self.x = newX self.y = newY def moveUp(self, event): self.moveCursorRelative(event, 0, 1) def moveDown(self, event): self.moveCursorRelative(event, 0, -1) def moveRight(self, event): self.moveCursorRelative(event, 1, 0) def moveLeft(self, event): self.moveCursorRelative(event, -1, 0) def moveUpBig(self, event): self.moveCursorRelative(event, 0, 5) def moveDownBig(self, event): self.moveCursorRelative(event, 0, -5) def moveRightBig(self, event): self.moveCursorRelative(event, 5, 0) def moveLeftBig(self, event): self.moveCursorRelative(event, -5, 0) def writeString(self, s): """Write a string to status line""" stdout = self.window.getStdout(default=sys.stdout) stdout.write(s) stdout.flush() def readString(self, prompt=""): """Prompt and read a string""" self.writeString(prompt) stdin = self.window.getStdin(default=sys.stdin) return irafutils.tkreadline(stdin)[:-1] def getKey(self, event): import gkicmd # The main character handling routine where no special keys # are used (e.g., arrow keys) key = event.char if not key: # ignore keypresses of non printable characters return elif key == '\004': # control-D causes immediate EOF self.eof = "EOF from `^D'" self.top.quit() elif key == '\003': # control-C causes interrupt self.window.gcurTerminate("interrupted by `^C'") x,y = self.getNDCCursorPos() if self.markcur and key not in 'q?:=UR': metacode = gkicmd.markCross(x,y) self.appendMetacode(metacode) if key == ':': colonString = self.readString(prompt=": ") if colonString: if colonString[0] == '.': if colonString[1:] == 'markcur+': self.markcur = 1 elif colonString[1:] == 'markcur-': self.markcur = 0 elif colonString[1:] == 'markcur': self.markcur = not self.markcur else: self.writeString("Unimplemented CL gcur `:%s'" % colonString) else: self._setRetString(key,x,y,colonString) elif key == '=': # snap command - print the plot import gki gki.printPlot(self.window) elif key in string.ascii_uppercase: if key == 'I': # I is equivalent to keyboard interrupt self.window.gcurTerminate("interrupted by `I' keyboard command") elif key == 'R': self.window.redrawOriginal() elif key == 'T': textString = self.readString(prompt="Annotation string: ") metacode = gkicmd.text(textString,x,y) self.window.forceNextDraw() # we can afford a perf hit here, # a human just typed in text self.appendMetacode(metacode) elif key == 'U': self.window.undoN() elif key == 'C': wx,wy,gwcs = self._convertXY(x,y) self.writeString("%g %g" % (wx,wy)) else: self.writeString("Unimplemented CL gcur command `%s'" % key) else: self._setRetString(key,x,y,"") def appendMetacode(self, metacode): # appended code is undoable self.window.append(metacode, 1) def _convertXY(self, x, y): """Returns x,y,gwcs converted to physical units using current WCS""" wcs = self.window.wcs if wcs: return wcs.get(x,y) else: return (x,y,0) def _setRetString(self, key, x, y, colonString): wx,wy,gwcs = self._convertXY(x,y) if key <= ' ' or ord(key) >= 127: key = '\\%03o' % ord(key) self.retString = str(wx)+' '+str(wy)+' '+str(gwcs)+' '+key if colonString: self.retString = self.retString +' '+colonString self.top.quit() # time to go! pyraf-2.1.15/lib/pyraf/clast.py0000644000665500117240000000436013310762263017273 0ustar sontagpassdev00000000000000"""clast.py: abstract syntax tree node type for CL parsing $Id$ """ from __future__ import division # confidence high # Copyright (c) 1998-1999 John Aycock # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # # Minimal AST class -- N-ary trees. # from stsci.tools import compmixin class AST(compmixin.ComparableMixin): def __init__(self, type=None): self.type = type self._kids = [] # # Not all these may be needed, depending on which classes you use: # # __getitem__ GenericASTTraversal, GenericASTMatcher # __len__ GenericASTBuilder # __setslice__ GenericASTBuilder # _compare GenericASTMatcher # def __getitem__(self, i): return self._kids[i] def __len__(self): return len(self._kids) # __setslice__ is deprec.d, out in PY3K; use __setitem__ instead def __setslice__(self, low, high, seq): self._kids[low:high] = seq def __setitem__(self, idx, val): self._kids[idx] = val def __repr__(self): return self.type def _compare(self, other, method): if isinstance(other, AST): return method(self.type, other.type) else: return method(self.type, other) pyraf-2.1.15/lib/pyraf/clparse.py0000644000665500117240000004133513310762263017621 0ustar sontagpassdev00000000000000"""clparse.py: Parse IRAF CL $Id$ R. White, 1999 August 24 """ from __future__ import division # confidence high from generic import GenericASTBuilder, GenericASTTraversal from clast import AST from cltoken import Token import string class CLStrictParser(GenericASTBuilder): """Strict version of CL parser (flags some program errors that CL accepts) This can be used as the parser to get a lint-like mode. Use CLParser (which adds some more rules to allow the same errors as the CL) to run CL programs. """ def __init__(self, AST, start='program'): GenericASTBuilder.__init__(self, AST, start) # list of tokens that should not be flattened by nonterminal() self.primaryTypes = { 'proc_stmt': 1, 'param_declaration_block': 1, 'declaration_stmt': 1, 'declaration_block': 1, 'var_name': 1, 'decl_init_list': 1, 'decl_init_value': 1, 'decl_array_dims': 1, 'array_subscript': 1, 'list_flag': 1, 'body_block': 1, 'statement_block': 1, 'nonnull_stmt': 1, 'osescape_stmt': 1, 'assignment_stmt': 1, 'task_call_stmt': 1, 'if_stmt': 1, 'for_stmt': 1, 'while_stmt': 1, 'break_stmt': 1, 'next_stmt': 1, 'return_stmt': 1, 'goto_stmt': 1, 'label_stmt': 1, 'switch_stmt': 1, 'case_block': 1, 'case_stmt_block': 1, 'case_value': 1, 'compound_stmt': 1, 'empty_compound_stmt': 1, 'task_arglist': 1, 'comma_arglist': 1, 'fn_arglist': 1, 'arg': 1, 'empty_arg': 1, 'non_empty_arg': 1, 'no_arg': 1, 'param_name': 1, 'opt_comma': 1, 'bool_expr': 1, } self._currentFname = None def parse(self, tokens, fname=None): """ Override this, only so we can add the optional fname arg. Delegate all parse logic to parent. """ self._currentFname = fname return GenericASTBuilder.parse(self, tokens) def typestring(self, token): try: return token.type except AttributeError: return token def error(self, token, value=None): finfo = '' if self._currentFname: finfo = 'file "'+self._currentFname+'"' if hasattr(token, 'lineno'): if len(finfo): finfo += ', ' errmsg = "CL syntax error at `%s' (%sline %d)" % (token, finfo, token.lineno) else: if len(finfo): finfo = '('+finfo+')' errmsg = "CL syntax error at `%s' %s" % (token, finfo) if value is not None: errmsg = errmsg + "\n" + str(value) raise SyntaxError(errmsg) def p_program(self, args): ''' program ::= proc_stmt param_declaration_block body_block program ::= statement_block proc_stmt ::= PROCEDURE IDENT proc_arguments end_of_line proc_arguments ::= ( proc_arglist ) proc_arguments ::= proc_arglist ::= IDENT proc_arglist ::= proc_arglist , IDENT proc_arglist ::= param_declaration_block ::= declaration_block declaration_block ::= declaration_list declaration_block ::= declaration_list ::= declaration_stmt end_of_line declaration_list ::= declaration_list declaration_stmt end_of_line declaration_stmt ::= TYPE decl_spec_list decl_spec_list ::= decl_spec decl_spec_list ::= decl_spec_list , decl_spec decl_spec ::= list_flag var_name opt_init_val declaration_options list_flag ::= * list_flag ::= decl_array_dims ::= INTEGER decl_array_dims ::= decl_array_dims , INTEGER var_name ::= IDENT var_name ::= IDENT [ decl_array_dims ] opt_init_val ::= = decl_init_list opt_init_val ::= decl_init_list ::= tdecl_init_list tdecl_init_list ::= decl_init_value tdecl_init_list ::= tdecl_init_list , decl_init_value decl_init_value ::= constant declaration_options ::= { decl_init_list , decl_options_list NEWLINE } declaration_options ::= { decl_options_list NEWLINE } declaration_options ::= { decl_init_list NEWLINE } declaration_options ::= decl_options_list ::= decl_option decl_options_list ::= decl_options_list , decl_option decl_option ::= IDENT = constant body_block ::= BEGIN end_of_line statement_block END end_of_line statement_block ::= statement_list statement_list ::= statement_list statement statement_list ::= statement ::= declaration_stmt end_of_line statement ::= nonnull_stmt end_of_line statement ::= end_of_line statement ::= label_stmt statement label_stmt ::= IDENT : end_of_line ::= NEWLINE end_of_line ::= ; nonnull_stmt ::= osescape_stmt nonnull_stmt ::= assignment_stmt nonnull_stmt ::= if_stmt nonnull_stmt ::= for_stmt nonnull_stmt ::= while_stmt nonnull_stmt ::= switch_stmt nonnull_stmt ::= break_stmt nonnull_stmt ::= next_stmt nonnull_stmt ::= return_stmt nonnull_stmt ::= goto_stmt nonnull_stmt ::= inspect_stmt nonnull_stmt ::= task_call_stmt nonnull_stmt ::= task_pipe_stmt nonnull_stmt ::= task_bkgd_stmt nonnull_stmt ::= { statement_list } opt_newline ::= NEWLINE opt_newline ::= opt_comma ::= , opt_comma ::= compound_stmt ::= opt_newline one_compound_stmt one_compound_stmt ::= empty_compound_stmt one_compound_stmt ::= nonnull_stmt empty_compound_stmt ::= ; osescape_stmt ::= OSESCAPE assignment_stmt ::= IDENT assignop expr assignment_stmt ::= array_ref assignop expr assignop ::= = assignop ::= ASSIGNOP if_stmt ::= IF ( bool_expr ) compound_stmt else_clause else_clause ::= opt_newline ELSE compound_stmt else_clause ::= while_stmt ::= WHILE ( bool_expr ) compound_stmt break_stmt ::= BREAK next_stmt ::= NEXT return_stmt ::= RETURN goto_stmt ::= GOTO IDENT inspect_stmt ::= = expr for_stmt ::= FOR ( opt_assign_stmt ; opt_bool ; opt_assign_stmt ) compound_stmt opt_assign_stmt ::= assignment_stmt opt_assign_stmt ::= opt_bool ::= bool_expr opt_bool ::= switch_stmt ::= SWITCH ( expr ) case_block case_block ::= opt_newline { case_stmt_list default_stmt_block NEWLINE } case_stmt_list ::= case_stmt_block case_stmt_list ::= case_stmt_list case_stmt_block case_stmt_block ::= opt_newline CASE case_value_list : compound_stmt case_value_list ::= case_value case_value_list ::= case_value_list , case_value case_value ::= INTEGER case_value ::= STRING case_value ::= QSTRING case_value ::= EOF default_stmt_block ::= opt_newline DEFAULT : compound_stmt default_stmt_block ::= task_call_stmt ::= IDENT task_arglist task_arglist ::= ( comma_arglist2 ) task_arglist ::= ( non_expr_arg ) task_arglist ::= ( no_arg ) task_arglist ::= comma_arglist no_arg ::= task_pipe_stmt ::= task_call_stmt PIPE task_call_stmt task_pipe_stmt ::= task_pipe_stmt PIPE task_call_stmt task_bkgd_stmt ::= task_call_stmt BKGD task_bkgd_stmt ::= task_pipe_stmt BKGD comma_arglist ::= ncomma_arglist comma_arglist ::= ncomma_arglist ::= non_empty_arg ncomma_arglist ::= empty_arg , arg ncomma_arglist ::= ncomma_arglist , arg comma_arglist2 ::= arg , arg comma_arglist2 ::= comma_arglist2 , arg non_empty_arg ::= expr non_empty_arg ::= non_expr_arg non_expr_arg ::= keyword_arg non_expr_arg ::= bool_arg non_expr_arg ::= redir_arg arg ::= non_empty_arg arg ::= empty_arg empty_arg ::= keyword_arg ::= param_name = expr bool_arg ::= param_name + bool_arg ::= param_name - param_name ::= IDENT redir_arg ::= REDIR expr bool_expr ::= expr expr ::= expr LOGOP not_expr expr ::= not_expr not_expr ::= ! comp_expr not_expr ::= comp_expr comp_expr ::= comp_expr COMPOP concat_expr comp_expr ::= concat_expr concat_expr ::= concat_expr // arith_expr concat_expr ::= arith_expr arith_expr ::= arith_expr + term arith_expr ::= arith_expr - term arith_expr ::= term term ::= term * factor term ::= term / factor term ::= term % factor term ::= factor factor ::= - factor factor ::= + factor factor ::= power power ::= power ** atom power ::= atom atom ::= number atom ::= IDENT atom ::= array_ref atom ::= STRING atom ::= QSTRING atom ::= EOF atom ::= BOOL atom ::= function_call atom ::= ( expr ) number ::= INTEGER number ::= FLOAT number ::= SEXAGESIMAL number ::= INDEF array_subscript ::= expr array_subscript ::= array_subscript , expr array_ref ::= IDENT [ array_subscript ] function_call ::= IDENT ( fn_arglist ) fn_arglist ::= comma_arglist constant ::= number constant ::= - number constant ::= + number constant ::= STRING constant ::= QSTRING constant ::= EOF constant ::= BOOL ''' pass def resolve(self, list): # resolve ambiguities # choose shortest; raise exception if two have same length rhs0 = list[0][1] rhs1 = list[1][1] assert len(rhs0) != len(rhs1) # print 'Ambiguity:' # for rule in list: # lhs, rhs = rule # print len(rhs), rule return list[0] def nonterminal(self, atype, args): # # Flatten AST a bit by not making nodes if there's only # one child, but retain a few primary structural # elements. # if len(args) == 1 and not atype in self.primaryTypes: return args[0] return GenericASTBuilder.nonterminal(self, atype, args) class CLParser(CLStrictParser): """Sloppy version of CL parser, with extra rules allowing some errors""" def __init__(self, AST, start='program'): CLStrictParser.__init__(self, AST, start) def p_additions(self, args): ''' program ::= statement_block END NEWLINE task_arglist ::= ( comma_arglist task_arglist ::= comma_arglist ) inspect_stmt ::= IDENT = ''' # - end without matching begin # - task argument list with missing closing parenthesis # - task argument list with missing opening parenthesis # - peculiar 'var =' form of inspect statement (as opposed to # normal '= var' form.) # # Note that the missing parentheses versions of # argument lists also permit parsing of 'pipe args' # in format taskname(arg, arg, | task2 arg, arg) pass class EclParser(CLParser): def __init__(self, AST, start='program'): CLParser.__init__(self, AST, start) self.primaryTypes['iferr_stmt'] = 1 def p_additions2(self, args): ''' nonnull_stmt ::= iferr_stmt iferr_stmt ::= if_kind guarded_stmt except_action iferr_stmt ::= if_kind guarded_stmt opt_newline THEN except_action iferr_stmt ::= if_kind guarded_stmt opt_newline THEN except_action opt_newline ELSE else_action if_kind ::= IFERR if_kind ::= IFNOERR guarded_stmt ::= { opt_newline statement_list } except_action ::= compound_stmt else_action ::= compound_stmt ''' pass # # list tree # class PrettyTree(GenericASTTraversal): def __init__(self, ast, terminal=1): GenericASTTraversal.__init__(self, ast) self.terminal = terminal self.indent = 0 self.nodeCount = 0 self.preorder() print print self.nodeCount,'total nodes in tree' def n_NEWLINE(self, node): self.nodeCount = self.nodeCount + 1 def n_compound_stmt(self, node): self.indent = self.indent + 1 # self.printIndentNode(node) self.default(node) self.nodeCount = self.nodeCount + 1 def n_compound_stmt_exit(self, node): self.indent = self.indent - 1 self.printIndentNode(node,tail='_exit') # self.default(node,tail='_exit') def n_declaration_block(self, node): # dedent declaration blocks self.indent = self.indent - 1 self.default(node) self.nodeCount = self.nodeCount + 1 def n_declaration_block_exit(self, node): self.indent = self.indent + 1 self.default(node,tail='_exit') print def n_BEGIN(self, node): self.printIndentNode(node) self.indent = self.indent + 1 self.nodeCount = self.nodeCount + 1 def n_END(self, node): self.indent = self.indent - 1 self.printIndentNode(node) self.nodeCount = self.nodeCount + 1 def n_nonnull_stmt(self, node): self.printIndentNode(node) self.nodeCount = self.nodeCount + 1 def n_declaration_stmt(self, node): self.printIndentNode(node) self.nodeCount = self.nodeCount + 1 def n_iferr_stmt(self, node): self.printIndentNode(node) self.nodeCount += 1 # print newline and indent def printIndent(self): print '\n', for i in range(self.indent): print ' ', # print newline, indent, and token def printIndentNode(self, node, tail=''): self.printIndent() self.default(node,tail=tail) def default(self, node, tail=''): if node.type == '}': self.printIndent() if isinstance(node, Token) or (not self.terminal): print `node`+tail, self.nodeCount = self.nodeCount + 1 class TreeList(GenericASTTraversal): def __init__(self, ast, terminal=0): GenericASTTraversal.__init__(self, ast) self.terminal = terminal self.indent = '' # self.postorder() self.preorder() def n_compound_stmt(self, node): self.indent = self.indent + '\t' def n_compound_stmt_exit(self, node): self.indent = self.indent[:-1] def default(self, node): if node.type == 'NEWLINE': print '\n' + self.indent, elif isinstance(node, Token) or (not self.terminal): print node, def treelist(ast,terminal=1): PrettyTree(ast,terminal) def getParser(): import pyrafglobals if pyrafglobals._use_ecl: _parser = EclParser(AST) else: _parser = CLParser(AST) return _parser def parse(tokens, fname=None): global _parser return _parser.parse(tokens, fname=fname) pyraf-2.1.15/lib/pyraf/msgiobuffer.py0000644000665500117240000002050213310762263020471 0ustar sontagpassdev00000000000000"""module 'msgiobuffer.py' -- module containing the MsgIOBuffer class. This class creates a scrolling canvas composed of a message box and an I/O frame. The message box contains the history of I/O messages; the I/O frame contains the latest I/O from the interactive program. M.D. De La Pena, 2000 June 28 $Id$ """ from __future__ import division # confidence high # System level modules from Tkinter import * # requires 2to3 import string class MsgIOBuffer(Frame): """MsgIOBuffer class""" def __init__(self, parent, width = 100, viewHeight = None, text = ""): """Constructor for the MsgIOBuffer class""" Frame.__init__(self) # Initialize class attributes self.messageText = "" self.currentText = text self.minHgt = 25 # try 65 with Tk8.5 on OSX self.viewHeight = viewHeight self.entrySetting = StringVar() self.entrySetting.set("") self.entryValue = self.entrySetting.get() self.waitFlag = BooleanVar() self.waitFlag.set(TRUE) # Set up the frame to hold the message and the I/O self.parent = parent self.msgIO = Frame(self.parent, bd = 2, relief = FLAT, takefocus = FALSE) # Overlay a canvas on the frame self.msgIO.canvas = Canvas(self.msgIO, takefocus = FALSE, highlightthickness = 0) # Attach a vertical scrollbar to the canvas self.msgIO.vscroll = Scrollbar(self.msgIO, orient = VERTICAL, width = 11, relief = SUNKEN, activerelief = RAISED, takefocus = FALSE) self.msgIO.canvas['yscrollcommand'] = self.msgIO.vscroll.set self.msgIO.vscroll['command'] = self.msgIO.canvas.yview self.msgIO.vscroll.pack(side = RIGHT, fill = Y) # Pack the canvas self.msgIO.canvas.pack(side = LEFT, fill = X, expand = TRUE, padx = 4) ### Do not pack the frame here. Do it in the application. ### #self.msgIO.pack(side = TOP, fill = X, expand = TRUE) # Define a frame that will sit on the canvas # This frame will hold a message box and a small I/O frame self.msgIO.canvas.f = Frame(self.msgIO.canvas) self.msgIO.canvas.f.pack(fill = X, expand = TRUE) # Generate the window for the canvas self.msgIO.canvas.create_window(0, 0, anchor = NW, window = self.msgIO.canvas.f) # Define a frame for I/O to be placed on the canvas self.msgIO.canvas.f.iomb = Frame(self.msgIO.canvas.f, relief = FLAT, bd = 0) # Frame may contain message and a label, or message, label and entry. self.msgIO.canvas.f.iomb.label = Label(self.msgIO.canvas.f.iomb, text = self.currentText, bd = 5, takefocus = FALSE) self.msgIO.canvas.f.iomb.entry = Entry(self.msgIO.canvas.f.iomb, highlightthickness = 0, bg = "#d9d9d9", relief = FLAT, textvariable = self.entrySetting, state = DISABLED, insertwidth = 2, takefocus = FALSE) # Bind the carriage return to the entry self.msgIO.canvas.f.iomb.entry.bind('', self.__getEntryValue) # Define a message box to be placed in the frame self.msgIO.canvas.f.iomb.msg = Message(self.msgIO.canvas.f.iomb, bd = 0, relief = FLAT, text = self.messageText, anchor = SW, width = width, takefocus = FALSE) # Pack the widgets in the frame self.msgIO.canvas.f.iomb.msg.pack(side = TOP, fill = X, expand = TRUE) self.msgIO.canvas.f.iomb.label.pack(side = LEFT) self.msgIO.canvas.f.iomb.entry.pack(side = LEFT, fill = X, expand = TRUE) self.msgIO.canvas.f.iomb.pack(side = TOP, fill = X, expand = TRUE) # The full scrolling region is the width of the parent and # the height of the label/entry (25) and the message box (18) # combined. Hardcoded to avoid too much updating which causes # redraws in PyRAF. scrollHgt = 43 self.msgIO.canvas.itemconfigure(1, height = scrollHgt) self.msgIO.canvas.configure(scrollregion = (0, 0, 0, scrollHgt)) # The displayed portion of the window on the canvas is primarily # the label/entry region. if (self.viewHeight == None or self.viewHeight < self.minHgt): self.msgIO.canvas.configure(height = self.minHgt) else: self.msgIO.canvas.configure(height = self.viewHeight) # View is to show the information just moved into the message area self.msgIO.canvas.yview_moveto(1) def updateIO(self, text = ""): """Method to update the I/O portion of the scrolling canvas""" # Move the current contents of the I/O frame to the message box self.__updateMsg(self.currentText) # Update the class variable with the latest text self.currentText = text # Now reconfigure the I/O frame self.msgIO.canvas.f.iomb.label.configure(text = text) def readline(self): """Method to set focus to the Entry widget and XXX""" self.__enableEntry() self.msgIO.canvas.f.iomb.entry.wait_variable(self.waitFlag) self.waitFlag.set(TRUE) # Important to have the "\n" on the returned value return (self.entryValue + "\n") def __getEntryValue(self, event = None): """Private method to obtain any value entered in the Entry""" self.entryValue = self.entrySetting.get() self.msgIO.canvas.f.iomb.entry.delete(0, END) # Combine any label value and the entry value in order # to update the current text self.currentText = self.currentText + " " + self.entryValue # Disable the entry self.msgIO.canvas.f.iomb.entry.configure(state = DISABLED) self.waitFlag.set(FALSE) if self.lastFocus: self.lastFocus.focus_set() def __enableEntry(self): """Private method to put the Entry into a normal state for input.""" # Input is requested, so enable the entry box f = self.focus_displayof() if f: self.lastFocus = f.focus_lastfor() else: self.lastFocus = None self.msgIO.canvas.f.iomb.entry.configure(state = NORMAL) self.msgIO.canvas.f.iomb.entry.focus_set() def __updateMsg(self, text = ""): """Private method to update the message box of the scrolling canvas.""" # Ensure there is a new line text = "\n" + text # Append the new text to the previous message text self.messageText = self.messageText + text self.msgIO.canvas.f.iomb.msg.configure(text = self.messageText) self.msgIO.canvas.f.update_idletasks() # Reconfigure the canvas size/scrollregion based upon the message box mbHgt = self.msgIO.canvas.f.iomb.msg.winfo_height() scrollHgt = mbHgt + self.minHgt self.msgIO.canvas.itemconfigure(1, height = scrollHgt) self.msgIO.canvas.configure(scrollregion = (0, 0, 0, scrollHgt)) self.msgIO.canvas.yview_moveto(1) pyraf-2.1.15/lib/pyraf/irafgwcs.py0000644000665500117240000003221213310762263017767 0ustar sontagpassdev00000000000000"""irafgwcs.py: WCS handling for graphics This contains some peculiar code to work around bugs in splot (and possibly other tasks) where the WCS for an existing plot gets changed before the plot is cleared. I save the changed wcs in self.pending and only commit the change when it appears to really be applicable. $Id$ """ from __future__ import division # confidence high import struct, numpy, math from stsci.tools.for2to3 import ndarr2bytes, tobytes, BNULLSTR, PY3K from stsci.tools.irafglobals import IrafError import irafinst # global vars _IRAF64BIT = False _WCS_RECORD_SIZE = 0 # constants WCS_SLOTS = 16 WCSRCSZ_vOLD_32BIT = 22 WCSRCSZ_v215_32BIT = 24 WCSRCSZ_v215_64BIT = 48 LINEAR = 0 LOG = 1 ELOG = 2 DEFINED = 1 CLIP = 2 # needed for this? NEWFORMAT = 4 def init_wcs_sizes(forceResetTo=None): """ Make sure global size var has been defined correctly """ global _WCS_RECORD_SIZE, _IRAF64BIT # _WCS_RECORD_SIZE is 24 in v2.15, but was 22 in prior versions. # It counts in 2 byte integers, ie. it was 11 shorts when it was size=22. # Either way however, there are still only 11 pieces of information - in # the case of size=24, it is padded by/in IRAF. # The 64-bit case uses a size of 48. # # This function HAS TO BE FAST. It is called multiple times during a # single plot. Do not check IRAF version unless absolutely necessary. # # See ticket #156 and http://iraf.net/phpBB2/viewtopic.php?p=1466296 if _WCS_RECORD_SIZE != 0 and forceResetTo == None: return # been here already # Given a value for _WCS_RECORD_SIZE ? if forceResetTo: if not forceResetTo in \ (WCSRCSZ_vOLD_32BIT, WCSRCSZ_v215_32BIT, WCSRCSZ_v215_64BIT): raise IrafError("Unexpected value for wcs record size: "+\ str(forceResetTo)) _WCS_RECORD_SIZE = forceResetTo _IRAF64BIT = _WCS_RECORD_SIZE == WCSRCSZ_v215_64BIT return # Define _WCS_RECORD_SIZE, based on IRAF ver - assume 32-bit for now vertup = irafinst.getIrafVerTup() _WCS_RECORD_SIZE = WCSRCSZ_vOLD_32BIT if vertup[0] > 2 or vertup[1] > 14: _WCS_RECORD_SIZE = WCSRCSZ_v215_32BIT def elog(x): """Extended range log scale. Handles negative and positive values. values between 10 and -10 are linearly scaled, values outside are log scaled (with appropriate sign changes. """ if x > 10: return math.log10(float(x)) elif x > -10.: return x/10. else: return -math.log10(-float(x)) class IrafGWcs: """Class to handle the IRAF Graphics World Coordinate System Structure""" def __init__(self, arg=None): self.wcs = None self.pending = None self.set(arg) def commit(self): if self.pending: self.wcs = self.pending self.pending = None def clearPending(self): self.pending = None def __nonzero__(self): self.commit() return self.wcs is not None def set(self, arg=None): """Set wcs from metacode stream""" init_wcs_sizes() if arg is None: # commit immediately if arg=None self.wcs = _setWCSDefault() self.pending = None # print "Default WCS set for plotting window." return # Even in v2.14, arg[] elements are of type int64, but here we cast to # int16 and assume we lose no data wcsStruct = arg[1:].astype(numpy.int16) # Every time set() is called, reset the wcs sizes. We may be plotting # with old-compiled 32-bit tasks, then with new-compiled 32-bit tasks, # then with 64-bit tasks, all within the same PyRAF session. init_wcs_sizes(forceResetTo=int(arg[0]/(1.*WCS_SLOTS))) # Check that eveything is sized as expected if arg[0] != len(wcsStruct): raise IrafError("Inconsistency in length of WCS graphics struct: "+\ str(arg[0])) if len(wcsStruct) != _WCS_RECORD_SIZE*WCS_SLOTS: raise IrafError("Unexpected length of WCS graphics struct: "+\ str(len(wcsStruct))) # Read through the input to populate self.pending SZ = 2 if _IRAF64BIT: SZ = 4 self.pending = [None]*WCS_SLOTS for i in xrange(WCS_SLOTS): record = wcsStruct[_WCS_RECORD_SIZE*i:_WCS_RECORD_SIZE*(i+1)] # read 8 4-byte floats from beginning of record fvals = numpy.fromstring(ndarr2bytes(record[:8*SZ]),numpy.float32) if _IRAF64BIT: # seems to send an extra 0-valued int32 after each 4 bytes fvalsView = fvals.reshape(-1,2).transpose() if fvalsView[1].sum() != 0: raise IrafError("Assumed WCS float padding is non-zero") fvals = fvalsView[0] # read 3 4-byte ints after that ivals = numpy.fromstring(ndarr2bytes(record[8*SZ:11*SZ]),numpy.int32) if _IRAF64BIT: # seems to send an extra 0-valued int32 after each 4 bytes ivalsView = ivals.reshape(-1,2).transpose() if ivalsView[1].sum() != 0: raise IrafError("Assumed WCS int padding is non-zero") ivals = ivalsView[0] self.pending[i] = tuple(fvals) + tuple(ivals) if len(self.pending[i]) != 11: raise IrafError("Unexpected WCS struct record length: "+\ str(len(self.pending[i]))) if self.wcs is None: self.commit() def pack(self): """Return the WCS in the original IRAF format (in bytes-string)""" init_wcs_sizes() self.commit() wcsStruct = numpy.zeros(_WCS_RECORD_SIZE*WCS_SLOTS, numpy.int16) pad = tobytes('\x00\x00\x00\x00') if _IRAF64BIT: pad = tobytes('\x00\x00\x00\x00\x00\x00\x00\x00') for i in xrange(WCS_SLOTS): x = self.wcs[i] farr = numpy.array(x[:8],numpy.float32) iarr = numpy.array(x[8:11],numpy.int32) if _IRAF64BIT: # see notes in set(); adding 0-padding after every data point lenf = len(farr) # should be 8 farr_rs = farr.reshape(lenf,1) # turn array into single column farr = numpy.append(farr_rs, numpy.zeros((lenf,1), numpy.float32), axis=1) farr = farr.flatten() leni = len(iarr) # should be 3 iarr_rs = iarr.reshape(leni,1) # turn array into single column iarr = numpy.append(iarr_rs, numpy.zeros((leni,1), numpy.int32), axis=1) iarr = iarr.flatten() # end-pad? if len(farr)+len(iarr) == (_WCS_RECORD_SIZE//2): pad = BNULLSTR #for IRAF2.14 or prior; all new vers need end-pad # Pack the wcsStruct - this will throw "ValueError: shape mismatch" # if the padding doesn't bring the size out to exactly the # correct length (_WCS_RECORD_SIZE) wcsStruct[_WCS_RECORD_SIZE*i:_WCS_RECORD_SIZE*(i+1)] = \ numpy.fromstring(ndarr2bytes(farr)+ndarr2bytes(iarr)+pad, numpy.int16) return ndarr2bytes(wcsStruct) def transform(self, x, y, wcsID): """Transform x,y to wcs coordinates for the given wcs (integer 0-16) and return as a 2-tuple""" self.commit() if wcsID == 0: return (x, y, wcsID) # Since transformation is defined by a direct linear (or log) mapping # between two rectangular windows, apply the usual linear # interpolation. # log scale does not affect the w numbers at all, a plot # ranging from 10 to 10,000 will have wx1,wx2 = (10,10000), # not (1,4) return (self.transform1d(coord=x,dimension='x',wcsID=wcsID), self.transform1d(coord=y,dimension='y',wcsID=wcsID), wcsID) def transform1d(self, coord, dimension, wcsID): wx1, wx2, wy1, wy2, sx1, sx2, sy1, sy2, xt, yt, flag = \ self.wcs[wcsID-1] if dimension == 'x': w1,w2,s1,s2,type = wx1,wx2,sx1,sx2,xt elif dimension == 'y': w1,w2,s1,s2,type = wy1,wy2,sy1,sy2,yt if (s2-s1) == 0.: raise IrafError("IRAF graphics WCS is singular!") fract = (coord-s1)/(s2-s1) if type == LINEAR: val = (w2-w1)*fract + w1 elif type == LOG: lw2, lw1 = math.log10(w2), math.log10(w1) lval = (lw2-lw1)*fract + lw1 val = 10**lval elif type == ELOG: # Determine inverse mapping to determine corresponding values of s to w # This must be done to figure out which regime of the elog function the # specified point is in. (cs*ew + c0 = s) ew1, ew2 = elog(w1), elog(w2) cs = (s2-s1)/(ew2-ew1) c0 = s1 - cs*ew1 # linear part is between ew = 1 and -1, so just map those to s s10p = cs + c0 s10m = -cs + c0 if coord > s10p: # positive log area frac = (coord-s10p)/(s2-s10p) val = 10.*(w2/10.)**frac elif coord >= s10m and coord <= s10p: # linear area frac = (coord-s10m)/(s10p-s10m) val = frac*20 - 10. else: # negative log area frac = -(coord-s10m)/(s10m-s1) val = -10.*(-w1/10.)**frac else: raise IrafError("Unknown or unsupported axis plotting type") return val def _isWcsDefined(self, i): w = self.wcs[i] if w[-1] & NEWFORMAT: if w[-1] & DEFINED: return 1 else: return 0 else: if w[4] or w[5] or w[6] or w[7]: return 0 else: return 1 def get(self, x, y, wcsID=None): """Returned transformed values of x, y using given wcsID or closest WCS if none given. Return a tuple (wx,wy,wnum) where wnum is the selected WCS (0 if none defined).""" self.commit() if wcsID is None: wcsID = self._getWCS(x,y) return self.transform(x,y,wcsID) def _getWCS(self, x, y): """Return the WCS (16 max possible) that should be used to transform x and y. Returns 0 if no WCS is defined.""" # The algorithm for determining which of multiple wcs's # should be selected is thus (and is different in one # respect from the IRAF cl): # # 1 determine which viewports x,y fall in # 2 if more than one, the tie is broken by choosing the one # whose center is closer. # 3 in case of ties, the higher number wcs is chosen. # 4 if inside none, the distance is computed to the nearest part # of the viewport border, the one that is closest is chosen # 5 in case of ties, the higher number wcs is chosen. indexlist = [] # select subset of those wcs slots which are defined for i in xrange(len(self.wcs)): if self._isWcsDefined(i): indexlist.append(i) # if 0 or 1 found, we're done! if len(indexlist) == 1: return indexlist[0]+1 elif len(indexlist) == 0: return 0 # look for viewports x,y is contained in newindexlist = [] for i in indexlist: x1,x2,y1,y2 = self.wcs[i][4:8] if (x1 <= x <= x2) and (y1 <= y <= y2): newindexlist.append(i) # handle 3 cases if len(newindexlist) == 1: # unique, so done return newindexlist[0]+1 # have to find minimum distance either to centers or to edge dist = [] if len(newindexlist) > 1: # multiple, find one with closest center for i in newindexlist: x1,x2,y1,y2 = self.wcs[i][4:8] xcen = (x1+x2)/2 ycen = (y1+y2)/2 dist.append((xcen-x)**2 + (ycen-y)**2) else: # none, now look for closest border newindexlist = indexlist for i in newindexlist: x1,x2,y1,y2 = self.wcs[i][4:8] xdelt = min([abs(x-x1),abs(x-x2)]) ydelt = min([abs(y-y1),abs(y-y2)]) if x1 <= x <= x2: dist.append(ydelt**2) elif y1 <= y <= y2: dist.append(xdelt**2) else: dist.append(xdelt**2 + ydelt**2) # now return minimum distance viewport # reverse is used to give priority to highest WCS value newindexlist.reverse() dist.reverse() minDist = min(dist) return newindexlist[dist.index(minDist)]+1 def _setWCSDefault(): """Define default WCS for STDGRAPH plotting area.""" # set 8 4 byte floats farr = numpy.array([0.,1.,0.,1.,0.,1.,0.,1.],numpy.float32) # set 3 4 byte ints iarr = numpy.array([LINEAR,LINEAR,CLIP+NEWFORMAT],numpy.int32) wcsarr = tuple(farr)+tuple(iarr) wcs = [] for i in xrange(WCS_SLOTS): wcs.append(wcsarr) return wcs pyraf-2.1.15/lib/pyraf/filecache.py0000644000665500117240000001523113310762263020067 0ustar sontagpassdev00000000000000"""filecache.py: In-memory cache for files with automatic update FileCache is the base class for objects that get data from files and that need to stay in sync with files in case they change. It tracks the file creation/modification times and size and calls the updateValue() method if the file has gotten out of date. If the file has not previously been accessed, calls the newValue() method (which by default is the same as updateValue). Use the get() method to get the value associated with a file. The getValue() method does not check to see if the file has changed and may also be called if that is the desired effect. The base implementation of FileCache just stores and returns the file contents as a string. Extensions should implement at a minimum the getValue and updateValue methods. MD5Cache is an implementation of a FileCache that returns the MD5 digest value for a file's contents, updating it only if the file has changed. FileCacheDict is a dictionary-like class that keeps FileCache objects for a list of filenames. It is instantiated with the *class* (not an instance) of the objects to be created for each entry. New files are added with the add() method, and values are retrieved by index (cachedict[filename]) or using the .get() method. $Id$ R. White, 2000 October 1 """ from __future__ import division # confidence high import os, stat, sys, hashlib from stsci.tools.for2to3 import PY3K class FileCache: """File cache base class""" def __init__(self, filename): self.filename = filename self.attributes = self._getAttributes() self.newValue() # methods that should be supplied in extended class def getValue(self): """Get info associated with file. Usually this is not called directly by the user (use the get() method instead.) """ return self.value def updateValue(self): """Called when file has changed.""" self.value = self._getFileHandle().read() # method that may be changed in extended class def newValue(self): """Called when file is new. By default same as updateValue.""" self.updateValue() # basic method to get cached value or to update if needed def get(self, update=1): """Get info associated with file. Updates cache if needed, then calls getValue. If the update flag is false, simply returns the value without checking to see if it is out-of-date. """ if update: newattr = self._getAttributes() # update value if file has changed oldattr = self.attributes if oldattr != newattr: if oldattr[1]>newattr[1] or oldattr[2]>newattr[2]: # warning if current file appears older than cached version self._warning("Warning: current version of file %s" " is older than cached version" % self.filename) self.updateValue() self.attributes = newattr return self.getValue() # internal utility methods def _getFileHandle(self, filename=None): """Get file handle for a filename or filehandle instance""" if filename==None: filename = self.filename if isinstance(filename,str): fh = open(filename, 'r') elif hasattr(filename, 'read'): fh = filename if hasattr(filename, 'seek'): fh.seek(0) else: raise TypeError( "Argument to _getFileHandle must be name or file handle") return fh def _getAttributes(self, filename=None): """Get file attributes for a file or filehandle""" if filename is None: filename = self.filename if not filename: return None elif isinstance(filename,str): st = os.stat(filename) elif hasattr(filename, 'fileno') and hasattr(filename, 'name'): fh = filename st = os.fstat(fh.fileno()) else: return None # file attributes are size, creation, and modification times return st[stat.ST_SIZE], st[stat.ST_CTIME], st[stat.ST_MTIME] def _warning(self, msg): """Print warning message to stderr, using verbose flag""" sys.stdout.flush() sys.stderr.write(msg + "\n") sys.stderr.flush() class MD5Cache(FileCache): """Cached MD5 digest for file contents""" def getValue(self): """Return MD5 digest value associated with file.""" return self.value def updateValue(self): """Called when file has changed.""" contents = self._getFileHandle().read() # is unicode str in PY3K # md5 digest is the value associated with the file h = hashlib.md5() if PY3K: # unicode must be encoded to be hashed h.update(contents.encode('ascii')) self.value = str(h.digest()) else: h.update(contents) self.value = h.digest() class FileCacheDict: """Dictionary-like set of cached values for a set of files Initialize with class to be instantiated for each file """ def __init__(self, FileCacheClass): self.__Class = FileCacheClass self.data = {} def add(self, filename): """Add filename to dictionary. Does not overwrite existing entry.""" abspath = self.abspath(filename) if not abspath in self.data: self.data[abspath] = self.__Class(abspath) def abspath(self, filename): if isinstance(filename,str): return os.path.abspath(filename) elif hasattr(filename, 'name') and hasattr(filename, 'read'): return os.path.abspath(filename.name) else: return filename def __getitem__(self, filename): abspath = self.abspath(filename) return self.data[abspath].get() def get(self, filename, update=1): """Get value; add it if filename is not already in cache Note that this behavior differs from the usual dictionary get() method -- effectively it never fails. """ abspath = self.abspath(filename) obj = self.data.get(abspath) if obj is None: self.add(abspath) obj = self.data[abspath] return obj.get(update=update) def __delitem__(self, filename): abspath = self.abspath(filename) del self.data[abspath] def has_key(self, key): return self._has(key) def __contains__(self, key): return self._has(key) def _has(self, filename): abspath = self.abspath(filename) return abspath in self.data def keys(self): return self.data.keys() pyraf-2.1.15/lib/pyraf/irafhelp.py0000644000665500117240000003616713310762263017771 0ustar sontagpassdev00000000000000"""Give help on variables, functions, modules, classes, IRAF tasks, IRAF packages, etc. - help() with no arguments will list all the defined variables. - help("taskname") or help(IrafTaskObject) display the IRAF help for the task - help("taskname",html=1) or help(IrafTaskObject,html=1) will direct a browser to display the HTML version of IRAF help for the task - help(object) where object is a module, instance, ? will display information on the attributes and methods of the object (or the variables, functions, and classes in the module) - help(function) will give the calling sequence for the function (except for built-in functions) and so on. There are optional keyword arguments to help that specify what information is to be printed: variables=1 Print info on variables/attributes functions=1 Print info on function/method calls modules=1 Print info on modules tasks=0 Print info on IrafTask objects packages=0 Print info on IrafPkg objects hidden=0 Print info on hidden variables/attributes (starting with '_') html=0 Use HTML help instead of standard IRAF help for tasks regexp=None Specify a regular expression that matches the names of variables of interest. E.g. help(sys, regexp='std') will give help on all attributes of sys that start with std. regexp can use all the re patterns. The padchars keyword determines some details of the format of the output. The **kw argument allows minimum matching for the keyword arguments (so help(func=1) will work). $Id$ R. White, 1999 September 23 """ from __future__ import division # confidence high import __main__, re, os, sys, types try: import io except ImportError: # only for Python 2.5 io = None from stsci.tools import minmatch, irafutils from stsci.tools.irafglobals import IrafError, IrafTask, IrafPkg from stsci.tools.for2to3 import PY3K import describe # use this form since the iraf import is circular import pyraf.iraf # print info on numpy arrays if numpy is available try: import numpy _numpyArrayType = numpy.ndarray except ImportError: # no numpy available, so we won't encounter arrays _numpyArrayType = None _MODULE = 0 _FUNCTION = 1 _METHOD = 2 _OTHER = 3 _functionTypes = (types.BuiltinFunctionType, types.FunctionType, types.LambdaType) _methodTypes = (types.BuiltinMethodType, types.MethodType) if not PY3K: # in PY3K, UnboundMethodType is simply FunctionType _methodTypes += (types.UnboundMethodType,) _listTypes = (list, tuple, dict) _numericTypes = (float, int, long, complex) if 'bool' in globals(): _numericTypes = _numericTypes + (bool,) _allSingleTypes = _functionTypes + _methodTypes + _listTypes + _numericTypes # set up minimum-match dictionary with function keywords kwnames = ( 'variables', 'functions', 'modules', 'tasks', 'packages', 'hidden', 'padchars', 'regexp', 'html' ) _kwdict = minmatch.MinMatchDict() for key in kwnames: _kwdict.add(key,key) del kwnames, key # additional keywords for IRAF help task irafkwnames = ( 'file_template', 'all', 'parameter', 'section', 'option', 'page', 'nlpp', 'lmargin', 'rmargin', 'curpack', 'device', 'helpdb', 'mode' ) _irafkwdict = {} for key in irafkwnames: _kwdict.add(key,key) _irafkwdict[key] = 1 del irafkwnames, key def _isinstancetype(an_obj): """ Transitional function to handle all basic cases which we care about, in both Python 2 and 3, with both old and new-style classes. Return True if the passed object is an instance of a class, else False. """ if an_obj is None: return False if not PY3K: return isinstance(an_obj, types.InstanceType) typstr = str(type(an_obj)) # the following logic works, as PyRAF users expect, in both v2 and v3 return typstr=="" or \ (typstr.startswith("