pyraf-2.1.14/0000755000665500117240000000000013033515775013730 5ustar sontagsts_dev00000000000000pyraf-2.1.14/data/0000755000665500117240000000000013033515772014636 5ustar sontagsts_dev00000000000000pyraf-2.1.14/data/blankcursor.xbm0000644000665500117240000000055213033515752017673 0ustar sontagsts_dev00000000000000#define xcur_width 16 #define xcur_height 20 #define xcur_x_hot 1 #define xcur_y_hot 1 static char xcur_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; pyraf-2.1.14/data/epar.optionDB0000644000665500117240000000044713033515760017227 0ustar sontagsts_dev00000000000000*Listbox*background: white *Listbox*selectBackground: white *Listbox*foreground: black *Listbox*selectForeground: black *Entry*background: white *Entry*foreground: black *Entry*selectForeground: black *Scrollbar*background: #d9d9d9 *Scrollbar*Foreground: #d9d9d9 *Menu*selectColor: black pyraf-2.1.14/data/pyraflogo_rgb_web.gif0000644000665500117240000001520713033515761021021 0ustar sontagsts_dev00000000000000GIF89aSj[obxk{ObrKZ㙦֪ݏ]='ю8w09Hz6AiKXo(B&0XxNT6hD"*EŊ>0hF!T5 tG<*@PO3QL@EKC7g*.̉ƠϣT řQP TSX'I*i`.E@IXD״$ڷ&T9ٷY`(k7L!oD+L0+ĈN]a)f**3  q|4 Zy#M3r_o2wǥXs+vPqRسXq9(y6xHA"Ǝ1ǠAc>Na+0;?쀂2߂ Rtp Dpnp&L|%%H/&B(PR pk%c ATg/@VlOZ )0DҘ+`aɨK¯DA>@H0%(`lHSJl`C 2$\)``N>j0*7 BȿJ\I "Px 4Thqɐ/='E U$rl CA4G|JIv1/r\!b$S@<)z5"k}JC9.9%,B-u%,ukީ×2 -*']-25*\ t  &)0>(cQ|(#4͕,khA . "kvh 3<撃=%9p  ,P\^KѢ@Ѐc~PAyP u< wMOp!# `Fn> "  -酕 g;$C\'>JfOH8].LI)RHP!b}L"lVw"NbrbĄ hA-p *R ,N8a҈  QdZpǴ&pD8#j0>}Y :Tu/+/yB`TH,BLIEnaA/Y)@`8X\`Ih"`A @fAv(l-J( %2)@.&8(e7-Aj h `nBP<-0T~B PJ“@@#'O H wDPr)SY|80IJ" )` !$з@F$ RC$ \+n u :$k!cY F!+@dB(vМ\ ^pS|%WN 3RCڀ r K`+XE7 (A ( O Pc 8$d@B vG^sk{)7Dmu[✷7@%pӘPrGp:> OO@`Jn5<ړj `n](`"0n{ß%t p}5{ѠoH i \HѾP@p< Lhʴp.C-*p^> z%_Fp9$"#,4pf&0G:S!ÇVc)bl +qv7/ 5RU |["ԧSP VS (6Cf$Æ#*Bk8o@x/{Kw36LL0!cs>@h8`z(IFPva<[GH`Pfeo Z`g,öB#T+B}B+B 6@UF09BMC\æ\%+д1#rV+b<}#2x! )׌/ ?p&}s!(n{( P*F9g9q!odoTX{.fT8s A :U;p!8q0K3r\gh.o7ڲ[-钦 5.9.8=5r 5"β\}l@0`V]vXyK~@?9P2F`\ANj1')hSW _w0f ra\rq"хZp7 JǗyP$]C >x eYd&LV QV \Y ؊ۗt{}y7e\D\ي Di@dAʉ HW) pt)C@Pqy`/6.VX5n{ep~qF0`Q˵ }]{pzg[iGDˉ(\Kbck Y 5@v0zhy\)dhogpl/zڢy)VHPɚ"=oYx^`l!p! x7ʢ4[ g %P\U 5ubG0K*ۢnzK2 z¤`2l{z~+tfkLlP ЀkJ㫰{ ICi:*)PY2*'禆 X@GK)UID? .s9)%\chgefdF A%ٷ s nMj+Ñ}/+g6hGl4d/f ee6@e{{k +KHS^^"f"+-Y; uy@| L늸܊:옻1k3u15ePSf-Bp{`Q <NiUB@ `)k^Lu\{kr[ Ñ {qr +˜3ka D'rrOODH',ʶMz_Ȭ/XpM`vl^DhƿOː^+Ѐ0]z%r+yD?\5eP.V#\crRxVŰ%Ҵ[:U=zHjKJ[kl:n,l_XN\` 9lu*{I:˰m<1@!D:]L)锧͖, L6`0 ! 0j绻ꯅ`zt~tOé'&s))m`; fJ]znٱܳ۶1 k l]NeYvP#\"o] `AߘMy K Hm:= iwM0y,g)xO UԚd:1-]Ȼ nS]muG0R!ǘ PI\&^y ]]l+K\1eEY+vl,вP]k=r,ɂ} I*_ٷS0[.A^"Osih./jL ,sәnyL֒z2 P*BIx$Mm1o dЍ%; jz my|ۣ^XZSϣퟍP G]؎a:>M {UPרEY6Qyf {=*cʪ ̾Sq\0 ~Ih{ aآ-ԭ6jq n 穟Ǻ^ϴptl:vl`an^h\ Ww'J:Q~   M(яe"ܩ滥Ńν Q 砎7P!YR`jyi--.ͽ߀h y=Z[f6'sɕ?ɯd<˛im?Z ĝS< \S1g@(   Pxh hlƢ.bAT19ugE%##bB Q h-SJ2!sak+!A@"a7ndPbQ  mS{!T+HC=c"I!C1YY ፴i^5+ӗS B$Р,@[%9(uԓ HP87__˴oYhÆ*P"5Xc; &`@8l8f`,H?Ăy\& ƜVʙ=L֭ӠW#e FY"2׀0 (dq::vjU F1[H< OIԞDM0)xX+6YȲfBN`$K"@3Jςv"س\36KtJΜ&#=,Ӓ`>0Ʉ-|Jv DS &xqk\i#C׀zܪ J4;<`$RPN`ՖHL:*c"}⚃S@ @aF%dʱJ"HiJK,LT@ tLḛ% ;  ȔH MFK.<=GDBIJE E7x(TQ[M@ZM!AXOM`]د* |5Z&-X9 oXgShU@^Xa T[ j0r;i׽7`IWew ܅}V~}륀roU@PA`=NzͶ؅cU#u4=uEAuW޹i=]5sAGoy^z T(]{I)Ȩ=@an:A((!먥.Tk垛X;pyraf-2.1.14/lib/0000755000665500117240000000000013033515774014475 5ustar sontagsts_dev00000000000000pyraf-2.1.14/lib/pyraf/0000755000665500117240000000000013033515774015616 5ustar sontagsts_dev00000000000000pyraf-2.1.14/lib/pyraf/GkiMpl.py0000644000665500117240000007235313033515761017361 0ustar sontagsts_dev00000000000000""" matplotlib implementation of the gki kernel class $Id$ """ from __future__ import division # confidence high import math, sys, numpy, os import Tkinter as TKNTR import matplotlib # (done in mca file) matplotlib.use('TkAgg') # set backend from matplotlib.lines import Line2D from matplotlib.figure import Figure from matplotlib.patches import Rectangle from stsci.tools.for2to3 import ndarr2str import gki, gkitkbase, textattrib import gkigcur import MplCanvasAdapter as mca from wutil import moveCursorTo try: import readline except ImportError: readline = None # MPL version MPL_MAJ_MIN = matplotlib.__version__.split('.') # tmp var MPL_MAJ_MIN = float(MPL_MAJ_MIN[0]+'.'+MPL_MAJ_MIN[1]) # MPL linewidths seem to be thicker by default GKI_TO_MPL_LINEWIDTH = 0.65 # GKI seems to use: 0: clear, 1: solid, 2: dash, 3: dot, 4: dot-dash, 5: ? GKI_TO_MPL_LINESTYLE = ['None','-','--',':','-.','steps'] # Convert GKI alignment int values to MPL (idx 0 = default), 0 is invalid GKI_TO_MPL_HALIGN = ['left','center','left','right',0,0,0,0] GKI_TO_MPL_VALIGN = ['bottom','center',0,0,0,0,'top','bottom'] # "surface dev$pix" uses idx=5, though that's not allowed GKI_TO_MPL_VALIGN[4]='top' GKI_TO_MPL_VALIGN[5]='bottom' # some text is coming out too high by about this much GKI_TEXT_Y_OFFSET = 0.005 # marktype seems unused at present (most markers are polylines), but for # future use, the GIO document lists: # 'Acceptable choices are "point", "box", "plus", "cross", "circle" ' GKI_TO_MPL_MARKTYPE = ['.','s','+','x','o'] # Convert other GKI font attributes to MPL (cannot do bold italic?) GKI_TO_MPL_FONTATTR = ['normal',1,2,3,4,5,6,7,'roman','greek','italic','bold', 'low','medium','high'] #----------------------------------------------- class GkiMplKernel(gkitkbase.GkiInteractiveTkBase): """matplotlib graphics kernel implementation""" def makeGWidget(self, width=600, height=420): """Make the graphics widget. Also perform some self init.""" self.__pf = TKNTR.Frame(self.top) self.__pf.pack(side=TKNTR.TOP, fill=TKNTR.BOTH, expand=1) self.__xsz = width self.__ysz = height ddd = 100 self.__fig = Figure(figsize=(self.__xsz/(1.*ddd),self.__ysz/(1.*ddd)), dpi=ddd) self.__fig.set_facecolor('k') # default to black self.__mca = mca.MplCanvasAdapter(self, self.__fig, master=self.__pf) self.__mca.pack(side=TKNTR.TOP, fill=TKNTR.BOTH, expand=1) self.__mca.gwidgetize(width, height) # Add attrs to the gwidget self.gwidget = self.__mca.get_tk_widget() self.__normLines = [] # list of Line2D objs self.__normPatches = [] # list of Patch objs self.__extraHeightMax = 25 self.__firstPlotDone = 0 self.__skipPlotAppends = False # allow gki_ funcs to be reused self.__allowDrawing = False # like taskStart, but can't rely on that self._forceNextDraw = False # self.__lastGkiCmds = (None, None, None) # unused for now self.colorManager = tkColorManager(self.irafGkiConfig) self.startNewPage() self.__gcursorObject = gkigcur.Gcursor(self) self.__mca.show() # or, could: self.gRedraw() with a .show() # not currently using getAdjustedHeight because the background is drawn and # it is not black (or the same color as the rest of the empty window) def getAdjustedHeight(self): """ Calculate an adjusted height to make the plot look better in the widget's viewfield - otherwise the graphics are too close to the top of the window. Use in place of self.__ysz""" adjHt = self.__ysz - min(0.05*self.__ysz, self.__extraHeightMax) return adjHt def getTextPointSize(self, gkiTextScaleFactor, winWidth, winHeight): """ Make a decision on the best font size (point) based on the size of the graphics window and other factors """ # The default point size for the initial window size dfltPtSz = 8.0 WIN_SZ_FACTOR = 300.0 # honestly just trying a number that looks good # The contribution to the sizing from the window itself, could # be taken from just the height, but that would leave odd font # sizes in the very tall/thin windows. Another option is to average # the w & h. We will try taking the minimum. winSzContrib = min(winWidth, winHeight) ptSz = dfltPtSz * (winSzContrib/WIN_SZ_FACTOR) # The above gives us a proportionally sized font, but it can be larger # than what we are used to with the standard gkitkplot, so trim down # the large sizes. if (ptSz > dfltPtSz): ptSz = (ptSz+dfltPtSz)/2.0 # Now that the best standard size for this window has been # determined, apply the GKI text scale factor used to it (deflt: 1.0) ptSz = ptSz*gkiTextScaleFactor # leave as float (not N.float64), it'll get truncated by Text if needed return float(ptSz) def clearMplData(self): """ Clear all lines, patches, text, etc. from the figure as well as any of our own copies we may be keeping around to facilitate redraws/resizes/etc. of the figure. """ self.__normLines = [] # clear our lines self.__normPatches = [] # clear our patches self.__fig.clear() # clear all from fig def resizeGraphics(self, width, height): """ It is time to set a magnitude to our currently normalized lines, and send them to the figure. Here we assume that __normLines & __normPatches are already fully populated. """ self.__fig.lines = [] # clear all old lines from figure self.__fig.patches = [] # clear all old patches from figure self.__xsz = width self.__ysz = height # scale each text item for t in self.__fig.texts: t.set_size(self.getTextPointSize(t.gkiTextSzFactor, width, height)) # scale each line, then apply it to the figure for nrln in self.__normLines: ll = Line2D([], []) ll.update_from(nrln) ll.set_data(nrln.get_xdata(True)*self.__xsz, nrln.get_ydata(True)*self.__ysz) self.__fig.lines.append(ll) # scale each patch, then apply it to the figure for nrpa in self.__normPatches: rr = Rectangle((0,0),0,0) rr.update_from(nrpa) rr.set_x(nrpa.get_x()*self.__xsz) rr.set_y(nrpa.get_y()*self.__ysz) rr.set_width(nrpa.get_width()*self.__xsz) rr.set_height(nrpa.get_height()*self.__ysz) self.__fig.patches.append(rr) # do not redraw here - we are called only to set the sizes # done def gcur(self): """Return cursor value after key is typed""" # BUT, before we rush into sending that gcur obj back, this happens to # be an excellent spot to redraw! If we are an interactive task (yes # since they want a gcur) AND if we haven't been drawing because we # are saving draws for performance-sake, then do so now. if not self.__allowDrawing: self.gki_flush(None, force=True) # else, we have already been drawing, so no need to flush here # Now, do what we came here for. 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 pre_imcur(self): """ Override this so as to redraw if needed """ # Just like in gcur(), this may be an excellent time to redraw. if not self.__allowDrawing: self.gki_flush(None, force=True) # else, we have already been drawing, so no need to flush here def prePageSelect(self): """ Override this so as to redraw when needed """ self._forceNextDraw = True # make sure to flush at end of redraw! def forceNextDraw(self): """ Override this so as to force a redraw at the next opportunity """ self._forceNextDraw = True def taskStart(self, name): """ Because of redirection, this is not always called on the first plot but it should be called for every successive one. """ # We take this opportuninty to simply set our draw-saving flag. self.__allowDrawing = False # Note the task command given to be shown in the status widget if readline != None: self._toWriteAtNextClear = readline.get_history_item( readline.get_current_history_length()) def taskDone(self, name): """Called when a task is finished""" # This is the usual hack to prevent the double redraw after first # Tk plot, but this version does not seem to suffer from the bug. # self.doubleRedrawHack() # No need to draw now, UNLESS we haven't yet if not self.__allowDrawing: # If we have not yet made our first draw, then we need to now. # For interactive tasks, we should have drawn already (see gcur). # For non-interactive tasks, we will not have drawn yet. self.gki_flush(None, force=True) # else, we have already been drawing, so no need to flush here 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. if not self.__firstPlotDone: self.__mca.ignoreNextRedraw = 1 self.__firstPlotDone = 1 def prepareToRedraw(self): """This is a hook for things that need to be done before the redraw from metacode. We'll simply clear drawBuffer.""" 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() self.clearMplData() def clearPage(self): """Clear buffer for new page""" self.drawBuffer.reset() self.clearMplData() def isPageBlank(self): """Returns true if this page is blank""" # or, could use: return len(self.drawBuffer) == 0 return len(self.__normLines) == 0 and \ len(self.__normPatches) == 0 and \ len(self.__fig.texts) == 0 # ----------------------------------------------- # Overrides of GkiInteractiveTkBase functions def activate(self): """Not really needed for Tkplot widgets (used to set OpenGL win)""" pass # ----------------------------------------------- # GkiKernel implementation def incrPlot(self): """Plot any new commands in the buffer""" # This step slows us down but is needed, e.g. 'T' in implot. # So, save it for ONLY after we have allowed drawing. This can # seriously hinder performance in interactive tasks but at least # we can avoid taking that hit until after gcur() is 1st called and # we are waiting on a human. We *could* look into checking what the # actual incr/change was and see if it is worth redrawing. if self.gwidget and (self.__allowDrawing or self._forceNextDraw): active = self.gwidget.isSWCursorActive() if active: self.gwidget.deactivateSWCursor() # Render any new contents of draw buffer (this is costly). self.__mca.draw() # in mpl v0.99, .draw() == .show() # Do NOT add the logic here (as in redraw()) to loop through the # drawBuffer func-arg pairs, calling apply(), using the # __skipPlotAppends attr. Do not do so since the MPL kernel version # keeps its own data cache and doesn't use the drawBuffer that way. if active: self.gwidget.activateSWCursor() # reset this no matter what self._forceNextDraw = False # special methods that go into the function tables def _plotAppend(self, plot_function, *args): """ Append a 2-tuple (plot_function, args) to the draw buffer """ # Allow for this draw buffer append to be skipped at times if not self.__skipPlotAppends: self.drawBuffer.append((plot_function,args)) # Run _noteGkiCmd here as well, as almost all gki_* funcs call us # self._noteGkiCmd(plot_function) # def _noteGkiCmd(self, cmdFunc): # """ Append the func to our history, but keep the len constant. """ # Track any and all gki commands - this is used for pattern watching # in the gki stream, not for redraws. Track the func itself. # Since we track everything, we can't just use the drawBuffer here. # self.__lastGkiCmds = self.__lastGkiCmds[1:] + (cmdFunc,) def gki_clearws(self, arg): # don't put clearws command in the draw buffer, just clear the display self.clear() # clear the canvas self.clearMplData() self.__mca.draw() # Note this explicitly (not for redrawing) since _plotAppend isn't used # self._noteGkiCmd(self.gki_clearws) def gki_cancel(self, arg): self.gki_clearws(arg) # Note this explicitly (not for redrawing) since _plotAppend isn't used # self._noteGkiCmd(self.gki_cancel) def gki_flush(self, arg, force=False): """ Asked to render current plot immediately. Also used by redraw(). NOTE: This is called multiple times (~8) for a single prow call. There is a performance improvement gained by skipping the resize calculation between taskStart() and taskDone(). This class adds the 'force' arg which forces it to redraw once whether we are "saving draws" or not. """ # don't put flush command into the draw buffer if self.__allowDrawing or force: self.resizeGraphics(self.__xsz, self.__ysz) # do NOT use adjusted y! self.__mca.draw() self.__mca.flush() # Note this explicitly (not for redrawing) since _plotAppend isn't used # self._noteGkiCmd(self.gki_flush) def gki_polyline(self, arg): """ Instructed to draw a GKI polyline """ # record this operation as a tuple in the draw buffer self._plotAppend(self.gki_polyline, arg) # commit pending WCS changes when draw is found self.wcs.commit() # Reshape to get x's and y's # arg[0] is the num pairs, so: len(arg)-1 == 2*arg[0] verts = gki.ndc(arg[1:]) rshpd = verts.reshape(arg[0],2) xs = rshpd[:,0] ys = rshpd[:,1] # Put the normalized data into a Line2D object, append to our list. # Later we will scale it and append it to the fig. For the sake of # performance, don't draw now, it slows things down. # Note that for each object we make and store here (which is # normalized), there will be a second (sized) copy of the same object # created in resizeGraphics(). We could consider storing this data # in some other way for speed, but perf. tests for #122 showed # that this use of multiple object creation wasn't a big hit at all. ll=Line2D(xs, ys, linestyle=self.lineAttributes.linestyle, linewidth=GKI_TO_MPL_LINEWIDTH*self.lineAttributes.linewidth, color=self.lineAttributes.color) self.__normLines.append(ll) # While we are here and obviously getting drawing commands from the # task, set our draw-saving flag. This covers the case of the # interactive task during a redraw from a user-typed 'r'. That entire # case goes: 1) no drawing during initial plot creation, 2) task is # done giving us gki commands when gcur() seen, so drawing is allowed # 3) user types 'r' or similar to cause a redraw so this turns off # drawing again (to speed it up) until the next gcur() is seen. self.__allowDrawing = False def gki_polymarker(self, arg): """ Instructed to draw a GKI polymarker. IRAF only implements points for polymarker, so that makes it simple. """ # record this operation as a tuple in the draw buffer self._plotAppend(self.gki_polymarker, arg) # commit pending WCS changes when draw is found self.wcs.commit() # Reshape to get x's and y's # arg[0] is the num pairs, so: len(arg)-1 == 2*arg[0] verts = gki.ndc(arg[1:]) rshpd = verts.reshape(arg[0],2) xs = rshpd[:,0] ys = rshpd[:,1] # put the normalized data into a Line2D object, append to our list # later we will scale it and append it to the fig. See performance # note in gki_polyline() ll=Line2D(xs, ys, linestyle='', marker='.', markersize=3.0, markeredgewidth=0.0, markerfacecolor=self.markerAttributes.color, color=self.markerAttributes.color) self.__normLines.append(ll) def calculateMplTextAngle(self, charUp, textPath): """ From the given GKI charUp and textPath values, calculate the rotation angle to be used for text. Oddly, it seems that textPath and charUp both serve similar purposes, so we will have to look at them both in order to figure the rotation angle. One might have assumed that textPath could have meant "L to R" vs. "R to L", but that does not seem to be the case - it seems to be rotation angle. """ # charUp range if charUp < 0: charUp += 360. charUp = math.fmod(charUp, 360.) # get angle from textPath angle = charUp+270. # deflt CHARPATH_RIGHT if textPath == textattrib.CHARPATH_UP: angle = charUp elif textPath == textattrib.CHARPATH_LEFT: angle = charUp+90. elif textPath == textattrib.CHARPATH_DOWN: angle = charUp+180. # return from 0-360 return math.fmod(angle,360.) def gki_text(self, arg): """ Instructed to draw some GKI text """ # record this operation as a tuple in the draw buffer self._plotAppend(self.gki_text, arg) # commit pending WCS changes self.wcs.commit() # add the text x = gki.ndc(arg[0]) y = gki.ndc(arg[1]) text = ndarr2str(arg[3:].astype(numpy.int8)) ta = self.textAttributes # For now, force this to be non-bold for decent looking plots. It # seems (oddly) that IRAF/GKI tends to overuse boldness in graphics. # A fix to mpl (near 0.91.2) makes bold text show as reeeeally bold. # However, assume the user knows what they are doing with their # settings if they have set a non-standard (1.0) charSize. weight = 'normal' if (MPL_MAJ_MIN < 0.91) or (abs(ta.charSize - 1.0) > .0001): # only on these cases do we pay attention to 'bold' in textFont if ta.textFont.find('bold') >= 0: weight = 'bold' style = 'italic' if ta.textFont.find('italic') < 0: style = 'normal' # Calculate initial fontsize fsz = self.getTextPointSize(ta.charSize, self.__xsz, self.__ysz) # figure rotation angle rot = self.calculateMplTextAngle(ta.charUp, ta.textPath) # Kludge alert - only use the GKI_TEXT_Y_OFFSET in cases where # we know the text is a simple level label (not in a contour, etc) yOffset = 0.0 if abs(rot) < .0001 and ta.textHorizontalJust=='center': yOffset = GKI_TEXT_Y_OFFSET # Note that we add the text here in NDC (0.0-1.0) x,y and that # the fig takes care of resizing for us. t = self.__fig.text(x, y-yOffset, text, \ color=ta.textColor, rotation=rot, horizontalalignment=ta.textHorizontalJust, verticalalignment=ta.textVerticalJust, fontweight=weight, # [ 'normal' | 'bold' | ... ] fontstyle=style, # [ 'normal' | 'italic' | 'oblique'] fontsize=fsz) # To this Text object just created, we need to attach the GKI charSize # scale factor, since we will need it later during a resize. Mpl # knows nothing about this, but we support it for GKI. t.gkiTextSzFactor = ta.charSize # add attribute def gki_fillarea(self, arg): """ Instructed to draw a GKI fillarea """ # record this operation as a tuple in the draw buffer self._plotAppend(self.gki_fillarea, arg) # commit pending WCS changes self.wcs.commit() # plot the fillarea fa = self.fillAttributes verts = gki.ndc(arg[1:]) # fillstyle 0=clear, 1=hollow, 2=solid, 3-6=hatch # default case is 'solid' (fully filled solid color) # 'hatch' case seems to be unused ec = fa.color fc = fa.color fll = 1 if fa.fillstyle == 0: # 'clear' (fully filled with black) ec = self.colorManager.setDrawingColor(0) fc = ec fll = 1 if fa.fillstyle == 1: # 'hollow' (just the rectangle line, empty) ec = fa.color fc = None fll = 0 lowerleft = (verts[0], verts[1]) width = verts[4]-verts[0] height = verts[5]-verts[1] rr = Rectangle(lowerleft, width, height, edgecolor=ec, facecolor=fc, fill=fll) self.__normPatches.append(rr) def gki_putcellarray(self, arg): self.wcs.commit() self.errorMessage(gki.standardNotImplemented % "GKI_PUTCELLARRAY") def gki_setcursor(self, arg): # record this operation as a tuple in the draw buffer self._plotAppend(self.gki_setcursor, arg) # get x and y cursorNumber = arg[0] x = gki.ndc(arg[1]) y = gki.ndc(arg[2]) # 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 = self.__mca.getSWCursor() if swCurObj: swCurObj.moveTo(x, y, SWmove=1) # wutil.moveCursorTo uses 0,0 <--> upper left, need to convert sx = int( x * self.gwidget.winfo_width()) sy = int((1-y) * self.gwidget.winfo_height()) rx = self.gwidget.winfo_rootx() ry = self.gwidget.winfo_rooty() # call the wutil version to move the cursor moveCursorTo(self.gwidget.winfo_id(), rx, ry, sx, sy) def gki_plset(self, arg): # record this operation as a tuple in the draw buffer self._plotAppend(self.gki_plset, arg) # Handle case where some terms (eg. xgterm) allow higher values, # by looping over the possible visible patterns. (ticket #172) arg0 = arg[0] if arg0 >= len(GKI_TO_MPL_LINESTYLE): num_visible = len(GKI_TO_MPL_LINESTYLE)-1 arg0 = 1 + (arg0 % num_visible) # Note that GkiTkplotKernel saves color (arg[2]) in numeric format, # but we keep it in the rgb strng form which mpl can readily use. # Same note for linestyle, changed from number to mpl symbol. self.lineAttributes.set(GKI_TO_MPL_LINESTYLE[arg0], # linestyle arg[1]/gki.GKI_FLOAT_FACTOR, # linewidth self.colorManager.setDrawingColor(arg[2])) def gki_pmset(self, arg): # record this operation as a tuple in the draw buffer self._plotAppend(self.gki_pmset, arg) # set attrs. See notes about GKI_TO_MPL_MARKTYPE self.markerAttributes.set(0, #GKI_TO_MPL_MARKTYPE[arg[0]] ! type unused 0, #gki.ndc(arg[1]) ! size unused self.colorManager.setDrawingColor(arg[2])) def gki_txset(self, arg): # record this operation as a tuple in the draw buffer self._plotAppend(self.gki_txset, arg) # Set text attrs # charSize: To quote from tkplottext.py: # "We draw the line at fontSizes less than 1/2! Get real." # without the 0.5 floor, "contour dev$pix" ticklabels are too small charUp = float(arg[0]) # default: 90.0 charSize = max(0.5, arg[1]/gki.GKI_FLOAT_FACTOR) # default: 1.0 charSpace = arg[2]/gki.GKI_FLOAT_FACTOR # unused yet (0.0) textPath = arg[3] # leave as GKI code # btw, in unit testsing never saw a case where textPath != 3 textHorizontalJust = GKI_TO_MPL_HALIGN[arg[4]] textVerticalJust = GKI_TO_MPL_VALIGN[arg[5]] textFont = GKI_TO_MPL_FONTATTR[arg[6]] textQuality = GKI_TO_MPL_FONTATTR[arg[7]] # unused ? (lo,md,hi) textColor = self.colorManager.setDrawingColor(arg[8]) self.textAttributes.set(charUp, charSize, charSpace, textPath, textHorizontalJust, textVerticalJust, textFont, textQuality, textColor) def gki_faset(self, arg): # record this operation as a tuple in the draw buffer self._plotAppend(self.gki_faset, arg) # set the fill attrs self.fillAttributes.set(arg[0], # fillstyle self.colorManager.setDrawingColor(arg[1])) 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") # Note this explicitly (not for redrawing) since _plotAppend isn't used # self._noteGkiCmd(self.gki_unknown) def gRedraw(self): if self.gwidget: self.gwidget.tkRedraw() def redraw(self, o=None): """Redraw for expose or resize events, also called when page menu is used. This method generally should not be called directly -- call gwidget.tkRedraw() instead since it does some other preparations. """ # Argument o is not needed because we only get redraw # events for our own gwidget. # # DESIGN NOTE: Make sure this is not getting called for window # resizes! Using the drawBuffer is too slow and unnecessary. Resizes # should only be hooking into resizeGraphics() for performance sake. # See if we need to force the flush (of course wait until the end) frc = self._forceNextDraw self._forceNextDraw = False # Clear the screen self.clearMplData() # Plot the current buffer self.__skipPlotAppends = True for (function, args) in self.drawBuffer.get(): function(*args) self.__skipPlotAppends = False self.gki_flush(None, force=frc) # does: resize-calc's; draw; flush #----------------------------------------------- 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 noop""" 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 tkinter""" 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.14/lib/pyraf/MplCanvasAdapter.py0000644000665500117240000002161413033515761021355 0ustar sontagsts_dev00000000000000#!/usr/bin/env python """ $Id$ """ from __future__ import division # confidence high import os, matplotlib matplotlib.use('TkAgg') # set backend import matplotlib.backends.backend_tkagg as tkagg from Ptkplot import hideTkCursor from Ptkplot import FullWindowCursor from wutil import moveCursorTo, WUTIL_USING_X class MplCanvasAdapter(tkagg.FigureCanvasTkAgg): """ Is a FigureCanvasTkAgg, with extra methods to look like Canvas """ def __init__(self, gkikernel, figure, master=None): tkagg.FigureCanvasTkAgg.__init__(self, figure, master, self.resize_event) # members self.__theGwidget = self.get_tk_widget() # THE gwidget self.__gkiKernel = gkikernel self.__doIdleRedraw = 0 self.__doIdleSWCursor = 0 self.ignoreNextRedraw = 0 # used externally to this class ... # Add a placeholder software cursor attribute. If it is None, # that means no software cursor is in effect. If it is not None, # then it will be used to render the cursor. self.__isSWCursorActive = 0 self.__SWCursor = None # Basic bindings for the virtual trackball. # ! Do NOT set these without undergoing a major GkiMpl design # check ! Binding Expose to tkExpose forces tkRedraw to be # called WAY too many times during a window resize. This uses the # draw buffer, which is slow and unnecessary for resizes. # self.__theGwidget.bind('', self.tkExpose) # self.__theGwidget.bind('', self.tkExpose) # init draw issue def pack(self, **kw): """ delegate to the gwidget """ self.__theGwidget.pack(kw) def winfo_id(self): """ delegate to the gwidget """ self.__theGwidget.winfo_id() def gwidgetize(self, width, height): """ This is a one-stop shopping spot to add all the extra attributes to the gwidget object it needs to be seen as a "gwidget" in the GKI sense. See requirements in GkiInteractiveTkBase. """ gw = self.__theGwidget # Add attributes to the gwidget gw.lastX = None gw.lastY = None gw.width = width gw.height = height # Add our functions to the gwidget gw.activateSWCursor = self.activateSWCursor gw.deactivateSWCursor = self.deactivateSWCursor gw.isSWCursorActive = self.isSWCursorActive gw.getSWCursor = self.getSWCursor gw.moveCursorTo = self.moveCursorTo gw.tkRedraw = self.tkRedraw def resize_event(self, evt=None): # See also get/set_size_inches, figure.get_window_extent(). The latter # is Bbox, use .width/.height() # # before rsz: w = self.figure.get_window_extent().width # not func .98 # before rsz: h = self.figure.get_window_extent().height() # is in .91 # Note that the 'evt' arg was always part of the signature for # versions before MPL v 0.98.5.2, when it became optional. # So see if it is there. Always use it if present, it is accurate. if evt != None: curW = evt.width curH = evt.height else: dpi = self.figure.get_dpi() curW = self.figure.get_figwidth() * dpi curH = self.figure.get_figheight() * dpi # Need to deactivate cursor before resizing, then re-act. after self.wrappedRedrawOrResize(w=curW, h=curH) # also update the widget's w/h attrs; we will need this for the cursor self.__theGwidget.width = curW self.__theGwidget.height = curH def flush(self): self.__doIdleRedraw = 0 # show and draw could be defined to catch events before passing through # def show(self): tkagg.FigureCanvasTkAgg.show(self) # def draw(self): tkagg.FigureCanvasTkAgg.draw(self) def wrappedRedrawOrResize(self, w=None, h=None): """Wrap the redraw (or resize) with a deactivate and then re-activate of the cursor. If w or h are provided then we are only resizing.""" # need to indicate cursor is not visible before this, since # cursor sleeps are now issued by redraw. The presumption is that # redraw/resize will affect cursor visibility, so we set it first resizing = w != None cursorActivate = 0 if self.__isSWCursorActive: # deactivate cursor for duration of redraw # otherwise it slows the redraw to a glacial pace cursorActivate = 1 x = self.__SWCursor.lastx y = self.__SWCursor.lasty self.deactivateSWCursor() if resizing: self.__gkiKernel.resizeGraphics(w, h) else: # should document how this line gets to GkiMplKernel.redraw() self.__theGwidget.redraw(self.__theGwidget) if cursorActivate: # Need to activate it, but don't draw it if only resizing, there is # a bug where the previous crosshair cursor is drawn too. self.activateSWCursor(x, y, drawToo=(not resizing)) # tkRedraw() is used as if it belonged to the gwidget's class def tkRedraw(self, *dummy): """ delegate to the gwidget """ gw = self.__theGwidget self.__doIdleRedraw = 1 gw.after_idle(self.idleRedraw) def idleRedraw(self): """Do a redraw, then set buffer so no more happen on this idle cycle""" if self.__doIdleRedraw: self.__doIdleRedraw = 0 self.wrappedRedrawOrResize() # isSWCursorActive() is used as if it belonged to the gwidget's class def isSWCursorActive(self): """ getter """ return self.__isSWCursorActive # activateSWCursor() is used as if it belonged to the gwidget's class def activateSWCursor(self, x=None, y=None, type=None, drawToo=True): gw = self.__theGwidget hideTkCursor(gw) # from Ptkplot # ignore type for now since only one type of software cursor # is implemented gw.update_idletasks() if not self.__isSWCursorActive: if not self.__SWCursor: self.__SWCursor = FullWindowCursor(0.5, 0.5, gw) self.__isSWCursorActive = 1 gw.bind("",self.moveCursor) if drawToo and not self.__SWCursor.isVisible(): self.__SWCursor.draw() # deactivateSWCursor() is used as if it belonged to the gwidget's class def deactivateSWCursor(self): gw = self.__theGwidget gw.update_idletasks() if self.__isSWCursorActive: self.__SWCursor.erase() gw.unbind("") self.__SWCursor.isLastSWmove = 1 self.__isSWCursorActive = 0 gw['cursor'] = 'arrow' # set back to normal # getSWCursor() is used as if it belonged to the gwidget's class def getSWCursor(self): """ getter """ return self.__SWCursor def SWCursorWake(self): """ Wake cursor only after idle """ self.__doIdleSWCursor = 1 self.after_idle(self.idleSWCursorWake) def idleSWCursorWake(self): """Do cursor redraw, then reset so no more happen on this idle cycle""" if self.__doIdleSWCursor: self.__doIdleSWCursor = 0 self.SWCursorImmediateWake() def SWCursorImmediateWake(self): if self.__isSWCursorActive: self.__SWCursor.draw() def moveCursor(self, event): """Call back for mouse motion events""" # Kludge to handle the fact that MacOS X (X11) doesn't remember # software driven moves, the first move will just move nothing # but will properly update the coordinates. Do not do this under Aqua. gw = self.__theGwidget if WUTIL_USING_X and self.__SWCursor.isLastSWmove: x = self.__SWCursor.lastx y = self.__SWCursor.lasty # call the wutil version moveCursorTo(gw.winfo_id(), gw.winfo_rootx(), gw.winfo_rooty(), int(x*gw.winfo_width()), int((1.-y)*gw.winfo_height())) else: x = (event.x+0.5)/gw.winfo_width() y = 1.-(event.y+0.5)/gw.winfo_height() self.__SWCursor.moveTo(x,y,SWmove=0) # moveCursorTo() is used as if it belonged to the gwidget's class def moveCursorTo(self, x, y, SWmove=0): self.__SWCursor.moveTo(float(x)/self.__theGwidget.width, float(y)/self.__theGwidget.height, SWmove) def activate(self): """Not really needed for Tkplot widgets""" pass def tkExpose(self, *dummy): """Redraw the widget upon the tkExpose event. Make it active, update tk events, call redraw procedure.""" if self.ignoreNextRedraw: self.ignoreNextRedraw = 0 else: self.tkRedraw() pyraf-2.1.14/lib/pyraf/Ptkplot.py0000644000665500117240000002501613033515761017625 0ustar sontagsts_dev00000000000000#!/usr/bin/env python """ $Id$ """ from __future__ import division # confidence high import os from Tkinter import _default_root # requires 2to3 from Tkinter import * import wutil import sys, time # XBM file for cursor is in same directory as this module _blankcursor = 'blankcursor.xbm' dirname = os.path.dirname(__file__) if os.path.isabs(dirname): _blankcursor = os.path.join(dirname, _blankcursor) else: # change relative directory paths to absolute _blankcursor = os.path.join(os.getcwd(), dirname, _blankcursor) del dirname _TK_HAS_NONE_CURSOR = True # assume True until we learn otherwise if _default_root is None: from stsci.tools import irafutils _default_root = irafutils.init_tk_default_root() # This code is needed to avoid faults on sys.exit() # [DAA, Jan 1998] # [Modified by RLW to use new atexit module, Dec 2001] def cleanup(): try: from Tkinter import _default_root, TclError # requires 2to3 import Tkinter as TKNTR try: if _default_root: _default_root.destroy() except TclError: pass TKNTR._default_root = None except SystemError: # If cleanup() is called before pyraf fully loads, we will # see: "SystemError: Parent module 'pyraf' not loaded". In that case, # since nothing was done yet w/ _default_root, we can safely skip this. pass import atexit atexit.register(cleanup) # [end DAA] # crosshair cursor color (only has an effect in indexed color mode) # this is global so that it applies to all Ptkplot widgets cursorColor = 1 cursorTrueColor = (1.0, 0.0, 0.0) # visuals that use true colors truevis = { 'truecolor' : 1, 'directcolor' : 1, } def hideTkCursor(theCanvas): """ Make the existing cursor disappear. """ # Make the existing cursor disappear. There currently isn't a # better way to disable a cursor in Tk. In Tk 8.5, there will be a # 'none' option to set the cursor to. Until then, load a blank cursor # from an XBM file - is in same directory as this module. Might, on OSX # only, be able to use: CGDisplay[Hide,Show]Cursor() - the problem with # this is that the cursor s gone even when trying to use menu items, as # long as the GUI is the front process. # # Note - the blankcursor format is causing errors on some non-Linux # platforms, so we need to use 'none' or 'tcross' for now. global _TK_HAS_NONE_CURSOR global _blankcursor if _TK_HAS_NONE_CURSOR: # See if this supports the 'none' cursor try: theCanvas['cursor'] = 'none' return except TclError: _TK_HAS_NONE_CURSOR = False # If we get here, the 'none' cursor is not yet supported. Load the blank # one, or use 'tcross'. if wutil.WUTIL_USING_X: theCanvas['cursor'] = '@' + _blankcursor + ' black' else: theCanvas['cursor'] = 'tcross' # this'll do for now class PyrafCanvas(Canvas): """Widget""" def __init__(self, master=None, **kw): Canvas.__init__(self, master, kw) self.doIdleRedraw = 0 self.doIdleSWCursor = 0 self.ignoreNextRedraw = 0 # Add a placeholder software cursor attribute. If it is None, # that means no software cursor is in effect. If it is not None, # then it will be used to render the cursor. self._isSWCursorActive = 0 self._SWCursor = None self.initialised = 0 # to save last cursor position if switching to another window self.lastX = None self.lastY = None self.width = self.winfo_width() # to avoid repeated calls self.height = self.winfo_height() # Basic bindings for the virtual trackball self.bind('', self.tkExpose) self.bind('', self.tkExpose) #self.after_idle(self.refresh_cursor) def flush(self): self.doIdleRedraw = 0 def immediateRedraw(self): # need to indicate cursor is not visible before redraw, since # cursor sleeps are now issued by redraw. The presumption is that # redraw will wipe out cursor visibility, so we set it first if self._isSWCursorActive: # deactivate cursor for duration of redraw # otherwise it slows the redraw to a glacial pace cursorActivate = 1 x = self._SWCursor.lastx y = self._SWCursor.lasty self.deactivateSWCursor() else: cursorActivate = 0 self.redraw(self) if cursorActivate: self.activateSWCursor(x,y) def tkRedraw(self, *dummy): self.doIdleRedraw = 1 self.after_idle(self.idleRedraw) def idleRedraw(self): """Do a redraw, then set buffer so no more happen on this idle cycle""" if self.doIdleRedraw: self.doIdleRedraw = 0 self.immediateRedraw() def isSWCursorActive(self): return self._isSWCursorActive def activateSWCursor(self, x=None, y=None, type=None): hideTkCursor(self) # ignore type for now since only one type of software cursor # is implemented self.update_idletasks() if not self._isSWCursorActive: if not self._SWCursor: self._SWCursor = FullWindowCursor(0.5, 0.5, self) self._isSWCursorActive = 1 self.bind("",self.moveCursor) if not self._SWCursor.isVisible(): self._SWCursor.draw() def deactivateSWCursor(self): self.update_idletasks() if self._isSWCursorActive: self._SWCursor.erase() self.unbind("") self._SWCursor.isLastSWmove = 1 self._isSWCursorActive = 0 self['cursor'] = 'arrow' def getSWCursor(self): return self._SWCursor def SWCursorWake(self): self.doIdleSWCursor = 1 self.after_idle(self.idleSWCursorWake) def idleSWCursorWake(self): """Do cursor redraw, then reset so no more happen on this idle cycle""" if self.doIdleSWCursor: self.doIdleSWCursor = 0 self.SWCursorImmediateWake() def SWCursorImmediateWake(self): if self._isSWCursorActive: self._SWCursor.draw() def moveCursor(self, event): """Call back for mouse motion events""" # Kludge to handle the fact that MacOS X (X11) doesn't remember # software driven moves, the first move will just move nothing # but will properly update the coordinates. Do not do this under Aqua. if wutil.WUTIL_USING_X and self._SWCursor.isLastSWmove: x = self._SWCursor.lastx y = self._SWCursor.lasty wutil.moveCursorTo(self.winfo_id(), self.winfo_rootx(), self.winfo_rooty(), int(x*self.winfo_width()), int((1.-y)*self.winfo_height())) else: x = (event.x+0.5)/self.winfo_width() y = 1.-(event.y+0.5)/self.winfo_height() self._SWCursor.moveTo(x,y,SWmove=0) def moveCursorTo(self, x, y, SWmove=0): self._SWCursor.moveTo(float(x)/self.width, float(y)/self.height, SWmove) def activate(self): """Not really needed for Tkplot widgets (used to set OpenGL win)""" pass def set_background(self, r, g, b): """Change the background color of the widget.""" self.tkRedraw() def tkExpose(self, *dummy): """Redraw the widget. Make it active, update tk events, call redraw procedure and swap the buffers. Note: swapbuffers is clever enough to only swap double buffered visuals.""" self.width = self.winfo_width() self.height = self.winfo_height() if self.ignoreNextRedraw: self.ignoreNextRedraw = 0 else: if not self.initialised: self.initialised = 1 self.tkRedraw() class FullWindowCursor: """This implements a full window crosshair cursor. This class can operate in the xutil-wrapping mode or in a tkinter-only mode. """ # Perhaps this should inherit from an abstract Cursor class eventually def __init__(self, x, y, window): """Display the cursor for the first time. The passed in window also needs to act as a Tk Canvas object.""" self.lastx = x self.lasty = y self.__useX11 = wutil.WUTIL_USING_X and (not wutil.WUTIL_ON_MAC) self.__window = window self.__isVisible = 0 self.isLastSWmove = 1 # indicates if last position driven by # sofware command or by mouse events. # Kludgy, and used by modules using the # cursor position. self.__tkHorLine = None self.__tkVerLine = None self.draw() def _xutilXorDraw(self): wutil.drawCursor(self.__window.winfo_id(), self.lastx, self.lasty, int(self.__window.width), int(self.__window.height)) def _tkDrawCursor(self): self._tkEraseCursor() # coords and window sizes ww = self.__window.width wh = self.__window.height x = self.lastx*ww y = (1.0-self.lasty)*wh # Draw the crosshairs. __window is a Tk Canvas object self.__tkHorLine = self.__window.create_line(0,y,ww,y,fill='red') self.__tkVerLine = self.__window.create_line(x,0,x,wh,fill='red') def _tkEraseCursor(self): if self.__tkHorLine != None: self.__window.delete(self.__tkHorLine); self.__tkHorLine = None if self.__tkVerLine != None: self.__window.delete(self.__tkVerLine); self.__tkVerLine = None def isVisible(self): return self.__isVisible def erase(self): if self.__isVisible: if self.__useX11: self._xutilXorDraw() else: self._tkEraseCursor() self.__isVisible = 0 def draw(self): if not self.__isVisible: if self.__useX11: self._xutilXorDraw() else: self._tkDrawCursor() self.__isVisible = 1 def moveTo(self,x,y, SWmove=0): if (self.lastx != x) or (self.lasty != y): self.erase() # erase previous cursor self.lastx = x self.lasty = y self.draw() # xdraw new position if SWmove: self.isLastSWmove = 1 else: self.isLastSWmove = 0 pyraf-2.1.14/lib/pyraf/__init__.py0000644000665500117240000002204613033515761017727 0ustar sontagsts_dev00000000000000""" __init__.py: main Pyraf package initialization Checks sys.argv[0] == 'pyraf' to determine whether IRAF initialization is done verbosely or quietly. $Id$ R. White, 2000 February 18 """ from __future__ import division # confidence high from .version import * import os, sys, __main__ # dev only: add revision number if possible (if not done yet) if __version__.endswith('dev'): try: # did we set this via git? from .version_vcs import __vcs_revision__ __version__ = __version__+'-'+__vcs_revision__ except: # must be using svn still if len(__svn_revision__) > 0 and __svn_revision__[0].isdigit(): __version__ = __version__+__svn_revision__ # Dump version and exit here, if requested if '-V' in sys.argv or '--version' in sys.argv: print __version__ sys.stdout.flush() os._exit(0) # see note in usage() # Do a quick, non-intrusive check to see how verbose we are. This is # just for here. This does not correctly count -v vs. -vv, -vvv, etc. _verbosity_ = len([j for j in sys.argv if j in ('--verbose','-v','-vv','-vvv')]) # Show version at earliest possible moment when in debugging/verbose mode. if _verbosity_ > 0: print 'pyraf version '+__version__ def usage(): print __main__.__doc__ sys.stdout.flush() irafimport.restoreBuiltins() # exit with prejudice, not a raised exc; we don't want/need anything # to be run at this point - else we'd see run-time import warnings os._exit(0) # set search path to include current directory if "." not in sys.path: sys.path.insert(0, ".") # Grab the terminal window's id at the earliest possible moment import wutil # Since numpy as absolutely required for any PyRAF use, go ahead and # import it now, just to check it if _verbosity_ > 0: print "pyraf: importing numpy" try: import numpy except ImportError: print "The numpy package is required by PyRAF and was not found. Please visit http://numpy.scipy.org" os._exit(1) if _verbosity_ > 0: print "pyraf: imported numpy" # Modify the standard import mechanism to make it more # convenient for the iraf module if _verbosity_ > 0: print "pyraf: importing irafimport" import irafimport if _verbosity_ > 0: print "pyraf: imported irafimport" # this gives more useful tracebacks for CL scripts import cllinecache import irafnames # initialization is silent unless program name is 'pyraf' or # silent flag is set on command line if _verbosity_ > 0: print "pyraf: setting _pyrafMain" # follow links to get to the real executable filename executable = sys.argv[0] while os.path.islink(executable): executable = os.readlink(executable) _pyrafMain = os.path.split(executable)[1] in ('pyraf', 'runpyraf.py') del executable runCmd = None import irafexecute, clcache from stsci.tools import capable if _verbosity_ > 0: print "pyraf: setting exit handler" # set up exit handler to close caches def _cleanup(): if iraf: iraf.gflush() if hasattr(irafexecute,'processCache'): del irafexecute.processCache if hasattr(clcache,'codeCache'): del clcache.codeCache # Register the exit handler, but only if 'pyraf' is going to import fully # But, always register it when in Python-API mode (CNSHD817031) if not _pyrafMain or ('-h' not in sys.argv and '--help' not in sys.argv): import atexit atexit.register(_cleanup) del atexit if _verbosity_ > 0: print "pyraf: finished all work prior to IRAF use" # now get ready to do the serious IRAF initialization if not _pyrafMain: # if not executing as pyraf main, just initialize iraf module # quietly load initial iraf symbols and packages if _verbosity_ > 0: print "pyraf: initializing IRAF" import iraf if _verbosity_ > 0: print "pyraf: imported iraf" # If iraf.Init() throws an exception, we cannot be confident # that it has initialized properly. This can later lead to # exceptions from an atexit function. This results in a lot # of help tickets about "gki", which are really caused by # something wrong in login.cl # # By setting iraf=None in the case of an exception, the cleanup # function skips the parts that don't work. By re-raising the # exception, we ensure that the user sees what really happened. # # This is the case for "import pyraf" try : iraf.Init(doprint=0, hush=1) except : iraf = None raise if _verbosity_ > 0: print "pyraf: initialized IRAF" else: if _verbosity_ > 0: print "pyraf: is main program" # special initialization when this is the main program # command-line options import pyrafglobals as _pyrafglobals import getopt try: optlist, args = getopt.getopt(sys.argv[1:], "imc:vhsney", ["commandwrapper=", "command=", "verbose", "help", "silent", "nosplash","ipython", "ecl"]) if len(args) > 1: print 'Error: more than one savefile argument' usage() except getopt.error, e: print str(e) usage() verbose = 0 doCmdline = 1 _silent = 0 _dosplash = capable.OF_GRAPHICS _use_ipython_shell = 0 if optlist: for opt, value in optlist: if opt == "-i": doCmdline = 0 elif opt == "-m": doCmdline = 1 elif opt == "--commandwrapper": if value in ("yes", "y"): doCmdline = 1 elif value in ("no", "n"): doCmdline = 0 else: usage() elif opt in ("-c", "--command"): if value != None and len(value) > 0: runCmd = value else: usage() elif opt in ("-v", "--verbose"): verbose = verbose + 1 elif opt in ("-h", "--help"): usage() elif opt in ("-s", "--silent"): _silent = 1 elif opt in ("-n", "--nosplash"): _dosplash = 0 elif opt in ("-y", "--ipython"): _use_ipython_shell = 1 elif opt in ("-e", "--ecl"): _pyrafglobals._use_ecl = True else: print "Program bug, uninterpreted option", opt raise SystemExit if "epyraf" in sys.argv[0]: # See also -e and --ecl switches _pyrafglobals._use_ecl = True if _verbosity_ > 0: print "pyraf: finished arg parsing" import iraf if _verbosity_ > 0: print "pyraf: imported iraf" iraf.setVerbose(verbose) del getopt, verbose, usage, optlist # If not silent and graphics is available, use splash window if _silent: _splash = None _initkw = {'doprint': 0, 'hush': 1} else: _initkw = {} if _dosplash: import splash _splash = splash.splash('PyRAF '+__version__) else: _splash = None if _verbosity_ > 0: print "pyraf: splashed" # load initial iraf symbols and packages # If iraf.Init() throws an exception, we cannot be confident # that it has initialized properly. This can later lead to # exceptions from an atexit function. This results in a lot # of help tickets about "gki", which are really caused by # something wrong in login.cl # # By setting iraf=None in the case of an exception, the cleanup # function skips the parts that don't work. By re-raising the # exception, we ensure that the user sees what really happened. # # This is the case for pyraf invoked from the command line. try : if args: iraf.Init(savefile=args[0], **_initkw) else: iraf.Init(**_initkw) except : iraf = None raise del args if _verbosity_ > 0: print "pyraf: finished iraf.Init" if _splash: _splash.Destroy() del _splash, _silent, _dosplash del _verbosity_ help = iraf.help # We have too many users on systems that have stack limits that # are too low to run IRAF tasks. Instead of waiting for all of # them to send us a help ticket and then telling them to raise # the limit, we raise it here. try : import resource except ImportError : # ok to skip this on Windows pass else : def raise_limit(which, howmuch): # We have to know the old limit so we don't ask to go above that. # Some systems don't let you use -1 for the max limit if the # max limit is already set. oldlims = resource.getrlimit(which) # Raise it to the max. if (howmuch is None) or (howmuch == -1) : newlims = (oldlims[1], oldlims[1]) else : newlims = (howmuch, oldlims[1]) # Try to set it. try : resource.setrlimit(which, newlims) # this naughty little bit of code can raise either resource.error, # ValueError, or OSError, depending on the Python version. Catch all. except: # Well, we tried; this is nothing worth killing pyraf over, # though -- either we will get by anyway, or we will find # out later. pass # Currently, we just raise the stack limit. raise_limit(resource.RLIMIT_STACK, None) pyraf-2.1.14/lib/pyraf/aqutil.py0000644000665500117240000002176513033515761017476 0ustar sontagsts_dev00000000000000""" Contains Python routines to do special Aqua (OSX) window manipulations not possible in tkinter. In general, an attempt is made to use the Pyobjc bridging package so that compiling another C extension is not needed. $Id$ """ from __future__ import division # confidence high import os, struct, time import objc import AppKit from stsci.tools.for2to3 import tobytes # Arbitrary module constants for term vs. gui. 0 and negative numbers have # special meanings when queried elsewhere (e.g. wutil). It is assumed that # these two values are very unlikely to collide with actual Tk widget id's. WIN_ID_TERM = 101 WIN_ID_GUI = 102 # There are different calling sequences/requirements in 10.4 vs. 10.5. # See what the Darwin major version number is. __objcReqsVoids = os.uname()[2] # str: darwin num __objcReqsVoids = int(__objcReqsVoids.split('.')[0]) # int: darwin maj __objcReqsVoids = __objcReqsVoids > 8 # bool: if 9+ # module variables __thisPSN = None __termPSN = None __screenHeight = 0 __initialized = False def focusOnGui(): """ Set focus to GUI """ global __thisPSN err = SetFrontProcess(__thisPSN) if err: raise Exception("SetFrontProcess: "+str(err)) def focusOnTerm(after=0): """ Set focus to terminal """ global __termPSN if after > 0: time.sleep(after) err = SetFrontProcess(__termPSN) if err: raise Exception("SetFrontProcess: "+str(err)) def guiHasFocus(after=0): """ Return True if GUI has focus """ global __objcReqsVoids if after > 0: time.sleep(after) if __objcReqsVoids: err, aPSN = GetFrontProcess(None) else: err, aPSN = GetFrontProcess() if err: raise Exception("GetFrontProcess: "+str(err)) return aPSN == __thisPSN def termHasFocus(): """ Return True if terminal has focus """ global __objcReqsVoids if __objcReqsVoids: err, aPSN = GetFrontProcess(None) else: err, aPSN = GetFrontProcess() if err: raise Exception("GetFrontProcess: "+str(err)) return aPSN == __termPSN def getTopIdFor(winId): """ In Aqua we only use the two IDs and they are both "top-level" ids.""" if winId == WIN_ID_TERM: return WIN_ID_TERM # its either the terminal window else: return WIN_ID_GUI # or some kind of gui (e.g. all Tk winfo id values) def getParentID(winId): """ In Aqua we only use the two IDs and they are both "top-level" ids.""" return winId def getFocalWindowID(): """ Return the window ID for the window which currently has the focus. On OSX, the actual window ID's are not important here. We only need to distinguish between the terminal and the GUI. In fact, we treat all GUI windows as having the same ID. """ if termHasFocus(): return WIN_ID_TERM # 1 == terminal else: return WIN_ID_GUI # 2 == any GUI window def setFocusTo(windowID): """ Move the focus to the given window ID (see getFocalWindowID docs) """ # We could do something fancy like create unique window id's out of the # process serial numbers (PSN's), but for now stick with WIN_ID_* if not windowID in (WIN_ID_TERM, WIN_ID_GUI): raise RuntimeError("Bug: unexpected OSX windowID: "+str(windowID)) if windowID == WIN_ID_TERM: focusOnTerm() else: focusOnGui() def moveCursorTo(windowID, rx, ry, x, y): """ Move the cursor to the given location. This (non-X) version does not use the windowID for a GUI location - it uses rx and ry. """ loc = (rx+x, ry+y) err = CGWarpMouseCursorPosition(loc) # CG is for CoreGraphics (Quartz) if err: raise Exception("CGWarpMouseCursorPosition: "+str(err)) def getPointerGlobalPosition(): """ Gets value of the mouse/cursor loc on screen; origin = top left. """ global __screenHeight # We could use CGSGetCurrentCursorLocation (CGS: CoreGraphics Services) to # get cursor position, but it is a private, questionable API (June 2008). # NSEvent supports a class-method to always hold the cursor loc. # It gives us the values in NSPoint coords (pixels). These are fine, # except that the NSPoint origin is the bottom left of the screen, so we # need to convert to the top-left origin. pos = AppKit.NSEvent.mouseLocation() # current mouse location if __screenHeight <= 0: raise Exception("Bug: aqutil module uninitialized") return { 'x' : pos.x, 'y' : __screenHeight - pos.y } def getPointerPosition(windowID): """ Cursor position with respect to a window corner is not supported. """ return None def redeclareTerm(): """ Sometimes the terminal process isn't chosen correctly. This is used to fix that by declaring again which process is the terminal. Call this from the terminal ONLY when it is foremost on the desktop. """ global __termPSN, __objcReqsVoids oldval = __termPSN if __objcReqsVoids: err, __termPSN = GetFrontProcess(None) else: err, __termPSN = GetFrontProcess() if err: __termPSN = oldval raise Exception("GetFrontProcess: "+str(err)) def __doPyobjcWinInit(): """ Initialize the Pyobjc bridging and make some calls to get our PSN and the parent terminal's PSN. Do only ONCE per process. """ # for #108, also see # http://www.fruitstandsoftware.com/blog/2012/08/quick-and-easy-debugging-of-unrecognized-selector-sent-to-instance/ # and # http://www.raywenderlich.com/10209/my-app-crashed-now-what-part-1 global __thisPSN, __termPSN, __screenHeight, __initialized, __objcReqsVoids # Guard against accidental second calls if __initialized: return # Taken in part from PyObjc's Examples/Scripts/wmEnable.py # Be aware that '^' means objc._C_PTR # # input par: n^ # output par: o^ # inout par: N^ # return values are the first in the list, and 'v' is void # OSErr = objc._C_SHT OSStat = objc._C_INT CGErr = objc._C_INT INPSN = tobytes('n^{ProcessSerialNumber=LL}') OUTPSN = tobytes('o^{ProcessSerialNumber=LL}') # OUTPID = tobytes('o^_C_ULNG') # invalid as of objc v3.2 WARPSIG = tobytes('v{CGPoint=ff}') if struct.calcsize("l") > 4: # is 64-bit python WARPSIG = tobytes('v{CGPoint=dd}') FUNCTIONS=[ # These are public API ( u'GetCurrentProcess', OSErr+OUTPSN), ( u'GetFrontProcess', OSErr+OUTPSN), # ( u'GetProcessPID', OSStat+INPSN+OUTPID), # see OUTPID note ( u'SetFrontProcess', OSErr+INPSN), ( u'CGWarpMouseCursorPosition', WARPSIG), ( u'CGMainDisplayID', objc._C_PTR+objc._C_VOID), ( u'CGDisplayPixelsHigh', objc._C_ULNG+objc._C_ULNG), ( u'CGDisplayHideCursor', CGErr+objc._C_ULNG), ( u'CGDisplayShowCursor', CGErr+objc._C_ULNG), # This is undocumented API ( u'CPSSetProcessName', OSErr+INPSN+objc._C_CHARPTR), ( u'CPSEnableForegroundOperation', OSErr+INPSN), ] bndl = AppKit.NSBundle.bundleWithPath_(objc.pathForFramework( u'/System/Library/Frameworks/ApplicationServices.framework')) if bndl is None: raise Exception("Error in aqutil with bundleWithPath_") # Load the functions into the global (module) namespace objc.loadBundleFunctions(bndl, globals(), FUNCTIONS) for (fn, sig) in FUNCTIONS: if fn not in globals(): raise Exception("Not found: "+str(fn)) # Get terminal's PSN (on OSX assume terminal is now frontmost process) # Do this before even setting the PyRAF process to a FG app. # Or use GetProcessInformation w/ __thisPSN, then pinfo.processLauncher if __objcReqsVoids: err, __termPSN = GetFrontProcess(None) else: err, __termPSN = GetFrontProcess() if err: raise Exception("GetFrontProcess: "+str(err)) # Get our PSN # [debug PSN numbers (get pid's) via psn2pid, or use GetProcessPID()] if __objcReqsVoids: err, __thisPSN = GetCurrentProcess(None) else: err, __thisPSN = GetCurrentProcess() if err: raise Exception("GetCurrentProcess: "+str(err)) # Set Proc name err = CPSSetProcessName(__thisPSN, tobytes('PyRAF')) if err: raise Exception("CPSSetProcessName: "+str(err)) # Make us a FG app (need to be in order to use SetFrontProcess on us) # This must be done unless we are called with pythonw. # Apparently the 1010 error is more of a warning... err = CPSEnableForegroundOperation(__thisPSN) if err and err != 1010: raise Exception("CPSEnableForegroundOperation: "+str(err)) # Get the display's absolute height (pixels). # The next line assumes the tkinter root window has already been created # (and withdrawn), but it may have not yet been. # __screenHeight = tkinter._default_root.winfo_screenheight() # So, we will use the less-simple but just as viable CoreGraphics funcs. dispIdPtr = CGMainDisplayID() # no need to keep around? __screenHeight = CGDisplayPixelsHigh(dispIdPtr) # # Must be done exactly once, at the very start of the run # if not __initialized: __doPyobjcWinInit() __initialized = True pyraf-2.1.14/lib/pyraf/cgeneric.py0000644000665500117240000000374213033515761017751 0ustar sontagsts_dev00000000000000"""cgeneric.py: Context-sensitive scanner class Maintains a stack of instances of John Aycock's generic.py scanner class and allows context-sensitive switches between them. Self.current is a stack (list) of integers, with the last value pointing to the current scanner to use; by default it is initialized to zero. The ContextSensitiveScanner object is passed to the action functions, which are permitted to access and modify the current stack in order to change the state. The ContextSensitiveScanner object should also be used for instance-specific attributes (e.g., the generated token list and current line number) so that the same scanners list can be used by several different ContextSensitiveScanner objects. I also added the re match object as an argument to the action function. $Id$ Created 1999 September 10 by R. White """ from __future__ import division # confidence high class ContextSensitiveScanner: """Context-sensitive scanner""" def __init__(self, scanners, start=0): # scanners is a list or dictionary containing the # stack of scanners # start is default starting state self.scanners = scanners self.start = start def tokenize(self, s, start=None): if start is None: start = self.start self.current = [start] iend = 0 slen = len(s) while iend < slen: if not self.current: self.current = [start] scanner = self.scanners[self.current[-1]] m = scanner.re.match(s, iend) assert m groups = m.groups() for i in scanner.indexlist: if groups[i] is not None: scanner.index2func[i](groups[i],m,self) # assume there is only a single match break else: print 'cgeneric: No group found in match?' print 'Returning match object for debug' self.rv = m return iend = m.end() pyraf-2.1.14/lib/pyraf/cl2py.py0000644000665500117240000025162013033515761017223 0ustar sontagsts_dev00000000000000"""cl2py.py: Translate IRAF CL program to Python $Id$ R. White, 1999 December 20 """ from __future__ import division # confidence high import cStringIO, os, sys from generic import GenericASTTraversal from clast import AST from cltoken import Token import clscan, clparse from clcache import codeCache, DISABLE_CLCACHING from stsci.tools.irafglobals import Verbose from stsci.tools.for2to3 import PY3K from stsci.tools import basicpar, minmatch, irafutils import irafpar, pyrafglobals # The parser object can be constructed once and used many times. # The other classes have instance variables (e.g. lineno in CLScanner), # so using a single instance could screw up if several threads are trying # to use the same object. # # I handled this in the CLScanner class by creating cached versions # of the various scanners that are stateless. _parser = None def cl2py(filename=None, string=None, parlist=None, parfile="", mode="proc", local_vars_dict=None, local_vars_list=None, usecache=1): """Read CL program from file and return pycode object with Python equivalent filename: Name of the CL source file or a filehandle from which the source code can be read. string: String containing the source code. Either filename or string must be specified; if both are specified, only filename is used parlist: IrafParList object with list of parameters (which may have already been defined from a .par file) parfile: Name of the .par file used to define parlist. parlist may be defined even if parfile is null, but a null parfile is interpreted to mean that the parameter definitions in the CL script should override the parlist. If parfile is not null, it is an error if the CL script parameters conflict with the parlist. mode: Mode of translation. Default "proc" creates a procedure script (which defines a Python function.) Normally CL scripts will be translated using this default. If mode is "single" then the necessary environment is assumed to be set and the Python code simply gets executed directly. This is used in the CL compatibility mode and other places where a single line of CL must be executed. Mode also determines whether parameter sets are saved in calls to CL tasks. In "single" mode parameters do get saved; in "proc" mode they do not get saved. This is intended to be consistent with the behavior of the IRAF CL, where parameter changes in scripts are not preserved. local_vars_dict, local_vars_list: Initial definitions of local variables. May be modified by declarations in the CL code. This is used only for "single" mode to allow definitions to persist across statements. usecache: Set to false value to omit use of code cache for either saving or retrieving code. This is useful mainly for compiler testing. """ global _parser, codeCache if PY3K or DISABLE_CLCACHING: usecache = False # ! turn caching off until it is fully tested/worked # when this is turned on, see corresponding PY3K note in clcache.py! if _parser is None: _parser = clparse.getParser() if mode not in ["proc", "single"]: raise ValueError("Mode = `%s', must be `proc' or `single'" % (mode,)) if not filename in (None, ''): if isinstance(filename,str): efilename = os.path.expanduser(filename) if usecache: index, pycode = codeCache.get(efilename,mode=mode) if pycode is not None: if Verbose>1: print efilename,"filename found in CL script cache" return pycode else: index = None fh = open(efilename) clInput = fh.read() fh.close() elif hasattr(filename,'read'): clInput = filename.read() if usecache: index, pycode = codeCache.get(filename,mode=mode,source=clInput) if pycode is not None: if Verbose>1: print filename,"filehandle found in CL script cache" return pycode else: index = None if hasattr(filename,'name'): efilename = filename.name else: efilename = '' else: raise TypeError('filename must be a string or a filehandle') elif string is not None: #if not isinstance(string,str): #raise TypeError('string must be a string') clInput = string efilename = 'string_proc' # revisit this setting (tik #24), maybe '' ? if usecache: index, pycode = codeCache.get(None,mode=mode,source=clInput) if pycode is not None: if Verbose>3: print "Found in CL script cache: ",clInput.strip()[:20] return pycode else: index = None else: raise ValueError('Either filename or string must be specified') if mode == "single": taskObj = 'cl' else: taskObj = None # tokenize and parse to create the abstract syntax tree scanner = clscan.CLScanner() tokens = scanner.tokenize(clInput) tree = _parser.parse(tokens, fname=efilename) # add filename to tree root tree.filename = efilename # first pass -- get variables vars = VarList(tree,mode,local_vars_list,local_vars_dict,parlist) # check variable list for consistency with the given parlist # this may change the vars list _checkVars(vars, parlist, parfile) # second pass -- check all expression types # type info is added to tree TypeCheck(tree, vars, efilename) # third pass -- generate python code tree2python = Tree2Python(tree, vars, efilename, taskObj) # just keep the relevant fields of Tree2Python output # attach tokens to the code object too pycode = Pycode(tree2python) # add to cache if index is not None: codeCache.add(index, pycode) pycode.index = index if Verbose>1: if efilename == 'string_proc': print >> sys.stderr, "Code-string compiled by cl2py:" print >> sys.stderr, "-"*80 print >> sys.stderr, clInput print >> sys.stderr, "-"*80 else: print >> sys.stderr, "Code-file compiled by cl2py:"+efilename return pycode def checkCache(filename, pycode): """Returns true if pycode is up-to-date""" global codeCache if pycode is None: return 0 index = codeCache.getIndex(filename) return (index is not None) and (pycode.index == index) class Container: """Simple container class (no methods) for holding picklable objects""" pass class Pycode: """Container for Python CL translation""" def __init__(self, tree2python): self.code = tree2python.code self.vars = Container() self.vars.local_vars_dict = tree2python.vars.local_vars_dict self.vars.local_vars_list = tree2python.vars.local_vars_list self.vars.parList = tree2python.vars.parList self.vars.proc_name = tree2python.vars.proc_name self.vars.has_proc_stmt = tree2python.vars.has_proc_stmt def setFilename(self, filename): """Set the filename used for parameter list This is used by codeCache, which needs to be able to read a Pycode object created from some other file and attach it to the current file. """ self.vars.parList.setFilename(filename) def _checkVars(vars, parlist, parfile): """Check variable list for consistency with the given parlist""" # if there is no parfile specified, the parlist was created by default # if parlist is None, the parfile was empty # in either case, just use the parameter list specified in the CL code if (not parfile) or (parlist is None): return # parfile and parlist are specified, so create a new # list of procedure variables from parlist # check for consistency with the CL code if there was a procedure stmt if vars.has_proc_stmt and not parlist.isConsistent(vars.parList): # note we continue even if parameter lists are inconsistent. # That agrees with IRAF's approach, in which the .par file # overrides the CL script in determining parameters... #XXX Maybe could improve this by allowing certain types of #XXX mismatches (e.g. additional parameters) but not others #XXX (name or type disagreements for the same parameters.) if Verbose>0: sys.stdout.flush() sys.stderr.write("Parameters from CL code inconsistent " "with .par file for task %s\n" % vars.getProcName()) sys.stderr.flush() # create copies of the list and dictionary plist = parlist.getParList() newlist = [] newdict = {} for par in plist: newlist.append(par.name) newdict[par.name] = Variable(irafParObject=par) vars.proc_args_list = newlist vars.proc_args_dict = newdict # add mode, $nargs, other special parameters to all tasks vars.addSpecialArgs() # Check for local variables that conflict with parameters vars.checkLocalConflict() vars.parList = parlist class FindLineNumber(GenericASTTraversal): """Helper class to find first line number in an AST""" class FoundIt(Exception): pass def __init__(self,ast): GenericASTTraversal.__init__(self,ast) self.lineno = 0 try: self.preorder() except self.FoundIt: pass def default(self,node): if hasattr(node,'lineno'): self.lineno = node.lineno raise self.FoundIt class ErrorTracker: """Mixin class that does error tracking during AST traversal""" def _error_init(self): self.errlist = [] # list of 2-tuples self.warnlist = [] # list of 2-tuples self.comments = [] # list of strings def error(self, msg, node=None): """Add error to the list with line number""" if not hasattr(self, 'errlist'): self._error_init() self.errlist.append((self.getlineno(node),msg)) def warning(self, msg, node=None): """Add warning to the list with line number""" if not hasattr(self, 'errlist'): self._error_init() self.warnlist.append((self.getlineno(node),"Warning: %s" % msg)) def comment(self, msg): """Add comments to the list - to be helpful to the debugging soul""" if not hasattr(self, 'errlist'): self._error_init() self.comments.append(msg) def getlineno(self, node): # find terminal token that contains the line number if node: return FindLineNumber(node).lineno else: return 0 def errorappend(self, other): """Add errors from another ErrorTracker""" if not hasattr(other, 'errlist'): return if not hasattr(self, 'errlist'): self._error_init() self.errlist.extend(other.errlist) self.warnlist.extend(other.warnlist) self.comments.extend(other.comments) def printerrors(self): """Print all warnings and errors and raise SyntaxError if errors were found""" if not hasattr(self,'errlist'): return if self.errlist: self.errlist.extend(self.warnlist) self.errlist.sort() try: errmsg = ["Error in CL script %s" % self.filename] except AttributeError: errmsg = ["Error in CL script"] for lineno, msg in self.errlist: if lineno: errmsg.append("%s (line %d)" % (msg, lineno)) else: errmsg.append(msg) for comment in self.comments: errmsg.append(comment) raise SyntaxError("\n".join(errmsg)) elif self.warnlist: self.warnlist.sort() try: warnmsg = ["Warning in CL script %s" % self.filename] except AttributeError: warnmsg = ["Warning in CL script"] for lineno, msg in self.warnlist: if lineno: warnmsg.append("%s (line %d)" % (msg, lineno)) else: warnmsg.append(msg) for comment in self.comments: warnmsg.append(comment) warnmsg = "\n".join(warnmsg) sys.stdout.flush() sys.stderr.write(warnmsg) if warnmsg[-1:] != '\n': sys.stderr.write('\n') class ExtractProcInfo(GenericASTTraversal): """Extract name and args from procedure statement""" def __init__(self, ast): GenericASTTraversal.__init__(self, ast) self.preorder() def n_proc_stmt(self, node): # get procedure name and list of argument names self.proc_name = node[1].attr self.proc_args_list = [] if len(node[2]): self.preorder(node[2]) self.prune() def n_IDENT(self, node): self.proc_args_list.append(irafutils.translateName(node.attr)) _longTypeName = { "s": "string", "f": "file", "struct": "struct", "i": "int", "b": "bool", "r": "real", "d": "double", "gcur": "gcur", "imcur": "imcur", "ukey": "ukey", "pset": "pset", } class Variable: """Container for properties of a variable""" def __init__(self, name=None, type=None, mode="h", array_size=None, init_value=None, list_flag=0, min=None, max=None, prompt=None, enum=None, irafParObject=None): if irafParObject is not None: # define the variable info from an IrafPar object ipo = irafParObject self.name = ipo.name if ipo.type[:1] == "*": self.type = _longTypeName[ipo.type[1:]] self.list_flag = 1 else: self.type = _longTypeName[ipo.type] self.list_flag = 0 if isinstance(ipo, basicpar.IrafArrayPar): self.shape = ipo.shape else: self.shape = None self.init_value = ipo.value self.options = minmatch.MinMatchDict({ "mode": ipo.mode, "min": ipo.min, "max": ipo.max, "prompt": ipo.prompt, "enum": ipo.choice, "length": None, }) else: # define from the parameters self.name = name self.type = type self.shape = array_size self.list_flag = list_flag self.options = minmatch.MinMatchDict({ "mode": mode, "min": min, "max": max, "prompt": prompt, "enum": enum, "length": None, }) self.init_value = init_value def getName(self): """Get name without translations""" return irafutils.untranslateName(self.name) def toPar(self, strict=0): """Convert this variable to an IrafPar object""" return irafpar.makeIrafPar(self.init_value, datatype=self.type, name=self.getName(), array_size=self.shape, list_flag=self.list_flag, mode=self.options["mode"], min=self.options["min"], max=self.options["max"], enum=self.options["enum"], prompt=self.options["prompt"], strict=strict) def procLine(self): """Return a string usable as parameter declaration with default value in the function definition statement""" name = irafutils.translateName(self.name) if self.shape is None: if self.init_value is None: return name + "=None" else: return name + "=" + `self.init_value` else: # array arg = name + "=[" if self.init_value is None: arglist = ["INDEF"]*len(self) else: arglist = [] for iv in self.init_value: arglist.append(`iv`) return arg + ", ".join(arglist) + "]" def parDefLine(self, filename=None, strict=0, local=0): """Return a list of string arguments for makeIrafPar""" name = irafutils.translateName(self.name) arglist = [name, "datatype=" + `self.type`, "name=" + `self.getName()` ] # if local is set, use the default initial value instead of name # also set mode="u" for locals so they never prompt if local: arglist[0] = `self.init_value` self.options["mode"] = "u" if self.shape is not None: arglist.append("array_size=" + `self.shape`) if self.list_flag: arglist.append("list_flag=" + `self.list_flag`) keylist = self.options.keys() keylist.sort() for key in keylist: option = self.options[key] if option is not None: arglist.append(key + "=" + `self.options[key]`) if filename: arglist.append("filename=" + `filename`) if strict: arglist.append("strict=" + `strict`) return arglist def __repr__(self): s = self.type + " " if self.list_flag: s = s + "*" s = s + self.name if self.init_value is not None: s = s + " = " + `self.init_value` optstring = "{" for key, value in self.options.items(): if (value is not None) and (key != "mode" or value != "h"): # optstring = optstring + " " + key + "=" + str(value) optstring = optstring + " " + key + "=" + str(value) if len(optstring) > 1: s = s + " " + optstring + " }" return s def __len__(self): array_size = 1 if self.shape: for d in self.shape: array_size = array_size*d return array_size class ExtractDeclInfo(GenericASTTraversal, ErrorTracker): """Extract list of variable definitions from parameter block""" def __init__(self, ast, var_list, var_dict, filename): GenericASTTraversal.__init__(self, ast) self.var_list = var_list n = len(var_list) self.var_dict = var_dict self.filename = filename self.preorder() self.printerrors() def n_declaration_stmt(self, node): self.current_type = node[0].attr def _get_dims(self, node, rv=None): # expand array shape declaration if len(node)>1: return self._get_dims(node[0]) + (int(node[2]),) else: return (int(node[0]),) def n_decl_spec(self, node): var_name = node[1] name = irafutils.translateName(var_name[0].attr) if len(var_name) > 1: # array declaration shape = tuple(self._get_dims(var_name[2])) else: # apparently not an array (but this may change later # if multiple initial values are found) shape = None if name in self.var_dict: if self.var_dict[name]: self.error("Variable `%s' is multiply declared" % name, node) self.prune() else: # existing but undefined entry comes from procedure line # set mode = "a" by default self.var_dict[name] = Variable(name, self.current_type, array_size=shape, mode="a") else: self.var_list.append(name) self.var_dict[name] = Variable(name, self.current_type, array_size=shape) self.current_var = self.var_dict[name] self.preorder(node[0]) # list flag self.preorder(node[2]) # initialization self.preorder(node[3]) # declaration options self.prune() def n_list_flag(self, node): if len(node) > 0: self.current_var.list_flag = 1 self.prune() def n_decl_init_list(self, node): # begin list of initial values if self.current_var.init_value is not None: # oops, looks like this was already initialized errmsg = \ "%s: Variable `%s' has more than one set of initial values" % \ (self.filename, self.current_var.name,) self.error(errmsg, node) else: self.current_var.init_value = [] def n_decl_init_list_exit(self, node): # convert from list to scalar if not an array # also convert all the initial values from tokens to native form v = self.current_var ilist = v.init_value if len(ilist) == 1 and v.shape is None: try: v.init_value = _convFunc(v, ilist[0]) except ValueError, e: self.error("Bad initial value for variable `%s': %s" % (v.name, e), node) else: # it is an array, set size or pad initial values if v.shape is None: v.shape = (len(ilist),) elif len(v) > len(ilist): for i in range(len(v)-len(ilist)): v.init_value.append(None) elif len(v) < len(ilist): self.error("Variable `%s' has too many initial values" % (v.name,), node) else: try: for i in range(len(v.init_value)): v.init_value[i] = _convFunc(v, v.init_value[i]) except ValueError, e: self.error("Bad initial value for array variable `%s': %s" % (v.name, e), node) def n_decl_init_value(self, node): # initial value is token with value vnode = node[0] if isinstance(vnode, Token): self.current_var.init_value.append(vnode) else: # have to create a new token for sign, number self.current_var.init_value.append( Token(type=vnode[1].type, attr=vnode[0].type+vnode[1].attr, lineno=vnode[0].lineno)) self.prune() def n_decl_option(self, node): optname = node[0].attr vnode = node[2] if isinstance(vnode, Token): optvalue = vnode.get() else: # have to combine sign, number if vnode[0] == "-": optvalue = - vnode[1].get() else: optvalue = vnode[1].get() optdict = self.current_var.options if optname not in optdict: errmsg = "Unknown option `%s' for variable `%s'" % (optname, self.current_var.name) self.error(errmsg, node) else: optdict[optname] = optvalue self.prune() # special keyword arguments added to parameter list _SpecialArgs = { 'taskObj': None, } class VarList(GenericASTTraversal, ErrorTracker): """Scan tree and get info on procedure, parameters, and local variables""" def __init__(self, ast, mode="proc", local_vars_list=None, local_vars_dict=None, parlist=None): GenericASTTraversal.__init__(self, ast) self.mode = mode self.proc_name = "" self.proc_args_list = [] self.proc_args_dict = {} self.has_proc_stmt = 0 if local_vars_list is None: self.local_vars_list = [] self.local_vars_count = 0 else: self.local_vars_list = local_vars_list self.local_vars_count = len(local_vars_list) if local_vars_dict is None: self.local_vars_dict = {} else: self.local_vars_dict = local_vars_dict if hasattr(ast, 'filename'): self.filename = ast.filename else: self.filename = '' self.input_parlist = parlist self.preorder() del self.input_parlist # If in "proc" mode, add default procedure name for # non-procedure scripts # (Need to do something like this so non-procedure scripts can # be compiled, but this may not be ideal solution.) if self.mode != "single" and not self.proc_name: if not self.filename: self.proc_name = 'proc' else: path, fname = os.path.split(self.filename) root, ext = os.path.splitext(fname) self.setProcName(root) # add mode, $nargs, other special parameters to all tasks self.addSpecialArgs() # Check for local variables that conflict with parameters self.checkLocalConflict() self.printerrors() # convert procedure arguments to IrafParList p = [] for var in self.proc_args_list: if var not in _SpecialArgs: arg = self.proc_args_dict[var].toPar() p.append(arg) self.parList = irafpar.IrafParList(self.getProcName(), filename=self.filename, parlist=p) def has_key(self, key): return self._has(key) def __contains__(self, key): return self._has(key) def _has(self, name): """Check both local and procedure dictionaries for this name""" return name in self.proc_args_dict or name in self.local_vars_dict def get(self, name): """Return entry from local or procedure dictionary (None if none)""" return self.proc_args_dict.get(name) or self.local_vars_dict.get(name) def setProcName(self, proc_name, node=None): """Set procedure name""" # names with embedded dots are allow by the CL but should be illegal pdot = proc_name.find('.') if pdot==0: self.error("Illegal procedure name `%s' starts with `.'" % proc_name, node) if pdot >= 0: self.warning("Bad procedure name `%s' truncated after dot to `%s'" % (proc_name, proc_name[:pdot]), node) proc_name = proc_name[:pdot] # Procedure name is stored in translated form ('PY' added # to Python keywords, etc.) self.proc_name = irafutils.translateName(proc_name) def getProcName(self): """Get procedure name, undoing translations""" return irafutils.untranslateName(self.proc_name) def addSpecial(self, name, type, value): # just delete $nargs and add it back if it is already present if name in self.proc_args_dict: self.proc_args_list.remove(name) del self.proc_args_dict[name] targ = irafutils.translateName(name) if targ not in self.proc_args_dict: self.proc_args_list.append(targ) self.proc_args_dict[targ] = Variable(targ, type, init_value=value) def addSpecialArgs(self): """Add mode, $nargs, other special parameters to all tasks""" if 'mode' not in self.proc_args_dict: self.proc_args_list.append('mode') self.proc_args_dict['mode'] = Variable('mode','string', init_value='al') self.addSpecial("$nargs", 'int', 0) ## self.addSpecial("$errno", 'int', 0) ## self.addSpecial("$errmsg", 'string', "") ## self.addSpecial("$errtask", 'string',"") ## self.addSpecial("$err_dzvalue", 'int', 1) for parg, ivalue in _SpecialArgs.items(): if parg not in self.proc_args_dict: self.proc_args_list.append(parg) self.proc_args_dict[parg] = ivalue def checkLocalConflict(self): """Check for local variables that conflict with parameters""" errlist = ["Error in procedure `%s'" % self.getProcName()] for v in self.local_vars_list: if v in self.proc_args_dict: errlist.append( "Local variable `%s' overrides parameter of same name" % (v,)) if len(errlist) > 1: self.error("\n".join(errlist)) def list(self): """List variables""" print "Procedure arguments:" for var in self.proc_args_list: v = self.proc_args_dict[var] if var in _SpecialArgs: print 'Special',var,'=',v else: print v print "Local variables:" for var in self.local_vars_list: print self.local_vars_dict[var] def getParList(self): """Return procedure arguments as IrafParList""" return self.parList def n_proc_stmt(self, node): self.has_proc_stmt = 1 # get procedure name and list of argument names p = ExtractProcInfo(node) self.setProcName(p.proc_name, node) self.proc_args_list = p.proc_args_list for arg in self.proc_args_list: if arg in self.proc_args_dict: errmsg = "Argument `%s' repeated in procedure statement %s" % \ (arg,self.getProcName()) self.error(errmsg, node) else: self.proc_args_dict[arg] = None self.prune() def n_param_declaration_block(self, node): # get list of parameter variables p = ExtractDeclInfo(node, self.proc_args_list, self.proc_args_dict, self.ast.filename) # check for undefined parameters declared in procedure stmt d = self.proc_args_dict for arg in d.keys(): if not d[arg]: # try substituting from parlist parameter list d[arg] = self.getFromInputList(arg) if not d[arg]: errmsg = "Procedure argument `%s' is not declared" % (arg,) self.error(errmsg, node) self.prune() def getFromInputList(self, param): # look up missing parameter in input_parlist if self.input_parlist and self.input_parlist.hasPar(param): return Variable(irafParObject= self.input_parlist.getParObject(param)) def n_statement_block(self, node): # declarations in executable section are local variables p = ExtractDeclInfo(node, self.local_vars_list, self.local_vars_dict, self.ast.filename) self.prune() # conversion between parameter types and data types _typeDict = { 'int': 'int', 'real': 'float', 'double': 'float', 'bool': 'bool', 'string': 'string', 'char': 'string', 'struct': 'string', 'file': 'string', 'gcur': 'string', 'imcur': 'string', 'ukey': 'string', 'pset': 'unknown', } # nested dictionary mapping required data type (primary key) and # expression type (secondary key) to the name of the function used to # convert to the required type _rfuncDict = { 'int': {'int': None, 'float': None, 'string': 'int', 'bool': None, 'unknown': 'int', 'indef': None}, 'float': {'int': None, 'float': None, 'string': 'float', 'bool': 'float', 'unknown': 'float', 'indef': None}, 'string':{'int': 'str', 'float': 'str', 'string': None, 'bool': 'iraf.bool2str', 'unknown': 'str', 'indef': None}, 'bool': {'int': 'iraf.boolean', 'float': 'iraf.boolean', 'string': 'iraf.boolean', 'bool': None, 'unknown': 'iraf.boolean', 'indef': None}, 'indef': {'int': None, 'float': None, 'string': None, 'bool': None, 'unknown': None, 'indef': None}, 'unknown': {'int': None, 'float': None, 'string': None, 'bool': None, 'unknown': None, 'indef': None}, } def _funcName(requireType, exprType): return _rfuncDict[requireType][exprType] # given two nodes with defined types in an arithmetic expression, # set their required times and return the result type # (using standard promotion rules) _numberTypes = ['float', 'int', 'unknown'] def _arithType(node1, node2): if node1.exprType in _numberTypes: if node2.exprType not in _numberTypes: rv = node1.exprType node2.requireType = rv else: # both numbers -- don't change required types, but # determine result type if 'float' in [node1.exprType, node2.exprType]: rv = 'float' elif 'unknown' in [node1.exprType, node2.exprType]: rv = 'unknown' else: rv = node1.exprType else: if node2.exprType in _numberTypes: rv = node2.exprType node1.requireType = rv else: rv = 'float' node1.requireType = rv node2.requireType = rv return rv # force node to be a number type and return the type def _numberType(node): if node.exprType in _numberTypes: return node.exprType else: node.requireType = 'float' return node.requireType _CLVarDict = {} def _getCLVarType(name): """Returns CL parameter data type if this is a CL variable, "unknown" if not Note that this can be incorrect about the data type for CL variables that are masked by package level variables. Too bad, that is just too ugly to be believed anyway. Don't do that. """ global _CLVarDict try: if not _CLVarDict: import pyraf.iraf d = pyraf.iraf.cl.getParDict() # construct type dictionary for all variables # don't use minimum matching -- require exact match for pname, pobj in d.items(): iraftype = pobj.type if iraftype[:1] == "*": iraftype = iraftype[1:] _CLVarDict[pname] = _typeDict[_longTypeName[iraftype]] except AttributeError: pass return _CLVarDict.get(name, "unknown") class TypeCheck(GenericASTTraversal): """Determine types of all expressions""" def __init__(self, ast, vars, filename): GenericASTTraversal.__init__(self, ast) self.vars = vars self.filename = filename self.postorder() # atoms def n_FLOAT(self, node): node.exprType = 'float' node.requireType = node.exprType def n_INTEGER(self, node): node.exprType = 'int' node.requireType = node.exprType def n_SEXAGESIMAL(self, node): node.exprType = 'float' node.requireType = node.exprType def n_INDEF(self, node): node.exprType = 'indef' node.requireType = node.exprType def n_STRING(self, node): node.exprType = 'string' node.requireType = node.exprType def n_QSTRING(self, node): node.exprType = 'string' node.requireType = node.exprType def n_EOF(self, node): node.exprType = 'string' node.requireType = node.exprType def n_BOOL(self, node): node.exprType = 'bool' node.requireType = node.exprType def n_IDENT(self, node): s = irafutils.translateName(node.attr) v = self.vars.get(s) if v is not None: node.exprType = _typeDict[v.type] node.requireType = node.exprType else: # not a local variable # try CL as a common case node.exprType = _getCLVarType(node.attr) node.requireType = node.exprType def n_array_ref(self, node): node.exprType = node[0].exprType node.requireType = node.exprType def n_function_call(self, node): functionname = node[0].attr ftype = _functionType.get(functionname) if ftype is None: ftype = 'unknown' node.exprType = ftype node.requireType = node.exprType def n_atom(self, node): assert len(node)==3 node.exprType = node[1].exprType node.requireType = node.exprType def n_power(self, node): assert len(node)==3 node.exprType = _arithType(node[0], node[2]) node.requireType = node.exprType def n_factor(self, node): assert len(node)==2 node.exprType = _numberType(node[1]) node.requireType = node.exprType def n_term(self, node): assert len(node)==3 node.exprType = _arithType(node[0], node[2]) node.requireType = node.exprType if node[0].exprType=='int' and node[2].exprType=='int' and \ node[1].type=='/': # mark this node, we want it to use integer division (truncating) node[1].trunc_int_div = True # only place we add this attr def n_concat_expr(self, node): assert len(node)==3 node.exprType = 'string' node.requireType = node.exprType node[0].requireType = 'string' node[2].requireType = 'string' def n_arith_expr(self, node): assert len(node)==3 if node[1].type == '-': node.exprType = _arithType(node[0], node[2]) node.requireType = node.exprType else: # plus -- could mean add or concatenate if node[0].exprType == 'string' or node[2].exprType == 'string': node.exprType = 'string' node.requireType = node.exprType node[0].requireType = 'string' node[2].requireType = 'string' else: node.exprType = _arithType(node[0], node[2]) node.requireType = node.exprType def n_comp_expr(self, node): assert len(node) == 3 node.exprType = 'bool' node.requireType = node.exprType def n_not_expr(self, node): assert len(node) == 2 node.exprType = 'bool' node.requireType = node.exprType node[1].requireType = 'bool' def n_expr(self, node): assert len(node) == 3 node.exprType = 'bool' node.requireType = node.exprType node[0].requireType = 'bool' node[2].requireType = 'bool' def n_assignment_stmt(self, node): assert len(node) == 3 node[2].requireType = node[0].exprType class BlockInfo: """Helper class to store block structure info for GOTO analysis""" def __init__(self, node, blockid, parent): self.node = node self.blockid = blockid self.parent = parent class GoToAnalyze(GenericASTTraversal, ErrorTracker): """AST traversal for CL GOTO analysis Analyze GOTO structure looking for branches into blocks (which are forbidden), backward branches (which are not supported), and other errors. Adds information to the AST that is used to generate Python equivalent code. """ def __init__(self, ast): GenericASTTraversal.__init__(self, ast) self.blocks = [] self.label_blockid = {} self.goto_blockidlist = {} self.goto_nodelist = {} self.current_blockid = -1 # walk the tree self.preorder() # check for missing labels for label in self.goto_blockidlist.keys(): if label not in self.label_blockid: node = self.goto_nodelist[label][0] self.error("GOTO refers to unknown label `%s'" % label, node) # note that we count on the Tree2Python class to print errors # add label count info to blocks if all is OK label_count = [0]*len(self.blocks) for label, ib in self.label_blockid.items(): # only count labels that are actually used if label in self.goto_blockidlist: label_count[ib] += 1 for ib in range(len(self.blocks)): self.blocks[ib].node.label_count = label_count[ib] #------------------------- # public interface methods #------------------------- def labels(self): """Get a list of known labels used in GOTOs""" labels = self.goto_blockidlist.keys() labels.sort() return labels def __contains__(self, key): return self._has(key) def has_key(self, key): return self._has(key) def _has(self, label): """Check if label is used in a GOTO""" return label in self.goto_blockidlist #------------------------------------ # methods called during AST traversal #------------------------------------ def n_compound_stmt(self, node): newid = len(self.blocks) self.blocks.append(BlockInfo(node, newid, self.current_blockid)) self.current_blockid = newid def n_statement_block(self, node): newid = len(self.blocks) self.blocks.append(BlockInfo(node, newid, self.current_blockid)) self.current_blockid = newid def n_compound_stmt_exit(self, node): self.current_blockid = self.blocks[self.current_blockid].parent def n_statement_block_exit(self, node): self.current_blockid = self.blocks[self.current_blockid].parent def n_label_stmt(self, node): label = node[0].attr if label in self.label_blockid: self.error("Duplicate statement label `%s'" % label, node) else: cblockid = self.current_blockid self.label_blockid[label] = cblockid # make sure all gotos for this label are in this or deeper blocks for i in self.goto_blockidlist.get(label,[]): if self.blocks[i].blockid < cblockid: self.error("GOTO branches to label `%s' in inner block" % label, node) def n_goto_stmt(self, node): label = str(node[1]) if label in self.label_blockid: self.error("Backwards GOTO to label `%s' is not allowed" % label, node) elif label in self.goto_blockidlist: self.goto_blockidlist[label].append(self.current_blockid) self.goto_nodelist[label].append(node) else: self.goto_blockidlist[label] = [self.current_blockid] self.goto_nodelist[label] = [node] # tokens that are translated or skipped outright _translateList = { "{": "", "}": "", ";": "", "!": "not ", "//": " + ", } # builtin task names that are translated _taskList = { "print" : "clPrint", "_curpack" : "curpack", "_allocate" : "clAllocate", "_deallocate" : "clDeallocate", "_devstatus" : "clDevstatus", } # builtin functions that are translated # other functions just have 'iraf.' prepended _functionList = { "int": "iraf.integer", "str": "str", "abs": "iraf.absvalue", "min": "iraf.minimum", "max": "iraf.maximum", } # return types of IRAF built-in functions _functionType = { "int": "int", "real": "float", "sin": "float", "cos": "float", "tan": "float", "atan2": "float", "exp": "float", "log": "float", "log10": "float", "sqrt": "float", "frac": "float", "abs": "float", "min": "unknown", "max": "unknown", "fscan": "int", "scan": "int", "fscanf": "int", "scanf": "int", "nscan": "int", "stridx": "int", "strlen": "int", "str": "string", "substr": "string", "envget": "string", "mktemp": "string", "radix": "string", "osfn": "string", "_curpack": "string", "defpar": "bool", "access": "bool", "defvar": "bool", "deftask": "bool", "defpac": "bool", "imaccess": "bool", } # logical operator conversion _LogOpDict = { "&&": " and ", "||": " or ", } # redirection conversion _RedirDict = { ">": "Stdout", ">>": "StdoutAppend", ">&": "Stderr", ">>&": "StderrAppend", "<": "Stdin", } # tokens printed with both leading and trailing space _bothSpaceList = { "=": 1, "ASSIGNOP": 1, "COMPOP": 1, "+": 1, "-": 1, "/": 1, "*": 1, "//": 1, } # tokens printed with only trailing space _trailSpaceList = { ",": 1, "REDIR": 1, "IF": 1, "WHILE": 1, } # Convert token value to IRAF type specified by Variable object # always returns a string, suitable for use in assignment like: # 'var = ' + _convFunc(var, value) # The only permitted conversion is int->float. _stringTypes = { "string": 1, "char": 1, "file": 1, "struct": 1, "gcur": 1, "imcur": 1, "ukey": 1, "pset": 1, } def _convFunc(var, value): if var.list_flag or var.type in _stringTypes: if value is None: return "" else: return str(value) elif var.type == "int": if value is None: return "INDEF" elif isinstance(value,str) and value[:1] == ")": # parameter indirection return value else: return int(value) elif var.type == "real": if value is None: return "INDEF" elif isinstance(value,str) and value[:1] == ")": # parameter indirection return value else: return float(value) elif var.type == "bool": if value is None: return "INDEF" elif isinstance(value, (int,float)): if value == 0: return 'no' else: return 'yes' elif isinstance(value,str): s = value.lower() if s == "yes" or s == "y": s = "yes" elif s == "no" or s == "n": s = "'no'" elif s[:1] == ")": # parameter indirection return value else: raise ValueError( "Illegal value `%s' for boolean variable %s" % (s, var.name)) return s else: try: return value.bool() except AttributeError, e: raise AttributeError(var.name + ':' + str(e)) raise ValueError("unimplemented type `%s'" % (var.type,)) class CheckArgList(GenericASTTraversal, ErrorTracker): """Check task argument list for errors""" def __init__(self, ast): GenericASTTraversal.__init__(self, ast) # keywords is a list of keyword dictionaries (to handle # nested task calls) self.keywords = [] self.taskname = [] self.tasknode = [] self.preorder() # note that we count on the Tree2Python class to print any errors def n_task_call_stmt(self, node): self.taskname.append(node[0].attr) self.tasknode.append(node) self.keywords.append({}) def n_task_call_stmt_exit(self, node): self.taskname.pop() self.tasknode.pop() self.keywords.pop() def n_function_call(self, node): self.taskname.append(node[0].attr) self.tasknode.append(node) self.keywords.append({}) def n_function_call_exit(self, node): self.taskname.pop() self.tasknode.pop() self.keywords.pop() def n_param_name(self, node): keyword = node[0].attr if keyword in self.keywords[-1]: self.error("Duplicate keyword `%s' in call to %s" % (keyword, self.taskname[-1]), node) else: self.keywords[-1][keyword] = 1 def n_non_empty_arg(self, node): if node[0].type not in ['keyword_arg', 'bool_arg', 'redir_arg', 'non_expr_arg'] and self.keywords[-1]: self.error("Non-keyword arg after keyword arg in call to %s" % self.taskname[-1], node) def n_empty_arg(self, node): if self.keywords[-1]: # empty args don't have line number, so use task line self.error("Non-keyword (empty) arg after keyword arg in call to %s" % self.taskname[-1], self.tasknode[-1]) class Tree2Python(GenericASTTraversal, ErrorTracker): def __init__(self, ast, vars, filename='', taskObj=None): self._ecl_iferr_entered = 0 GenericASTTraversal.__init__(self, ast) self.filename = filename self.column = 0 self.vars = vars self.inSwitch = 0 self.caseCount = [] # printPass is an array of flags indicating whether the # corresponding indentation level is empty. If empty when # the block is terminated, a 'pass' statement is generated. # Start with a reasonable size for printPass array. # (It gets extended if necessary.) self.printPass = [1]*10 self.code_buffer = cStringIO.StringIO() self.importDict = {} self.specialDict = {} self.pipeOut = [] self.pipeIn = [] self.pipeCount = 0 # These three are used only by n_while_stmt, n_for_stmt, n_next_stmt, # and decrIndent; they are for incrementing the loop variable before # writing "continue" in a "for" loop (but not in a "while" loop). self.save_incr = [] # info to increment the loop variable self.save_indent = [] # indentation level in a while loop self.IN_A_WHILE_LOOP = "while" # this is a constant value self._ecl_pyline = 1 self._ecl_clline = None self._ecl_linemap = {} if self.vars.proc_name: self.indent = 1 else: self.indent = 0 if taskObj and self._ecl_iferr_entered: self.write("taskObj = iraf.getTask('%s')\n" % taskObj) # analyze goto structure # this assigns the label_count field for statement blocks self.gotos = GoToAnalyze(ast) # propagate any errors from goto analysis, but continue to see # if we can identify more problems self.errorappend(self.gotos) # This performs the actual translation. It traverses the # abstract syntax tree. self has methods called n_WHATEVER # for each WHATEVER node type in the tree. Each method # writes python source code to self.code_buffer. self.preorder() self.write("\n") # Get the python source that is the translation of the cl. self.code = self.code_buffer.getvalue() self.code_buffer.close() # The translated python requires a header with initialization # code. Now that we have performed the entire translation, # we know which of the initialization steps we need. Stick # them on the front of the translated python. self.code_buffer = cStringIO.StringIO() self.writeProcHeader() header = self.code_buffer.getvalue() if pyrafglobals._use_ecl: self.code = self._ecl_linemapping(header) + \ header + \ self.code else: self.code = header + self.code self.code_buffer.close() del self.code_buffer # if self.filename == 'string_proc': self.comment('The code for "string_proc":') self.comment('-'*80) self.comment(self.code) self.comment('-'*80) self.printerrors() def _ecl_linemapping(self, header): lines = header.count("\n") + 2 # count + 2 because we will add two more lines to the header # adjust all the line numbers up by the size of the header newmap = {} for key,value in self._ecl_linemap.items(): newmap[ key + lines ] = value # return a python assignment statement that initializes the dictionary return "_ecl_linemap_" + self.vars.proc_name + " = " + repr(newmap) + "\n\n" def incrIndent(self): """Increment indentation count""" # printPass is used to recognize empty indentation blocks # and add 'pass' statement when indentation level is decremented self.indent = self.indent+1 if len(self.printPass) <= self.indent: # extend array to length self.indent+1 self.printPass = self.printPass + \ (self.indent+1-len(self.printPass)) * [1] self.printPass[self.indent] = 1 def decrIndent(self): """Decrement indentation count and write 'pass' if required""" if self.printPass[self.indent]: self.writeIndent('pass') self.indent = self.indent-1 if len(self.save_indent) > 0 and self.save_indent[-1] == self.indent: del self.save_incr[-1] del self.save_indent[-1] def write(self, s, requireType=None, exprType=None): """Write string to output code buffer""" self._ecl_pyline += s.count("\n") self._ecl_linemap[ self._ecl_pyline ] = self._ecl_clline if requireType != exprType: # need to wrap this subexpression in a conversion function cf = _funcName(requireType, exprType) if cf is not None: s = cf + '(' + s + ')' self.code_buffer.write(s) # maintain column count to help with breaking across lines self.column = self.column + len(s) # handle simple cases of a single initial tab or trailing newline if s[:1] == "\t": self.column = self.column + 3 if s[-1:] == "\n": self.column = 0 def writeIndent(self, value=None): """Write newline and indent""" self.write("\n") for i in range(self.indent): self.write("\t") if value: self.write(value) self.printPass[self.indent] = 0 def writeProcHeader(self): """Write function definition and other header info""" # save printPass flag -- if it is set, the body of # the procedure is currently empty and so 'pass' may be added printPass = self.printPass[1] # reset indentation level; never need 'pass' stmt in header self.indent = 0 self.printPass[0] = 0 # most header info is omitted in 'single' translation mode noHdr = self.vars.mode == "single" and self.vars.proc_name == "" # do basic imports and definitions outside procedure definition, # mainly so INDEF can be used as a default value for keyword # parameters in the def statement if not noHdr: self.write("from pyraf import iraf") self.writeIndent("from pyraf.irafpar import makeIrafPar, IrafParList") self.writeIndent("from stsci.tools.irafglobals import *") self.writeIndent("from pyraf.pyrafglobals import *") self.write("\n") if self.vars.proc_name: # create list of procedure arguments # make list of IrafPar definitions at the same time n = len(self.vars.proc_args_list) namelist = n*[None] proclist = n*[None] deflist = n*[None] for i in range(n): p = self.vars.proc_args_list[i] v = self.vars.proc_args_dict[p] namelist[i] = irafutils.translateName(p) if p in _SpecialArgs: # special arguments are Python types proclist[i] = p + '=' + str(v) deflist[i] = '' else: try: proclist[i] = v.procLine() deflist[i] = v.parDefLine() except AttributeError, e: raise AttributeError(self.filename + ':' + str(e)) # allow long argument lists to be broken across lines self.writeIndent("def " + self.vars.proc_name + "(") self.writeChunks(proclist) self.write("):\n") self.incrIndent() # reset printPass in case procedure is empty self.printPass[self.indent] = printPass else: namelist = [] deflist = [] # write additional required imports wnewline = 0 if not noHdr: keylist = self.importDict.keys() if keylist: keylist.sort() self.writeIndent("import ") self.write(", ".join(keylist)) wnewline = 1 if "PkgName" in self.specialDict: self.writeIndent("PkgName = iraf.curpack(); " "PkgBinary = iraf.curPkgbinary()") wnewline = 1 if wnewline: self.write("\n") # add local variables to deflist for p in self.vars.local_vars_list[self.vars.local_vars_count:]: v = self.vars.local_vars_dict[p] try: deflist.append(v.parDefLine(local=1)) except AttributeError, e: raise AttributeError(self.filename + ':' + str(e)) if deflist: # add local and procedure parameters to Vars list if not noHdr: self.writeIndent("Vars = IrafParList(" + `self.vars.proc_name` + ")") for defargs in deflist: if defargs: self.writeIndent("Vars.addParam(makeIrafPar(") self.writeChunks(defargs) self.write("))") self.write("\n") if pyrafglobals._use_ecl: self.writeIndent("from pyraf.irafecl import EclState") self.writeIndent("_ecl = EclState(_ecl_linemap_%s)\n" % self.vars.proc_name) # write goto label definitions if needed for label in self.gotos.labels(): self.writeIndent("class GoTo_%s(Exception): pass" % label) # decrement indentation (which writes the pass if necessary) self.decrIndent() #------------------------------ # elements that can be ignored #------------------------------ def n_proc_stmt(self, node): self.prune() def n_declaration_block(self, node): self.prune() def n_declaration_stmt(self, node): self.prune() def n_BEGIN(self, node): pass def n_END(self, node): pass def n_NEWLINE(self, node): pass #------------------------------ #XXX unimplemented features #------------------------------ def n_BKGD(self, node): # background execution ignored for now self.warning("Background execution ignored", node) #------------------------------ # low-level conversions #------------------------------ def n_FLOAT(self, node): # convert d exponents to e for Python s = node.attr i = s.find('d') if i>=0: s = s[:i] + 'e' + s[i+1:] else: i = s.find('D') if i>=0: s = s[:i] + 'E' + s[i+1:] self.write(s, node.requireType, node.exprType) def n_INTEGER(self, node): # convert octal and hex constants value = node.attr last = value[-1].lower() if last == 'b': # octal self.write('0'+value[:-1], node.requireType, node.exprType) elif last == 'x': # hexadecimal self.write('0x'+value[:-1], node.requireType, node.exprType) else: # remove leading zeros on decimal values i=0 for digit in value: if digit != '0': break i = i+1 else: # all zeros i = i-1 self.write(value[i:], node.requireType, node.exprType) def n_SEXAGESIMAL(self, node): # convert d:m:s values to float v = node.attr.split(':') # at least 2 values in expression s = 'iraf.clSexagesimal(' + v[0] + ',' + v[1] if len(v)>2: s = s + ',' + v[2] s = s + ')' self.write(s, node.requireType, node.exprType) def n_IDENT(self, node, array_ref=0): s = irafutils.translateName(node.attr) if s in self.vars and s not in _SpecialArgs: # Prepend 'Vars.' to all procedure and local variable references # except for special args, which are normal Python variables. # The main reason I do it this way is so the IRAF scan/fscan # functions can work correctly, but it simplifies # other code generation as well. Vars does all the type # conversions and applies constraints. #XXX Note we are not doing minimum match on parameter names self.write('Vars.'+s, node.requireType, node.exprType) elif '.' in s: # Looks like a task.parameter or field reference # Add 'Vars.' or 'iraf.' or 'taskObj.' prefix to name. # Also look for special p_ extensions -- need to use parameter # objects instead of parameter values if they are specified. attribs = s.split('.') ipf = basicpar.isParField(attribs[-1]) if attribs[0] in self.vars: attribs.insert(0, 'Vars') elif ipf and (len(attribs)==2): attribs.insert(0, 'taskObj') else: attribs.insert(0, 'iraf') if ipf: attribs[-2] = 'getParObject(' + `attribs[-2]` + ')' self.write(".".join(attribs), node.requireType, node.exprType) else: # not a local variable; use task object to search other # dictionaries if self.vars.mode == "single": self.write('iraf.cl.'+s, node.requireType, node.exprType) else: self.write('taskObj.'+s, node.requireType, node.exprType) def _print_subscript(self, node): # subtract one from IRAF subscripts to get Python subscripts # returns number of subscripts if len(node)>1: n = self._print_subscript(node[0]) self.write(", ") else: n = 0 if node[-1].type == "INTEGER": self.write(str(int(node[-1])-1)) else: self.preorder(node[-1]) self.write("-1") return n+1 def n_array_ref(self, node): # in array reference, do not add .p_value to parameter identifier # because we can index the parameter directly # wrap in a conversion function if necessary cf = _funcName(node.requireType, node.exprType) if cf: self.write(cf + "(") self.n_IDENT(node[0], array_ref=1) self.write("[") nsub = self._print_subscript(node[2]) self.write("]") if cf: self.write(")") # check for correct number of subscripts for local arrays s = irafutils.translateName(node[0].attr) if s in self.vars: v = self.vars.get(s) if nsub < len(v.shape): self.error("Too few subscripts for array %s" % s, node) elif nsub > len(v.shape): self.error("Too many subscripts for array %s" % s, node) self.prune() def n_param_name(self, node): s = irafutils.translateName(node[0].attr,dot=1) self.write(s) self.prune() def n_LOGOP(self, node): self.write(_LogOpDict[node.attr]) def n_function_call(self, node): # all functions are built-in (since CL does not allow new definitions) # wrap in a conversion function if necessary cf = _funcName(node.requireType, node.exprType) if cf: self.write(cf + "(") functionname = node[0].attr newname = _functionList.get(functionname) if newname is None: # just add "iraf." prefix newname = "iraf." + functionname self.write(newname + "(") # argument list for scan statement sargs = self.captureArgs(node[2]) if functionname in ["scan", "fscan", "scanf", "fscanf"]: # scan is weird -- effectively uses call-by-name # call special routine to change the args sargs = self.modify_scan_args(functionname, sargs) self.writeChunks(sargs) self.write(")") if cf: self.write(")") self.prune() def modify_scan_args(self, functionname, sargs): # modify argument list for scan statement # If fscan, first argument is the string to read from. # But we still want to pass it by name because if the # first argument is a list parameter, we want to postpone # its evaluation until we get into the fscan function so # we can catch EOF exceptions. # Add quotes to names (we're literally passing the names, not # the values) sargs = map(repr, sargs) # pass in locals dictionary so we can get names of variables to set sargs.insert(0, "locals()") return sargs def default(self, node): """Handle other tokens""" if hasattr(node, 'exprType'): requireType = node.requireType exprType = node.exprType else: requireType = None exprType = None if isinstance(node, Token): s = _translateList.get(node.type) if s is not None: self.write(s, requireType, exprType) elif node.type in _trailSpaceList: self.write(`node`, requireType, exprType) self.write(" ") elif node.type in _bothSpaceList: self.write(" ") if hasattr(node, 'trunc_int_div'): self.write('//', requireType, exprType) else: self.write(`node`, requireType, exprType) self.write(" ") else: self.write(`node`, requireType, exprType) elif requireType != exprType: cf = _funcName(requireType, exprType) if cf is not None: self.write(cf + '(') for nn in node: self.preorder(nn) self.write(')') self.prune() def n_term(self, node): if pyrafglobals._use_ecl and node[1] in ['/','%']: kind = {"/":"divide", "%":"modulo"}[node[1]] self.write("taskObj._ecl_safe_%s(" % kind) self.preorder(node[0]) self.write(",") self.preorder(node[2]) self.write(")") self.prune() else: self.default(node) #------------------------------ # block indentation control #------------------------------ def n_statement_block(self, node): for i in range(node.label_count): self.writeIndent("try:") self.incrIndent() def n_compound_stmt(self, node): self.write(":") self.incrIndent() for i in range(node.label_count): self.writeIndent("try:") self.incrIndent() def n_compound_stmt_exit(self, node): self.decrIndent() def n_nonnull_stmt(self, node): if node[0].type == "{": # indentation already done for compound statements self.preorder(node[1]) self.prune() else: ## if self._ecl_iferr_entered: ## self.writeIndent("try:") ## self.incrIndent() ## self.writeIndent() ## for kid in node: ## self.preorder(kid) ## self.decrIndent() ## self.writeIndent("except Exception, e:") ## self.incrIndent() ## self.writeIndent("taskObj._ecl_record_error(e)") ## self.decrIndent() ## self.prune() ## else: self._ecl_clline = FindLineNumber(node).lineno self.writeIndent() #------------------------------ # statements #------------------------------ def n_osescape_stmt(self, node): self.write("iraf.clOscmd(" + `node[0].attr` + ")") self.prune() def n_assignment_stmt(self, node): if node[1].type == "ASSIGNOP": # convert +=, -=, etc. self.preorder(node[0]) self.write(" = ") self.preorder(node[0]) self.write(" " + node[1].attr[0] + " ") self.preorder(node[2]) self.prune() def n_else_clause(self, node): # recognize special 'else if' case # pattern is: # else_clause ::= opt_newline ELSE compound_stmt # compound_stmt ::= opt_newline one_compound_stmt # one_compound_stmt ::= nonnull_stmt # nonnull_stmt ::= if_stmt if len(node) == 3: stmt = node[2][1] if stmt.type == "nonnull_stmt" and stmt[0].type == "if_stmt": self.writeIndent("el") self.preorder(stmt[0]) self.prune() def n_ELSE(self, node): # else clause is not a 'nonnull_stmt', so must explicitly # print the indentation self.writeIndent("else") def n_iferr_stmt(self, node): # 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 if len(node) == 3: ifkind, guarded_stmt, except_action, else_action = node[0], node[1], node[2], None elif len(node) == 5: ifkind, guarded_stmt, except_action, else_action = node[0], node[1], node[4], None else: ifkind, guarded_stmt, except_action, else_action = node[0], node[1], node[4], node[7] if ifkind.type == "IFNOERR": except_action, else_action = else_action, except_action self.writeIndent("taskObj._ecl_push_err()\n") self._ecl_iferr_entered += 1 self.preorder(guarded_stmt) self._ecl_iferr_entered -= 1 self.write("\n") self.writeIndent("if taskObj._ecl_pop_err()") self.preorder(except_action) if else_action: self.writeIndent("else") self.preorder(else_action) self.prune() ## self.writeIndent("try:") ## self.incrIndent() ## self._ecl_iferr_entered += 1 ## self.preorder(guarded_stmt) ## self._ecl_iferr_entered -= 1 ## self.decrIndent() ## self.writeIndent("except") ## self.preorder(except_action) ## if else_action: ## self.writeIndent("else") ## self.preorder(else_action) ## self.prune() def n_while_stmt(self, node): """we've got a 'while' statement""" # Append this value as a flag to tell n_next_stmt that it should # not increment the loop variable before writing "continue". self.save_incr.append(self.IN_A_WHILE_LOOP) # Save the indentation level, so we can tell when we're leaving # the 'while' loop. self.save_indent.append(self.indent) def n_for_stmt(self, node): # convert for loop into while loop # # 0 1 2 3 4 5 6 7 8 # for ( initialization ; condition ; increment ) compound_stmt # # any of the components inside the parentheses may be empty # # -------- initialization -------- init = node[2] if init.type == "opt_assign_stmt" and len(init)==0: # empty initialization self.write("while (") else: self.preorder(init) self.writeIndent("while (") # -------- condition -------- condition = node[4] if condition.type == "opt_bool" and len(condition)==0: # empty condition self.write("1") else: self.preorder(condition) self.write(")") # -------- execution block -------- # go down inside the compound_stmt item so the increment can # be included inside the same block self.save_incr.append(node[6]) # needed if there's a 'next' statement self.write(":") self.incrIndent() for i in range(node[8].label_count): self.writeIndent("try:") self.incrIndent() for subnode in node[8]: self.preorder(subnode) # -------- increment -------- incr = node[6] if incr.type == "opt_assign_stmt" and len(incr)==0: # empty increment pass else: self.writeIndent() self.preorder(incr) self.decrIndent() if len(self.save_incr) > 0: del(self.save_incr[-1]) self.prune() def n_next_stmt(self, node): if len(self.save_incr) > 0 and \ self.save_incr[-1] != self.IN_A_WHILE_LOOP: # increment the loop variable -- copied from n_for_stmt() incr = self.save_incr[-1] if incr.type == "opt_assign_stmt" and len(incr)==0: pass else: self.preorder(incr) self.writeIndent() self.write("continue") self.prune() def n_label_stmt(self, node): # labels translate to except statements # skip unsued labels label = node[0].attr if label in self.gotos: self.decrIndent() self.writeIndent("except GoTo_%s:" % irafutils.translateName(label)) self.incrIndent() self.writeIndent("pass") self.decrIndent() self.prune() def n_goto_stmt(self, node): self.write("raise GoTo_%s" % irafutils.translateName(node[1].attr)) self.prune() def n_inspect_stmt(self, node): # The following will create/call print as a statement, but is a function # However, there may not be a valid use case to worry about here. if PY3K: raise RuntimeError("Error - this code is incorrect in PY3K") self.write("print ") if node[0].type == "=": # '= expr' version of inspect self.preorder(node[1]) else: # 'IDENT =' version of inspect self.preorder(node[0]) self.prune() def n_switch_stmt(self, node): self.inSwitch = self.inSwitch + 1 self.caseCount.append(0) self.write("SwitchVal%d = " % (self.inSwitch,)) self.preorder(node[2]) self.preorder(node[4]) self.inSwitch = self.inSwitch - 1 del self.caseCount[-1] self.prune() def n_case_block(self, node): self.preorder(node[2]) self.preorder(node[3]) self.prune() def n_case_stmt_block(self, node): if self.caseCount[-1] == 0: self.caseCount[-1] = 1 self.writeIndent("if ") else: self.writeIndent("elif ") self.write("SwitchVal%d in [" % (self.inSwitch,)) self.preorder(node[2]) self.write("]") self.preorder(node[4]) self.prune() def n_default_stmt_block(self, node): if len(node)>0: if self.caseCount[-1] == 0: # only a default in this switch self.writeIndent("if 1") else: self.writeIndent("else") self.preorder(node[3]) self.prune() #------------------------------ # pipes implemented using redirection + task return values #------------------------------ def n_task_pipe_stmt(self, node): self.pipeCount = self.pipeCount+1 pipename = 'Pipe' + str(self.pipeCount) self.pipeOut.append(pipename) self.preorder(node[0]) self.pipeOut.pop() self.pipeIn.append(pipename) self.writeIndent() self.preorder(node[2]) self.pipeIn.pop() self.pipeCount = self.pipeCount-1 self.prune() #------------------------------ # task execution #------------------------------ def n_task_call_stmt(self, node): self.errorappend(CheckArgList(node)) taskname = node[0].attr self.currentTaskname = taskname # '$' prefix means print time required for task (just ignore it for now) if taskname[:1] == '$': taskname = taskname[1:] # translate some special task names and add "iraf." to all names # additionalArguments will get appended at the end of the # argument list self.additionalArguments = [] addsep = "" # add plumbing for pipes if necessary if self.pipeIn: # read from existing input line list self.additionalArguments.append("Stdin=" + self.pipeIn[-1]) if self.pipeOut: self.write(self.pipeOut[-1] + " = ") self.additionalArguments.append("Stdout=1") # add extra arguments for task, package commands newname = _taskList.get(taskname, taskname) newname = "iraf." + irafutils.translateName(newname) if taskname in ('task', 'pyexecute'): # task, pyexecute need additional package, bin arguments self.specialDict['PkgName'] = 1 self.additionalArguments.append("PkgName=PkgName") self.additionalArguments.append("PkgBinary=PkgBinary") elif taskname == 'package': # package needs additional package, bin arguments and returns args self.specialDict['PkgName'] = 1 self.additionalArguments.append("PkgName=PkgName") self.additionalArguments.append("PkgBinary=PkgBinary") # package is a function returning new values for PkgName etc. # except when pipe is specified if not self.pipeOut: self.write("PkgName, PkgBinary = ") # add extra argument to save parameters if in "single" mode if self.vars.mode == "single": self.additionalArguments.append("_save=1") self.write(newname) self.preorder(node[1]) if self.pipeIn: # done with this input pipe self.writeIndent("del " + self.pipeIn[-1]) if taskname == "clbye" or taskname == "bye": # must do a return after clbye() or bye() if not in 'single' mode if self.vars.mode != "single": self.writeIndent("return") self.prune() def n_task_arglist(self, node): # print task_arglist, adding parentheses if necessary if len(node) == 3: # parenthesized arglist # i is index for args in node i = 1 elif len(node) == 1: # unparenthesized arglist i = 0 else: # len(node)==2 # fix some common CL script errors # (these are parsed in sloppy mode) if node[0].type == "(": # missing close parenthesis self.warning("Missing closing parenthesis", node) i = 1 elif node[1].type == ")": # missing open parenthesis self.warning("Missing opening parenthesis", node) i = 0 # tag argument list with parent for context analysis in case of # keyword args later node[i].parent = node # get the list of arguments sargs = self.captureArgs(node[i]) # Delete the extra parentheses on a single argument that already # has parentheses. This is fixing a parsing ambiguity created by # the ability to interpret a single parenthesized argument either # as a parenthesized list or as an unparenthesized list consisting # of an expression. if len(sargs) == 1: s = sargs[0] if s[:1] == "(" and s[-1:] == ")": sargs[0] = s[1:-1] if self.currentTaskname in ["scan", "fscan", "scanf", "fscanf"]: # scan is weird -- effectively uses call-by-name # call special routine to change the args sargs = self.modify_scan_args(self.currentTaskname, sargs) # combine CL arguments with additional (redirection) arguments sargs = sargs + self.additionalArguments self.additionalArguments = [] # break up arg list into line-sized chunks self.write("(") self.writeChunks(sargs) self.write(")") self.prune() def captureArgs(self, node): """Process the arguments list and return a list of the args""" # arguments get written to a separate string so we can # decide whether extra parens are really needed or not # Also add special character after arguments to make it # easier to break up long lines arg_buffer = cStringIO.StringIO() saveColumn = self.column saveBuffer = self.code_buffer self.code_buffer = arg_buffer # add a special character after commas to make it easy # to break up argument list for long lines global _translateList # save current translation for comma to handle nested lists curComma = _translateList.get(',') _translateList[','] = ',\255' self.preorder(node) # restore original comma translation and buffer pointers if curComma is None: del _translateList[','] else: _translateList[','] = curComma self.code_buffer = saveBuffer self.column = saveColumn args = arg_buffer.getvalue() arg_buffer.close() # split arguments into list sargs = args.split(',\255') if sargs[0] == '': del sargs[0] return sargs def writeChunks(self, arglist, linelength=78): # break up arg list into line-sized chunks if not arglist: return maxline = linelength - self.column newargs = arglist[0] for arg in arglist[1:]: if len(newargs)+len(arg)+2>maxline: self.write(newargs + ',') #self.writeIndent('\t') newargs = arg maxline = linelength - self.column else: newargs = newargs + ', ' + arg self.write(newargs) def n_empty_arg(self, node): #XXX This is an omitted argument #XXX Not really correct yet -- need to work on this self.write('None') self.prune() def n_bool_arg(self, node): self.preorder(node[0]) if node[1].type == "+": self.write("=yes") else: self.write("=no") self.prune() def n_redir_arg(self, node): # redirection is handled by special keyword parameters # Stdout=, Stdin=, Stderr=, etc. s = node[0].attr redir = _RedirDict.get(s) if redir is None: # must be GIP redirection, construct a standard name # using GIP in sorted order tail = [] while s[-1] in 'PIG': tail.append(s[-1]) s = s[:-1] tail.sort() redir = _RedirDict[s] + ''.join(tail) self.write(redir + '=') self.preorder(node[1]) self.prune() def n_keyword_arg(self, node): # This is needed to handle cursor parameters, which should # be passed as objects rather than by value. assert len(node)==3 self.preorder(node[0]) self.preorder(node[1]) # only the value needs special handling if node[2].type == 'IDENT': s = irafutils.translateName(node[2].attr) v = self.vars.get(s) if v and v.type in ['gcur','imcur']: # pass cursors by value self.write('Vars.getParObject("'+s+'")') self.prune() return self.preorder(node[2]) self.prune() if __name__ == "__main__": import time t0 = time.time() # scan file "simple.cl" filename = "simple.cl" lines = open(filename).read() scanner = clscan.CLScanner() tokens = scanner.tokenize(lines) t1 = time.time() # parse tree = _parser.parse(tokens, fname=filename) tree.filename = filename t2 = time.time() # first pass -- get variables vars = VarList(tree) # second pass -- check all expression types # type info is added to tree TypeCheck(tree, vars, '') # third pass -- generate python code pycode = Tree2Python(tree, vars) t3 = time.time() print "Scan:", t1-t0, "sec, Parse:", t2-t1, "sec" print "CodeGen:", t3-t2, "sec" pyraf-2.1.14/lib/pyraf/clast.py0000644000665500117240000000436013033515761017275 0ustar sontagsts_dev00000000000000"""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.14/lib/pyraf/clcache.py0000644000665500117240000002732613033515761017560 0ustar sontagsts_dev00000000000000"""clcache.py: Implement cache for Python translations of CL tasks $Id$ R. White, 2000 January 19 """ from __future__ import division # confidence high import os, sys from stsci.tools.for2to3 import PY3K from stsci.tools.irafglobals import Verbose, userIrafHome if __name__.find('.') < 0: # for unit test need absolute import for mmm in ('filecache', 'pyrafglobals', 'dirshelve'): exec('import '+mmm, globals()) # 2to3 messes up simpler form else: import filecache import pyrafglobals import dirshelve # In case you wish to disable all CL script caching (for whatever reason) DISABLE_CLCACHING = False # enable caching by default if PY3K or 'PYRAF_NO_CLCACHE' in os.environ: DISABLE_CLCACHING = True # set up pickle so it can pickle code objects import copy_reg, marshal, types try: import cPickle as pickle except ImportError: import pickle def code_unpickler(data): return marshal.loads(data) def code_pickler(code): return code_unpickler, (marshal.dumps(code),) copy_reg.pickle(types.CodeType, code_pickler, code_unpickler) # Code cache is implemented using a dictionary clFileDict and # a list of persistent dictionaries (shelves) in cacheList. # # - clFileDict uses CL filename as the key and has # the md5 digest of the file contents as its value. # The md5 digest is automatically updated if the file changes. # # - the persistent cache has the md5 digest as the key # and the Pycode object as the value. # # This scheme allows files with different path names to # be found in the cache (since the file contents, not the # name, determine the shelve key) while staying up-to-date # with changes of the CL file contents when the script is # being developed. import stat, hashlib _versionKey = 'CACHE_VERSION' def _currentVersion(): if not pyrafglobals._use_ecl: return "v2" else: return "v3" class _FileContentsCache(filecache.FileCacheDict): def __init__(self): # create file dictionary with md5 digest as value filecache.FileCacheDict.__init__(self,filecache.MD5Cache) class _CodeCache: """Python code cache class Note that old out-of-date cached code never gets removed in this system. That's because another CL script might still exist with the same code. Need a utility to clean up the cache by looking for unused keys... """ def __init__(self, cacheFileList): cacheList = [] flist = [] nwrite = 0 for file in cacheFileList: db = self._cacheOpen(file) if db is not None: cacheList.append(db[0:2]) nwrite = nwrite+db[0] flist.append(db[2]) self.clFileDict = _FileContentsCache() self.cacheList = cacheList self.cacheFileList = flist self.nwrite = nwrite # flag indicating preference for system cache self.useSystem = 0 if not cacheList: self.warning("Warning: unable to open any CL script cache, " "performance may be slow") elif nwrite == 0: self.warning("Unable to open any CL script cache for writing") def _cacheOpen(self, filename): """Open shelve database in filename and check version Returns tuple (writeflag, shelve-object, filename) on success or None on failure. This may modify the filename if necessary to open the correct version of the cache. """ # filenames to try, open flags to use filelist = [(filename, "w"), ('%s.%s' % (filename, _currentVersion()), "c")] msg = [] for fname, flag in filelist: # first try opening the cache read-write try: fh = dirshelve.open(fname, flag) writeflag = 1 except dirshelve.error: # initial open failed -- try opening the cache read-only try: fh = dirshelve.open(fname,"r") writeflag = 0 except dirshelve.error: # give up on this file and try the next one msg.append("Unable to open CL script cache %s" % fname) continue # check version of cache -- don't use it if version mismatch if len(fh) == 0: fh[_versionKey] = _currentVersion() oldVersion = fh.get(_versionKey, 'v0') if oldVersion == _currentVersion(): # normal case -- cache version is as expected return (writeflag, fh, fname) elif fname.endswith(_currentVersion()): # uh-oh, something is seriously wrong msg.append("CL script cache %s has version mismatch, may be corrupt?" % fname) elif oldVersion > _currentVersion(): msg.append(("CL script cache %s was created by " + "a newer version of pyraf (cache %s, this pyraf %s)") % (fname, `oldVersion`, `_currentVersion()`)) else: msg.append("CL script cache %s is obsolete version (old %s, current %s)" % (fname, `oldVersion`, `_currentVersion()`)) fh.close() # failed to open either cache self.warning("\n".join(msg)) return None def warning(self, msg, level=0): """Print warning message to stderr, using verbose flag""" if Verbose >= level: sys.stdout.flush() sys.stderr.write(msg + "\n") sys.stderr.flush() def writeSystem(self, value=1): """Add scripts to system cache instead of user cache""" if value==0: self.useSystem = 0 elif self.cacheList: writeflag, cache = self.cacheList[-1] if writeflag: self.useSystem = 1 else: self.warning("System CL script cache is not writable") else: self.warning("No CL script cache is active") def close(self): """Close all cache files""" for writeflag, cache in self.cacheList: cache.close() self.cacheList = [] self.nwrite = 0 # Note that this does not delete clFileDict since the # in-memory info for files already read is still OK # (Just in case there is some reason to close cache files # while keeping _CodeCache object around for future use.) def __del__(self): self.close() def getIndex(self, filename, source=None): """Get cache key for a file or filehandle""" if filename: return self.clFileDict.get(filename) elif source: # there is no filename, but return md5 digest of source as key h = hashlib.md5() if PY3K: # unicode must be encoded to be hashed h.update(source.encode('ascii')) return str(h.digest()) else: h.update(source) return h.digest() def add(self, index, pycode): """Add pycode to cache with key = index. Ignores if index=None.""" if index is None or self.nwrite==0: return if self.useSystem: # system cache is last in list cacheList = self.cacheList[:] cacheList.reverse() else: cacheList = self.cacheList for writeflag, cache in cacheList: if writeflag: cache[index] = pycode return def get(self, filename, mode="proc", source=None): """Get pycode from cache for this file. Returns tuple (index, pycode). Pycode=None if not found in cache. If mode != "proc", assumes that the code should not be cached. """ if mode != "proc": return None, None index = self.getIndex(filename, source=source) if index is None: return None, None for i in range(len(self.cacheList)): writeflag, cache = self.cacheList[i] if index in cache: pycode = cache[index] pycode.index = index pycode.setFilename(filename) return index, pycode return index, None def remove(self, filename): """Remove pycode from cache for this file or IrafTask object. This deletes the entry from the shelve persistent database, under the assumption that this routine may be called to fix a bug in the code generation (so we don't want to keep the old version of the Python code around.) """ if not isinstance(filename,str): try: task = filename filename = task.getFullpath() except (AttributeError, TypeError): raise TypeError( "Filename parameter must be a string or IrafCLTask") index = self.getIndex(filename) # system cache is last in list irange = range(len(self.cacheList)) if self.useSystem: irange.reverse() nremoved = 0 for i in irange: writeflag, cache = self.cacheList[i] if index in cache: if writeflag: del cache[index] self.warning("Removed %s from CL script cache %s" % \ (filename,self.cacheFileList[i]), 2) nremoved = nremoved+1 else: self.warning("Cannot remove %s from read-only " "CL script cache %s" % \ (filename,self.cacheFileList[i])) if nremoved==0: self.warning("Did not find %s in CL script cache" % filename, 2) # 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 = '" $Id$ """ from __future__ import division # confidence high import linecache, string, os, sys from stat import * from stsci.tools.irafglobals import IrafError def checkcache(filename=None,orig_checkcache=linecache.checkcache): """Discard cache entries that are out of date. (This is not checked upon each call!)""" # Rather than repeat linecache.checkcache code, we check & save the # CL script entries, call the original function, and then # restore the saved entries. (Modelled after Idle/PyShell.py.) cache = linecache.cache save = {} if filename is None: filenames = cache.keys() else: if filename in cache: filenames = [filename] else: return import pyraf.iraf # used below for filename in filenames: # for filename in cache.keys(): if filename[:10] == " (>? ( [GIP]+ | & ) | >)' # matches >> >& >>& >G >I >P >>G >>GI etc. parent.addToken(type='REDIR', attr=s) #XXX may not need following -- I think redirection in #XXX compute-eqn mode should always be trapped by #XXX accept-REDIR mode, and exitComputeEqnMode does #XXX not do anything in other modes parent.exitComputeEqnMode() parent.current.append(_SWALLOW_NEWLINE_MODE) def t_comment(self, s, m, parent): r'\#(?P.*)' # skip comment, leaving newline in string # look for special mode-shifting commands comment = m.group('Comment') if comment[:1] == '{': parent.default_mode = _COMPUTE_START_MODE elif comment[:1] == '}': parent.default_mode = _COMMAND_MODE def t_osescape(self, s, m, parent): r'(^|\n)[ \t]*!.*' # Host OS command escape. Strip off everything # up through the '!'. if s[0] == '\n': parent.addToken(type='NEWLINE') parent.lineno = parent.lineno + 1 cmd = s.strip()[1:] parent.addToken(type='OSESCAPE', attr=cmd.strip()) def t_singlequote(self, s, m, parent): r"' [^'\\\n]* ( ( ((\\(.|\n)|\n)[\s?]*) | '' ) [^'\\\n]* )*'" # this pattern allows both escaped embedded quotes and # embedded double quotes ('embedded''quotes') # it also allows escaped newlines if parent.current[-1] == _COMMAND_MODE: parent.addToken(type=parent.argsep) parent.argsep = ',' nline = _countNewlines(s) # Recognize and remove any embedded comments s = comment_pat.sub('',s) s = filterEscapes(irafutils.removeEscapes( irafutils.stripQuotes(s),quoted=1)) # We use a different type for quoted strings to protect them # against conversion to other token types by enterComputeEqnMode parent.addToken(type='QSTRING', attr=s) parent.lineno = parent.lineno + nline def t_doublequote(self, s, m, parent): r'" [^"\\\n]* ( ( ((\\(.|\n)|\n)[\s?]*) | "" ) [^"\\\n]* )* "' if parent.current[-1] == _COMMAND_MODE: parent.addToken(type=parent.argsep) parent.argsep = ',' nline = _countNewlines(s) # Recognize and remove any embedded comments s = comment_pat.sub('',s) s = filterEscapes(irafutils.removeEscapes( irafutils.stripQuotes(s),quoted=1)) parent.addToken(type='QSTRING', attr=s) parent.lineno = parent.lineno + nline def t_semicolon(self, s, m, parent): r';' parent.addToken(type=';') # usually we reset mode just like on newline # if semicolon inside parentheses, just stay in compute mode # this occurs legally only in the (e1;e2;e3) clause of a `for' stmt if parent.parencount <= 0: parent.startLine() # addition for sloppy scanner # ignores binary data embedded in CL files class _LaxScanner: def t_default(self, s, m, parent): r'.' # skip binary data if '\x1a' < s < '\x7f': parent.addToken(type=s) #--------------------------------------------------------------------- # StartScanner: Tokens recognized in start-line mode #--------------------------------------------------------------------- class _StartScanner_1(_BasicScanner_1): def t_ident(self, s, m, parent): r'[a-zA-Z\$_][a-zA-Z\$_\d.]*' # Go to command mode parent.addIdent(s, mode=parent.default_mode) def t_lparen(self, s, m, parent): r'\(' parent.addToken(type='(') parent.current.append(_COMPUTE_MODE) parent.parencount = parent.parencount + 1 # redirection can follow open parens parent.current.append(_ACCEPT_REDIR_MODE) def t_equals(self, s, m, parent): r'=' parent.addToken(type=s) parent.current.append(_COMPUTE_MODE) def t_help(self, s, m, parent): r'\?\??' if len(s) == 2: parent.addIdent('allPkgHelp',mode=parent.default_mode) else: parent.addIdent('pkgHelp',mode=parent.default_mode) class _StrictStartScanner(_BasicScanner_3,_BasicScanner_2,_StartScanner_1): """Strict scanner class for tokens recognized in start-line mode""" pass class _StartScanner(_LaxScanner,_StrictStartScanner): """Scanner class for tokens recognized in start-line mode""" pass #--------------------------------------------------------------------- # CommandScanner: Tokens recognized in command mode #--------------------------------------------------------------------- class _CommandScanner_1(_BasicScanner_1): def t_string(self, s, m, parent): r'[^ \t\n()\\;{}&]+(\\(.|\n)[^ \t\n()\\;{}&]*)*' # What other characters are forbidden in unquoted strings? # Allowing escaped newlines, blanks, quotes, etc. # Increment line count for embedded newlines (after adding token) parent.addToken(type=parent.argsep) parent.argsep = ',' nline = _countNewlines(s) # Handle special escapes then, escape all remaining backslashes # since IRAF doesn't deal with special characters in this mode. # Thus PyRAF should leave them as literal backslashes within its # strings. Why IRAF does this I have no idea. s = irafutils.removeEscapes(s).replace('\\','\\\\') parent.addToken(type='STRING', attr=s) parent.lineno = parent.lineno + nline def t_lbracket(self, s, m, parent): r'\[' parent.addToken(type=s) # push to compute mode parent.current.append(_COMPUTE_MODE) def t_lparen(self, s, m, parent): r'\(' parent.addToken(type=parent.argsep) parent.argsep = ',' parent.addToken(type='(') # push to compute mode parent.current.append(_COMPUTE_MODE) parent.parencount = parent.parencount + 1 # redirection can follow open parens parent.current.append(_ACCEPT_REDIR_MODE) class _CommandScanner_2(_BasicScanner_2,_CommandScanner_1): def t_keyval(self, s, m, parent): r'(?P[a-zA-Z\$_\d][a-zA-Z\$_\d.]*) [ \t]* =(?!=)' # note that keywords can start with a number (!) in command mode parent.addToken(type=parent.argsep) parent.argsep = None parent.addIdent(m.group('KeyName'), usekey=0) parent.addToken(type='=') def t_keybool(self, s, m, parent): r'[a-zA-Z\$_\d][a-zA-Z\$_\d.]*[+\-]($|(?=[ \t\n<>\|]))' # note that keywords can start with a number (!) in command mode parent.addToken(type=parent.argsep) parent.argsep = ',' parent.addIdent(s[:-1], usekey=0) parent.addToken(type=s[-1]) def t_functioncall(self, s, m, parent): r'[a-zA-Z\$_\d][a-zA-Z\$_\d.]*\(' # matches identifier follow by open parenthesis (no whitespace) # note that keywords can start with a number (!) in command mode parent.addToken(type=parent.argsep) parent.argsep = ',' parent.addIdent(s[:-1], usekey=0) parent.addToken(type='(') # push to compute mode parent.current.append(_COMPUTE_MODE) parent.parencount = parent.parencount + 1 # redirection can follow open parens parent.current.append(_ACCEPT_REDIR_MODE) def t_assignop(self, s, m, parent): r'( [+\-*/] | // )? =' if s == '=': parent.addToken(type=s) else: parent.addToken(type='ASSIGNOP',attr=s) parent.current.append(_COMPUTE_MODE) class _StrictCommandScanner(_BasicScanner_3,_CommandScanner_2): """Strict scanner class for tokens recognized in command mode""" def t_redir(self, s, m, parent): r' < | >>? ([GIP]+|&?) | \|&? ' # Redirection is accepted anywhere in command mode if s[0] == '|': parent.addToken(type='PIPE', attr=s) parent.startLine(parencount=parent.parencount) else: parent.addToken(type=parent.argsep) parent.argsep = None parent.addToken(type='REDIR', attr=s) parent.current.append(_SWALLOW_NEWLINE_MODE) class _CommandScanner(_LaxScanner,_StrictCommandScanner): """Scanner class for tokens recognized in command mode""" pass #--------------------------------------------------------------------- # ComputeStartScanner: Tokens recognized in initial compute mode # (similar to command mode) #--------------------------------------------------------------------- class _ComputeStartScanner_1(_BasicScanner_1): def t_string(self, s, m, parent): r'[a-zA-Z_$][a-zA-Z_$.0-9]*' # This is a quoteless string with some strict syntax limits. # Most special characters are excluded. Escapes are not allowed # either. parent.addToken(type='STRING', attr=s) def t_integer(self, s, m, parent): r' \d+([bB]|([\da-fA-F]*[xX]))? ' parent.addToken(type='INTEGER', attr=s) def t_comma(self, s, m, parent): r',' # commas are parameter separators in this mode # newlines, redirection allowed after comma parent.addToken(type=s) parent.current.append(_ACCEPT_REDIR_MODE) parent.current.append(_SWALLOW_NEWLINE_MODE) def t_lbracket(self, s, m, parent): r'\[' parent.addToken(type=s) # push to compute mode parent.current.append(_COMPUTE_MODE) def t_lparen(self, s, m, parent): r'\(' parent.enterComputeEqnMode() parent.addToken(type='(') # push to compute mode parent.current.append(_COMPUTE_MODE) parent.parencount = parent.parencount + 1 # redirection can follow open parens parent.current.append(_ACCEPT_REDIR_MODE) def t_op(self, s, m, parent): r'\*\*|//|\*|\+|-|/|%' #XXX Could make this type OP if we don't need to distinguish them parent.enterComputeEqnMode() parent.addToken(type=s) # line breaks are allowed after operators parent.current.append(_SWALLOW_NEWLINE_MODE) class _ComputeStartScanner_2(_BasicScanner_2,_ComputeStartScanner_1): def t_keyval(self, s, m, parent): r'(?P[a-zA-Z\$_][a-zA-Z\$_\d.]*) [ \t]* =(?!=)' parent.addIdent(m.group('KeyName'), usekey=0) parent.addToken(type='=') def t_keybool(self, s, m, parent): r'[a-zA-Z\$_][a-zA-Z\$_\d.]*[+\-]($|(?=[ \t]*[\n<>\|,)]))' # Difference from command mode t_keybool is that comma/paren can # terminate argument # This pattern requires a following comma, newline, or # redirection so that expressions can be distinguished from # boolean args in this mode parent.addIdent(s[:-1], usekey=0) parent.addToken(type=s[-1]) parent.current.append(_ACCEPT_REDIR_MODE) def t_assignop(self, s, m, parent): r'( [+\-*/] | // )? =' if s == '=': parent.addToken(type=s) else: parent.addToken(type='ASSIGNOP',attr=s) parent.current.append(_COMPUTE_MODE) def t_redir(self, s, m, parent): r' < | >>? ([GIP]+|&?) | \|&? ' # Redirection is accepted in command mode if s[0] == '|': parent.addToken(type='PIPE', attr=s) parent.startLine(parencount=parent.parencount) else: parent.addToken(type='REDIR', attr=s) parent.current.append(_SWALLOW_NEWLINE_MODE) def t_sexagesimal(self, s, m, parent): r'\d+:\d+(:\d+(\.\d*)?)?' parent.addToken(type='SEXAGESIMAL', attr=s) def t_float(self, s, m, parent): r'(\d+[eEdD][+\-]?\d+) | (((\d*\.\d+)|(\d+\.\d*))([eEdD][+\-]?\d+)?)' parent.addToken(type='FLOAT', attr=s) class _StrictComputeStartScanner(_BasicScanner_3,_ComputeStartScanner_2): """Strict scanner class for tokens recognized in initial compute mode (similar to command mode) """ pass class _ComputeStartScanner(_LaxScanner,_StrictComputeStartScanner): """Scanner class for tokens recognized in initial compute mode (similar to command mode) """ pass #--------------------------------------------------------------------- # ComputeEqnScanner: Tokens recognized in compute equation mode # Mostly like standard Compute mode, but reverts to ComputeStart # mode on comma #--------------------------------------------------------------------- class _ComputeEqnScanner_1(_BasicScanner_1): def t_lparen(self, s, m, parent): r'\(' parent.addToken(type='(') parent.current.append(_COMPUTE_MODE) parent.parencount = parent.parencount + 1 # redirection can follow open parens #XXX get rid of this? parent.current.append(_ACCEPT_REDIR_MODE) def t_op(self, s, m, parent): r'\*\*|//|\*|\+|-|/|%' #XXX Could make this type OP if we don't need to distinguish them parent.addToken(type=s) # line breaks are allowed after operators parent.current.append(_SWALLOW_NEWLINE_MODE) def t_logop(self, s, m, parent): r'\|\||&&|!' # split '!' off separately if len(s) > 1: parent.addToken(type='LOGOP',attr=s) else: parent.addToken(type=s) parent.current.append(_SWALLOW_NEWLINE_MODE) def t_integer(self, s, m, parent): r' \d+([bB]|([\da-fA-F]*[xX]))? ' parent.addToken(type='INTEGER', attr=s) def t_ident(self, s, m, parent): r'[a-zA-Z\$_][a-zA-Z\$_\d.]*' parent.addIdent(s) def t_comma(self, s, m, parent): r',' # commas are parameter separators in this mode # commas also terminate this mode parent.exitComputeEqnMode() parent.addToken(type=s) # newlines, redirection allowed after comma parent.current.append(_ACCEPT_REDIR_MODE) parent.current.append(_SWALLOW_NEWLINE_MODE) class _ComputeEqnScanner_2(_BasicScanner_2,_ComputeEqnScanner_1): def t_keyval(self, s, m, parent): r'(?P[a-zA-Z\$_][a-zA-Z\$_\d.]*) [ \t]* =(?!=)' parent.addIdent(m.group('KeyName'), usekey=0) parent.addToken(type='=') def t_keybool(self, s, m, parent): r'[a-zA-Z\$_][a-zA-Z\$_\d.]*[+\-]($|(?=[ \t]*[\n<>\|,)]))' # Difference from command mode t_keybool is that comma/paren can # terminate argument # This pattern requires a following comma, newline, or # redirection so that expressions can be distinguished from # boolean args in this mode parent.addIdent(s[:-1], usekey=0) parent.addToken(type=s[-1]) parent.current.append(_ACCEPT_REDIR_MODE) def t_sexagesimal(self, s, m, parent): r'\d+:\d+(:\d+(\.\d*)?)?' parent.addToken(type='SEXAGESIMAL', attr=s) def t_assignop(self, s, m, parent): r'( [+\-*/] | // ) =' parent.addToken(type='ASSIGNOP',attr=s) # switch to compute mode parent.current[-1] = _COMPUTE_MODE def t_float(self, s, m, parent): r'(\d+[eEdD][+\-]?\d+) | (((\d*\.\d+)|(\d+\.\d*))([eEdD][+\-]?\d+)?)' parent.addToken(type='FLOAT', attr=s) class _StrictComputeEqnScanner(_BasicScanner_3,_ComputeEqnScanner_2): """Strict scanner class for tokens recognized in compute equation mode""" def t_compop(self, s, m, parent): r'[<>!=]=|<|>' parent.addToken(type='COMPOP',attr=s) parent.current.append(_SWALLOW_NEWLINE_MODE) class _ComputeEqnScanner(_LaxScanner,_StrictComputeEqnScanner): """Scanner class for tokens recognized in compute mode""" pass #--------------------------------------------------------------------- # ComputeScanner: Tokens recognized in compute mode #--------------------------------------------------------------------- class _ComputeScanner_1(_BasicScanner_1): def t_lparen(self, s, m, parent): r'\(' parent.addToken(type='(') # push to compute mode parent.current.append(_COMPUTE_MODE) parent.parencount = parent.parencount + 1 # redirection can follow open parens # XXX get rid of this? parent.current.append(_ACCEPT_REDIR_MODE) def t_op(self, s, m, parent): r'\*\*|//|\*|\+|-|/|%' #XXX Could make this type OP if we don't need to distinguish them parent.addToken(type=s) # line breaks are allowed after operators parent.current.append(_SWALLOW_NEWLINE_MODE) def t_logop(self, s, m, parent): r'\|\||&&|!' # split '!' off separately if len(s) > 1: parent.addToken(type='LOGOP',attr=s) else: parent.addToken(type=s) parent.current.append(_SWALLOW_NEWLINE_MODE) def t_integer(self, s, m, parent): r' \d+([bB]|([\da-fA-F]*[xX]))? ' parent.addToken(type='INTEGER', attr=s) def t_ident(self, s, m, parent): r'[a-zA-Z\$_][a-zA-Z\$_\d.]*' parent.addIdent(s) def t_comma(self, s, m, parent): r',' # commas are parameter separators in this mode parent.addToken(type=s) # newlines, redirection allowed after comma parent.current.append(_ACCEPT_REDIR_MODE) parent.current.append(_SWALLOW_NEWLINE_MODE) class _ComputeScanner_2(_BasicScanner_2,_ComputeScanner_1): def t_keyval(self, s, m, parent): r'(?P[a-zA-Z\$_][a-zA-Z\$_\d.]*) [ \t]* =(?!=)' parent.addIdent(m.group('KeyName'), usekey=0) parent.addToken(type='=') def t_keybool(self, s, m, parent): r'[a-zA-Z\$_][a-zA-Z\$_\d.]*[+\-]($|(?=[ \t]*[\n<>\|,)]))' # Difference from command mode t_keybool is that comma/paren can # terminate argument # This pattern requires a following comma, newline, or # redirection so that expressions can be distinguished from # boolean args in this mode parent.addIdent(s[:-1], usekey=0) parent.addToken(type=s[-1]) parent.current.append(_ACCEPT_REDIR_MODE) def t_sexagesimal(self, s, m, parent): r'\d+:\d+(:\d+(\.\d*)?)?' parent.addToken(type='SEXAGESIMAL', attr=s) def t_assignop(self, s, m, parent): r'( [+\-*/] | // ) =' parent.addToken(type='ASSIGNOP',attr=s) def t_float(self, s, m, parent): r'(\d+[eEdD][+\-]?\d+) | (((\d*\.\d+)|(\d+\.\d*))([eEdD][+\-]?\d+)?)' parent.addToken(type='FLOAT', attr=s) class _StrictComputeScanner(_BasicScanner_3,_ComputeScanner_2): """Strict scanner class for tokens recognized in compute mode""" def t_compop(self, s, m, parent): r'[<>!=]=|<|>' parent.addToken(type='COMPOP',attr=s) parent.current.append(_SWALLOW_NEWLINE_MODE) class _ComputeScanner(_LaxScanner,_StrictComputeScanner): """Scanner class for tokens recognized in compute mode""" pass #--------------------------------------------------------------------- # SwallowNewlineScanner: Tokens recognized at points where # embedded newlines are allowed #--------------------------------------------------------------------- class _StrictSwallowNewlineScanner(GenericScanner): """Strict scanner class where embedded newlines allowed""" def t_swallow_newlines(self, s, m, parent): r'[ \t\n]* ( ( \\ | (\#.*) ) [ \t\n]+ )*' # Just grab all the following newlines # Also consumes backslash continuations and comments # Note that this always matches, so we always leave this # mode after one match parent.lineno = parent.lineno + _countNewlines(s) # pop to previous mode del parent.current[-1] _SwallowNewlineScanner = _StrictSwallowNewlineScanner #--------------------------------------------------------------------- # AcceptRedirScanner: Tokens that are recognized at points where # redirection is allowed #--------------------------------------------------------------------- class _StrictAcceptRedirScanner(_BasicScanner_3,_BasicScanner_2, _BasicScanner_1): """Strict scanner class where redirection is allowed""" def t_accept_redir(self, s, m, parent): r' < | >>? ([GIP]+|&?) | \|&? ' if s[0] == '|': parent.addToken(type='PIPE', attr=s) parent.startLine(parencount=parent.parencount) else: parent.addToken(type='REDIR', attr=s) # pop this state del parent.current[-1] # allow following newlines parent.current.append(_SWALLOW_NEWLINE_MODE) def t_ignore_spaces(self, s, m, parent): r'[ \t]+' # whitespace ignored (but does not cause us to leave this mode) pass def t_not_redir(self, s, m, parent): r'(?![ \t<>\|])' # if not redirection or whitespace, just pop the state del parent.current[-1] class _AcceptRedirScanner(_LaxScanner,_StrictAcceptRedirScanner): """Scanner class where redirection is allowed""" pass #--------------------------------------------------------------------- # Main context-sensitive scanner #--------------------------------------------------------------------- # dictionary of reserved keywords # SEE ALSO ClScanner.__init__ for more ECL keywords. _keywordDict = { 'begin': 1, 'break': 1, 'case': 1, 'default': 1, 'else': 1, 'end': 1, 'for': 1, 'goto': 1, 'if': 1, 'next': 1, 'procedure': 1, 'return': 1, 'switch': 1, 'while': 1, } _typeDict = { 'bool': 1, 'char': 1, 'file': 1, 'gcur': 1, 'imcur': 1, 'int': 1, 'pset': 1, 'real': 1, 'string': 1, 'struct': 1, 'ukey': 1, } _boolDict = { 'yes': 1, 'no': 1, } # list of scanners for each state # only need to create these once, since they are designed to # contain no state information _scannerDict = None _strictScannerDict = None def _getScannerDict(): global _scannerDict if _scannerDict is None: _scannerDict = { _START_LINE_MODE: _StartScanner(), _COMMAND_MODE: _CommandScanner(), _COMPUTE_START_MODE: _ComputeStartScanner(), _COMPUTE_EQN_MODE: _ComputeEqnScanner(), _COMPUTE_MODE: _ComputeScanner(), _SWALLOW_NEWLINE_MODE: _SwallowNewlineScanner(), _ACCEPT_REDIR_MODE: _AcceptRedirScanner(), } return _scannerDict def _getStrictScannerDict(): global _strictScannerDict # create strict scanners if _strictScannerDict is None: _strictScannerDict = { _START_LINE_MODE: _StrictStartScanner(), _COMMAND_MODE: _StrictCommandScanner(), _COMPUTE_START_MODE: _StrictComputeStartScanner(), _COMPUTE_EQN_MODE: _StrictComputeEqnScanner(), _COMPUTE_MODE: _StrictComputeScanner(), _SWALLOW_NEWLINE_MODE: _StrictSwallowNewlineScanner(), _ACCEPT_REDIR_MODE: _StrictAcceptRedirScanner(), } return _strictScannerDict class CLScanner(ContextSensitiveScanner): """CL scanner class""" def __init__(self, strict=0): if pyrafglobals._use_ecl: _keywordDict["iferr"] = 1 _keywordDict["ifnoerr"] = 1 _keywordDict["then"] = 1 self.strict = strict if strict: sdict = _getStrictScannerDict() else: sdict = _getScannerDict() ContextSensitiveScanner.__init__(self, sdict) def startLine(self, parencount=0, argsep=None): # go to _START_LINE_MODE self.parencount = parencount self.argsep = argsep self.current = [ _START_LINE_MODE ] def tokenize(self, input, default_mode=_COMMAND_MODE): self.rv = [] self.lineno = 1 # default mode when leaving _START_LINE_MODE self.default_mode = default_mode # argsep is used to insert commas as argument separators # in command mode self.argsep = None self.parencount = 0 ContextSensitiveScanner.tokenize(self, input) self.addToken(type='NEWLINE') return self.rv def addToken(self, type, attr=None): # add a token to the list (with some twists to simplify parsing) if type is None: return # insert NEWLINE before '}' if type == '}' and self.rv and self.rv[-1].type != 'NEWLINE': self.rv.append(Token(type='NEWLINE', attr=None, lineno=self.lineno)) ## suppress newline after '{' or ';' #if type != 'NEWLINE' or (self.rv and self.rv[-1].type != 'NEWLINE' and # self.rv[-1].type != '{' and # self.rv[-1].type != ';'): # compress out multiple/leading newlines # suppress newline after '{' if type != 'NEWLINE' or (self.rv and self.rv[-1].type != 'NEWLINE' and self.rv[-1].type != '{'): # Another ugly hack -- the syntax # # taskname(arg, arg, | taskname2 arg, arg) # # causes parsing problems. To help solve them, delete any # comma that just precedes a PIPE if type=='PIPE' and self.rv and self.rv[-1].type == ',': del self.rv[-1] self.rv.append(Token(type=type, attr=attr, lineno=self.lineno)) # insert NEWLINE after '}' too # go to start-line mode if type == '}' and self.rv and self.rv[-1].type != 'NEWLINE': self.rv.append(Token(type='NEWLINE', attr=None, lineno=self.lineno)) self.startLine() def addIdent(self, name, mode=None, usekey=1): # Add identifier token, recognizing keywords if usekey parameter is set # Note keywords may be in any case # For normal (non-keyword) identifiers, goes to mode keyword = name.lower() if usekey and keyword in _keywordDict: self.addToken(type=keyword.upper(), attr=keyword) if keyword == "procedure": # Procedure scripts are always in compute mode self.default_mode = _COMPUTE_START_MODE if keyword == "if" or keyword == "else": # For `if', `else' go into _START_LINE_MODE self.startLine() elif self.current[-1] != _COMPUTE_MODE: # Other keywords put us into _COMPUTE_MODE self.current.append(_COMPUTE_MODE) elif usekey and keyword in _typeDict and \ self.current[-1] == _START_LINE_MODE: # types are treated as keywords only if first token on line self.addToken(type='TYPE', attr=keyword) self.current.append(_COMPUTE_MODE) elif keyword == "indef" or keyword == "eof": # INDEF, EOF always get recognized self.addToken(type=keyword.upper()) elif keyword == "epsilon": # epsilon always gets recognized self.addToken(type="FLOAT", attr=keyword) # xxx self.addToken(type="FLOAT") # AttributeError: 'NoneType' object has no attribute 'find' # xxx self.addToken(type=keyword.upper()) # epsilon was quoted elif keyword in _boolDict: # boolean yes, no always gets recognized self.addToken(type='BOOL', attr=keyword) else: self.addToken(type='IDENT',attr=name) if mode is not None: self.current.append(mode) def enterComputeEqnMode(self): # Nasty hack to work around weird CL syntax # In compute-start mode, tokens are strings or identifiers # or numbers depending on what follows them, and the mode # once switched to compute-mode stays there until a # terminating comma. Ugly stuff. # # This is called when a token is received that triggers the # transition to the compute-eqn mode from compute-start mode. # It may be necessary to change tokens already on the # list when this is called... self.current.append(_COMPUTE_EQN_MODE) if self.rv and self.rv[-1].type == "STRING": # if last token was a string, we must remove it and # rescan it using the compute-mode scanner # Hope this works! last = self.rv[-1].attr del self.rv[-1] ContextSensitiveScanner.tokenize(self, last) def exitComputeEqnMode(self): # Companion to enterComputeEqnMode -- called when we encounter # a token that may cause us to exit the mode if self.current[-1] == _COMPUTE_EQN_MODE: del self.current[-1] def _countNewlines(s): """Return number of newlines in string""" n = 0 i = s.find('\n') while (i>=0): n = n+1 i = s.find('\n', i+1) return n def scan(f): input = f.read() scanner = CLScanner() return scanner.tokenize(input) def toklist(tlist,filename=None): # list tokens import cltoken if filename: import sys sys.stdout = open(filename,'w') for tok in tlist: if tok.type == 'NEWLINE': if cltoken.verbose: print 'NEWLINE' else: print else: print `tok`, if filename: sys.stdout.close() sys.stdout = sys.__stdout__ if __name__ == '__main__': s = CLScanner() # scan file 'simple.cl' lines = open('simple.cl').read() tokens = s.tokenize(lines) toklist(tokens[:30]) pyraf-2.1.14/lib/pyraf/cltoken.py0000644000665500117240000001531013033515761017623 0ustar sontagsts_dev00000000000000"""cltoken.py: Token definition for CL parser $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. # # Token class for IRAF CL parsing # import string from stsci.tools.irafglobals import INDEF from stsci.tools import compmixin verbose = 0 class Token(compmixin.ComparableMixin): def __init__(self, type=None, attr=None, lineno=None): self.type = type self.attr = attr self.lineno = lineno # # Not all these may be needed: # # _compare required for GenericParser, required for # GenericASTMatcher only if your ASTs are # heterogeneous (i.e., AST nodes and tokens) # __repr__ recommended for nice error messages in GenericParser # __getitem__ only if you have heterogeneous ASTs # def _compare(self, other, method): if isinstance(other, Token): return method(self.type, other.type) else: return method(self.type, other) def __hash__(self): return hash(self.type) def verboseRepr(self): if self.attr: return self.type + '(' + self.attr + ')' else: return self.type def __repr__(self): global verbose if verbose: return self.verboseRepr() else: if self.type in ["STRING", "QSTRING"]: # add quotes to strings # but replace double escapes with single escapes return repr(self.attr).replace('\\\\','\\') else: rv = self.attr if rv is None: rv = self.type return rv def __getitem__(self, i): raise IndexError def __len__(self): return 0 def get(self): """Return native representation of this token""" if self.type == "INTEGER": return self.__int__() elif self.type == "FLOAT": return self.__float__() elif self.type in ["STRING","QSTRING"]: return self.attr elif self.type == "BOOL": return self.bool() # special conversions def __str__(self): rv = self.attr if rv is None: rv = self.type return rv def __int__(self): if self.type == "INTEGER": return _str2int(self.attr) elif self.type == "INDEF": return int(INDEF) elif self.type == "FLOAT": # allow floats as values if they are exact integers f = self.__float__() i = int(f) if float(i) == f: return i elif self.type in ["STRING", "QSTRING"]: try: if self.attr == "": return int(INDEF) elif self.attr[:1] == ')': # indirection to another parameter return self.attr else: return _str2int(self.attr) except Exception, e: print 'Exception', str(e) pass raise ValueError("Cannot convert " + self.verboseRepr() + " to int") def __float__(self): if self.type == "FLOAT": # convert d exponents to e for Python value = self.attr i = value.find('d') if i>=0: value = value[:i] + 'e' + value[i+1:] else: i = value.find('D') if i>=0: value = value[:i] + 'E' + value[i+1:] return float(value) elif self.type == "INTEGER": # convert to int first because of octal, hex formats return float(_str2int(self.attr)) elif self.type == "SEXAGESIMAL": # convert d:m:s values directly to float flist = self.attr.split(':') flist.reverse() value = float(flist[0]) for v in flist[1:]: value = float(v) + value/60.0 return value elif self.type == "INDEF": return float(INDEF) elif self.type in ["STRING", "QSTRING"]: try: if self.attr == "": return float(INDEF) elif self.attr[:1] == ')': # indirection to another parameter return self.attr else: return float(self.attr) except (ValueError, TypeError): pass raise ValueError("Cannot convert " + self.verboseRepr() + " to float") def bool(self): #XXX convert INTEGER to bool too? if self.type == "BOOL": return self.attr elif self.type == "INDEF": return INDEF elif self.type in ["STRING", "QSTRING"]: keyword = self.attr.lower() if keyword in ["yes", "y"]: return "yes" elif keyword in ["no", "n"]: return "no" elif self.attr[:1] == ')': # indirection to another parameter return self.attr elif keyword == "": return INDEF raise ValueError("Cannot convert " + self.verboseRepr() + " to bool") def _str2int(value): # convert integer string to python int # handles IRAF octal, hex values last = value[-1].lower() if last == 'b': # octal return eval('0'+value[:-1]) elif last == 'x': # hexadecimal return eval('0x'+value[:-1]) # remove leading zeros on decimal values i=0 for digit in value: if digit != '0': break i = i+1 else: # all zeros return 0 return int(value[i:]) pyraf-2.1.14/lib/pyraf/describe.py0000644000665500117240000001103213033515761017741 0ustar sontagsts_dev00000000000000# http://www.dejanews.com/getdoc.xp?AN=382948703 # # Instant Python # $Id$ # # utilities to describe functions, methods, and classes # # history: # 96-10-27 fl created # 98-02-24 fl added code to handle unpacked arguments # 01-11-13 rlw added UNPACK_SEQUENCE to UNPACK_TUPLE for tuple args # (Changed for Python2.0) # # notes: # This has been tested with Python 1.4 and 1.5. The code and # function object attributes might change in future versions of # Python. # # written by fredrik lundh. last updated february 1998. # # fredrik@pythonware.com # http://www.pythonware.com # from __future__ import division # confidence high import string from dis import opname, HAVE_ARGUMENT # -------------------------------------------------------------------- # code object attributes # -------------------------------------------------------------------- # co_argcount INT # co_nlocals INT # co_flags INT # CO_OPTIMIZED # CO_NEWLOCALS # CO_VARARGS # CO_VARKEYWORDS # co_code OBJECT # co_consts OBJECT # co_names OBJECT # co_varnames OBJECT # co_filename OBJECT # co_name OBJECT # -------------------------------------------------------------------- # function object attributes # -------------------------------------------------------------------- # func_code OBJECT # func_globals OBJECT # func_name OBJECT (__name__) # func_defaults OBJECT # func_doc OBJECT (__doc__) # copied from Python header file CO_OPTIMIZED = 0x0001 CO_NEWLOCALS = 0x0002 CO_VARARGS = 0x0004 CO_VARKEYWORDS = 0x0008 def describeParams(func, name = None): # get argument list code = func.func_code n = code.co_argcount a = list(code.co_varnames[:n]) p = 0 for i in range(n): # anonymous arguments c = code.co_code if not a[i] or a[i][0] == ".": vars = [] while p < len(c): v = ord(c[p]) if v >= HAVE_ARGUMENT: s, v = opname[v], ord(c[p+1]) + ord(c[p+2])*256 p = p + 3 if s in ("UNPACK_SEQUENCE", "UNPACK_TUPLE"): count = v elif s == "STORE_FAST": vars.append(code.co_varnames[v]) if len(vars) >= count: break else: p = p + 1 if vars: a[i] = "(" + ", ".join(vars) + ")" if func.func_defaults: # defaults i = n - len(func.func_defaults) for d in func.func_defaults: a[i] = (a[i], d) i = i + 1 if code.co_flags & CO_VARARGS: # extra arguments a.append("*"+code.co_varnames[n]) n = n + 1 if code.co_flags & CO_VARKEYWORDS: # extra keyword arguments a.append("**"+code.co_varnames[n]) n = n + 1 return a def describe(func, name = None): "Return the function or method declaration as a string" # argument list a = describeParams(func) args = [] for arg in a: if type(arg) == type(""): args.append(arg) else: args.append("%s=%s" % (arg[0], repr(arg[1]))) args = ", ".join(args) # function name if not name: # func_name is considered obsolete, use __name__ instead # name = func.func_name name = func.__name__ if name == "": return "lambda %s" % args return "%s(%s)" % (name, args) def __getmethods(c, m): for k, v in c.__dict__.items(): if type(v) == type(__getmethods): # and k[0] != "_": if not k in m: m[k] = describe(v, k), c.__name__ for c in c.__bases__: __getmethods(c, m) def describe_class(cls): "Return a dictionary describing all methods available in a class" m = {} __getmethods(cls, m) return m def describe_instance(self): "Return a dictionary describing all methods available in an instance" return describe_class(self.__class__) # # -------------------------------------------------------------------- if __name__ == "__main__": def foo(a, b=1, *c, **d): e = a + b + c f = None bar = lambda a: 0 # from Duncan Booth def baz(a, (b, c) = ('foo','bar'), (d, e, f) = (None, None, None), g = None): pass print "describeParams(foo)", describeParams(foo) print "describeParams(bar)", describeParams(bar) print "describeParams(baz)", describeParams(baz) print describe(foo) print describe(bar) print describe(baz) pyraf-2.1.14/lib/pyraf/dirdbm.py0000644000665500117240000001066213033515761017432 0ustar sontagsts_dev00000000000000"""Version of dbm that uses files in a directory Allows simultaneous read-write access to the data since the OS allows multiple processes to have access to the file system. $Id$ XXX need to implement 'n' open flag (force new database creation) XXX maybe allow for known key with None as value in dict? XXX keys, len are incomplete if directory is not writable? R. White, 2000 September 26 """ from __future__ import division # confidence high import os, binascii, string, __builtin__ _os = os _binascii = binascii _string = string del os, binascii, string # For anydbm error = IOError class _Database(object): """Dictionary-like object with entries stored in separate files Keys and values must be strings. Name of file is constructed using base64 translation of key. """ def __init__(self, directory, flag='c'): self._directory = directory self._dict = {} self._writable = flag in ['w','c'] if not _os.path.exists(directory): if flag == 'c': # create directory if it doesn't exist try: _os.mkdir(directory) except OSError, e: raise IOError(str(e)) else: raise IOError("Directory "+directory+" does not exist") elif not _os.path.isdir(directory): raise IOError("File "+directory+" is not a directory") elif self._writable: # make sure directory is writable try: testfile = _os.path.join(directory, 'junk' + `_os.getpid()`) fh = __builtin__.open(testfile, 'w') fh.close() _os.remove(testfile) except IOError, e: raise IOError("Directory %s cannot be opened for writing" % (directory,)) # initialize dictionary # get list of files from directory and translate to keys try: flist = _os.listdir(self._directory) except OSError: raise IOError("Directory "+directory+" is not readable") for fname in flist: # replace hyphens and add newline in base64 key = fname.replace('-', '/') + '\n' try: key = _binascii.a2b_base64(key) self._dict[key] = None except _binascii.Error: # just ignore files with names that do not look like keys pass def _getFilename(self, key): """Return filename equivalent to this string key""" filename = _binascii.b2a_base64(key) # get rid of trailing newline in base64 and replace slashes filename = filename[:-1].replace('/', '-') return _os.path.join(self._directory, filename) def __getitem__(self, key): if key in self._dict and self._dict[key]: return self._dict[key] # look for file even if dict doesn't have key because # another process could create it try: fh = __builtin__.open(self._getFilename(key),'rb') value = fh.read() fh.close() # cache object in memory self._dict[key] = value return value except IOError: raise KeyError(key) def __setitem__(self, key, value): # use just in-memory dictionary if directory is not writable self._dict[key] = value if self._writable: try: fname = self._getFilename(key) fh = __builtin__.open(fname,'wb') fh.write(value) fh.close() except IOError, e: # clean up on IO error (e.g., if disk fills up) try: if _os.path.exists(fname): _os.remove(fname) except IOError: pass raise e def __delitem__(self, key): del self._dict[key] if self._writable: _os.remove(self._getFilename(key)) def has_key(self, key): return self._has(key) def __contains__(self, key): return self._has(key) def _has(self, key): return key in self._dict or _os.path.exists(self._getFilename(key)) def __len__(self): return len(self._dict) def keys(self): return self._dict.keys() def close(self): self._dict = None self._writable = 0 def open(filename, flag='c', mode=None): # mode is ignored return _Database(filename, flag) pyraf-2.1.14/lib/pyraf/dirshelve.py0000644000665500117240000000502713033515761020155 0ustar sontagsts_dev00000000000000"""Version of shelve that uses files in a directory with binary pickle format Allows simultaneous read-write access to the data since the OS allows multiple processes to have access to the file system. $Id$ XXX keys, len may be incorrect if directory database is modified XXX by another process after open R. White, 2000 Sept 26 """ from __future__ import division # confidence high import shelve, sys from stsci.tools.for2to3 import PY3K if __name__.find('.') < 0: # for unit test need absolute import exec('import dirdbm', globals()) # 2to3 messes up simpler form else: import dirdbm # tuple of errors that can be raised error = (dirdbm.error, ) class Shelf(shelve.Shelf): """Extension of Shelf using binary pickling""" def __getitem__(self, key): f = shelve.StringIO(self.dict[key]) try: return shelve.Unpickler(f).load() except EOFError: # apparently file is truncated; delete it and raise # and exception del self.dict[key] raise KeyError("Corrupted or truncated file for key %s " "(bad file has been deleted)" % (`key`,)) def __setitem__(self, key, value): f = shelve.StringIO() p = shelve.Pickler(f,1) p.dump(value) self.dict[key] = f.getvalue() def close(self): if hasattr(self,'dict') and hasattr(self.dict,'close'): try: self.dict.close() except: pass self.dict = 0 class DirectoryShelf(Shelf): """Shelf implementation using the directory db interface. This is initialized with the filename for the dirdbm database. """ def __init__(self, filename, flag='c'): Shelf.__init__(self, dirdbm.open(filename, flag)) def open(filename, flag='c'): """Open a persistent dictionary for reading and writing. Argument is the filename for the dirdbm database. Start using builtin shelve.DbfilenameShelf class as of Python 3. Note - flags to the anydbm.open() function mean: 'r' Open existing database for reading only (default) 'w' Open existing database for reading and writing 'c' Open db for read and write, creating if it doesn't exist 'n' Always create a new, empty db, open for reading and writing """ if PY3K: try: return shelve.DbfilenameShelf(filename, flag) except Exception, ex: # is dbm.error raise dirdbm.error(str(ex)) else: return DirectoryShelf(filename, flag) pyraf-2.1.14/lib/pyraf/epar.py0000644000665500117240000003334113033515761017117 0ustar sontagsts_dev00000000000000""" Main module for the PyRAF-version of the Epar parameter editor $Id$ M.D. De La Pena, 2000 February 04 """ from __future__ import division # confidence high from stsci.tools import capable if capable.OF_GRAPHICS: from Tkinter import * # requires 2to3 from tkMessageBox import askokcancel, showwarning, showerror import os, sys, cStringIO from stsci.tools import listdlg, eparoption, editpar, irafutils import iraf, irafpar, irafhelp, wutil from pyrafglobals import pyrafDir else: wutil = None class editpar(): class EditParDialog(): pass # dummy so that code below can import from stsci.tools.irafglobals import IrafError # tool help eparHelpString = """\ The PyRAF Parameter Editor window is used to edit IRAF parameter sets. It allows multiple parameter sets to be edited concurrently (e.g., to edit IRAF Psets). It also allows the IRAF task help to be displayed in a separate window that remains accessible while the parameters are being edited. Editing Parameters -------------------- Parameter values are modified using various GUI widgets that depend on the parameter properties. It is possible to edit parameters using either the mouse or the keyboard. Most parameters have a context-dependent menu accessible via right-clicking that enables unlearning the parameter (restoring its value to the task default), clearing the value, and activating a file browser that allows a filename to be selected and entered in the parameter field. Some items on the right-click pop-up menu may be disabled depending on the parameter type (e.g., the file browser cannot be used for numeric parameters.) The mouse-editing behavior should be familiar, so the notes below focus on keyboard-editing. When the editor starts, the first parameter is selected. To select another parameter, use the Tab key (Shift-Tab to go backwards) or Return to move the focus from item to item. The Up and Down arrow keys also move between fields. The toolbar buttons can also be selected with Tab. Use the space bar to "push" buttons or activate menus. Enumerated Parameters Parameters that have a list of choices use a drop-down menu. The space bar causes the menu to appear; once it is present, the up/down arrow keys can be used to select different items. Items in the list have accelerators (underlined, generally the first letter) that can be typed to jump directly to that item. When editing is complete, hit Return or Tab to accept the changes, or type Escape to close the menu without changing the current parameter value. Boolean Parameters Boolean parameters appear as Yes/No radio buttons. Hitting the space bar toggles the setting, while 'y' and 'n' can be typed to select the desired value. Parameter Sets Parameter sets (Psets) appear as a button which, when clicked, brings up a new editor window. Note that two (or more) parameter lists can be edited concurrently. The Package and Task identification are shown in the window and in the title bar. Text Entry Fields Strings, integers, floats, etc. appear as text-entry fields. Values are verified to to be legal before being stored in the parameter. If an an attempt is made to set a parameter to an illegal value, the program beeps and a warning message appears in the status bar at the bottom of the window. To see the value of a string that is longer than the entry widget, either use the left mouse button to do a slow "scroll" through the entry or use the middle mouse button to "pull" the value in the entry back and forth quickly. In either case, just click in the entry widget with the mouse and then drag to the left or right. If there is a selection highlighted, the middle mouse button may paste it in when clicked. It may be necessary to click once with the left mouse button to undo the selection before using the middle button. You can also use the left and right arrow keys to scroll through the selection. Control-A jumps to the beginning of the entry, and Control-E jumps to the end of the entry. The Menu Bar -------------- File menu: Execute Save all the parameters, close the editor windows, and start the IRAF task. This is disabled in the secondary windows used to edit Psets. Save & Quit Save the parameters and close the editor window. The task is not executed. Save As... Save the parameters to a user-specified file. The task is not executed. Unlearn/Defaults Restore all parameters to the system default values for this task. Note that individual parameters can be unlearned using the menu shown by right-clicking on the parameter entry. Cancel Cancel editing session and exit the parameter editor. Changes that were made to the parameters are not saved; the parameters retain the values they had when the editor was started. Open... menu: Load parameters from any applicable user file found. Values are not stored unless Execute or Save is then selected. If no such files are found, this menu is not shown. Options menu: Display Task Help in a Window Help on the IRAF task is available through the Help menu. If this option is selected, the help text is displayed in a pop-up window. This is the default behavior. Display Task Help in a Browser If this option is selected, instead of a pop-up window help is displayed in the user's web browser. This requires access to the internet and is a somewhat experimental feature. The HTML version of help does have some nice features such as links to other IRAF tasks. Help menu: Task Help Display help on the IRAF task whose parameters are being edited. By default the help pops up in a new window, but the help can also be displayed in a web browser by modifying the Options. EPAR Help Display this help. Show Log Display the historical log of all the status messages that so far have been displayed in the status area at the very bottom of the user interface. Toolbar Buttons ----------------- The Toolbar contains a set of buttons that provide shortcuts for the most common menu bar actions. Their names are the same as the menu items given above: Execute, Save & Quit, Defaults, Cancel, and Task Help. The Execute button is disabled in the secondary windows used to edit Psets. Note that the toolbar buttons are accessible from the keyboard using the Tab and Shift-Tab keys. They are located in sequence before the first parameter. If the first parameter is selected, Shift-Tab backs up to the "Task Help" button, and if the last parameter is selected then Tab wraps around and selects the "Execute" button. """ def epar(theTask, parent=None, isChild=0): if wutil is None or not wutil.hasGraphics: raise IrafError("Cannot run epar without graphics windows") if not isChild: oldFoc = wutil.getFocalWindowID() wutil.forceFocusToNewWindow() PyrafEparDialog(theTask, parent, isChild) if not isChild: wutil.setFocusTo(oldFoc) class PyrafEparDialog(editpar.EditParDialog): def __init__(self, theTask, parent=None, isChild=0, title="PyRAF Parameter Editor", childList=None): # Init base - calls _setTaskParsObj(), sets self.taskName, etc editpar.EditParDialog.__init__(self, theTask, parent, isChild, title, childList, resourceDir=pyrafDir) def _setTaskParsObj(self, theTask): """ Overridden version, so as to use Iraf tasks and IrafParList """ if isinstance(theTask, irafpar.IrafParList): # IrafParList acts as an IrafTask for our purposes self._taskParsObj = theTask else: # theTask must be a string name of, or an IrafTask object self._taskParsObj = iraf.getTask(theTask) def _doActualSave(self, filename, comment, set_ro=False, overwriteRO=False): """ Overridden version, so as to check for a special case. """ # Skip the save if the thing being edited is an IrafParList without # an associated file (in which case the changes are just being # made in memory.) if isinstance(self._taskParsObj,irafpar.IrafParList) and \ not self._taskParsObj.getFilename(): return '' # skip it else: retval = '' try: if filename and os.path.exists(filename) and overwriteRO: irafutils.setWritePrivs(filename, True) retval = self._taskParsObj.saveParList(filename=filename, comment=comment) except IOError: retval="Error saving to "+str(filename)+". Please check privileges." showerror(message=retval, title='Error Saving File') if set_ro: irafutils.setWritePrivs(filename, False, ignoreErrors=True) return retval def _showOpenButton(self): """ Override this so that we can use rules in irafpar. """ # See if there exist any special versions on disk to load # Note that irafpar caches the list of these versions return irafpar.haveSpecialVersions(self.taskName, self.pkgName) def _overrideMasterSettings(self): """ Override this to tailor the GUI specifically for epar. """ self._useSimpleAutoClose = True self._saveAndCloseOnExec = True self._showSaveCloseOnExec = False self._showFlaggingChoice = False self._showExtraHelpButton = True self._appName = "EPAR" self._appHelpString = eparHelpString self._unpackagedTaskTitle = "Filename" self._defaultsButtonTitle = "Unlearn" self._defSaveAsExt = '.par' if not wutil.WUTIL_USING_X: x = "#ccccff" self._frmeColor = x self._taskColor = x self._bboxColor = x self._entsColor = x def _nonStandardEparOptionFor(self, paramTypeStr): """ Override to allow use of PsetEparOption. Return None or a class which derives from EparOption. """ if paramTypeStr == "pset": import pseteparoption return pseteparoption.PsetEparOption else: return None # Two overrides of deafult behavior, related to unpackaged "tasks" def _isUnpackagedTask(self): return isinstance(self._taskParsObj, irafpar.IrafParList) def _getOpenChoices(self): return irafpar.getSpecialVersionFiles(self.taskName, self.pkgName) # OPEN: load parameter settings from a user-specified file def pfopen(self, event=None): """ Load the parameter settings from a user-specified file. Any changes here must be coordinated with the corresponding tpar pfopen function. """ fname = self._openMenuChoice.get() if fname == None: return newParList = irafpar.IrafParList(self.taskName, fname) # Set the GUI entries to these values (let the user Save after) self.setAllEntriesFromParList(newParList) self.freshenFocus() self.showStatus("Loaded parameter values from: "+fname, keep=2) def _getSaveAsFilter(self): """ Return a string to be used as the filter arg to the save file dialog during Save-As. """ filt = '*.par' upx = iraf.envget("uparm_aux","") if 'UPARM_AUX' in os.environ: upx = os.environ['UPARM_AUX'] if len(upx) > 0: filt = iraf.Expand(upx)+"/*.par" return filt def _saveAsPreSave_Hook(self, fnameToBeUsed): """ Override to check for (and warn about) PSETs. """ # Notify them that pset children will not be saved as part of # their special version pars = [] for par in self._taskParsObj.getParList(): if par.type == "pset": pars.append(par.name) if len(pars): msg = "If you have made any changes to the PSET "+ \ "values for:\n\n" for p in pars: msg += "\t\t"+p+"\n" msg = msg+"\nthose changes will NOT be explicitly saved to:"+ \ '\n\n"'+fnameToBeUsed+'"' showwarning(message=msg, title='PSET Save-As Not Yet Supported') def _saveAsPostSave_Hook(self, fnameToBeUsed): """ Override this to notify irafpar. """ # Notify irafpar that there is a new special-purpose file on the scene irafpar.newSpecialParFile(self.taskName, self.pkgName, fnameToBeUsed) def htmlHelp(self, helpString=None, title=None, istask=False, tag=None): """ Overridden version, use irafhelp to invoke the HTML help """ # Help on EPAR itself will use helpString and title. If so, defer # to base, otherwise call irafhelp.help() for task specific text. if helpString and title: editpar.EditParDialog.htmlHelp(self, helpString, title, istask=istask, tag=tag) else: # Invoke the STSDAS HTML help irafhelp.help(self.taskName, html=1) # Get the task help in a string (RLW) def getHelpString(self, taskname): """ Override this - in PyRAF we'll always use use iraf system help. Do not query the task object. """ fh = cStringIO.StringIO() iraf.system.help(taskname, page=0, Stdout=fh, Stderr=fh) result = fh.getvalue() fh.close() return result pyraf-2.1.14/lib/pyraf/filecache.py0000644000665500117240000001523113033515761020071 0ustar sontagsts_dev00000000000000"""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.14/lib/pyraf/fontdata.py0000644000665500117240000003171213033515761017770 0ustar sontagsts_dev00000000000000"""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.14/lib/pyraf/generic.py0000644000665500117240000005272613033515761017614 0ustar sontagsts_dev00000000000000"""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.14/lib/pyraf/gki.py0000644000665500117240000014145713033515761016752 0ustar sontagsts_dev00000000000000 """ 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.14/lib/pyraf/gki_psikern_tests.py0000644000665500117240000002406513033515761021722 0ustar sontagsts_dev00000000000000""" 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.14/lib/pyraf/gki_sys_tests.py0000644000665500117240000000300613033515761021055 0ustar sontagsts_dev00000000000000""" GKI System tests The first version of this will be under-representative of the GKI functionality, but tests will be added over time, as code issues are researched and addressed. $Id$ """ from __future__ import division # confidence high import os, sys from pyraf import iraf from pyraf import gki def psdevs_in_graphcap_test(): """ Verify that the graphcap file supports psdump and psi_land """ is_in_graphcap('psdump') is_in_graphcap('psi_land') def is_in_graphcap(devname): """ Verify that the graphcap file supports a given device name """ gc = gki.getGraphcap() assert gc, "default graphcap not found" assert devname in gc, "default graphcap does not support "+devname theDev = gc[devname] assert theDev.devname==devname, "Invalid graphcap device for "+devname def opcodeList_test(): """ Simple aliveness test for the opcode2name dict """ for opc in gki.GKI_ILLEGAL_LIST: assert gki.opcode2name[opc] == 'gki_unknown' def controlList_test(): """ Simple aliveness test for the control2name dict """ for ctl in gki.GKI_ILLEGAL_LIST: assert gki.control2name[ctl] == 'control_unknown' def run_all(): tsts = sorted([x for x in globals().keys() if x.find('test')>=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.14/lib/pyraf/gkicmd.py0000644000665500117240000000466413033515761017434 0ustar sontagsts_dev00000000000000"""gki metacode generating functions for use by Pyraf in generating iraf gki metacode (primarily for interactive graphics)""" # $Id$ from __future__ import division # confidence high import gki, gwm import numpy def gkiCoord(ndcCoord): """Convert Normalized Device Coordinates to GKI coordinates""" return numpy.array(ndcCoord * (gki.GKI_MAX + 1), numpy.int16) def text(textstring, x, y): """Return metacode for text string written at x,y""" gkiX = gkiCoord(x) gkiY = gkiCoord(y) data = numpy.fromstring(textstring,numpy.int8) # OK if str or uni data = data.astype(numpy.int16) size = 6 + len(textstring) metacode = numpy.zeros(size,numpy.int16) metacode[0] = gki.BOI metacode[1] = gki.GKI_TEXT metacode[2] = size metacode[3:4] = gkiX metacode[4:5] = gkiY metacode[5] = len(textstring) metacode[6:] = data return metacode def markCross(x, y, size=1., xflag=0, yflag=0, linetype=1, linewidth=100, color=1): """Return metacode to plot a cross at the given position. Size = 1 => 10 pixels x 10 pixels. flags = 0 means normal, = 1 full screen. """ gkiX = gkiCoord(x) gkiY = gkiCoord(y) gwidget = gwm.getActiveWindowGwidget() width = gwidget.winfo_width() height = gwidget.winfo_height() xsize = 5./width ysize = 5./height limit = gki.NDC_MAX if not xflag: gkiXmin = gkiCoord(max(x - xsize*size, 0.)) gkiXmax = gkiCoord(min(x + xsize*size, limit)) else: gkiXmin = gkiCoord(0.) gkiXmax = gkiCoord(limit) if not yflag: gkiYmin = gkiCoord(max(y - ysize*size, 0.)) gkiYmax = gkiCoord(min(y + ysize*size, limit)) else: gkiYmin = gkiCoord(0.) gkiYmax = gkiCoord(limit) metacode = numpy.zeros(22,numpy.int16) i = 0 metacode[i ] = gki.BOI metacode[i+1] = gki.GKI_PLSET metacode[i+2] = 6 metacode[i+3] = linetype metacode[i+4] = linewidth metacode[i+5] = color i = i+6 metacode[i ] = gki.BOI metacode[i+1] = gki.GKI_POLYLINE metacode[i+2] = 8 metacode[i+3] = 2 metacode[i+4] = gkiX[0] metacode[i+5] = gkiYmin[0] metacode[i+6] = gkiX[0] metacode[i+7] = gkiYmax[0] i = i+8 metacode[i ] = gki.BOI metacode[i+1] = gki.GKI_POLYLINE metacode[i+2]= 8 metacode[i+3]= 2 metacode[i+4]= gkiXmin[0] metacode[i+5]= gkiY[0] metacode[i+6]= gkiXmax[0] metacode[i+7]= gkiY[0] return metacode pyraf-2.1.14/lib/pyraf/gkigcur.py0000644000665500117240000002172113033515761017622 0ustar sontagsts_dev00000000000000""" 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.14/lib/pyraf/gkiiraf.py0000644000665500117240000000734113033515761017605 0ustar sontagsts_dev00000000000000""" OpenGL implementation of the gki kernel class $Id$ """ from __future__ import division # confidence high import sys, os, string from stsci.tools.for2to3 import ndarr2bytes import gki, irafgwcs, iraftask, iraf # kernels to flush frequently # imdkern does not erase, so always flush it _alwaysFlush = {"imdkern": 1} # dictionary of IrafTask objects for known kernels _kernelDict = {} class GkiIrafKernel(gki.GkiKernel): """This is designed to route metacode to an IRAF kernel executable. It needs very minimal functionality. The basic function is to collect metacode in the buffer and ship it off on flushes and when the kernel is shut down.""" def __init__(self, device): import irafecl module = irafecl.getTaskModule() gki.GkiKernel.__init__(self) graphcap = gki.getGraphcap() if not device in graphcap: raise iraf.IrafError( "No entry found for specified stdgraph device `%s'" % device) gentry = graphcap[device] self.device = device self.executable = executable = gentry['kf'] self.taskname = taskname = gentry['tn'] self.wcs = None if not taskname in _kernelDict: # create special IRAF task object for this kernel _kernelDict[taskname] = module.IrafGKITask(taskname, executable) self.task = _kernelDict[taskname] def control_openws(self, arg): # control_openws precedes gki_openws, so trigger on it to # send everything before the open to the device mode = arg[0] if mode == 5 or self.taskname in _alwaysFlush: self.flush() def control_setwcs(self, arg): self.wcs = irafgwcs.IrafGWcs(arg) 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 gki_closews(self, arg): # gki_closews follows control_closews, so trigger on it to # send everything up through the close to the device if self.taskname in _alwaysFlush: self.flush() def gki_flush(self, arg): if self.taskname in _alwaysFlush: self.flush() def flush(self): # grab last part of buffer and delete it metacode = ndarr2bytes(self.gkibuffer.delget()) # only plot if buffer contains something if metacode: # write to a temporary file tmpfn = iraf.mktemp("iraf") + ".gki" fout = open(tmpfn,'wb') fout.write(metacode) fout.close() try: if self.taskname == "stdgraph": # this is to allow users to specify via the # stdgraph device parameter the device they really # want to display to device = iraf.stdgraph.device else: device = self.device #XXX In principle we could read from Stdin by #XXX wrapping the string in a StringIO buffer instead of #XXX writing it to a temporary file. But that will not #XXX work until binary redirection is implemented in #XXX irafexecute #XXX task(Stdin=tmpfn,device=device,generic="yes") # Explicitly set input to sys.__stdin__ to avoid possible # problems with redirection. Sometimes graphics kernel tries # to read from stdin if it is not the default stdin. self.task(tmpfn,device=device,generic="yes",Stdin=sys.__stdin__) finally: os.remove(tmpfn) pyraf-2.1.14/lib/pyraf/gkitkbase.py0000644000665500117240000010261213033515761020132 0ustar sontagsts_dev00000000000000""" Tk gui implementation for the gki plot widget $Id$ """ from __future__ import division # confidence high import numpy, os, sys, string, time import Tkinter as TKNTR # requires 2to3 import msgiobuffer, msgiowidget, wutil from stsci.tools import capable, filedlg, irafutils from stsci.tools.irafglobals import IrafError, userWorkingHome from stsci.tools.for2to3 import ndarr2bytes import gki, textattrib, irafgwcs from pyrafglobals import pyrafDir import tkFileDialog, tkMessageBox, tkSimpleDialog nIrafColors = 16 #----------------------------------------------- helpString = """\ PyRAF graphics windows provide the capability to recall previous plots, print the current plot, save and load metacode to a file, undo/redo edits to plots, and create new graphics windows. The windows are active at all times (not just in interactive cursor mode) and can be resized. The status bar at the bottom of the window displays messages from the task and is used for input. Note that it has a scroll bar so that old messages can be recalled. File menu: Print Print the current plot to the IRAF stdplot device. Save... Save metacode for the current plot to a user-specified file. Load... Load metacode from to a user-specified file. Close Window Close (iconify) the window. Quit Window Destroy this window. Note that if the window is destroyed while a graphics task is running, the results are unpredictable. Edit menu: Undo Undo the last editable change to the plot. Most changes added by the task (e.g., overplotted lines) cannot currently be undone, but user changes (text annotations, marks) can be undone. You can make overplots undoable by inserting blank annotations. Redo Redo the last change. Undo All Remove all undoable changes. Refresh Redraw the plot. Delete Plot Delete this plot. (This is not undoable.) Note that plots are renumbered in the Page listing. Delete All Plots Delete all plots. User is prompted to be sure. Page menu: (tearoff) Selecting the dotted tearoff line at the top of the menu creates a separate page-selector window. Next Go to the next page in the list of plots. Back Go to the previous page in the list of plots. First Go to the first page in the list of plots. Last Go to the last page in the list of plots. (page list) Go directly to the selected page. Pages are labelled with the name of the task that created them. If there are many pages, a subset around the currently active page is shown; selecting a page (or using First, Last, etc.) changes the displayed subset. Window menu: New... Create a new graphics window. Prompts for a name; if no name is given, the new name will be 'graphics' where is a unique number. If a window with the given name already exists, it simply switches the graphics focus to the new window. (window list) Switch graphics focus to the selected window. Subsequent plots will appear in that window. The results are unpredictable if the window is changed while an interactive graphics task is running. Help menu: Help... Display this help. """ #----------------------------------------------- class GkiInteractiveTkBase(gki.GkiKernel, wutil.FocusEntity): """Base class for interactive graphics kernel implementation This class implements the supporting functionality for the interactive graphics kernel: menu bar, status line, page caching, etc. The actual graphics pane is implemented in a separate class, which extends this class and must have the attributes: makeGWidget() Create the gwidget Tk object and colorManager object redraw() Redraw method (don't call this directly, used by the gwidget class) gRedraw() Redraw that defers to gwidget gcur() Wait for key to be typed and return cursor value gcurTerminate() Terminate active gcur so window can be destroyed incrPlot() Plot the stuff added to buffer since last draw prepareToRedraw() Prepare for complete redraw from metacode getHistory() Get information that needs to be saved in page history setHistory() Restore page using getHistory info clearPage() Clear page (for initialization) startNewPage() Setup for new page isPageBlank() Returns true if current page is blank gki_*() Implement various GKI metacode commands The gwidget object (created by makeGWidget) should have these attributes (in addition to the usual Tk methods): lastX, lastY Last cursor position (int), initially None rgbamode Flag indicating RGB (if true) or indexed color mode activate() Make this the focus of plots activateSWCursor() Various methods for handling the crosshair cursor deactivateSWCursor() (Should rename and clean these up) isSWCursorActive() getSWCursor() #XXX Still need to work on the ColorManager class, which has a bunch of OpenGL specific stuff embedded in it. Could also probably integrate the gl_ functions into a class and use introspection to create the dispatch table, just like for the gki functions. #XXX """ # GKI control functions that are ignored on redraw _controlOps = [ gki.GKI_OPENWS, gki.GKI_CLOSEWS, gki.GKI_REACTIVATEWS, gki.GKI_DEACTIVATEWS, gki.GKI_MFTITLE, gki.GKI_CLEARWS, gki.GKI_CANCEL, gki.GKI_FLUSH, ] # maximum number of error messages for a plot MAX_ERROR_COUNT = 3 def __init__(self, windowName, manager): gki.GkiKernel.__init__(self) self.name = 'Tkplot' self._errorMessageCount = 0 self._slowraise = 0 self._toWriteAtNextClear = None self.irafGkiConfig = gki._irafGkiConfig self.windowName = windowName self.manager = manager # redraw table ignores control functions self.redrawFunctionTable = self.functionTable[:] for opcode in self._controlOps: self.redrawFunctionTable[opcode] = None # Create the root window as required, but hide it irafutils.init_tk_default_root() # note size is just an estimate that helps window manager place window self.top = TKNTR.Toplevel(visual='best',width=600,height=485) # Read the epar options database file optfile = "epar.optionDB" try: self.top.option_readfile(os.path.join(os.curdir,optfile)) except TKNTR.TclError: try: self.top.option_readfile(os.path.join(userWorkingHome,optfile)) except TKNTR.TclError: self.top.option_readfile(os.path.join(pyrafDir,optfile)) self.top.title(windowName) self.top.iconname(windowName) self.top.protocol("WM_DELETE_WINDOW", self.gwdestroy) self.makeMenuBar() self.makeGWidget() self.makeStatus() self.gwidget.redraw = self.redraw self.gwidget.pack(side=TKNTR.TOP, expand=1, fill=TKNTR.BOTH) self.gwidget.bind('', self.focusOnGwidget) # if mouse enters gw self.colorManager.setColors(self.gwidget) self.wcs = irafgwcs.IrafGWcs() self.linestyles = gki.IrafLineStyles() self.hatchfills = gki.IrafHatchFills() self.textAttributes = gki.TextAttributes() self.lineAttributes = gki.LineAttributes() self.fillAttributes = gki.FillAttributes() self.markerAttributes = gki.MarkerAttributes() self.StatusLine = gki.StatusLine(self.top.status, self.windowName) self.history = [(self.gkibuffer, self.wcs, "", self.getHistory())] self._currentPage = 0 # Master page variable, pageVar, any change to it is watched & acted on self.pageVar = TKNTR.IntVar() self.pageVar.set(self._currentPage) # _setPageVar is callback for changes to pageVar self.pageVar.trace('w', self._setPageVar) # Also hold a var just for the # of the selected page button: bttnVar # This one causes no events when it is set! self.bttnVar = TKNTR.IntVar() self.bttnVar.set(0) windowID = self.gwidget.winfo_id() self.flush() if sys.platform != 'darwin': # this step is unneeded on OSX wutil.setBackingStore(windowID) def focusOnGwidget(self, event): # For all kernels, when mouse enters area, give gwidget the focus. # This is a request. This should NOT change which app has focus. # Without this, some tasks could become inoperable if somehow the # focus were to leave the gwidget during interactive input. if self.gwidget: self.gwidget.focus_set() # ----------------------------------------------- def makeStatus(self): """Make status display at bottom of window""" if 'PYRAF_OLD_STATUS' in os.environ: self.top.status = msgiobuffer.MsgIOBuffer(self.top, width=600) self.top.status.msgIO.pack(side=TKNTR.BOTTOM, fill = TKNTR.X) else: self.top.status = msgiowidget.MsgIOWidget(self.top, width=600) self.top.status.pack(side=TKNTR.BOTTOM, fill=TKNTR.X) # ----------------------------------------------- # Menu bar definitions def makeMenuBar(self): """Make menu bar at top of window""" self.menubar = TKNTR.Frame(self.top, bd=1, relief=TKNTR.FLAT) self.fileMenu = self.makeFileMenu(self.menubar) self.editMenu = self.makeEditMenu(self.menubar) self.pageMenu = self.makePageMenu(self.menubar) self.windowMenu = self.makeWindowMenu(self.menubar) self.helpMenu = self.makeHelpMenu(self.menubar) self.menubar.pack(side=TKNTR.TOP, fill=TKNTR.X) def makeFileMenu(self, menubar): button = TKNTR.Menubutton(menubar, text='File') button.pack(side=TKNTR.LEFT, padx=2) button.menu = TKNTR.Menu(button, tearoff=0) button.menu.add_command(label="Print", command=self.doprint) button.menu.add_command(label="Save...", command=self.save) button.menu.add_command(label="Load...", command=self.load) button.menu.add_command(label="Close Window", command=self.iconify) button.menu.add_command(label="Quit Window", command=self.gwdestroy) button["menu"] = button.menu return button def doprint(self): stdout = sys.stdout sys.stdout = self.StatusLine try: gki.printPlot(self) finally: sys.stdout = stdout def save(self): """Save metacode in a file""" curdir = os.getcwd() if capable.OF_TKFD_IN_EPAR: fname = tkFileDialog.asksaveasfilename(parent=self.top, title="Save Metacode") else: fd = filedlg.PersistSaveFileDialog(self.top, "Save Metacode", "*") if fd.Show() != 1: fd.DialogCleanup() os.chdir(curdir) # in case file dlg moved us return fname = fd.GetFileName() fd.DialogCleanup() os.chdir(curdir) # in case file dlg moved us if not fname: return fh = open(fname, 'wb') fh.write(ndarr2bytes(self.gkibuffer.get())) fh.close() def load(self, fname=None): """Load metacode from a file""" if not fname: if capable.OF_TKFD_IN_EPAR: fname = tkFileDialog.askopenfilename(parent=self.top, title="Load Metacode") else: fd = filedlg.PersistLoadFileDialog(self.top, "Load Metacode", "*") if fd.Show() != 1: fd.DialogCleanup() return fname = fd.GetFileName() fd.DialogCleanup() if not fname: return fh = open(fname, 'rb') metacode = numpy.fromstring(fh.read(), numpy.int16) # OK: bytes in PY3K fh.close() self.clear(name=fname) self.append(metacode,isUndoable=1) self.forceNextDraw() self.redraw() def iconify(self): self.top.iconify() def makeEditMenu(self, menubar): button = TKNTR.Menubutton(menubar, text='Edit') button.pack(side=TKNTR.LEFT, padx=2) button.menu = TKNTR.Menu(button, tearoff=0, postcommand=self.editMenuInit) num = 0 button.menu.add_command(label="Undo", command=self.undoN) button.undoNum = num button.menu.add_command(label="Redo", command=self.redoN) num = num+1 button.redoNum = num button.menu.add_command(label="Undo All", command=self.redrawOriginal) num = num+1 button.redrawOriginalNum = num button.menu.add_command(label="Refresh", command=self.refreshPage) num = num+1 button.redrawNum = num button.menu.add_separator() num = num+1 button.menu.add_command(label="Delete Plot", command=self.deletePlot) num = num+1 button.deleteNum = num button.menu.add_command(label="Delete All Plots", command=self.deleteAllPlots) num = num+1 button.deleteAllNum = num button["menu"] = button.menu return button #XXX additional items: # annotate (add annotation to plot using gcur -- need # to migrate annotation code to this module?) # zoom, etc (other IRAF capital letter equivalents) #XXX def editMenuInit(self): button = self.editMenu # disable Undo item if not undoable buffer = self.getBuffer() if buffer.isUndoable(): self.editMenu.menu.entryconfigure(button.undoNum, state=TKNTR.NORMAL) self.editMenu.menu.entryconfigure(button.redrawOriginalNum, state=TKNTR.NORMAL) else: self.editMenu.menu.entryconfigure(button.undoNum, state=TKNTR.DISABLED) self.editMenu.menu.entryconfigure(button.redrawOriginalNum, state=TKNTR.DISABLED) # disable Redo item if not redoable if buffer.isRedoable(): self.editMenu.menu.entryconfigure(button.redoNum, state=TKNTR.NORMAL) else: self.editMenu.menu.entryconfigure(button.redoNum, state=TKNTR.DISABLED) # disable Delete items if no plots if len(self.history)==1 and self.isPageBlank(): self.editMenu.menu.entryconfigure(button.deleteNum, state=TKNTR.DISABLED) self.editMenu.menu.entryconfigure(button.deleteAllNum, state=TKNTR.DISABLED) else: self.editMenu.menu.entryconfigure(button.deleteNum, state=TKNTR.NORMAL) self.editMenu.menu.entryconfigure(button.deleteAllNum, state=TKNTR.NORMAL) def deletePlot(self): # delete current plot del self.history[self._currentPage] if len(self.history)==0: # that was the last plot # clear all buffers and put them back on the history self.gkibuffer.reset() self.clearPage() self.wcs.set() self.history = [(self.gkibuffer, self.wcs, "", self.getHistory())] n = max(0, min(self._currentPage, len(self.history)-1)) # ensure that redraw happens self._currentPage = -1 self.pageVar.set(n) self.bttnVar.set(n) def deleteAllPlots(self): if tkMessageBox.askokcancel("", "Delete all plots?"): del self.history[:] # clear all buffers and put them back on the history self.gkibuffer.reset() self.clearPage() self.wcs.set() self.history = [(self.gkibuffer, self.wcs, "", self.getHistory())] # ensure that redraw happens self._currentPage = -1 self.pageVar.set(0) self.bttnVar.set(0) def makePageMenu(self, menubar): button = TKNTR.Menubutton(menubar, text='Page') button.pack(side=TKNTR.LEFT, padx=2) button.menu = TKNTR.Menu(button, tearoff=1, postcommand=self.pageMenuInit) num = 1 # tearoff is entry 0 on menu button.nextNum = num num = num+1 button.menu.add_command(label="Next", command=self.nextPage) button.backNum = num num = num+1 button.menu.add_command(label="Back", command=self.backPage) button.firstNum = num num = num+1 button.menu.add_command(label="First", command=self.firstPage) button.lastNum = num num = num+1 button.menu.add_command(label="Last", command=self.lastPage) # need to add separator here because menu.delete always # deletes at least one item button.sepNum = num num = num+1 button.menu.add_separator() button["menu"] = button.menu return button def pageMenuInit(self): button = self.pageMenu menu = button.menu page = self._currentPage # Next if page < len(self.history)-1: menu.entryconfigure(button.nextNum, state=TKNTR.NORMAL) else: menu.entryconfigure(button.nextNum, state=TKNTR.DISABLED) # Back if page>0: menu.entryconfigure(button.backNum, state=TKNTR.NORMAL) else: menu.entryconfigure(button.backNum, state=TKNTR.DISABLED) # First if page>0: menu.entryconfigure(button.firstNum, state=TKNTR.NORMAL) else: menu.entryconfigure(button.firstNum, state=TKNTR.DISABLED) # Last if page < len(self.history)-1: menu.entryconfigure(button.lastNum, state=TKNTR.NORMAL) else: menu.entryconfigure(button.lastNum, state=TKNTR.DISABLED) # Delete everything past the separator menu.delete(str(button.sepNum),'10000') menu.add_separator() # Add radio buttons for pages # Only show limited window around active page halfsize = 10 pmin = self._currentPage-halfsize pmax = self._currentPage+halfsize+1 lhis = len(self.history) if pmin<0: pmax = pmax-pmin pmin = 0 elif pmax>lhis: pmin = pmin-(pmax-lhis) pmax = lhis pmax = min(pmax, lhis) pmin = max(0, pmin) h = self.history for i in range(pmin,pmax): task = h[i][2] if i==pmin and pmin>0: label = "<< %s" % task elif i==pmax-1 and pmax= maxX or y >= maxY: return gwidget.lastX = x gwidget.lastY = y def forceFocus(self, cursorToo=True): # only force focus if window is viewable if not wutil.isViewable(self.top.winfo_id()): return # warp cursor # if no previous position, move to center gw = self.gwidget if gw: if gw.lastX is None or \ (gw.lastX == 0 and gw.lastY == 0): swCurObj = gw.getSWCursor() if swCurObj: gw.lastX = int(swCurObj.lastx*gw.winfo_width()) gw.lastY = int((1.-swCurObj.lasty)*gw.winfo_height()) else: gw.lastX = int(gw.winfo_width()/2.) gw.lastY = int(gw.winfo_height()/2.) if cursorToo: wutil.moveCursorTo(gw.winfo_id(), gw.winfo_rootx(), gw.winfo_rooty(), gw.lastX, gw.lastY) # On non-X, "focus_force()" places focus on the gwidget canvas, but # this may not have the global focus; it may only be the widget seen # when the application itself has focus. We may need to force the # app itself to have focus first, so we do that here too. wutil.forceFocusToNewWindow() gw.focus_force() def getWindowID(self): if self.gwidget: return self.gwidget.winfo_id() # ----------------------------------------------- # GkiKernel methods def clear(self, name=None): """Clear the plot and start a new page""" # don't create new plot if current plot is empty if not self.isPageBlank(): # ignore any pending WCS changes self.wcs.clearPending() self.gkibuffer = self.gkibuffer.split() self.wcs = irafgwcs.IrafGWcs() self.startNewPage() if name is None: if gki.tasknameStack: name = gki.tasknameStack[-1] else: name = "" self.history.append( (self.gkibuffer, self.wcs, name, self.getHistory()) ) self.pageVar.set(len(self.history)-1) self.bttnVar.set(len(self.history)-1) self.StatusLine.write(text=" ") if self._toWriteAtNextClear and self.StatusLine: # Often clear() is called at the start of a task, and we (or # the derived class) may have requested some text be shown # right after the next possible clear(). Show and delete it. self.StatusLine.write(text=self._toWriteAtNextClear) self._toWriteAtNextClear = None # note - this will only be seen for interactive task starts self.flush() elif (self.history[-1][2] == "") and gki.tasknameStack: # plot is empty but so is name -- set name h = self.history[-1] self.history[-1] = h[0:2] + (gki.tasknameStack[-1],) + h[3:] def translate(self, gkiMetacode, redraw=0): if redraw: table = self.redrawFunctionTable else: table = self.functionTable gki.gkiTranslate(gkiMetacode, table) # render new stuff immediately self.incrPlot() # pure virtual, must be overridden def control_openws(self, arg): self._errorMessageCount = 0 mode = arg[0] ta = self.textAttributes ta.setFontSize(self) self.raiseWindow() # redirect stdin & stdout to status line self.stdout = self.StatusLine self.stdin = self.stdout # disable stderr while graphics is active (to supress xgterm gui # messages) self.stderr = gki.FilterStderr() if mode == 5: # clear the display self.clear() elif mode == 4: # append, i.e., do nothing! pass elif mode == 6: # Tee mode (?), ignore for now pass def raiseWindow(self): if self.top.state() != TKNTR.NORMAL: self.top.deiconify() if self._slowraise == 0: # Get start time for tkraise... _stime = time.time() self.top.tkraise() _etime = time.time() # If it takes longer than 1 second to raise the window (ever), # set _slowraise to 1 so that tkraise will never be called again # during this session. if int(_etime - _stime) > 1: self._slowraise = 1 if wutil.GRAPHICS_ALWAYS_ON_TOP: # This code runs for all graphics wins but is only useful for wins # like prow (non-interactive graphics wins). For these, we don't # want the mouse to move too. The interactive graphics windows # will call forceFocus via another route due to the gcur obj # and will move the mouse then, so for them it will get done. self.forceFocus(cursorToo=False) def control_clearws(self, arg): # apparently this control routine is not used? self.clear() def control_reactivatews(self, arg): self._errorMessageCount = 0 self.raiseWindow() if not self.stdout: # redirect stdout if not already self.stdout = self.StatusLine self.stdin = self.stdout if not self.stderr: self.stderr = gki.FilterStderr() def control_deactivatews(self, arg): if self.stdout: self.stdout.close() self.stdout = None self.stdin = None if self.stderr: self.stderr.close() self.stderr = None def control_setwcs(self, arg): self.wcs.set(arg) def gki_setwcs(self, arg): # Ordinarily the control_setwcs opcode sets the WCS, but # when we are loading saved metacode only the gki_setwcs # code remains. (I think that sometimes the gki_setwcs # metacode is absent.) But doing this redundant operation # doesn't cost much. self.wcs.set(arg) def control_getwcs(self, arg): if not self.wcs: self.errorMessage("Error: can't append to a nonexistent plot!") raise IrafError if self.returnData: self.returnData = self.returnData + self.wcs.pack() else: self.returnData = self.wcs.pack() def control_closews(self, arg): gwidget = self.gwidget if gwidget: gwidget.deactivateSWCursor() # turn off software cursor if self.stdout: self.stdout.close() self.stdout = None self.stdin = None if self.stderr: self.stderr.close() self.stderr = None if not wutil.GRAPHICS_ALWAYS_ON_TOP: wutil.focusController.restoreLast() pyraf-2.1.14/lib/pyraf/gkitkplot.py0000644000665500117240000003416013033515761020200 0ustar sontagsts_dev00000000000000""" 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.14/lib/pyraf/graphcap.py0000644000665500117240000001003013033515761017743 0ustar sontagsts_dev00000000000000"""Finds device attributes from the graphcap $Id$ """ from __future__ import division # confidence high import string from stsci.tools import compmixin import filecache def merge(inlines): out = [] outbuff = [] for inline in inlines: tline = inline.strip() if len(tline) > 0 and tline[0] != '#': if tline[-1] == '\\': # continuation outbuff.append(tline[:-1]) else: outbuff.append(tline) out.append(''.join(outbuff)) outbuff = [] return out def getAliases(entry): # return list of aliases (and dump the comment) aend = entry.find(':') if aend<0: raise ValueError("Graphcap entry does not have any colons\n%s" % entry) return entry[:aend].split("|")[:-1] def getAttributes(entry): abeg = entry.find(':') if abeg<0: raise ValueError("Graphcap entry does not have any colons\n%s" % entry) astring = entry[abeg+1:] attr = {} attrlist = astring.split(':') for attrstr in attrlist: if attrstr.strip(): attrname = attrstr[:2] attrval = attrstr[2:] if len(attrstr) <= 2: value = -1 elif attrval[0] == '=': value = attrval[1:] elif attrval[0] == '#': try: value = int(attrval[1:]) except ValueError: try: value = float(attrval[1:]) except ValueError: print "problem reading graphcap" raise elif attrval[0] == '@': # implies false value = None else: # ignore silently, at least as long as IRAF has a bad # entry in its distribution (illegal colons) # print "problem reading graphcap attributes: ", attrstr # print entry pass attr[attrname] = value return attr def getDevices(devlist): devices = {} for devdef in devlist: aliases = getAliases(devdef) attributes = getAttributes(devdef) for alias in aliases: devices[alias] = attributes return devices class GraphCap(filecache.FileCache): """Graphcap class that automatically updates if file changes""" def __init__(self, graphcapPath): filecache.FileCache.__init__(self, graphcapPath) def updateValue(self): """Called on init and if file changes""" lines = open(self.filename,'r').readlines() mergedlines = merge(lines) self.dict = getDevices(mergedlines) def getValue(self): return self.dict def __getitem__(self, key): """Get up-to-date version of dictionary""" thedict = self.get() if not key in thedict: print "Error: device not found in graphcap" raise KeyError return Device(thedict, key) def has_key(self, key): return self._has(key) def __contains__(self, key): return self._has(key) def _has(self, key): thedict = self.get() return key in thedict class Device(compmixin.ComparableMixin): def __init__(self, devices, devname): self.dict = devices self.devname = devname def getAttribute(self, attrName): thedict = self.dict[self.devname] value = None while 1: if attrName in thedict: value = thedict[attrName] break else: if 'tc' in thedict: nextdev = thedict['tc'] thedict = self.dict[nextdev] else: break return value def _compare(self, other, method): if isinstance(other, Device): return method(id(self.dict[self.devname]), id(other.dict[other.devname])) else: return method(id(self), id(other)) def __getitem__(self, key): return self.getAttribute(key) pyraf-2.1.14/lib/pyraf/gwm.py0000644000665500117240000001741313033515761016764 0ustar sontagsts_dev00000000000000""" 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.14/lib/pyraf/ipython_api.py0000644000665500117240000004360413033515761020516 0ustar sontagsts_dev00000000000000#-*- coding: utf-8 -*- """Modified input for PyRAF CL-script execution and pre-processing. Modifies the IPython intepreter to process PyRAF "magic" prior to attempting a more conventional IPython interpretation of a command. Code derived from pyraf.pycmdline.py $Id$ """ #***************************************************************************** # Copyright (C) 2001-2004 Fernando Perez # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. #***************************************************************************** from __future__ import division # confidence high VERY_OLD_IPY = True # this means prior to v0.12 try: from IPython.iplib import InteractiveShell except: VERY_OLD_IPY = False if VERY_OLD_IPY: from IPython import Release as release import IPython.ipapi as ipapi else: from IPython.core import release try: import IPython.core.ipapi as ipapi except: ipapi = None # not available in v1.* __license__ = release.license # set search path to include directory above this script and current directory # ... but do not want the pyraf package directory itself in the path, since # that messes things up by allowing direct imports of pyraf submodules # (bypassing the __init__ mechanism.) import sys from pyraf import iraf, __version__ from pyraf.irafpar import makeIrafPar from stsci.tools.irafglobals import yes, no, INDEF, EOF _locals = globals() # del iraf, __version__, makeIrafPar, yes, no, INDEF, EOF, logout, quit, exit if '-nobanner' not in sys.argv and '--no-banner' not in sys.argv: print "\nPyRAF", __version__, "Copyright (c) 2002 AURA" # Start up command line wrapper keeping definitions in main name space # Keep the command-line object in namespace too for access to history # -------------------------------------------------------------------------- from pyraf.irafcompleter import IrafCompleter class IPythonIrafCompleter(IrafCompleter): import sys if VERY_OLD_IPY: from IPython.iplib import InteractiveShell else: try: # location of "terminal" as of v1.1 from IPython.terminal.interactiveshell import TerminalInteractiveShell as InteractiveShell except: # location of "terminal" prior to v1.1 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell as InteractiveShell def __init__(self, IP): IrafCompleter.__init__(self) self.IP = IP self._completer = None self.install_init_readline_hack() # Override activate to prevent PyRAF from stealing readline hooks as # well as to hook into IPython def activate(self): # print >>self.sys.stderr, "Activating pyraf readline completer" def completer(C, text): # C will be the IPython Completer; not used return self.global_matches(text) # set_custom_completer mutates completer self.IP.set_custom_completer(completer) # ... get the mutant... self._completer = self.IP.Completer.matchers[0] def deactivate(self): # print >>self.sys.stderr, "Deactivating pyraf readline completer" if self._completer in self.IP.Completer.matchers: self.IP.Completer.matchers.remove(self._completer) def install_init_readline_hack(self): """The IPython startup sequence calls IP.init_readline() after the IP has been created and after the pyraf profile has been read. This creates a new Completer and obliterates ours. We hook IP.init_readline here so that IPython doesn't override (or at least re-implements) changes PyRAF has already made. """ if not hasattr(self, "_ipython_init_readline"): self._ipython_init_readline = self.InteractiveShell.init_readline def pyraf_init_readline(IP): # Create function with built-in bindings to self assert self.IP is IP # IPythonShell shouldn't change... self._ipython_init_readline(IP) # Call IPython's original init_readline... make IP.Completer. self.activate() # activate PyRAF completer self.InteractiveShell.init_readline = pyraf_init_readline # Override class method def uninstall_init_readline_hack(self): self.InteractiveShell.init_readline = self._ipython_init_readline # restore class method del self._ipython_init_readline # --------------------------------------------------------------------------- class IPython_PyRAF_Integrator(object): """This class supports the integration of these features with IPython: 1. PyRAF readline completion 2. PyRAF CL translation 3. PyRAF exception traceback simplification """ import string if VERY_OLD_IPY: from IPython.iplib import InteractiveShell else: try: # location of "terminal" as of v1.1 from IPython.terminal.interactiveshell import TerminalInteractiveShell as InteractiveShell except: # location of "terminal" prior to v1.1 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell as InteractiveShell def __init__(self, clemulate=1, cmddict={}, cmdchars=("a-zA-Z_.","0-9")): import re, sys, os self.reword = re.compile('[a-z]*') self._cl_emulation = clemulate self.cmddict = cmddict self.recmd = re.compile( "[ \t]*(?P" + "[" + cmdchars[0] + "][" + cmdchars[0] + cmdchars[1] + "]*" + ")[ \t]*") self.locals = _locals import pyraf self.pyrafDir = os.path.dirname(pyraf.__file__) import IPython self.ipythonDir = os.path.dirname(IPython.__file__) self.traceback_mode = "Context" if VERY_OLD_IPY: self._ipython_api = ipapi.get() self._ipython_magic = self._ipython_api.IP.lsmagic() # skip % self._ipython_api.expose_magic( "set_pyraf_magic", self.set_pyraf_magic) self._ipython_api.expose_magic( "use_pyraf_traceback", self.use_pyraf_traceback) self._ipython_api.expose_magic( "use_pyraf_cl_emulation", self.use_pyraf_cl_emulation) self._ipython_api.expose_magic( "use_pyraf_readline_completer", self.use_pyraf_completer) self._pyraf_completer = IPythonIrafCompleter(self._ipython_api.IP) # Replace IPython prefilter with self.prefilter bound method. def foo_filter(*args): return self.prefilter(*args) self.InteractiveShell.prefilter = foo_filter else: self._ipython_api = pyraf._ipyshell # this is pretty far into IPython, i.e. very breakable # lsmagic() returns a dict of 2 dicts: 'cell', and 'line' if hasattr(self._ipython_api, 'magics_manager'): self._ipython_magic = self._ipython_api.magics_manager.lsmagic()['line'].keys() else: print('Please upgrade your version of IPython.') pfmgr = self._ipython_api.prefilter_manager self.priority = 0 # transformer needs this, low val = done first self.enabled = True # a transformer needs this pfmgr.register_transformer(self) def isLocal(self, value): """Returns true if value is local variable""" ff = value.split('.') return ff[0] in self.locals def transform(self, line, continue_prompt): """ This pre-processes input to do PyRAF substitutions before passing it on to IPython. Has this signature to match the needed API for the new (0.12) IPython's PrefilterTransformer instances. This class is to look like such an instance. """ # Check continue_prompt - we are not handling it currently if continue_prompt: return line # PyRAF assumes ASCII but IPython deals in unicode. We could handle # that also by simply rewriting some of this class to use unicode # string literals. For now, convert and check - if not OK (not # convertible to ASCII), simply move on (remove this assumption # after we no longer support Python2) try: asciiline = str(line) except: return line # Handle any weird special cases here. Most all transformations # should occur through the normal route (e.g. sent here, then # translated below), but some items never get the chance in ipython # to be prefiltered... if asciiline == 'get_ipython().show_usage()': # Hey! IPython translated '?' before we got a crack at it... asciiline = '?' # put it back! (only for single '?' by itself) # Now run it through our normal prefilter function return self.cmd(asciiline) def cmd(self, line): """Check for and execute commands from dictionary.""" mo = self.recmd.match(line) if mo is None: i = 0 cmd = '' method_name = None else: cmd = mo.group('cmd') i = mo.end() # look up command in dictionary method_name = self.cmddict.get(cmd) if method_name is None: # no method, but have a look at it anyway return self.default(cmd,line,i) else: # if in cmddict, there must be a method by this name f = getattr(self, method_name) return f(line, i) def _default(self, cmd, line, i): """Check for IRAF task calls and use CL emulation mode if needed cmd = alpha-numeric string from beginning of line line = full line (including cmd, preceding blanks, etc.) i = index in line of first non-blank character following cmd """ import os, keyword if len(cmd)==0: if line[i:i+1] == '!': # '!' is shell escape # handle it here only if cl emulation is turned off if not self._cl_emulation: iraf.clOscmd(line[i+1:]) return '' elif line[i:i+1] != '?': # leading '?' will be handled by CL code -- else this is Python return line elif self._cl_emulation == 0: # if CL emulation is turned off then just return return line elif keyword.iskeyword(cmd) or \ (cmd in os.__builtins__ and cmd not in ['type', 'dir', 'help', 'set']): # don't mess with Python keywords or built-in functions # except allow 'type', 'dir, 'help' to be used in simple syntax return line elif line[i:i+1] != "" and line[i] in '=,[': # don't even try if it doesn't look like a procedure call return line elif cmd in self._ipython_magic and cmd not in ['cd']: return line elif not hasattr(iraf,cmd): # not an IRAF command #XXX Eventually want to improve error message for #XXX case where user intended to use IRAF syntax but #XXX forgot to load package return line elif self.isLocal(cmd): # cmd is both a local variable and an IRAF task or procedure name # figure out whether IRAF or CL syntax is intended from syntax if line[i:i+1] == "" or line[i] == "(": return line if line[i] not in self.string.digits and \ line[i] not in self.string.ascii_letters and \ line[i] not in "<>|": # this does not look like an IRAF command return line # check for some Python operator keywords mm = self.reword.match(line[i:]) if mm.group() in ["is","in","and","or","not"]: return line elif line[i:i+1] == '(': if cmd in ['type', 'dir', 'set']: # assume a standalone call of Python type, dir functions # rather than IRAF task #XXX Use IRAF help function in every case (may want to # change this eventually, when Python built-in help # gets a bit better.) return line else: # Not a local function, so user presumably intends to # call IRAF task. Force Python mode but add the 'iraf.' # string to the task name for convenience. #XXX this find() may be improved with latest Python readline features j = line.find(cmd) return line[:j] + 'iraf.' + line[j:] elif not callable(getattr(iraf,cmd)): # variable from iraf module is not callable task (e.g., # yes, no, INDEF, etc.) -- add 'iraf.' so it can be used # as a variable and execute as Python j = line.find(cmd) return line[:j] + 'iraf.' + line[j:] code = iraf.clLineToPython(line) statements = code.split("\n") return "; ".join([ x for x in statements if x ])+"\n" def default(self, cmd, line, i): # print "input line:",repr(cmd),"line:",line,"i:",i code = self._default(cmd, line, i) if code is None: code = "" else: code = code.rstrip() # print "pyraf code:", repr(code) return code def showtraceback(self, IP, type, value, tb): """Display the exception that just occurred. We remove the first stack item because it is our own code. Strip out references to modules within pyraf unless reprint or debug is set. """ import linecache, traceback, sys, os import IPython.ultraTB # get the color scheme from the user configuration file and pass # it to the trace formatter csm = 'Linux' # default if ipapi: ip = ipapi.get() # this works in vers prior to 1.* csm = ip.options['colors'] linecache.checkcache() tblist = traceback.extract_tb(tb) tbskip = 0 for tb1 in tblist: path, filename = os.path.split(tb1[0]) path = os.path.normpath(os.path.join(os.getcwd(), path)) if path[:len(self.pyrafDir)] == self.pyrafDir or \ path[:len(self.ipythonDir)] == self.ipythonDir or \ filename == "": tbskip += 1 color_tb = IPython.ultraTB.AutoFormattedTB( mode=self.traceback_mode, tb_offset=tbskip, color_scheme=csm) color_tb(type, value, tb) def prefilter(self, IP, line, continuation): """prefilter pre-processes input to do PyRAF substitutions before passing it on to IPython. NOTE: this is ONLY used for VERY_OLD_IPY, since we use the transform hooks for the later versions. """ line = self.cmd(str(line)) # use type str here, not unicode return self.InteractiveShell._prefilter(IP, line, continuation) # The following are IPython "magic" functions when used as bound methods. def _evaluate_flag(self, flag, usage): try: if flag in [None,"", "on","ON", "On", "True","TRUE","true"]: return True elif flag in ["off","OFF","Off","False","FALSE","false"]: return False else: return int(flag) except: import sys print >>sys.stderr, "usage:", usage, "[on | off]" raise def _get_IP(self, IP): if IP is None: return self._ipython_api.IP else: return IP def _debug(self, *args): import sys for a in args: print >>sys.stderr, a, print >>sys.stderr def set_pyraf_magic(self, IP, line): """Setting flag="1" Enables PyRAF to intepret a magic identifier before IPython. """ magic, flag = line.split() if self._evaluate_flag(flag, "set_pyraf_magic "): self._debug("PyRAF magic for", magic,"on") while magic in self._ipython_magic: # should only be one self._ipython_magic.remove(magic) else: self._debug("PyRAF magic for", magic, "off") if magic not in self._ipython_magic: self._ipython_magic.append(magic) def use_pyraf_traceback(self, IP=None, flag=None, feedback=True): IP = self._get_IP(IP) if self._evaluate_flag(flag, "use_pyraf_traceback"): if feedback: self._debug("PyRAF traceback display: on") IP.set_custom_exc((Exception,), self.showtraceback) else: if feedback: self._debug("PyRAF traceback display: off") IP.custom_exceptions = ((), None) def use_pyraf_cl_emulation(self, IP=None, flag=None, feedback=True): """Turns PyRAF CL emulation on (1) or off (0)""" self._cl_emulation = self._evaluate_flag(flag, "use_pyraf_cl_emulation") if self._cl_emulation: if feedback: self._debug("PyRAF CL emulation on") else: if feedback: self._debug("PyRAF CL emulation off") def use_pyraf_completer(self, IP=None, flag=None, feedback=True): if self._evaluate_flag(flag, "use_pyraf_readline_completer"): if feedback: self._debug("PyRAF readline completion on") self._pyraf_completer.activate() else: if feedback: self._debug("PyRAF readline completion off") self._pyraf_completer.deactivate() fb = "-nobanner" not in sys.argv __PyRAF = IPython_PyRAF_Integrator() # __PyRAF.use_pyraf_completer(feedback=fb) Can't do this yet...but it's hooked. # __PyRAF.use_pyraf_cl_emulation(feedback=fb) if VERY_OLD_IPY: __PyRAF.use_pyraf_traceback(feedback=fb) else: if '-nobanner' not in sys.argv and '--no-banner' not in sys.argv: print "PyRAF traceback not enabled" del fb del IPythonIrafCompleter, IPython_PyRAF_Integrator, IrafCompleter pyraf-2.1.14/lib/pyraf/iraf.py0000644000665500117240000000067713033515761017117 0ustar sontagsts_dev00000000000000"""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.14/lib/pyraf/irafcompleter.py0000644000665500117240000003241713033515761021027 0ustar sontagsts_dev00000000000000"""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.14/lib/pyraf/irafdisplay.py0000644000665500117240000002531213033515761020476 0ustar sontagsts_dev00000000000000"""irafdisplay.py: Interact with IRAF-compatible image display Modeled after the NOAO Client Display Library (CDL) Public functions: readCursor(sample=0) Read image cursor position open(imtdev=None) Open a connection to the display server. This is called automatically by readCursor if the display has not already been opened, so it is not generally necessary for users to call it. See the open doc string for info on the imtdev argument, which allows various forms of socket and network connections. close() Close the active display server. Called automatically on exit. Various classes are defined for the different connections (ImageDisplay, ImageDisplayProxy, UnixImageDisplay, InetImageDisplay, FifoImageDisplay). They should generally be created using the _open factory function. This could be used to maintain references to multiple display servers. Ultimately more functionality may be added to make this a complete replacement for CDL. $Id$ """ from __future__ import division # confidence high import os, numpy, socket, sys from stsci.tools.for2to3 import PY3K, bytes_write, ndarr2bytes from stsci.tools import irafutils try: import fcntl except: if 0==sys.platform.find('win'): # not on win*, but IS on darwin & cygwin fcntl = None else: raise # FCNTL is deprecated in Python 2.2 if hasattr(fcntl,'F_SETFL') or fcntl==None: FCNTL = fcntl else: import FCNTL _default_imtdev = ("unix:/tmp/.IMT%d", "fifo:/dev/imt1i:/dev/imt1o") def _open(imtdev=None): """Open connection to the image display server This is a factory function that returns an instance of the ImageDisplay class for the specified imtdev. The default connection if no imtdev is specified is given in the environment variable IMTDEV (if defined) or is "unix:/tmp/.IMT%d". Failing that, a connection is attempted on the /dev/imt1[io] named fifo pipes. The syntax for the imtdev argument is :
where is one of "inet" (internet tcp/ip socket), "unix" (unix domain socket) or "fifo" (named pipe). The form of the address depends upon the domain, as illustrated in the examples below. inet:5137 Server connection to port 5137 on the local host. For a client, a connection to the given port on the local host. inet:5137:foo.bar.edu Client connection to port 5137 on internet host foo.bar.edu. The dotted form of address may also be used. unix:/tmp/.IMT212 Unix domain socket with the given pathname IPC method, local host only. fifo:/dev/imt1i:/dev/imt1o FIFO or named pipe with the given pathname. IPC method, local host only. Two pathnames are required, one for input and one for output, since FIFOs are not bidirectional. For a client the first fifo listed will be the client's input fifo; for a server the first fifo will be the server's output fifo. This allows the same address to be used for both the client and the server, as for the other domains. The address field may contain one or more "%d" fields. If present, the user's UID will be substituted (e.g. "unix:/tmp/.IMT%d"). """ if not imtdev: # try defaults defaults = list(_default_imtdev) if 'IMTDEV' in os.environ: defaults.insert(0, os.environ['IMTDEV']) for imtdev in defaults: try: return _open(imtdev) except IOError, error: pass raise IOError("Cannot open image display") # substitute user id in name (multiple times) if necessary nd = len(imtdev.split("%d")) dev = imtdev % ((os.getuid(),)*(nd-1)) fields = dev.split(":") domain = fields[0] if domain == "unix" and len(fields) == 2: return UnixImageDisplay(fields[1]) elif domain == "fifo" and len(fields) == 3: return FifoImageDisplay(fields[1],fields[2]) elif domain == "inet" and (2 <= len(fields) <= 3): try: port = int(fields[1]) if len(fields) == 3: hostname = fields[2] else: hostname = None return InetImageDisplay(port, hostname) except ValueError: pass raise ValueError("Illegal image device specification `%s'" % imtdev) class ImageDisplay: """Interface to IRAF-compatible image display""" # constants for cursor read _IIS_READ = 0100000 _IMC_SAMPLE = 0040000 _IMCURSOR = 020 _SZ_IMCURVAL = 160 def __init__(self): # Flag indicating that readCursor request is active. # This is used to handle interruption of readCursor before # read is complete. Without this kluge, ^C interrupts # leave image display in a bad state. self._inCursorMode = 0 def readCursor(self,sample=0): """Read image cursor value for this image display Return immediately if sample is true, or wait for keystroke if sample is false (default). Returns a string with x, y, frame, and key. """ if not self._inCursorMode: opcode = self._IIS_READ if sample: opcode |= self._IMC_SAMPLE self._writeHeader(opcode, self._IMCURSOR, 0, 0, 0, 0, 0) self._inCursorMode = 1 s = self._read(self._SZ_IMCURVAL) self._inCursorMode = 0 # only part up to newline is real data return s.split("\n")[0] def _writeHeader(self,tid,subunit,thingct,x,y,z,t): """Write request to image display""" a = numpy.array([tid,thingct,subunit,0,x,y,z,t], numpy.int16) # Compute the checksum sum = numpy.add.reduce(a) sum = 0xffff - (sum & 0xffff) a[3] = sum self._write(ndarr2bytes(a)) def close(self, os_close=os.close): """Close image display connection""" try: os_close(self._fdin) except (OSError, AttributeError): pass try: os_close(self._fdout) except (OSError, AttributeError): pass def _read(self, n): """Read n bytes from image display and return as string Raises IOError on failure. If a tkinter widget exists, runs a Tk mainloop while waiting for data so that the Tk widgets remain responsive. """ try: return irafutils.tkread(self._fdin, n) except (EOFError, IOError): raise IOError("Error reading from image display") def _write(self, s): """Write string s to image display Raises IOError on failure """ try: n = len(s) while n>0: nwritten = bytes_write(self._fdout, s[-n:]) n -= nwritten if nwritten <= 0: raise IOError("Error writing to image display") except OSError: raise IOError("Error writing to image display") class FifoImageDisplay(ImageDisplay): """FIFO version of image display""" def __init__(self, infile, outfile): ImageDisplay.__init__(self) try: self._fdin = os.open(infile, os.O_RDONLY | os.O_NDELAY) fcntl.fcntl(self._fdin, FCNTL.F_SETFL, os.O_RDONLY) self._fdout = os.open(outfile, os.O_WRONLY | os.O_NDELAY) fcntl.fcntl(self._fdout, FCNTL.F_SETFL, os.O_WRONLY) except OSError, error: raise IOError("Cannot open image display (%s)" % (error,)) except AttributeError: raise RuntimeError("Image fcntl is not supported on this platform") def __del__(self): self.close() class UnixImageDisplay(ImageDisplay): """Unix socket version of image display""" def __init__(self, filename, family=None, type=socket.SOCK_STREAM): ImageDisplay.__init__(self) try: if family==None: # set in func, not in decl so it works on win family = socket.AF_UNIX self._socket = socket.socket(family, type) self._socket.connect(filename) self._fdin = self._fdout = self._socket.fileno() except socket.error, error: raise IOError("Cannot open image display") def close(self): """Close image display connection""" self._socket.close() class InetImageDisplay(UnixImageDisplay): """INET socket version of image display""" def __init__(self, port, hostname=None): hostname = hostname or "localhost" UnixImageDisplay.__init__(self, (hostname, port), family=socket.AF_INET) class ImageDisplayProxy(ImageDisplay): """Interface to IRAF-compatible image display This is a proxy to the actual display that allows retries on failures and can switch between display connections. """ def __init__(self, imtdev=None): # if imtdev is specified, it becomes the default for the # life of this instance self._display = None self.imtdev = imtdev if imtdev: self.open() def open(self, imtdev=None): """Open image display connection, closing any active connection""" self.close() self._display = _open(imtdev or self.imtdev) def close(self): """Close active image display connection""" if self._display: self._display.close() self._display = None def readCursor(self,sample=0): """Read image cursor value for the active image display Return immediately if sample is true, or wait for keystroke if sample is false (default). Returns a string with x, y, frame, and key. Opens image display if necessary. """ if not self._display: self.open() try: value = self._display.readCursor(sample) # Null value indicates display was probably closed if value: return value except IOError, error: pass # This error can occur if image display was closed. # If a new display has been started then closing and # reopening the connection will fix it. If that # fails then give up. self.open() return self._display.readCursor(sample) _display = ImageDisplayProxy() # create aliases for _display methods readCursor = _display.readCursor open = _display.open close = _display.close pyraf-2.1.14/lib/pyraf/irafecl.py0000644000665500117240000003663313033515761017604 0ustar sontagsts_dev00000000000000"""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.14/lib/pyraf/irafexecute.py0000644000665500117240000011642613033515761020502 0ustar sontagsts_dev00000000000000"""irafexecute.py: Functions to execute IRAF connected subprocesses $Id$ """ from __future__ import division # confidence high import os, re, signal, string, struct, sys, time, types, numpy, cStringIO from stsci.tools import irafutils from stsci.tools.for2to3 import tobytes, ndarr2bytes, ndarr2str from stsci.tools.irafglobals import IrafError, IrafTask, Verbose import subproc, filecache, wutil import gki, irafukey, irafgwcs # use this form since the iraf import is circular import pyraf.iraf # test_probe is a flag that a testing system can use to tell pyraf # (when used as a library, e.g. in stsci_regtest) to print various # diagnostic information that we think might be useful in test logs. # This is different from verbose, because it is more selective. # # There is no interface to activate this feature. Use: # import pyraf.irafexecute # pyraf.irafexecute.test_probe = True test_probe = False #stdgraph = None IPC_PREFIX = ndarr2bytes(numpy.array([01120],numpy.int16)) # weirdo protocol to get output from task back to subprocess # definitions from cl/task.h and lib/clio.h IPCOUT = "IPC$IPCIO-OUT" IPCDONEMSG = "# IPC$IPCIO-FINISHED\n" # set flag indicating big endian or little endian byte order # sys.byteorder was added in Python 2.0 isBigEndian = sys.byteorder == "big" # Create an instance of the stdimage kernel stdimagekernel = gki.GkiController() class IrafProcessError(Exception): def __init__(self, msg, errno=-1, errmsg="", errtask=""): Exception.__init__(self, msg) self.errno = errno self.errmsg = errmsg self.errtask = errtask def _getExecutable(arg): """Get executable pathname. Arg may be a string with the path, an IrafProcess, an IrafTask, or a string with the name of an IrafTask. """ if isinstance(arg, IrafProcess): return arg.executable elif isinstance(arg, IrafTask): return arg.getFullpath() elif isinstance(arg, (str, unicode)): if os.path.exists(arg): return arg task = pyraf.iraf.getTask(arg, found=1) if task is not None: return task.getFullpath() raise IrafProcessError("Cannot find task or executable %s" % arg) class _ProcessProxy(filecache.FileCache): """Proxy for a single process that restarts it if needed Restart is triggered by change of executable on disk. """ def __init__(self, process): self.process = process self.envdict = {} # pass executable filename to FileCache filecache.FileCache.__init__(self, process.executable) def newValue(self): # no action required at proxy creation pass def updateValue(self): """Called when executable changes to start a new version""" self.process.terminate() # seems to be necessary to delete this process before starting # next one to avoid some weird problems... del self.process self.process = IrafProcess(self.filename) self.process.initialize(self.envdict) def getProcess(self, envdict): """Get the process; create & initialize using envdict if needed""" self.envdict = envdict return self.get() def getValue(self): return self.process class _ProcessCache: """Cache of active processes indexed by executable path""" DFT_LIMIT = 8 def __init__(self, limit=DFT_LIMIT): self._data = {} # dictionary with active process proxies self._pcount = 0 # total number of processes started self._plimit = limit # number of active processes allowed self._locked = {} # processes locked into cache def error(self, msg, level=0): """Write an error message if Verbose is set""" if Verbose>level: sys.stderr.write(msg) sys.stderr.flush() def get(self, task, envdict): """Get process for given task. Create a new one if needed.""" executable = _getExecutable(task) if executable in self._data: # use existing process rank, proxy = self._data[executable] process = proxy.getProcess(envdict) if not process.running: if process.isAlive(): return process # Hmm, looks like there is something wrong with this process # Kill it and start a new one #XXX Eventually can make this a level 0 message #XXX Leave as level -1 for now so we see if bug is gone self.error("Warning: process %s is bad, restarting it\n" % (executable,), level=-1) self.kill(executable, verbose=0) # Whoops, process is already active... # This could happen if one task in an executable tries to # execute another task in the same executable. Don't know # if IRAF allows this, but we can handle it by just creating # a new process running the same executable. # create and initialize a new process # this will be added to cache after successful task completion process = IrafProcess(executable) process.initialize(envdict) return process def add(self, process): """Add process to cache or update its rank if already there""" self._pcount = self._pcount+1 executable = process.executable if executable in self._data: # don't replace current cached process rank, proxy = self._data[executable] oldprocess = proxy.process if oldprocess != process: # argument is a duplicate process, terminate this copy process.terminate() elif self._plimit <= len(self._locked): # cache is null or all processes are locked process.terminate() return else: # new process -- make a proxy proxy = _ProcessProxy(process) if len(self._data) >= self._plimit: # delete the oldest entry to make room self._deleteOldest() self._data[executable] = (self._pcount, proxy) def _deleteOldest(self): """Delete oldest unlocked process from the cache If all processes are locked, delete oldest locked process. """ # each entry contains rank (to sort and find oldest) and process values = self._data.values() values.sort() if len(self._locked) < len(self._data): # find and delete oldest unlocked process for rank, proxy in values: process = proxy.process executable = process.executable if not (executable in self._locked or process.running): # terminate it self.terminate(executable) return # no unlocked processes or all unlocked are running # delete oldest locked process rank, proxy = values[0] executable = proxy.process.executable self.terminate(executable) def setenv(self, msg): """Update process value of environment variable by sending msg""" for rank, proxy in self._data.values(): # just save messages in a list, they all get sent at # once when a task is run proxy.process.appendEnv(msg) def setSize(self, limit): """Set number of processes allowed in cache""" self._plimit = limit if self._plimit <= 0: self._locked = {} self.flush() else: while len(self._data) > self._plimit: self._deleteOldest() def resetSize(self): """Set the number of processes allowed in cache back to the default""" self.setSize(_ProcessCache.DFT_LIMIT) def lock(self, *args): """Lock the specified tasks into the cache Takes task names (strings) as arguments. """ # don't bother if cache is disabled or full if self._plimit <= len(self._locked): return for taskname in args: task = pyraf.iraf.getTask(taskname, found=1) if task is None: print "No such task `%s'" % taskname elif task.__class__.__name__ == "IrafTask": # cache only executable tasks (not CL tasks, etc.) executable = task.getFullpath() process = self.get(task, pyraf.iraf.getVarDict()) self.add(process) if executable in self._data: self._locked[executable] = 1 else: self.error("Cannot cache %s\n" % taskname) def delget(self, process): """Get process object and delete it from cache process can be an IrafProcess, task name, IrafTask, or executable filename. """ executable = _getExecutable(process) if executable in self._data: rank, proxy = self._data[executable] if not isinstance(process, IrafProcess): process = proxy.process # don't delete from cache if this is a duplicate process if process == proxy.process: del self._data[executable] if executable in self._locked: del self._locked[executable] # could restart the process if locked? return process def kill(self, process, verbose=1): """Kill process and delete it from cache process can be an IrafProcess, task name, IrafTask, or executable filename. """ process = self.delget(process) if isinstance(process, IrafProcess): process.kill(verbose) def terminate(self, process): """Terminate process and delete it from cache""" # This is gentler than kill(), which should be used only # when there are process errors. process = self.delget(process) if isinstance(process, IrafProcess): process.terminate() def flush(self, *args): """Flush given processes (all non-locked if no args given) Takes task names (strings) as arguments. """ if args: for taskname in args: task = pyraf.iraf.getTask(taskname, found=1) if task is not None: self.terminate(task) else: for rank, proxy in self._data.values(): executable = proxy.process.executable if not executable in self._locked: self.terminate(executable) def list(self): """List processes sorted from newest to oldest with locked flag""" values = self._data.values() values.sort() values.reverse() n = 0 for rank, proxy in values: n = n+1 executable = proxy.process.executable if executable in self._locked: print "%2d: L %s" % (n, executable) else: print "%2d: %s" % (n, executable) def __del__(self): self._locked = {} self.flush() processCache = _ProcessCache() def IrafExecute(task, envdict, stdin=None, stdout=None, stderr=None, stdgraph=None): """Execute IRAF task (defined by the IrafTask object task) using the provided envionmental variables.""" global processCache try: # Start 'er up irafprocess = processCache.get(task, envdict) except (IrafError, subproc.SubprocessError, IrafProcessError), value: raise raise IrafProcessError("Cannot start IRAF executable\n%s" % value) # Run it try: taskname = task.getName() if stdgraph: # Redirect graphics prevkernel = gki.kernel gki.kernel = gki.GkiRedirection(stdgraph) gki.kernel.wcs = prevkernel.wcs else: # do graphics task initialization gki.kernel.taskStart(taskname) focusMark = wutil.focusController.getCurrentMark() gki.kernel.pushStdio(None,None,None) try: irafprocess.run(task, pstdin=stdin, pstdout=stdout, pstderr=stderr) finally: if stdgraph: # undo graphics redirection gki.kernel = prevkernel else: # for interactive graphics restore previous stdio wutil.focusController.restoreToMark(focusMark) gki.kernel.popStdio() # do any cleanup needed on task completion if not stdgraph: gki.kernel.taskDone(taskname) except KeyboardInterrupt: # On keyboard interrupt (^C), kill the subprocess processCache.kill(irafprocess) raise except (IrafError, IrafProcessError), exc: # on error, kill the subprocess, then re-raise the original exception try: processCache.kill(irafprocess) except Exception, exc2: # append new exception text to previous one (right thing to do?) exc.args = exc.args + exc2.args raise exc else: # add to the process cache on successful exit processCache.add(irafprocess) return # patterns for matching messages from process # '=param' and '_curpack' have to be treated specially because # they write to the task rather than to stdout # 'param=value' is special because it allows errors _p_par_get = r'\s*=\s*(?P[a-zA-Z_$][\w.]*(?:\[\d+\])?)\s*\n' _p_par_set = r'(?P[a-zA-Z_][\w.]*(?:\[\d+\])?)\s*=\s*(?P.*)\n' _re_msg = re.compile( r'(?P' + _p_par_get + ')|' + r'(?P' + _p_par_set + ')' ) _p_curpack = r'_curpack(?:\s.*|)\n' _p_stty = r'stty.*\n' _p_sysescape = r'!(?P.*)\n' _re_clcmd = re.compile( r'(?P' + _p_curpack + ')|' + r'(?P' + _p_stty + ')|' + r'(?P' + _p_sysescape + ')' ) class IrafProcess: """IRAF process class""" def __init__(self, executable): """Start IRAF task executable.""" if test_probe : sys.stdout.write("Starting IRAF process for %s\n"%executable) self.executable = executable self.process = subproc.Subprocess(executable+' -c') self.running = 0 # flag indicating whether process is active self.task = None self.stdin = None self.stdout = None self.stderr = None self.default_stdin = None self.default_stdout = None self.default_stderr = None self.stdinIsatty = 0 self.stdoutIsatty = 0 self.envVarList = [] self.par_set_msg_buf = '' def initialize(self, envdict): """Initialization: Copy environment variables to process""" outenvstr = [] for key, value in envdict.items(): outenvstr.append("set %s=%s\n" % (key, str(value))) outenvstr.append("chdir %s\n" % os.getcwd()) if outenvstr: self.writeString("".join(outenvstr)) self.envVarList = [] # end set up mode self.writeString('_go_\n') def appendEnv(self, msg): """Append environment variable set command to list""" # Changes are saved and sent to task before starting # it next time. Note that environment variable changes # are not immediately sent to a running task (because it is # not expecting them.) self.envVarList.append(msg) def run(self, task, pstdin=None, pstdout=None, pstderr=None): """Run the IRAF logical task (which must be in this executable) The IrafTask object must have these methods: getName(): return the name of the task getParam(param): get parameter value setParam(param,value): set parameter value getParObject(param): get parameter object """ if test_probe : sys.stdout.write( "Running IRAF task %s from %s\n"% (task, self.executable) ) self.task = task # set IO streams stdin = pstdin or sys.stdin stdout = pstdout or sys.stdout stderr = pstderr or sys.stderr self.stdin = stdin self.stdout = stdout self.stderr = stderr self.default_stdin = stdin self.default_stdout = stdout self.default_stderr = stderr # stdinIsatty flag is used in xfer to decide whether to # read inputs in blocks or not. As long as input comes # from __stdin__, consider it equivalent to a tty. self.stdinIsatty = (hasattr(stdin,'isatty') and stdin.isatty()) or \ self.stdin == sys.__stdin__ self.stdoutIsatty = hasattr(stdout,'isatty') and stdout.isatty() # stdinIsraw flag is used in xfer to decide whether to # read inputs as RAW input or not. self.stdinIsraw = False # redir_info tells task that IO has been redirected redir_info = '' if pstdin and pstdin != sys.__stdin__: redir_info = '<' if (pstdout and pstdout != sys.__stdout__) or \ (pstderr and pstderr != sys.__stderr__): redir_info = redir_info+'>' # update IRAF environment variables if necessary if self.envVarList: self.writeString(''.join(self.envVarList)) self.envVarList = [] # if stdout is a terminal, set the lines & columns sizes # this ensures that they are up-to-date at the start of the task # (which is better than the CL does) if self.stdoutIsatty: nlines, ncols = wutil.getTermWindowSize() self.writeString('set ttynlines=%d\nset ttyncols=%d\n' % (nlines, ncols)) taskname = self.task.getName() # remove leading underscore, which is just a convention for CL if taskname[:1]=='_': taskname = taskname[1:] self.writeString(taskname+redir_info+'\n') self.running = 1 try: # begin slave mode self.slave() finally: self.running = 0 def isAlive(self): """Returns true if process appears to be OK""" return self.process.active() def terminate(self): """Terminate the IRAF process (when process in normal end state)""" # Standard IRAF task termination (assuming we already have the # task's attention for input): # Send bye message to task # Wait briefly for EOF, which signals task is done # Kill it anyway if it is still hanging around if not self.process.pid: return # no need, process gone try: self.writeString("bye\n") if self.process.wait(0.5): return except (IrafProcessError, subproc.SubprocessError), e: pass # No more Mr. Nice Guy try: self.process.die() except subproc.SubprocessError, e: if Verbose>0: # too bad, if we can't kill it assume it is already dead self.stderr.write("Warning: cannot terminate process %s\n" % (e,)) self.stderr.flush() def kill(self, verbose=1): """Kill the IRAF process (more drastic than terminate)""" # Try stopping process in IRAF-approved way first; if that fails # blow it away. Copied with minor mods from subproc.py. if not self.process.pid: return # no need, process gone self.stdout.flush() self.stderr.flush() import pyrafglobals if verbose and not pyrafglobals._use_ecl: sys.stderr.write("Killing IRAF task `%s'\n" % self.task.getName()) sys.stderr.flush() if self.process.cont(): # get the task's attention for input try: os.kill(self.process.pid, signal.SIGTERM) except os.error: pass self.terminate() def writeString(self, s): """Convert ascii string to IRAF form and write to IRAF process""" self.write(Asc2IrafString(s)) def readString(self): """Read IRAF string from process and convert to ascii string""" return Iraf2AscString(self.read()) def write(self, data): """write binary data to IRAF process in blocks of <= 4096 bytes""" i = 0 block = 4096 try: while i 0: # enter this section if we got a par_set, OR if we are in the # middle of a par_set coming in multiple msgs... msg_last_line = msg.strip().split('\n')[-1] # either way, first line of msg is a par_set, since our re matched, # but check the LAST line to see if this is the end if msg_last_line == 'bye' or msg_last_line.startswith('='): # we have the whole msg now (or maybe did in 1st shot) # L.log('FULL matched (ps):\n'+('='*60+'\n')+msg+'\n'+('='*60)) if len(self.par_set_msg_buf) > 0: # is final part of a msg that came in parts self.par_set_msg_buf += msg mcmd = re_match(self.par_set_msg_buf) par_set(mcmd) self.par_set_msg_buf = '' # flag to not wait for more self.msg = 'bye\n' else: # is a normal par_set that came all in one shot par_set(mcmd) else: # We only have a partial message here, so don't # do any par_set-ing until we have the whole msg # L.log('PARTIAL matched (ps):\n'+('='*60+'\n')+msg+'\n'+('='*60)) self.par_set_msg_buf += msg # empty self.msg to trigger us to read more self.msg = '' elif mcmd and mcmd.group('par_get'): # L.log('matched (pg):\n'+('='*60+'\n')+msg+'\n'+('='*60)) par_get(mcmd) elif mcmd is None: # L.log('NO match!:\n'+('='*60+'\n')+msg+'\n'+('='*60)) # Could be any legal CL command. executeClCommand() else: # should never get here # L.log("Program bug: uninterpreted message: " + msg) raise RuntimeError("Program bug: uninterpreted message `%s'" % (msg,)) def _scanErrno(self, msg): sp = "\s*" quote = "\"" m = re.search( "(ERROR|error)" + sp + "\(" + sp + "(\d+)" + sp + "," + sp + quote + "([^\"]*)" + quote + sp + "\)" + sp, msg) if m: try: errno = int(m.group(2)) except: errno = -9999999 text = m.group(3) else: errno, text = -9999998, msg return errno, text def setStdio(self): """Set self.stdin/stdout based on current state If in graphics mode, I/O is done through status line. Else I/O is done through normal streams. """ self.stdout = gki.kernel.getStdout(default=self.default_stdout) self.stderr = gki.kernel.getStderr(default=self.default_stderr) self.stdin = gki.kernel.getStdin(default=self.default_stdin) def par_get(self, mcmd): # parameter get request # list parameters can generate EOF exception paramname = mcmd.group('gname') # interactive parameter prompts may be redirected to the graphics # status line, but do not get redirected to a file c_stdin = sys.stdin c_stdout = sys.stdout c_stderr = sys.stderr # # These lines reset stdin/stdout/stderr to the graphics # window. sys.stdin = gki.kernel.getStdin(default=sys.__stdin__) sys.stdout = gki.kernel.getStdout(default=sys.__stdout__) sys.stderr = gki.kernel.getStderr(default=sys.__stderr__) try: try: pmsg = self.task.getParam(paramname, native=0) if not isinstance(pmsg, (str, unicode)): # Only psets should return a non-string type (they # return the task object). # Work a little to get the underlying string value. # (Yes, this is klugy, but there are so many places # where it is necessary to return the task object # for a pset that this seems like a small price to # pay.) pobj = self.task.getParObject(paramname) pmsg = pobj.get(lpar=1) else: # replace all newlines in strings with "\n" pmsg = pmsg.replace('\n','\\n') pmsg = pmsg + '\n' except EOFError: pmsg = 'EOF\n' finally: # Make sure that STDIN/STDOUT/STDERR are reset to # tty mode instead of being stuck in graphics window. sys.stdin = c_stdin sys.stdout = c_stdout sys.stderr = c_stderr self.writeString(pmsg) self.msg = self.msg[mcmd.end():] def par_set(self, mcmd): # set value of parameter group = mcmd.group paramname = group('sname') newvalue = group('svalue') self.msg = self.msg[mcmd.end():] try: self.task.setParam(paramname,newvalue) except ValueError, e: # on ValueError, just print warning and then force set if Verbose>0: self.stderr.write('Warning: %s\n' % (e,)) self.stderr.flush() self.task.setParam(paramname,newvalue,check=0) def xmit(self): """Handle xmit data transmissions""" chan, nbytes = self.chanbytes() checkForEscapeSeq = (chan == 4 and (nbytes==6 or nbytes==5)) xdata = self.read() if len(xdata) != 2*nbytes: raise IrafProcessError( "Error, wrong number of bytes read\n" + ("(got %d, expected %d, chan %d)" % (len(xdata), 2*nbytes, chan))) if chan == 4: if self.task.getTbflag(): # for tasks with .tb flag, stdout is binary data txdata = xdata else: # normally stdout is translated text data txdata = Iraf2AscString(xdata) if checkForEscapeSeq: if (txdata[0:5] == "\033+rAw"): # Turn on RAW mode for STDIN self.stdinIsraw = True return if (txdata[0:5] == "\033-rAw"): # Turn off RAW mode for STDIN self.stdinIsraw = False return if (txdata[0:5] == "\033=rDw"): # ignore IRAF io escape sequences for now # This mode enables screen redraw code return self.stdout.write(txdata) self.stdout.flush() elif chan == 5: sys.stdout.flush() self.stderr.write(Iraf2AscString(xdata)) self.stderr.flush() elif chan == 6: gki.kernel.append(numpy.fromstring(xdata, dtype=numpy.int16)) # OK if str or uni elif chan == 7: stdimagekernel.append(numpy.fromstring(xdata, dtype=numpy.int16)) # OK if str or uni elif chan == 8: self.stdout.write("data for STDPLOT\n") self.stdout.flush() elif chan == 9: sdata = numpy.fromstring(xdata, dtype=numpy.int16) # OK if str or uni if isBigEndian: # Actually, the channel destination is sent # by the iraf process as a 4 byte int, the following # code basically chooses the right two bytes to # find it in. forChan = sdata[1] else: forChan = sdata[0] if forChan == 6: # STDPLOT control # Pass it to the kernel to deal with # Only returns a value for getwcs wcs = gki.kernel.control(sdata[2:]) if wcs: # Write directly to stdin of subprocess; # strangely enough, it doesn't use the # STDGRAPH I/O channel. self.write(wcs) gki.kernel.clearReturnData() self.setStdio() elif forChan == 7: # STDIMAGE control, see previous block for comments on details wcs = stdimagekernel.control(sdata[2:]) if wcs: self.write(wcs) stdimagekernel.clearReturnData() else: self.stdout.write("GRAPHICS control data for channel %d\n" % (forChan,)) self.stdout.flush() else: self.stdout.write("data for channel %d\n" % (chan,)) self.stdout.flush() def xfer(self): """Handle xfer data requests""" chan, nbytes = self.chanbytes() nchars = nbytes//2 if chan == 3: # Read data from stdin unless xferline already has # some untransmitted data from a previous read line = self.xferline if not line: if self.stdinIsatty: if not self.stdinIsraw: self.setStdio() # tty input, read a single line line = irafutils.tkreadline(self.stdin) else: # Raw input requested # Input character needs to be converted # to its ASCII integer code. #line = raw_input() line = irafukey.getSingleTTYChar() else: # file input, read a big chunk of data # NOTE: Here we are reading ahead in the stdin stream, # which works fine with a single IRAF task. This approach # could conceivably cause problems if some program expects # to continue reading from this stream starting at the # first line not read by the IRAF task. That sounds # very unlikely to be a good design and will not work # as a result of this approach. Sending the data in # large chunks is *much* faster than sending many # small messages (due to the overhead of handshaking # between the CL task and this main process.) That's # why it is done this way. line = self.stdin.read(nchars) self.xferline = line # Send two messages, the first with the number of characters # in the line and the second with the line itself. # For very long lines, may need multiple messages. Task # will keep sending xfer requests until it gets the # newline. if not self.stdinIsraw: if len(line)<=nchars: # short line self.writeString(str(len(line))) self.writeString(line) self.xferline = '' else: # long line self.writeString(str(nchars)) self.writeString(line[:nchars]) self.xferline = line[nchars:] else: self.writeString(str(len(line))) self.writeString(line) self.xferline = '' else: raise IrafProcessError("xfer request for unknown channel %d" % chan) def chanbytes(self): """Parse xmit(chan,nbytes) and return integer tuple Assumes first 5 characters have already been checked """ msg = self.msg try: i = msg.find(",",5) if i<0 or msg[-2:] != ")\n": raise ValueError chan = int(msg[5:i]) nbytes = int(msg[i+1:-2]) self.msg = '' except ValueError: raise IrafProcessError("Illegal message format `%s'" % self.msg) return chan, nbytes def executeClCommand(self): """Execute an arbitrary CL command""" # pattern match to handle special commands that write to task mcmd = _re_clcmd.match(self.msg) if mcmd is None: # general command i = self.msg.find("\n") if i>=0: cmd = self.msg[:i+1] self.msg = self.msg[i+1:] else: cmd = self.msg self.msg = "" if not (cmd.find(IPCOUT) >= 0): # normal case -- execute the CL script code # redirect I/O (but don't use graphics status line) pyraf.iraf.clExecute(cmd, Stdout=self.default_stdout, Stdin=self.default_stdin, Stderr=self.default_stderr) else: # # Bizzaro protocol -- redirection to file with special # name given by IPCOUT causes output to be written back # to subprocess instead of to stdout. # # I think this only occurs one place in the entire system # (in clio/clepset.x) so I'm not trying to handle it robustly. # Just raise an exception if it does not fit my preconceptions. # ll = -(len(IPCOUT)+3) if cmd[ll:] != "> %s\n" % IPCOUT: raise IrafProcessError( "Error: cannot understand IPCOUT syntax in `%s'" % (cmd,)) sys.stdout.flush() # strip the redirection off and capture output of command buffer = cStringIO.StringIO() # redirect other I/O (but don't use graphics status line) pyraf.iraf.clExecute(cmd[:ll]+"\n", Stdout=buffer, Stdin=self.default_stdin, Stderr=self.default_stderr) # send it off to the task with special flag line at end buffer.write(IPCDONEMSG) self.writeString(buffer.getvalue()) buffer.close() elif mcmd.group('stty'): # terminal window size if self.stdoutIsatty: nlines, ncols = wutil.getTermWindowSize() else: # a kluge -- if self.stdout is not a tty, assume it is a # file and give a large number for the number of lines nlines, ncols = 100000, 80 self.writeString('set ttynlines=%d\nset ttyncols=%d\n' % (nlines, ncols)) self.msg = self.msg[mcmd.end():] elif mcmd.group('curpack'): # current package request self.writeString(pyraf.iraf.curpack() + '\n') self.msg = self.msg[mcmd.end():] elif mcmd.group('sysescape'): # OS escape tmsg = mcmd.group('sys_cmd') # use my version of system command so redirection works sysstatus = pyraf.iraf.clOscmd(tmsg, Stdin=self.stdin, Stdout=self.stdout, Stderr=self.stderr) self.writeString(str(sysstatus)+"\n") self.msg = self.msg[mcmd.end():] # self.stdout.write(self.msg + "\n") else: # should never get here raise RuntimeError( "Program bug: uninterpreted message `%s'" % (self.msg,)) # IRAF string conversions using numpy module def Asc2IrafString(ascii_string): """translate ascii to IRAF 16-bit string format""" inarr = numpy.fromstring(ascii_string, numpy.int8) # OK if str or uni retval = ndarr2bytes(inarr.astype(numpy.int16)) # log_task_comm('Asc2IrafString (write to task)', retval, False) return retval def Iraf2AscString(iraf_string): """translate 16-bit IRAF characters to ascii""" inarr = numpy.fromstring(iraf_string, numpy.int16) # OK if str or uni retval = ndarr2str(inarr.astype(numpy.int8)) # log_task_comm('Iraf2AscString', retval, True) return retval def log_task_comm(pfx, strbuf, expectAsStr, shorten=True): import some_pkg_w_a_log_func as L assert isinstance(strbuf, (str,unicode,bytes)), "?!: "+str(type(strbuf)) if expectAsStr: assert isinstance(strbuf, str), "Unexpected type: "+str(type(strbuf)) if isinstance(strbuf, (str, unicode)): out = strbuf.strip() if shorten: out = out[0:30] L.log(pfx+' (str): '+out) else: # strbuf is bytes out = strbuf.decode().strip() if shorten: out = out[0:30] L.log(pfx+' (byt): '+out) pyraf-2.1.14/lib/pyraf/iraffunctions.py0000644000665500117240000035517313033515761021054 0ustar sontagsts_dev00000000000000"""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 # 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 test_sscanf(): """ A basic unit test that sscanf was built/imported correctly and can run. """ assert sscanf!=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' 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""" for arg in args: print arg, # don't put spaces after string arguments if isinstance(arg,(str,unicode)): _sys.stdout.softspace=0 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, 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.14/lib/pyraf/irafgwcs.py0000644000665500117240000003221213033515761017771 0ustar sontagsts_dev00000000000000"""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.14/lib/pyraf/irafhelp.py0000644000665500117240000003616713033515761017773 0ustar sontagsts_dev00000000000000"""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("